From bf7b66d5c1c0d7f7bdcc1c1aa43b6881d797d1e8 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 15 May 2024 11:49:07 -0700 Subject: [PATCH] Add push notification extensions (#4005) * add wav * add sound to config * add extension to `updateExtensions.sh` * add ios source files * add a build extension * add a new module * use correct type on ios * update the build plugin * add android handler * create a patch for expo-notifications * basic android implementation * add entitlements for notifications extension * add some generic logic for ios * add age check logic * add extension to app config * remove dash * move directory * rename again * update privacy manifest * add prefs storage ios * better types * create interface for setting and getting prefs * add notifications prefs for android * add functions to module * add types to js * add prefs context * add web stub * wrap the app * fix types * more preferences for ios * add a test toggle * swap vars * update patch * fix patch error * fix typo * sigh * sigh * get stored prefs on launch * anotehr type * simplify * about finished * comment * adjust plugin * use supported file types * update NSE * futureproof ios * futureproof android * update sound file name * handle initialization * more cleanup * update js types * strict js types * set the notification channel * rm * add silent channel * add mute logic * update patch * podfile * adjust channels * fix android channel * update readme * oreo or higher * nit * don't use getValue * nit --- app.config.js | 14 +- assets/blueskydm.wav | Bin 33540 -> 0 bytes assets/dm.aiff | Bin 0 -> 184392 bytes assets/dm.mp3 | Bin 0 -> 7935 bytes modules/BlueskyNSE/BlueskyNSE.entitlements | 10 + modules/BlueskyNSE/Info.plist | 29 +++ modules/BlueskyNSE/NotificationService.swift | 51 +++++ modules/Share-with-Bluesky/Info.plist | 2 +- .../Share-with-Bluesky.entitlements | 2 +- .../android/build.gradle | 93 +++++++++ .../android/src/main/AndroidManifest.xml | 2 + .../BackgroundNotificationHandler.kt | 39 ++++ .../BackgroundNotificationHandlerInterface.kt | 7 + ...ExpoBackgroundNotificationHandlerModule.kt | 70 +++++++ .../NotificationPrefs.kt | 134 ++++++++++++ .../expo-module.config.json | 9 + .../index.ts | 2 + .../ExpoBackgroundNotificationHandler.podspec | 21 ++ ...oBackgroundNotificationHandlerModule.swift | 116 +++++++++++ .../BackgroundNotificationHandlerProvider.tsx | 70 +++++++ ...ExpoBackgroundNotificationHandler.types.ts | 40 ++++ ...ExpoBackgroundNotificationHandlerModule.ts | 8 + ...BackgroundNotificationHandlerModule.web.ts | 27 +++ patches/expo-notifications+0.27.6.patch | 197 ++++++++++++++++++ patches/expo-notifications-0.27.6.patch.md | 9 + plugins/notificationsExtension/README.md | 17 ++ .../withAppEntitlements.js | 13 ++ .../withExtensionEntitlements.js | 31 +++ .../withExtensionInfoPlist.js | 39 ++++ .../withExtensionViewController.js | 31 +++ .../withNotificationsExtension.js | 55 +++++ plugins/notificationsExtension/withSounds.js | 27 +++ .../notificationsExtension/withXcodeTarget.js | 76 +++++++ scripts/updateExtensions.sh | 8 + src/App.native.tsx | 11 +- src/App.web.tsx | 9 +- src/lib/hooks/useNotificationHandler.ts | 25 ++- src/screens/Messages/Settings.tsx | 15 ++ 38 files changed, 1297 insertions(+), 12 deletions(-) delete mode 100644 assets/blueskydm.wav create mode 100644 assets/dm.aiff create mode 100644 assets/dm.mp3 create mode 100644 modules/BlueskyNSE/BlueskyNSE.entitlements create mode 100644 modules/BlueskyNSE/Info.plist create mode 100644 modules/BlueskyNSE/NotificationService.swift create mode 100644 modules/expo-background-notification-handler/android/build.gradle create mode 100644 modules/expo-background-notification-handler/android/src/main/AndroidManifest.xml create mode 100644 modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt create mode 100644 modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandlerInterface.kt create mode 100644 modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/ExpoBackgroundNotificationHandlerModule.kt create mode 100644 modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/NotificationPrefs.kt create mode 100644 modules/expo-background-notification-handler/expo-module.config.json create mode 100644 modules/expo-background-notification-handler/index.ts create mode 100644 modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandler.podspec create mode 100644 modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandlerModule.swift create mode 100644 modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx create mode 100644 modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandler.types.ts create mode 100644 modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.ts create mode 100644 modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts create mode 100644 patches/expo-notifications+0.27.6.patch create mode 100644 patches/expo-notifications-0.27.6.patch.md create mode 100644 plugins/notificationsExtension/README.md create mode 100644 plugins/notificationsExtension/withAppEntitlements.js create mode 100644 plugins/notificationsExtension/withExtensionEntitlements.js create mode 100644 plugins/notificationsExtension/withExtensionInfoPlist.js create mode 100644 plugins/notificationsExtension/withExtensionViewController.js create mode 100644 plugins/notificationsExtension/withNotificationsExtension.js create mode 100644 plugins/notificationsExtension/withSounds.js create mode 100644 plugins/notificationsExtension/withXcodeTarget.js diff --git a/app.config.js b/app.config.js index 4b54157c2..4ade9de31 100644 --- a/app.config.js +++ b/app.config.js @@ -110,7 +110,7 @@ module.exports = function (config) { { NSPrivacyAccessedAPIType: 'NSPrivacyAccessedAPICategoryUserDefaults', - NSPrivacyAccessedAPITypeReasons: ['CA92.1'], + NSPrivacyAccessedAPITypeReasons: ['CA92.1', '1C8F.1'], }, ], }, @@ -200,7 +200,7 @@ module.exports = function (config) { { icon: './assets/icon-android-notification.png', color: '#1185fe', - sounds: ['assets/blueskydm.wav'], + sounds: PLATFORM === 'ios' ? ['assets/dm.aiff'] : ['assets/dm.mp3'], }, ], './plugins/withAndroidManifestPlugin.js', @@ -209,6 +209,7 @@ module.exports = function (config) { './plugins/withAndroidStylesAccentColorPlugin.js', './plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js', './plugins/shareExtension/withShareExtensions.js', + './plugins/notificationsExtension/withNotificationsExtension.js', ].filter(Boolean), extra: { eas: { @@ -225,6 +226,15 @@ module.exports = function (config) { ], }, }, + { + targetName: 'BlueskyNSE', + bundleIdentifier: 'xyz.blueskyweb.app.BlueskyNSE', + entitlements: { + 'com.apple.security.application-groups': [ + 'group.app.bsky', + ], + }, + }, ], }, }, diff --git a/assets/blueskydm.wav b/assets/blueskydm.wav deleted file mode 100644 index 8d35258dd76dad52c98e68788a7ca1cd70b44c01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33540 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

h!}iKw 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 diff --git a/assets/dm.aiff b/assets/dm.aiff new file mode 100644 index 0000000000000000000000000000000000000000..364b814b7f5127cd05ada4ae7fe7c1501b46657f GIT binary patch literal 184392 zcmZ?s5AtPTy5QjG>E`Sf<{HGnz`(MoA<)6u-`AIcfkA?SiGg7Y1A~AA-x?PNFeuJ1 zFJWK^4)$|lV7kBo78)f+Ltr!nMnhmU1V{=2Mg~Tb)RU|DKOTV1%>Vxx7+_kk$&)IE z%Pe&N!_*|?f1_T80Ve&9N zFg`jBQj3bwRiN^b)go+UVgQMws$>9*p^Jm~$QWh@vKTrWW-iDt|NcWDh>eaxGJpT0 z!hiq&LUn*-AsAU7%xr8lNFBO3NDLXn;)6l}CJ&-v`ao;Yib487`4c3Hun)pQcPB^<3`6uGNRa6eUBr_xvoL9d z88H9A)PQURu|XOdI824RpmCWhz)iG$RDFo+Ej1<@cr z2!qT)#vn0N3`+Y56^slZJ}8txra;O=NX#KfkR31#QH4puxeQqhoeh!)VUS*!86Y+?hUo#RgRzn25p0ORLFVAYAoYagaoK}SAILnIdSo}k z#6UDi4M;C27LZE_kQhh~gh6bOJP3o>ATf|$keMJ1;^V_GwICXXLHa;>6oi?ec@-uH zqd{hZ>;qvC8(AF02Z{fOmSP|_NDQV1#0R+-hCzH#>VT<1&)YD4AbA*u=s_hR=KTc= zg3=ubgE?pf$PAd7AR5Gn=tYn)E&u)_`vM{gQ3Z>UKmS1x!~@BK%mQH$8y$n}f?<#v z7zU{WnF*p{@*o-}4`PGj1cpI;5QfRa!VbX)nS_c#sT`yShCyb5;+K^H0$CVX7(jf4 zJg8IvslvoCH4yty?ZL+b`4JROAR43&q8}97AlD&c3&cXU1H^{80>lRC1(^ZDAT|gi zn+5U*NIgspBoD$MK8OZk5F2CY!4ZG*lY}JU>YP3GaF_;$UGQE_yc6(|NkKMpil+n zU64Ky2C+fvA#xxc|Nnz(VGs*c=E7nCq!uCu(gAW6#4R9w5IIH$h%F$~7#JWv1hrWH zg7X201rh_vqGE)vVd5aafG~&;F&o4~V33wlD|9ALb#n8rZo$)H86|)xee3mAbzpNKo!`Nlm z7jT4gT;puu{J}Mq>koG?_XD0(p834eyk>kWc^C2V^F846;Csgx#CML*kS~evDDNs> zA>NZbW<2}2O}P8H__%C1k8zx1Phodsd&#GW-1XW9g@`kM}=5{!sRD@dulaQ$84e z-0~shBiqO2AKO2wf3o~^^^@r5wV$QGtoh>h_59aK-vqz^|6cT?`RB`@vcF4zAN#}i zxANb{e?|-u3@;h~G0tM1$867P#CnBoD_anU62~^qQqJF8tGQ%&_Hv8yuH<>no5$PD z$HDiXuZYi;e*<3>|9(DO{&{@w`F#0`_|Eel=T+x@#}mZ!mOFy`1eX<8J?AeDNsf8! zGugD*m{}LI#4+r*X$I~C;KCb`Z`SI9?l#kpW*M4mLsQgLo)A3JVKTY}k z?sMlCzOReE27bHyZS!~29|k{{{XG0j`*+%(1Aj#R#s53;pP8YCaT%iyvkUVjmg_9( zY|(5l*iW+ib1HDo;ELorz+J$7h$oq6Hm@$P2j5xV6MRm568s%}7W@nN)cL#kp7Ocy zmGE8Uy~1nA%gLL;Bg#|3{gW$+YY(RmXFSIZ_UCNbY*DPQS@tkTF$*yrU`%3=XW00! z{vYe#r+<=u2me0%^TJP`ABjJneB=H;eL5b?gP~FE~zcm~pXkC37osXY)w# z`0&2sVdk5}+r!7l_lz%=PmsTkkB7gI?<$`uUjg44-n+c!ymGu1Jf=Joxz)H^xju77 za4z8B!HsAU+#Re`mFe=_tT`0Jf93cZvFW8L+{5wAEtcN{CMJH>qpH`0-v^j zI{qpD^UBZuUp9UT{d(_f?Khq8hrdtzq4HDw*Q{S_ek=X)`@8+`kAH#xmomI(2xRJH z`pPWL(!)BRRghhfy`CeN<09u;P8RNSTp~PYxc~4h;n~d_%{sb8F<RvFo!3vmIqU!{W(e#=M_tDx)srhyT<6d;eqpx8_gjABo?ee@*(? z@KfoB(vMT$?tYu_b?eu%FKfRvem?bi%O~E?LZ7NW&HgC#Dd^+XkLDkbef0hK^y9>j z2A`NdE&sIeljrBG&mv!9zOa5R`YQQt^*7(|Oh4xSDEi6r>-(?z-#vdg{_6aj_3zSu zLxu{*11fFNyOSpe>P2zgV8PBwj8)hy4Ec^Wo3#9}PeBzgv9&@Rj@9#V`N9T>UKa z<^QLE&%vLze7f|}=~Lau-yb7CKKhvS@!Q9lAI(1f|G4PWq)%3#{XhTuZ1Uy77l*H} zzUF@u`@Z{o%nzlXyMHeHW$;_?&!#_T|2qF``v2m;JL6nNG3H3-=PY8Zo7v8?6>*et zNON&=E#t1^=HWfh<&WcE&F9NE zi}yc|KTkRL8LnNN;+$95ZP-t-+OsZa)?zMYF=G(RJUOy)M{QEQN_uk)Xe;fY3`DgWi5yMjkd!}-xUCd9Jd02H=_1Ik4 z?ud?4{59SEqn8DG{v4mq9M-4|7hav|r$4vGX_SbCJ*yPx_*(_N#Slw7GSp1ls znIo9|nF<)Q7-um|W4QPK*8jNwQU4YHOaJHj&-`EZKi~h5|Mvga{Gae&nc?66jSS5U zfsB%je;Ic(u4am33T6J!^o4mUa~+E~%Nv$D7E9J+EQzeISz=h9vS_ewWI4p*$D+lu zhdG}47gH(|H3hC$7(*KCk`K`*r5G z-0#PJbpF)+edqU~zmfl{7``%iGB0CRWQ$|_%E8aMm3s|$FrOOV5rHCsA41JSPepu1 z>cozU?i7C{)-N$v{FlT>@vjm$#p@)3#ixoVh}nuUi&lv^2nPwt3!dX&#`lBgKKDJ& zTO2)Xi&zDjU74!?U;KCfcjRxcA8)>&`!e%O&BwxzN$;B9O?-Xlwb;w9mw%qkc<%pH z;F;KypHCtlM?R5!Z1N=farYCMC$UeXo-TVP@I2v#>C4tv`(AUtUHtCh`^z60KRbSn z{C4NZ&7YJ16#flm5M&f!S|BK%z|4-2$YCi0J_wQZx+Z%7Ezu|i;^k(6k zH?RBOD8IS(X7ih(w^QCmzuW(==e^#CKOe4sT<~e-=e#cqzD9pr`#t8z@t^6x9{kS# z^X_l&| z1Ls|i$s9f$|Ji4=`?6nPYi9e*x{{TL^*D<%%M)e?W)9{ICUd66jMa=k7_Kv9G6XRE z`Ty$wrvJ3=Xo5W{JP6ATfIfsCIRpD|8is%J7}7Gpln zyoA}0MUiDMOFIic>jf5j*6%ECtlwA^SWmH>W+`NGXL-pync0MSJyRgld`4HsGzMV? z?*9k=P5B%8m*da1-vz(?eqH%-^oQMd=kGheUis?(rTz=YXaCRdKKgv(`q=O><-_|A zj2{+!c=>+T2e}XTKP>uC@Uh}!z^D12YCa2n`Sj)7*R|iyeqZ*3Y<=Q#H#k7JK%E?B6-9xTf)J=MmuN;|~)w5R4HP6c!g zzrO!8{=V+pW&hwQ|{huv+QuI{%3HMX(C(=(V zo-BGA|8&8#J>0`?0^QIRhsQL8x#9M_RZ`89C{qfIEpz=ahNxhOLFo zknJ>U1}iJ;OcraFQ_RK8%*|1JJ&{=4~i?Vq$iioc)!>i?Pe zli|n1?={~FzVUt){kr$_tEwGf!pmVhH;Gsx|B?N3?(dU-6#nZoEMn?s7G}H6mdwe()yVUiCy{?C z{}(|nAtMn95q`1bqOIbB;(ZeD#nmJeCHy4QB-kVwBmyK1#ZAN+#I}i+h&&LUDzry1 zPau?^n{NkCIrm4-{Tu@9A6OMxq?nBvJs5)iwg2t;{p8oXAK5>ezo~r-`6Blv`%}!P zvmc&*Xn4Q(edoKE?_RyldbjYc?YlK^liqQ?yY_C|yGietzwiHW{lkfmcAtzsU;KRd zOUKueZ{gpqewhB`{3Z7L=I@Vx7X01&FYJFI!zTt-rd+0C=0nWeSYET7VtvZGnr$6h z1iK6SMfMf!;vBcxbvPcd3v=vdzsR1)p3Tm{{(`N7&718O>pWIn)~77xEE+5ane&+i zm=7>zGRZMrVw}hr$SA}3oZ%3|EQU6QEQU;mNQPX7G=@Hgi412Lt}!Sx8Zq`WE@I?n zQec|Iw2VoXS&w-c^Co6B79EzUER$K@vOHqZVijR^V^w9fX60vPV?EEZlqH2loaHQY z3^O-#DU&=?3L`h8KEu`jxBfN#%l<3=_vP=ozhi&N{5t<*_7BhRD&H@E-T$@hOW7CO z&q1H{KSh5E{W#;}whsay!#|w(VE^IRhr$oMA3uDU`*HWj%1>85&Hrrnh5PHhuRFgT z|GxOg%b!Pn+58dsyX{}Ye;-D6rccZ>S>~|mu-kHO<~+_V&!fke#+NQoEs!J>BqSm7 zNVrB+Nwhr^M{bBj%#t_c9k$D~q3;Sty1Fo}N8oV=j@AE4NcnC!bMTl65aER>@4HUm2 zW+2fcK40Rh_#%m|;uaD*;%VZJVg_PAMCOY`3hxoh6PzgE$gjzFhi3w}FV_{0h3w31 zk6ARB6`6t?{hzNd?@|6|KsLQYM*7lZ2hwGYtlFW@5(>ee!ly8=+~~_ zHGgXVYW>swfB63q1}8>qruj@g%vYH=vOHv2!MdL{pDmY-h5a>K2D=seT=p3DCF~*W zP3)@d*6f$rF0-Yu1+v{^-Now0D#p5)C5z=Z^IqlMy?OqEPZOm7$$GL|r!GKw-j zX1K(#hG7lEWQN5I(-?LzY-M=G@R7lc(S>n3<0?ijCPk)hro~Kx%$m$Inb$H)v1qdN zu}ow6!1A8Of>n<-fz_KemDQEinN^(i8_PbHY8ES&tIXZZ%*-2^*qGKcK4NHRnDgJ^ zzvaLCe-Hl2`D69_?XR^zvwtf6c>8_Px7u$OUsb=p`TX|tu}^nDo&WgfBj3m1k3Aor zfAIdW_e0Q!TOXEuaQx`>@#9DBPp>{De%ASN@5|<|bH7!5pZ%l!=hG zG6q+se@rh~`dGW!B{}4{CUQ;ZdCl{jPl}&eP+0K3&}pG!5lfNHqQRmw#Dv9U#7o3% z#EZqgi1CTJiaCqwi;9VC7w!>yD7Z>s8-F>U9q(W6CN5Xb3G6X!B`p5T){I&VU;h66 zv+mcmpB3K^eT)Bc{&Vq1-j9dgwZ9900$7P?4zfAiY^?k#SCBL5iX8fo1KZ!Ai zX&cL2)_3e{Ieu~Vai8Zk<%<`XE-+7MxloHptO%Fb7Ew*{Wny>54aLzsrBm{Qk)MuJ`dD zLO)o3jQ!~SY38Ti&x~Kbed+yL|IPTj?2i{ej{RKxtMGU9AF;n2|JMDR|6h+mhH)Ze zIn!mP)y!|0x3Ju1nZP=a)sD@SZ7G z6F-{1`+w*9_WbLrFSEYnd@lSP@u~Pz*~b+h&wY^inEK(^2Zs+kKBRv5{Nc)nvXA{A zoj*tZ?Lg<^-qR*P;E?GgzT5fPp(6e2iJ zAdo+aPn`EF_eQQ{P92UNY?D|SSw1oOForWM{&)7T=%2*jtAEP;Ecnj$J?E?D*HxeU zKI?w6`1JdO&PT})c^}%||9Vx1%k&iz=s(ljqocTHW%at$3 zzQ%s@`u_X-{U1wzR{sk4E%xWzpA~<5|7rf`WthlNz<7soBNG$zX=Wyt-7Gg*>RDS^ zMcA0x>e#~BcC$6Ion>og+r^f|*372JrpI=K^#E%it3B&kmJKZSERrm1n46fTnBOz? zGo>-{GreY<&)CA~!f4F+pWzR~RfdNQ#~B_nTxR&k@SDMu(T1^=aSG#WMh2!trYfe# zO#hg&ncJ8@GxM;NvUIS#V)@SE!y3Rkg|&}$AL|;{!>r3#7qFJI2D8es-ej4{V$AY_ zIf0p%Ihl!*$&T>>!;k;d|4;sB{7>TVoa57l2Rzf1r4|9$mu*Z&GeYbH^aV=SB4eA%5jk84 zJWE(q^tcGW*c{Q-VxnTt#B{~>h&>k75i=7t6V(v8C%jikN9dP;lE8DmQ@mX~dfXp4 zqd8RAV^|GYY?xFSdH!?%yZ-z0uk}A(e_!yG;p>i1_MccjYd&`7?|$a;Jnos@^F_~=JP&+>I#e^w0sj8)9JEQ{F+*_U&AbCvUa;Njp8=JylK5_AyO6}~1iMTApq zw&)EpdoerldNEt^XtC>JS4DZnK8d^#*(sbSY#_8uFkWB|e=uJZuL#d;u63L-9J=hg zSm&__F!M0wGPL}^^;ht3%J03u#C}fs@$1{XZxUZ0eJTH(@mc1R@h9$&?jJorEc?>#^{FZJKw zesB8I`Pc9t*Z}8Z?dcvg5{DfJEWjD)3mNeE>)~BrJSS{Ij*;3fl*)rH< z*gV)iuyV33XI;nY#%jlUiDe&4Ad4{zs0VJ#EY7@*X#$fulPJ?M#?_2zjG>G&jN**1 z8NM)FVR+4Oo#7wDR|Z{1ea2G8dd5qPPZ*tad-DUD-PGi2#{DC=+rIh6^%Uc$2 z))3YutczLivtDNX$@-A>4eLqPZLICAp{$au$50vB5@g!Mc#ENeq4~eU zf1!UH|MvVb`19-6vR`RGWq)4(KIeP*H=S>9zg+#Y<=~{4u6>O!Q`X;$9EqMKYjR=_u1;p$1lgfZv8gt`<5S5e!lp1^tbC@*?&j= zPh<#Z5@r6(GLN-~U4cWCtB z{k{I{@6Wy8S-&6rqW|UN$M%nr@1@?ezy0!t<+beVsF(9!3cP4~q5u5M^IgyCo^O6O z=ef*twHKeBOTILGS@`POEA=om+*smv=J%IsUDJ2L&61n3b393@bC+7S@}r^{f+F4Ok6XPqFN0 z31e|&xxu`LIh5Ii`4ZDcrf?>6rl*Xj8Jigk8BG{<7=JS`Fy3Z(&v1?5C&Oz7IYw#5 zSjH5_&5VZ_<(W*FW;1PJQen1Z-pG80*`6hw59znA>Z_$Bu1 z;*ahhF5h{-pZvP`YsQzbFIu1VK1+Tw`DF7k@8ir5k3N`vnDIgK!=w+cA1;5`^C9kI z?MLHJ-Jb$JKm5GuOZC^#Z=v5!e`Nmj{I&LX=O3|uum8&R?Jw`L)1k?NaTl5 zy-=v&G=W6^cs>>0$K1=gbU44VyRu2K+A=FKDKbd^|MN%S&%K|5Kd*h${`UTJ_GinF zcR%cV*ZZ#PP3xPLuP(e&ep&YN|MR*RF3(>-|NQLGbHV4#FXld9|63JQZDQwEF`p0Fil_& ze=Xm9-b9`dZdtBp9J|?***saVGGAo!VvJ`v{qNu3v_Ct4tNfbvlmExc?*`xAf9?HJ z^hM{h*=LbYj-OmUPX4(6gWkuM4+0-2eX#iW?8AbOl^lOVO9GuLr*_ z`KI-qTiUBjyxlMiv>C6)bC5 z)L2DXYgi*#SF@I}Ze=ZGox|$On!(D?D#N;mWd}A4i;0E zMa=HZlbH;eVi>@t|F<{a z{(ZmqL+KaO?~*_Me_#CD^}mGCf{BM^AIlOpM|K0w&76z5Ie0$sn(}cA=nK3Od@0x@ z94&lGq)udysJCc<*ly8gu`{9}Vzr_NM9+$x64@_YCLAGjLU5MAG5!gB)w~Wo@3^*d zYIFQ#vtyNHF=0|RUO@BeT4-|v6u|M>Ye=Bvi%O`p0y*nQA@$Nf(Fjmn$YSCd}- zdJ+Bd_w%+Fsm~=|SUwkeQT@E^h2{&#m-a6UU+sGJ|MjFd&)yz*C-cGl@KD&Rt z_D%K2qn`@Dng444(_%1Z^kg<;31pLGH{ra(`II|~C!B8qU!%YrfhZvlq1(c9g#U_6 z64@)NB`PhJEGjN$E4op1m&iMj`@&a+Hw)zm*$D0yXyQM`H-UE!StVTGvflrIL07GDMm@gHw@ny zt}?u5xWT~4_?1DAQJ1llv4-&y;{!$)rU<5EO!t{WnDd!mGBdN(vP@;+W|d>@VV%MH zi}f$7ADcT{Gg|@MWVRBv8nys73pNI}1qFESO5}E%q*)d&b{KnAB(EnfU zKmWfKe{289{CV)JWy5SAKZ=!T3Yp z2Zj$NAH+W_{80Wu?4$C>s~@F5-Tf5vS^LZ5FMGbO`que<&5ypH4}Tr_ZTDCF-|qjN z3_eWU%uiS*uvW11aQx;h;Y#4y%`=-(9@oner=LzHf%DJCIll>2?1&b83KBEeQ)IW{C z9KTI|vHuAEuKab%m#j}rpI*LS^?v2ssc*NxKKEMsRotsPFT7qdy_or8;&cBOUC(n~ zynp`j#l9DJUVeJ1@w)T%g*TON&%E3DUg)F!r#RiufwRz zWW^%Ms>}Y2{VQi1S0~SVo;!S^{Er0Q2}~0z75XH+MEJc(xk$I@8N=&SJ;qc6grO+K4{s`|9#BipBlkJmm1eSGzC!bjUr+@DT< z+W)Eh^Rmy?U$%dl`1SSIqu;E)3;dYr3{i|K%=eggvnH^4a9rUy zz-7QK#+%M-$DhluBWNyoO=yphiO3gWThSXLuSJtYt;G68HN;{?w}_q*xhHa0c(w2p zAsL}>0yYBN{QP`3c@}VIasA{t!LG)}z-r7a%cRSo^Izew3BH+_rza^v&P zkMSRa-z&d2eyjU7@pb#_7cVVdoqgf)QsTv~7pI?3dhz`Er5AZG%3j92tbeus)$`YJ zZhH(D^ZzORKl|UB;Q@ms<26Qcrj<-rm?D|;nV&E} zWA6wA4?L85Q`McQRYL;SJK=!zG5t3`ZEAFkE2x&+wJOn9-QAfw6<}DdQK$IHqEzXH5T@s+p%U zi?JB6Y-BmX;>H@udY<(bt23J$+kCbeY)=116gmg z)U!NgZeTvj6wOr6D8wkiuL^iSO%*MB_nf#IX&hrSP=-p7CV^uFwa&4+6rE`6x~xcOt@r+c4Pe-8Mf{*~q1 z<8NQTU;iQb>-Vp$KW=~T{#*Y)jZvHFH}g7{el{I;QO+5h4ct$;kMeTy-QfStzfN$i zpslc&aE*w8NUG=;kx!zrq6%V}qOV28L_%%VzR3$|A*uXJ7)y<*;P*>DZ28FkIq=KE zuX5j$fAIWr{LTMY|DQO645JwHALhTT``He1#BhdiALU-f`+;{S|7HGa!A!yDLaT&+ z2u~DVD@?!WcFw!cDuYJce3)5=g_XshMS~@QMTaGf zMV!TmHNqG0kJ@VX|h@V7kqCim`>UjM1FYfbkc@ABGDI_ZW6F zTw~bB@PgqMg9sx(V+3Oi;}*sPjA~4lOskm=G1)SQGv8qT%v{bgfklK>lXW@kc2*rW zW44)W)7W0J-C>hp|Ien#{)_D&+bOnnY`JXuY>!wQSv6UgvbeC!Wj0~XV*1M{!nlNC z{(po2LjRWkE&U_-=lrjdUz$H}|LFbh_nqn6)vuGjRDN;&?EG2fljSF~kI5gKKb-s^ z@FDNR!}sAIe!p-3knn-=qrk@_AGtrB|K$H!{>zOoo4zjm*7SYJkG7vTe(m^e`j_wD z`u}wdwoHGRF0jb z&+D(<@4R0jKbC&)`1(*$U$1y6^y=!1^q1)`)#qO7#FXz45{ObN|>o*l|CEq2zcl|KsU!uM){-*sSGcMy*nUo4-9fV{va!HI&ugw_ka5H1t05_u@RR^+pAo5(t0 zHW3kFLt!l;exV-%O9fi^fAd}8mFE4*Ey(?z^9jdU_C;(nS%X<(nOT@r84vvb^Katc zmwy_5fB!Y@r~c17-=}>K|7P-y|10lT)-S4G>^|3i-t|fRbJeGhpQ=7_e%|pZ`m@33 zC!gHAhHQ|@b?;F2s|A_xx^Vj3wsedW|Fa6JC zIK&XZIE7Jy$(?CG(=8@fW*_Di%yXGvGM{7SVR^(X#BzuE5Az=8^UNL06PTTuotQr{ zy42V%qaou@hF=V)7%nrcWjMsJoZ&RXZie>^FBr5Lbr~BN zyBOav{$b2vYGV4sB+5LUc@wiGOEAlAmd`BZtevbJY{G19Y#nT`*dDU!u}iW0v+J?D zu}iQEvtMW1$dR0@aKKS2b~Z5J}mp- z^D*P3k_|^&R7VsA`7g{QuA-r6~RU|<4w8$LMuOc0yheSj~jYOP9JcPxB z*@Y$xmIxf+pT@U@w~WV&TZU^F$0T+pwkIrF%rZ<44DSDf{uckq`L*ij?C-z6z5bH- zCF7IOC;1N_-b=g_dKdeq@y(A{uCL#|OnT+=^8U+zFZRBaefjt0qL=eub-h~f`oim9 zZ}Q%*co+A6;fHA-?|kC=;{SF1w>RG_elGhJ_NVZ#?SBUbIVKk7H!K@iXR=#xNODc$ zO6J+blg-!9rzxN;FikLBaJ^8tP`>a-p{v3s!ViTNg{KMc68a{@EW|FvBzR6>4}S>1 z72iVM7M^|FOSrajF5;NT-pLljn!=*MY|HeLL6+g*KY@Rn|H%D0`YYfU zo$JTxA5K62{cQea`uo`LtUpYDr~ftlx8xkIW~TKQZrRzQ#PAc@c9Ab0jk_GXwKRrrAv1O!`a@84od*Ge$59F@9y( z#xR#5jUkLdg8{U@j+Nofe!e{TPY{HOK*1VcBYGxIm*eXKETx*VH0nz`O^ZQ=RHvyJaQU!6d+0He@j zK?~t;Lb4*;g;$Cwirf-06j?9wTv$!SMc7%`ONd?Qk3f|`BL70ZdfpbEcy2u|F3#ob zU2MNuUW4ZjQ~y{0oBZeS?=wFof69LE`8N5B>KDsTA3jQc`2ODZo%6eOZ|=M)etq)w zq*wf}1zv4@_2K2_S4OWyUSEB6@%6RWFW(rx4SRS0oxq119|S%LefIg%^mXSq*&kj% zum1YS*|M~p|1qDroE(snL zvK9I#6ejdgNJgkoXtLl&!2<#-1eWv1@CWi;;XT45#ly*M#-+t+%%RUN#iq*oomrUq zG9wG)&i_pRH~$s?d+N9MZ?<2{e&+ws`@#MF)wc&@5Qj3!I-g{@h_tx(^96ROj^ul%stF)%%_>RFu!0v#QcnTGxIU#PUc=_cV=7W zPfT~1dYST>w91Fw9`s$)Lp;$asPAH)9*qCMGxLLS`-&BbL)F?^v2y=d)_D*|62CqB4*MQA2wMwB3AqXR3;q%K%Adv`#Mi-_$CJ(N&n3ypz%hrdhxI@6A0|J>7=|hTcKp5j zTjsaf&&fYlf7AHp_T|}U;ZHX{N`GMgkom6q9m`wWx1V1Jy|H?|@Adsxt*;-wTJhTD zwb~ozH~-$qyw!Qv^={>Rw+{s$-9D9mZv3+0>)UUlKWcu?{H63q@9)Kb$N$$dMlwmT zTxFTgX3YMMBb8H-JCXYbk0S4EzBzoK_|NeF7FaK^Nzh49M5skjNvK5do}ieZk6@la zzCa^?0DmaoXWkDy2|Q8Ui@4@-?&mnfeuV8J>n@gS%&VAgFwSRq@_+F^#((?&nE(0x zYvQkjpCUiMd_Vqu-?#PO4t%}vmEo)I*SasqzgT~n`^EIjx-Z#ZzJIy#rQ~b*SNU&_ z-#&da`2Os>-H$In5`GH*n()i!_p#sUe}4Vx`>XPA#Xr0M8~-~oEMm}MtYG}XsL!;R z=@^qXvlVkca}VFVHuKl0$KkvWoe~JH}{@wbw@88yc%l@tXx8mQSe<%Na|Htv)>woJ1-T&|Z zcV{SMc*P*axQOv8VmK!YpS!S{BVD)5+WxLPzj4g!SmwhRFANwix zCF}>;d)PbKUD)N>PqQ_$DY9*4^{8ef_cONB(!y?~LE>eBJtG>6b~LXMUdlY2T+uAEiDeecbp_>0{?djgLz|mV9LU z#PMm(r^lbBe`fk}g;IsL3vmfM2t^2$3kC|t z3jF5(!57Wv$6LXZ${o$+!O73T$UcX41`8jvI8y;b@Bedunf{vmZv3_8hxiYtZ|A-; ze%bK(>&Fux)jr65nDcJeyM(uk-de)oZH>PiO->SWfewY6K z*89&N7JfYRY5C{VUmksB{_gf8;pd58SAR$U4g2@$|5b*1CJ*KxEDKrv*{`!#bKc`D z;9keA!E4Vui?5sS5Pu*4YyoEh6~Wa4F@i?~oCPNc+z}8G&=&CHH{W8&lGoz63x z`wtfbmo=vgM=X0TTQ+M4O9As-rc%aj49)*<|6BT3?C;~>`M+I%z5RLV$BZ8nzn6Y* z{nq|%^VdsX<-bOKJ@G~6>$)$JU(bE<`>OVJ$Jb3?1Ha{d6aDV{{m*ybAOC)&{uKGu z`^)V2-ruo*-u-F%EB0^BKcoN4{#!82Vo+r)Vf@Od&$N{3G?O8-GxJR58O(Q?Pc#2z zzQg>R`3&hpEv;N2ZSNi|{ z-|l~t{$>92`DgS`@t^oVp?}i<6#v=(3;5UkZ~njM|9Jmr{h$7ygTaPjAH#QscE*E@ zaZEFqY?(`#rCEGgKC{TO?qR*dTFBPQ#>Fnc-pF3get~^E`)Br(?9bTOu&-c`V>f5N z&o+fkpAED&U_Xlo%M@k_W>==;j5ipf7=r)b{CDuL|6h$i`+s-;GWhlV$C@8y-|fEh ze|!D)^q0L~Hh$jy`P8S^pQJy9d|L7G=g0Do_dj-jp8iNwk1*RMp4%W47M(j5^Tsfa`8FL@u`N~tk z*T8p={|rBy;AMfwf_;MdLcau82#E-F3*8Zv5(*Lw6RZ$$5D4Ud$oGWThS!`YiaU_Y znbU@Yf&D-09G0cbicFS_v;QCeC-yh;&+K16f13Z;^!>}%319Dhp8fgX$9o^6Kg56d z_fGx&ySG;F^xw{Vd+bf<+pTYs-u`}bRu>%YGFy7Q~WHWb`fK;^ z)Ia1KFe};VwWeg?^AOCOoU-IAVKhyuS{}%r%{}=Jk@}J5-k$>F( zc>eMKQ~qc0FXCUpzcv5P{geCe{(sf~TmStTni)74?HMmHvNJ7Yy3SP2yn@-2rG!P0 z)r9pV>qFLTwsN+2Y;V~D*zMW7*t6JYvS+bZvD>igus>j1&lbkU#5SK*lXWkP8p~$p zzf5gRTN!N`gBb4ozx^-kU*z8pe=hwl{q6tj|IZsgX8b7q?*3i>o9H**uk2q%zNmij z{apHa@25YX(m(C_6!>ZPr|M6?KE3!<^?CYd%P;L;%)V~@n*UAq`@Qcwf7JeL`sMb! z;E&VL-nXE3-j?q`Z+e$0}``jah>{Rc+~=T$CI?lzv4JWPCSd`|po{I&v20v`pY z2}B4!5GWA5BVZxeEwD-89sfOk4*o-YcX^X|i+B!mpW}MZ`Geyv`wzCGtT$Mim=`kX zF~%@l{wMOU=FgSiCcl>bWce}a`}eO)zKVT${5k7W#HZgMxIdnM&-mfZJNx(H?@qoG ze7EqO?7I{1s^8hY*LctHLFohc$K;QZpPqht{CVM*#b0~B^?hIPWA@JrzYhEs`1|p1 z*nbs<8I1l+Cz(@O?z6_RU0^rhn8JCPQ<>X=yNsuZr;|6CH=Iw9?>=8OpFIBpK5_mA zzSDg2e71b`yhXgrc=C8=a$9o8aoyzn%Mr#=!@h&Kk>ib|DXTP{ag62;a}`O_kV`}RQ}2RlliCqPye6KzxaQB|5p8b z^H1P^+W!gvzyDWfn9Fd1A%wA$QJl$}=>d}<^D5?R%y}%6S=3maSWmOwVU1vmV>`ii zfK8PBKbtkX7`p{KD?2Cq3AQzCacs(Lr&#k?c~~d1sI&Aj%Q0s%eP>i>+{SR>f71WV ze~kZr{h9rz`?vXT#b3979{(}nN8|U{?@`}8zlD8`{95y6>X$2@8NVcaUi?}0bM0rf z&yzlR|FQdL$*;2CdVd1`GXHb>|K`6F;{!%F z<}1vutOr<)*=Mk`bB1v4;CjVn!xO~Q!`s5UfUlabnBS0}QD70jyTDm~CxP|+pZUG` zqxt9Y)$*<9jp1$L`Nu8Iox#<_xtL=Q`!=@AtSed0Gq*GCWQ<^#^nQ$9?hn%Kh#4w};=)|G4$@z^~`O5B*{MclMtw!!-s&rb|o)EIU|4 z*c#X_u`6<+su2H=M|3`k2Fs- zw-k2}*IQ0S&e&3{k+mi%r0qxHw}_y1oi zzkdDH|0(w~<44|)3*X;<&-^~;yV3W`@4DYxzlVO`_kG28xgW|u7X3Kz!|i9)Pu5>v zzpnn$`Mu@0#Gl!J*#9>D{rorO-}Qes|JVL!VJKwS$)La(!MK(2E~7q^J5w*yRHm~` z$C*Ab-DUdDbc5*$({836OzlkNO!iDFOfMJ@F*Y-XGKw)iWLV6Q&0xUr?f?G&?f=96 zEB^oS@8rJ~|C;}0{R{Z#_|N2@{y)QirvE(th5f7gH{sv$e^3AE{SW@X=Kr<-4h(q= z<oRXEL5<3}tF%5@mK|zRdiKxszoDivg<}>tWV&tafa+Y_r&U*siecV0*!~pY00U zT(+rfL2No~_gH7KI3)k29FB8A8e)0bD=(F{gAD>gcczikf&Wl>f0q5d@~`{YMQi}F0=p2_3Q^MI#{=QU40&rTjK9)F$<-0QhnxbJakay{f!=iJA^#8Jb3 zkWG;-n{_J-JBvN@9HtYDMvS2h=l{R^SM_h!U#q|Ie?z8h7~B~I8SegH@xSuF+ke^r@Bf|qxBOr4zk+{p z|9t+r{ImaO|Ih1R@V~r&_5W7=JN%E~zrz3A|C9f}|1ZhV%&?I`gE5TpIwLDnH`8_| zN9GLXPs}1LGg&sV7=qVT?O>H=6J<+d3u5bK%VnFumduvRX2Pb#c9(S>Ydot6>mHU+ zmha3Z%!16NO#I+=Bzg>||9|>7`QMtqp?_2VNc>Uy{qWc8pWA;P{ju=J_U}u+pZs?G z+yAcu-(tTuetq>t@#~5&pT5lg^6ksvFZy4VzRvx+1MgWs?S!$zK8uAhbCtjR{>WicNcdjPXSL5 zuRQO4-buVVd?$FV`1bL#@s;sT=KaX?kjIecE4L;0B`!X$I?inz|JlvhW7wv$&SAOD ze2j^o=`n*9!{2|&|D^s-{^Rod(yykU8b9xSpYuKTo9Q?9ufM)L`uy?p*H3bvoj$dF zI{Q)U)69>2pO$|#{PgYPluxanj6Vl_=J^u(MdoYY*XVCwz8(4A_{0CF_OGwMUjJV6 zXXoGCe_j9e7(yAJF$yvFG4EuNV|8ZhV_VLCgZ%-=SB{sQk2&{o&EtyUR^~p$UBvx? zyNmlNcMkUwZYgek?ipNNTrW6xaSC!C<`Cmp&Hk6IlI<+35o-_2J!VJd7N$>(+KkH> zZv0RFKjojnzvRDyf4%?k{t5i8@w@U@>aP<&PyDp|8T9k+50;&K%XTYqr> z{Q4v8XZX*9Kd=3C|CRsi$uF7TlYZa$?fqxYAD+J{f6x8Z`q%yMF)m=tXY^r|V0_Q8 zo1upxl0k#v`~Rc=Xa3Ll@9Sm|F7m>+P~<3f&cve1^x^Bm-(;k-}HZL z{@wle^PkoK=>Kc}U;1yr5XEqm;R8b&<0M9YCQGK>Opn21queZNEQ?vTvuLrJu+CyP{2FMa3w&i_5?Tfw(WUw?fq`?}$) z=hvxU{lBjM+VS<%*DqglzD@Zi@jdnXr|&60e*EbBss8KUuZrLPf1dxj@i+Hh>3=2$ z8OC{xN13dcGg!{D{ACSbt7hNHew%}tQ;bWEONCpOn~CQU_bQ%39tqwPJl?!dd7OBU z^Zen-=c(ek$9;fXockh|Jl96f?;I%{E7(7=nX@Ibu4LKH%*OnOF^tiIVgCQ(e=q-T z{bTos_4lG**+1ofzWcuO`?PPB-!i_Ye$D#Q@MXd0^PhP?hkV}lN#yhFPyCaz2z$BTEHdErOY*% zvzqfU$65}4j(zO1?3>tx*e0-kW{G7v#;nLZndu>89OF_3eTKsST>pLkef?|w_xm50 zKSF;pe|!Dj@N3C0sb7k}R{cEuGx2BlPy3%0Kb?Md{>=Ef`{#zAyuZYMRsWjy>;EsU z-_w4d|84!J{?Cg)rhjMtee>7z-~4}H{(1hN^8dwub%rvAl?iIGX1iIs_;=_})B#!HOH80Rx~F~&1GF^VvLU^u`qjUk=Eih-Bm-v2HCTmQ%Z zH~ug5|JA?q|JMDR^{???$-mTp3IAgM#sACvSNyO0-<*Gk|K0j0@L%_T(f=v`pa17& z$YGeu@Sj1GaSr2AMmwfdrbkSy%uUP-m?c^CSr)TwWRYf7V69;-V?E5eiS-TZ1=g>u zXIXEvu3(+Vn#gL)`kiGLOAd<|%OU0v=HE2|-)Vmv{&D_iXXs#<%E-;6%-qeqf`x%q zhAoM$f_*jnHjc|2=Qyu&?&ey}Rlyy|&B$|vJA{XcC!dFdCy?g`_iyeZ?tJbWT5;b+?~v)?R#*8b`LtM^a-|Hl7o81xwRndUPsVE)Vei^Y-Ege{fL zn>~fyio=HEFUJdxiJWPi-#E8%YH?lX)Z;qE$;>r@b2X<9rzYn@jz*48?AzIu*)OnJ zvt3}dW!=Z3&$577oVkkWH)9CneFkHO)Bk1uANnWq@AO~Azc2o{|55nU^}FQvn_tg= z75u9CCH~9g*Q1}jzb^db`t{(a+AqOhiNE51ZU1%bm+Ehq-^+eq{%!Ur|IfugB7cki z?*1$KFY(`&e~kaV|Ihq?^S?4f1j8JL;|wf}%8ZeWIgB$H7cw4ZJji&H@hsyl#$${p z7}qi`V{BzCVf1G-W8`Ig%W#-s9z!vM7lQ)Bm;Yz}FZf^cKk&cdf6o8!{+;=^{okB_ zz5lBJ75~frm-R32U&+7Lf0O=g_;>i<`+pq&o&G2OU-|#^e|ZKMh9wNA8B7?X8P70& zVoYc1Vq#>LXP(Nuj#+`lh-D7TG8R@=R@QJ(U93x3t61w;y;#jzf3los z>11(Z`N=$=*_Zh)Q#%tA(`rUx#yt$e4Ez6c{@?lU$KUmTpZ!_(=jQLlzpwpT`|HWi z<3HJc{`lea!}-VD?`ywHf4Ba=|J(a-z28oMYx{Qj+v;y@-~WG0`(FS3<9D4O%YHEZ zZ2tN0XUi|y-%Ec-{dx3f{a=%RV*jW7?_l`H@Ru=zset(`^K}+gR$aDCwoLX(>}?#g zIqEoTIeoZHx!!Oc;ELjwGgmR!BhK}lqMQdf9Ov-kaN;<~K8sz1{VJOe+jrJnR#w(p7G{=4<{wNo zOfMKq8E-KZFI^|HJ-I z`+w`dAVVNS9m7tB+YJ1SYK%dQ@r;d(t&H;+XD}{joXI$kv6HctF_STt(Uwtzk%jRk z!(oO+3{?!F40;S~3=jWr|3CGA&VS$kYX7Z_~eP|6c!-{%`ibaxB-Gdzkf^A28)H z$uP}ebYD@5nEhDt5={Mz=*^!K^net#JL z*8O$=ckthw{{jp@8B!R-nYJ;lXJ%yi#bV29#FoMqz+S-a&k@a`$*IVBi*p}mJeNM# zR<1&>TU;$%XSve3CU8k}DRC|2Z0G#Lagalg19Ya$VKyzcsjPol{8@G~e`QKz>SN?! zRAgB8f6G6&f4+a;{`vTO@$Z$ta(-3(^!u6e!~aL|_oDABzwPh!jZ(WnIn;728R_# zILABoPwe&V+3cU#jNajS_m$tdze|40{5Jgk@)!5-E5De3-~A=_o9VanZ-?KVzo-0u z{QJ*uzdyx)j{W)i$K!AP-}8UD|AqgX{_pNT+5fTs=ls9?pP#{&p^RY`!x@I>3_^^` zjBbp+j2Vn+j1`P|jAe{zjH!%%jINAYjFODM7#=YkU|7u1z!1k^%^=F~`Tv>!YyP+V zPy6rqU;RJJ|M&kc{yXq*#lN}#djEC&Yy8*rukBy&zuEs*{5$;b>c7ALg#UZ|PyWC7 z|Dpf94EhW$49gi<88sMt8P_vPGFdV$VLHI1#q7wul6fby6pIQ=HA@Z4NtOdF|5@I$ z$gnc7%Ci1oVPL()a-3xXOFoMs%OB?L%(=|U%tx6jnAn(hFq$$xX9#8BWXSrj^uO|- z*}t~G-hZe4N%^z(ckAy}$`QiU_`cIBuIlmtLO8kBEcj2Fxe`fsU`*-1=<9}g>T83!GJ&cQ( zelR^`)?^W6^i}-)${}hHl496K2nBFj%GXG^Z zVfn%$$$Fmk5o;$~Cz~QWKl^0%eD?e7tJzsN4zjawY+=98-poFY-GE(({Q%oiHWM}$ zwtiM0)@v+HEPO00nJt-5FvT)GVyt8Q&9Hz$lHufk$Nzu+mHxB&x9e};U*W%B|1|$8 z{=@r+`_IPTdwv)F?)vTbyXd$3@6z80VGpuE}&hVOnk5Q7*gwdSQi_w)afYF7~ zjnRZrk5QbFi}5|fZHB!Jiy4|3QW;zslo*&8?)~5Yf7buH|B?S4{>%O6`2XtP&42s< zZT`3L->iTA|N8#*{G0G^=D)@Nw*Nc!@9Dpv|5X0l{4f6B|Nr9u_y27eVi-0uTxQT` z3}D>Ec#+YZDVS*&(4mo%-fjxSwvWpSrS;5u*_gN%d&&z9?JoiD=ceS*0I#H z;Yat6M?Zf4i1^v{ z^TSWuUn_qJ{%-iq@Tc$($KQ&-O8@%*dH>)1zlPy1!v;onrUy(q%xo;KEV8V=tnzI3 zY;5eJ>}T0evlns1bA04D!{N>;$XU#(%UQxH&*{baoZ}lu2S+6b6UTM-2zDX%J~myp z<*d4_D_F!?rZB%}Dqz~p=*(EpAjshK|NcMDf1Cc^{4?Rt_TP2CSN>}FwfX1lpErM8 z{-N+g`$zZp72jpPdwsw5P2~H|Z|vWXe^dVc|6BU^gzuNWzxbZ~W6BTFpNT)8{q*{E z;g|RC3%}$3-2YSi_s8GG|HS^E`ftqemcf&el_{8skJ*QrnZGd8{j0?b$5Z z_Oq>ElV<CSe>wgx`7`T};~(cg-+#0IIsW_M z?~T8&{oeBX(eLxWS^qHnvHj!vr~S|DKllIq{p0dC>+goYcmFE>3;s9d--&-L|1JOL z|6lz7(tj2P9fnATN`{3DI~Z;;ykuZuJ_GMP>> z-D9$0_GVtfyq5VN^M7V{76+DUmK>IuER8JlSn621Sdv&`SaeuKSne`!VlHL2VE)du zoGFQkiD?g`4+e^;ZvJxn<@jsI z&wW3ge}?{i^@H>0#vgZn%=mHs$D$ukew_ay^;7<5$#>sOWS-$qx;97@3KD@ zf4}@a|NFY{$=}z1uls)e`~L6BKh%DJa-HhWn4edFs{C5;i{*FY@1MVm|NQw={Fm!r z)jyg4&Hr^6IvLCv+ZoN6DwtH5Bbom*E3+JAxyF*gnhPE`iD7eMTgFz)cAjk}+jX{S zYzNp%*t*zE+05DQu%2ekVs&SI!m^&ln}wZaF>?^}N2V!E=1dnEs~CA1_cHh}urf^g zANv3CzxDqd|4IJa@OScGqrZxOPyRXbr~FUtADcgZf297H{So+M`bXxE=O4R2MSqI_ ztogI^&$mCke|`Vv{9XI^@?YV9cK@pXt@`)mpU8jD{}un2|G)PC-+xU8FNQ*fE{4?% zdl_ypJYx9H@QZ<+k(rT;k%93)!v}_!3|ASBGi+p-%h1A*!w|?|${@w?`~QRg2mUYr z-}*oAzwdv`|5D(d(!+nJ{vG_c@!y(%i~lYDx9H#Uf9wA3_;>Q(jenp2vHjQi@ASX; zf6xEp{~!HVV{m4ez_6Bqfl-dJjByg)p>EKRbWU|LOQM>8IFFr=MIu zoqo#yO!yi4bNJHvcpGkL2H)zr6qQ|MCCN{V&B($e_Vk z!Dzx%#bn7`!feEnz#_?N#rlf%59>m<#cWpWy6l_Sr?Rti+-5iD_{;9Z@ta+p;|lu= z_Ad5Tc6oLN_Q`CqY_D18vTCv(V+mxr$(+V~mnoO&3S$=IVTMG8jsGM5FZdVqZ`$9G zzccNMf%WA^L#pcH*$>z<*&8E%vi1h>OeAZd4F06X2 zS6J4u1hUAm>}RfFR$#u!RLP{kbcQj9QI2s3Lm>kv!>Rv?|F!=g`8VUA$v@$LTmR1g z>-pFA@5etM{;d16<4@zCseiKmwExNa)BmUL&#FI5|6Kob_mA*j<-f6iOaE^8d*Ltl zKZAc+|0e%C_3!IH&Ho|)oByu^r%(k3SB3lyYiTx7V;@QmRp!&`>u3@;e& zGTdM|!LXNMCBsaHW`IO|EB-R|NZ|P{#W?V`v3dC2mh}9 zJN$3ozm5Mk{9FBR-M#2#>tFp7+IL4n2MNMnQk$?V6tR(WbS6}Wj@b*ikXS!8?zz{FN+onAB!x@FXkW2 z=a~00_cCWOn=&&opI~ZZ@?`qXxPvi*QIc^jLq5az|9k%1{Ac-J`_KE|{=bv|vi^Pa zC*+UipLM_I{+9eL_{!ab->2LkNoBvAwpZZ_Gu%989aSLM_(-Nj|<}PL{mP8hA zR#nzxte05R*;3fvu-#${U^if&%$~}=g}st}BYPHm54#n+3;Rp9J8V^KVQlYMx3GG! z{$%N4(O}ub?8bbDDU9g@V=UtzhByXRhPeNH|C9d7{>%Ss`nT;*tEBqc7F^1HvirE_wrxPf13Z||26;H{_pNTzW-+bQ~r1V z-~9jP|3Clb8LSzi7z!A=7-ll8XV}hgoZ%$HWrhn3ml;km9B0_gu!&(2!xV-FhFpdS z1}6q>20@13|L^@j@qg9-ssGFWr~G&SZ}wmMKi~ha|6c#Q{_n!Sga7vb+xBnAzit2a z{5$mT%)i_JUjAeJFZAE!zsLXL|6Tw0{lETSm_e7JkfDd+8p8($d&Wq{Rg4E2MVU02 zs+qc(ZZSP&GGI1ku4JxY-pst3`8M+z<`2xbm_IUKX1>F`op}Ru8*@Ih1G5zKQ>G0} z6->rVKN*)Z<}mUyUSddR&|}#3zvVy2|EvE({;B_4{I}-s_dhrOg#5Anv-kIg-)6s! ze;@jF;#cyol3y~v%zwT6Dg5isPp)6je#-sg{pI^B=-13&n}0F<*8ko3`{-|}KSh5| z{xSaB{g>@u?7wIKEdQVTFUzo-fthhR<8#J7rlU;R%+r~zSfW_Ivv9G_WnIjw#iqnI zo2`xQ9ouO(MfOi@#_T`XRN3#dePdhBwvsK7&6e#x>o(R9R&mx%EQu_?nP)H?GaqD% zXS&DO!1#q>4g(v*w*Qji53i{C|r6T>fMFcky4w zf0h3({|o-V^uG>6DZ@VoJI3RTe;AvY)-dTYdov$nzQ*jy;?1&|WhDy>>w6X(R%zBK zR(;kuRxMUfR$f+F)|)K1SSGSGvzW6;v7BaJ$Q;Bh!+egZkI9MY2jd3D1V$Og;|!e) zh77;{FZ^HhU+VwofAjv;|I_~`_3y&pBY&Iz*8g?;>+@Ikug+igzjA+>|0@0!`fK*r z;BVC5xWCEF43hruQ3p?^pIo%wg|-@|`j{xSWR`)}|+ z3{!! zS%y@G-wd{lHy8z()-v5?s$ibWY|P@za+l>bO95*k>uc8gtX^zJY`tt5Y#Z4c*|xLQ zu+3+SV#{QcVH0CJ#JYmjpH+$VC`&7g8p~DYGGc5(QrvFs_J^p*=@1nnp{}%nN{u}Z)<*)nSxWC?iGycZ?ZTj2zcg5cge{cVN^;i6# z&cCRCCI1%w+yC#)Kc4?)|HJ>+{-5{%!2kRI|NNI^Fl6vzh-D~ZsAuSBn9i_>VF|-3 zhUE+^7#1?jVVK0w!BEAJ!w|#Z$zaZ)#K6z+{r{8yC;xB%Kj(k<|NQ^)|6Tu^|Cj$S z^8eqzZ~vbByZ`UfzYG6P{5$>cRaDsu4QI#=|v6=B4;{!%*CQGJ9rXHpXOjntNnT46Xn4OsmnNyhCnM;^^ zn2VTenPZuwna!A0nSV3gWm?bF%oNBZ!SsZ2K4TW6B;yl?4u(*M5C8Z75B{(Ef6Kq_ zf8zf>|E>L-@b~qftACRJg#LN)`_b>3-wnT&f9wCg_v`bo^}o*jn(=G@uUWrN{@VKM z^{-dI)PGz2uKqpm_p{$Jf71V~|MUA#$lry3Isb+IJM~ZW|J47t|0gpnWiV#UWBku( z#B_w|EmJM?9Ab&SL$+dY4s)jg8HPO_@T-p{J)%jN&gD}<@~GdSIe*SzwZCi z`yKFm@$aj@wf^M%IrK;HZ`$93e?|UP{5$^7=zsP9xBvAR7Bk#o2x4qv{KhE5)Wfub zNtjugxt+O}`33VMW@8p*mP{6ZmJXIwmWeFsEUhfTEU7HIECwuJm|rumW}eC%%xuK` zp6LivBa<%^H`95>iH!b?B8+DlrZf05$TD2|zv6%Ff2;qW{$2Yw>0jMH`+r9Ne*OLS z_sHK9f9L;Q^|$Nq?7yvl=l<>gyXNomzbF5m`TOzjpT8>qO#a3FEBQC?-;RF||NZ-? z`rr9~#{aheEB+t-|M>sE|Kbe#49*M@3~3DI3=IrD3=27@>QBg6au*Z&{;zvlm(|Be5P{zv}z`EUGRd*Z;I(x%l=>e|N6fog9Sq|Ll?tohKCHQ zjG$dolNoO@zG5_BvSF%cYG*pibb^V2`5%)Wvl_E6vmJ9ZvpaJHvmLWDvof;`^LM7l zOgouoFl8~>GVwB9W?aY^!>Gx4i(v(WKZ6{@zW)>cYySWLZ_2;?e~kYg{w?|&_4m!6 zTYn1vr2S#~!|-SE@0Gv9ey9GH`EBz1$1jQBuYU>s{`yPhxAz-hX+91q^o>k{G8m$}{;eU19ppRKq-tS(HVE zWj4!v79Lhs)-=`-*5#~StmjylvEF7~$$FM`GV4;-WY%O>X;v=Qy)3g?yjdhzjxo0| z8!|s+>Sr=yddyhOsL!~Mp`790|9$@*{yxv`8abgb0f1Wvo7-orgKaam{OTEn7%UZWNct` zXXIqO!Z3*;i9wFx^Z)h#yZ*cV*ZBYZ--Ulu{%ZIozWkH?Z}LCEIf{23A$vKh)5>KNJ>+8MeTIvCm+8X2k?iWt%vq8WS{>>2bJ|EGWN|2_Km=-=&s_x|1a_vqiVfA9bO z{>T1b^1sG^r~kqKOa8b2U;qEe|Ihz980;8A82T8NFg#}X&S1&t!`RQbfblWo2S!6C zYo_W##GLv&2*8mgHeU? zK0`5sF~h?@#|9r9ZNNcKqJ^JLz}9Z^hr1zrXzw z|NZzE@9*coB!4siw*GDUyZCqQ?*qSY{#N+o{%69UBY*h+`v0Bz_sL(KfA#;4{FDD5 z|9}7gpZ{YR`We13$TK!GE@Nb8Qe>)S>SKD!^oGfX*^0S|xr})W^8)58%sZGLGVf!) z&Af^E2=g@NIn0^Nsm%J!3e3-$&M{48DrT}`5@5Q)xSX+o(Tb6s@eIRUhIj^3hHw8b z{-5-}_`lhIng5UeUHCWaU*Esje-Z!m{^|eY{wMtJ`(M_7AOEua`~H{zAJ;#%f13Y1 z{ssIi`q%Jp@xQJAZv1=qPw>CyfA9Y(|C|5M`M>l3#s9DVGct%VXfaqZcr%1Eq%h<# zlrmH>)Pi9pLkUAJLn=cwgCBzvgE50DgAfA?!~6da{-6B6`~TwqQ~%fgFZ>_*-~YeG zfBpZ`|3&^Y|NsB*1eN)3_}}4w%>Uf~{r?yJKmGsy ze|`qgihx{(WemF*zB6z!dNM{c&SG52_=52>qXClzQvp*6({iR|OqZF?Fnwft%EZY0 zfr*LvHPdIN>r9uKHZv_@s%J`NvSkuydds+naROr~qbB1Mh8+yq3=Rx${vZCI`QQ2f z(|lmFQNar*P^H{+juzc2ot`Fr#4j^8VOcm3Y< zd(rO;zt8_>`Xl(q<4@Y3MSsr!;r;9Rx8v`nze4}g|1JN={@?Zgy8kc!M=;{FZ4AQwozfcvR7x@jAmy1|xb|Kk27|7-l${cqL3o&RovTcC>n9sh^_Fa6*9 zf5rd(|8M+%`=5zHj6t2jg29y`h#{IGl_86vfT56~n4y3nk0FC0ks*@7pTU*EoI#sG zih-Bm=l?hVul+yyA9RO8-~Zx6J&y3nkhD@nUX-rd@CNk}0+Q4*y=`hnRrsGUknf5XrU|Pa7m#Ky+i^-iy zi;0=(4&!FVCdLRxS;o%{8yMOctQmwE4*j3|-~GSJ|I`20{tNtP@$b&x6Mysm#{cE` z%kX#IpS6DyKxg0o(fjlHH~XLKzd!yy`}^JRYrp^f{_>*e2n=7 zvjU40O9o2>%WRe=mYpm!SPrtxVA;ab!ZML1nkAY=fklwz67x3Z5@v5^4(3Zt{owZ8 z0mcSK9me+zlNq8Je*Qo9KjFXe|I`1L{ImWi^Ka|lxqmJHYW_X-=kTAbKiPkz{;2%9 z`}_UxO~23np7VR(?^(YO|6cR^_U}u-+5T|-arqPaXX2mrf8PC(_#5%J=kJ-noc}!j z^?_T_e*fqHzw}>?A%tNi!vzLaMmNTpj2ju5n1q<(n9`WmFs)&F&GeW_ikX|)idlu( zlUbM9gIS%~f?0@JiunW6JEnt7+nL&#N|;=jG?{)dUSnLwSjQN^sLc4C;TXeQhAakG z1_6fm|9AbL_dol8$bY5(g8v`?yYX+`zZL&l{l zul(P{e>47V`FG&o-G6WYasQY7Z}s2*fA;@||Fi#Z_s4{|EnX{Xg&j)+RZzyAID$M~Q9zuWTy2@3z#-B&1YJ{G=Zs~shBB)$%Dy+Nr34S;~B;!jJ1s3jLM8J84fZuFhnv4 zGQ9pj?|=1w&Hr5gxBi>;&*h)qzZ-v#|1J5O@mJ!n@ZaNqPW);9)BVTqPs|^UKem5_ z|LFY@_+#`3R69ET$@^3GXW5^-iL;L$R927iVv z3=bIG8FLx0GJa)@VJcy|&h&!GjoFWRDf1HM&&;ow^;zUtB3SHLa#_4ta#>thB3ZOp zj9C6L|71SEypFkuIfPk^`8Cs8rgA0=rXP&E8H*Ur8J{t%XNX`>VL0)B)_?2&!v8n@ zoA6KnpXk4Be;56A|7-pC$)5**Cj6QD$LCMvAE`gOe}4WJ`SbcW@1OU-CH}DgG5KTg zC;3nIpM`(6{(1k0@2}_I;=k+vKKd*BFY@1{e^>u;{}2A(_W#m<1_nQdGKQlJ&lyY^ z0~qHpZeaY$$jao&%4|91YH^>5C900 zX5eOEV0icc(f@P*5C32HfARmm|1JOX{-^y9{O|SO{J+tE#s6~u`Tz6%XZg?i|KC5R z|NsB7{^$5F@L%G;@_&Q>w*LeE$NjJP-~NBu|Ly!0_t-iT}0#HX9DXZxQce?I-;`D^<( z`R}y9C;tBVYxytl-`0OG|5^Oc{(tcQ$N#6CmIe(A+{rA`4 zU)sL~|E~NK`S1F_^Z)k$fB&m7;>#$@rnX;MEnWi)K zFl}R6%5;or1JgJjM#fRg4=MA22>)j*YwZqpV~jrothngoBrDUwfp=356j;(f3E*o{%6OZ34fOV z>HM?w&!j(_|E&IV;m?&n|Nij(wfYdt zhEj&P4Br`~7)u!^F}`7BW{PIYX4=nmf=P^7oH>p;f_XY~7xR8*&}%(t0$GM{H& z&b*PimAQpEnAx3KnE4OWai%3qc}z}BJWSUa7c!i^9CQ}<`VpT0j^{;d6T z7+y25 zFuF2EFivM&%y^IS38NH~6q6T|3sW9b5>p#f3DX3oDyBZB5~fC`B&G~-X`#p@!StE& z3FAJ-6^u=c8H`Sh%8dURZZqs;n9NYZ;KQKLz|QdY|Iz>J|9AW^{~z?<^}o`8ssF$K zegAj&-`#)5|DE}_``?j&JO3U1xBuVye`o(a`1knV_kaKYiT;=WZ}#8$f7Ji1{|)~q zfJ?Dc|8M<&^Z)mME(Qq(6$X6A&57z5hD@<^Rk57x^#tpZ~w` ze}VsE|0Vt_{8#&L@Zb8s`~UF&DgUefcl=-SfAjy#{~!P7V31&NWC&!aW$0np&2W<8 zCj%R!8KWa(A!8-u3dXgJHyCd*GBPnRDKW`1SuzF``qX6T3hJE0Zd0ZJd8D9Ng^}qMO=YNC$ul`;6*YmIHpYcDne-HoO z_&e?Iq`x75L;uSDRr&kz&%ZyH|2+M3_|L6BhyL9CbNn%zlMLi|GoUD`#%YhUB>p@9ulc{^|AYT542}$m4D-Q#R31im#xTZy#uQQ|F8dF_&?yk>woqCivQXFv;Keg@58@)|DOE2{_o+xoBy8vd-U() zzfb>I{&W79`LFul^1sXf$p7j8YyNlrpZ9;=|AYT8{D1iW!~g&Pc^D)aR2cLa%o*$$ zTo^nUycv8Md>MQgJQ>^=92l$_j2N^T6c{8JxEL52KK_64|N8&4|9Ai2^nd>U>Hk~) z*Zj}=pYlKWzu$km|JMI?|Lgo${IB$1^1sY~@&7XarT;7aSN*T|-|WA`f8YO+|Fi#B z{O|ui_y5lS$N#_h|NXx-gEm7jLlQ$T!)%5V3|ASL82K1&7+o2Q7%LbTGcIR5$9SIc z6XSbEJ|=c11tw7@btXwB6(%7jF(wA4Ka5WpZ!qpsL#mH_>AEg!!(9+ z1}6p;hL``({_p=^`rq`w-2XfOPW)^8SN6~3pXR@pe;@r_`gh6S?7w+`t^d0G75}U9 z_x~TEzhD3G{QdDqt-soT{r*P&t^Yga@7}+6|FZux_!sl9@88aUU;fGePx#;U z|HA(t|D74q8MZQ9Wl&+XXY6BK!uXMqk;$7WlxY^zJf^!$x0r;PIhie)HJJmMt(l{k z?Z6}5cFYpYQp_KjUNY@vTFq3;l+0wzB*65LaVujRV+f-f<6DM93@r@N43Z3A{;&Jr z``_)q*8fNUPW@~CSNKoopZveOf3N?Y@ps1G_`fNCjsDvH75c0Em*KD2-`{`4|FZs7 z{VVg=;jiQ0^uL9FXa8OM_r~APf93x<{>%M0_1}Si@BWGZ_xfM>f7$<2|Ctz+7@`@< z8P+o#WBA7)!05^t#Mr{v&A5wkH{&zLN1&7P80DA*n6#KAn6#NhnUt6~nfRH$Fn(dY z#(05oBjZx=o{JDhdq#OiR>o%xCmGf-^e_}M1TdI0h%)^DfA{~X|11B`{9pM$?|;C5 zkN*g{ z`M=_Swg2k>_5YjvxBc(-Kk$G2|Lp&D|GWP${=eb>+5fk}<+mP#4MQ?RAp_{#u}cj1 z7`PZk80{I|84DOo8Rs(2XFSY!i188QJ;oo59~fDgelc<~{bFQfde8WQ@iyZX#yyPd z7$-B0jwT^M88( zKL36Hcl+P%e_Q@`{EhmX^4I3C_h0S5c7N6W+Wyu1>-pFDZ_?kiza4+4{oVEV(%&C{ zCI318%l|KIn2%l{w$Wf+nfS{TkUykan6^k$sOxSa7F<8MZLCO4*5 zrZ%SiOgow0Fg;-6X8y$_!_3Mo&&AC$uWImJkPk8 zv4YWuQJV21!)}Jj3=s^*3}60V{tvqA+vva4|Lgw_|7-kL{LlEG*1xxZU;f?rck|zd zzwLjc{-*zR{2Tn&^smofv%i60cgOy%|J(d`#osM|Z~uM&SNxyRzvzFp|Catc`|ry? zh5v5bFaTF#-0Y*ngFUAT+&`sFO7*8`EV+56y-x*&r{sW&@ z_?+|APOM|Cj&I{U7|__rLjn6L8rj{9pV(&wt_nJpaZ1i~LvkulV2KzsY}>|K9&& z{-^z~_}~10>ipS#8SEGw8Jrnh7+e^f85|gF z8O#`r7&IA_86+448CV$p{D1rZ>Hn+$&;H-@f6M>H|7ZX2`rq=u_==B&^R3GmHZ$C2c*elbD8cB!=*F1KSjae)aT?<`#x0B&7*8`kV!X-tn(;p4 zE5_T54;arfo@U&}xR!AmV;f@*V;G|~qZ}g><9&w146_*O83Gy17+4ry{one3?*G*P z0sqDRv;05xZ}-2(e`Wt{|C#;!|M&0TQ-4qYo%?t3->Sc@f0OVu z#iYvghw%a9cE-t!S&R;h65u^HeGFL)Mht=s*Z%MLU;RJvzw&>+{}=x4|JVGl9Pakh zf6x70_;=Oc=D!pE=KpQ{oAI~)Z|>iYzjc4-{GIi8_uqqmpZxs({ z{`>b&`M=Nq{QqtpEN0%l_y62c0kH_}>AXHq`!W|5y62^0$f822#lXI#U$f^jlq2V)^)0;30`0i!tMPlo#pdl(inR5Bzm7&AyQy!wCn z|NQ@5|3m*f{pbGw=ikwPJN`BOs|4@l=l#d?@7~{sf4BeL_jl&srGMN1PWxN`chcXc zzcc?%`n%@u+P|m%UjF;}FY7<`e~$lB{AIX&cjgrVUI}ndULo zGc_>9F@-Z3GpREDXMD+cm~k0n17j?sIU^tAONQMHGa1qt+!zEIKL6kSf8qa(|3UvH z{@997G|Jwh3{+Ilp{D1HNoBvrE3?nn+Cx)90M;X>KOlN3d$Y2O!uw+nU;9>av|Ka~j|9Adh_kY^|-v8zQi~h&` zkNNNY-}}G)f5-n8|Ly);{CD_o```V)$N!N35&u*F=lrkw-}Hat|Jnanfy=M+|L^>N z@&C*J|Nl7|gc)QQlo_-b3>ZuqEE#MVY#HnrY{BUhRKsgCC^N`12s7|7Fn~wvpZvcH zF2Q&FU;cmL|B3&5{@47k_@DVd?SI7ou>W5Fz5YA=cl>Yp-{!wLxYl?0@ATjEzu*7x z{|W!I{+Iu6`ak*qJa8#~5oAH!^h zCWdGRX9fudMuyA(_x%Y2xzW==b$^4W3_v!EVzZd`B z{JZn-(Z8$z?*6;{@9w|r{~rB&@b9g^_x}F+%l1#@pZULtf5rc%{M+{L#y`gYO8*1@ z7yV!M|LFfe|D_m081fiaF&t$0#~{e)!x+stm2p1fHOAYFd`x^yHcaMB2~1&36-=2- ztxQEsZQz-kB&KvGPbLp0btXBcKa6h}Pcm*|>}M=u^kdX!^-_vGJu zf3N&~`}fsf)_TQE7DjbOEk-X!PsTLHB*rSnBF1*cYQ`SMTEjzCvbR6GqNy#XL!JHo?!>WQicf(RSani{tPw@sth9F+7>jgv-$tZ|5Ly*Q1(CV zf6D*x{}KQFz@w7^|9$?4{tx*d_dnr(4!CY@`9JCZtp6+iZ~DLg|Ed4i|3Ccy_W$?) zObpx%A`CJNDhyf-`V1xv<_wk$)(qARRty#lW(S0{~!Io_5b?+^Z(EK-~GSsfBF9ssQqF8eg6CWclq!3-~PYze~14r|6Trj{rCGH z`akA>>i_)zmH*rSPyE05|GNK2{$Bv+5dj7n1}g?PhBSskhRF zlF^dUhB1;cim`;Th_REgg>gD#KjU1+NsO}@`xqxOHZs;T<}#)-1~NJ`>M?@C>kY#d zhV2aV8EP5Q7#tY17#J8{|KI;3=x@8iEi|91ZC`q%m|;$PT5oqu}& zIR5eed-M0}-y46Q{yq2i{@?R|AN{@g_x;~@f7$=>{L}bn`Y-5T`oGqH3;!MX_xK;v zf9?NX|I7bR`+xlZlmC(o#tc~uO$@sj&NHwxiZl8#Ml()eoWXdK@hsyn#-EHzOfpRN zOr}ghOwLSEOzun(OpZ+6OomJ*OyW$!OkWsZGM;4I!8o09f1Cf@|Ns3v`|rrVY5ykvOZ}Jf&*q=~Kk0u;|5*O<|NHuv z<==Tar<^IR=Pv)QWKhuBK|HA(z{j2{s;orJ{NB%wf_vfF~e~bU2|I7YQ{J-V@ z+5eyZb1>*JI5MO&lrzj>Six|H;VQ#-hCd9_jIxZDjOL61jNXh1j1i2PjB$+FjIoSq zjA4w?jGl~MjAo3+jB?r~HrkpZGuKe-gN6&-`EfzwCeG|Mvfr|Ihlr z{Qvs@d;TB&f9e07|Ihz_{Qvtu3j;5MD1$76GJ__A9)l5sDT6tK1%oAnIk*LCz@WvT z#vsQa$-vLR#qjU{kN>a#KmC9G|Hc1@{_pv}_Wz3iGyhNd-}b-hf64#C|Ed3z|400f z`0xKe;J@d8@Bi-LmZ{%=|NmkCqd;eV{V)1o`@jAFwEqkKZ~njU|K7cq#8Ajk%`lr`3Byr_GYoGTJ~Ie0iZL29f@VDe7&91C7%Lfz8Jiia7~2@D7#kUj z8A};c850@(8QmC-7?m0M8Gkc8WjM>QjRCY5Ac?_;L7hRE;r0I;|JVMX^S|JK;(wF> z>i>WJ`|$7ZzdipZ{_Fjh@-O+H?LUWqa{pBRvHcVH_x&%+zmISNm`BzYYJ+{Co9}=fCFv!2fywXZ_#y|LOn#|BV>D7-|_NGaP5Q$-v7f z&FIG%!Pvpr$GDquFXJ=D$BZ0I3``14B20Qra!iIya^QZk3==bWoa`av5yl;ivlu%V zGZ_OJjTprkzcX9|&p1>t1TdH}a5H@VfBgTZ|1JNE{=57)`_J+J@4t)xPW@Z(Z_dBc zf2IF|{)PQB`)Bu0`Jetj$$uLEB>rjrQ}}1{&)}c?KhJ+j|1$qI|C{h{^}jvf^%z|L zmH*rSkN#i!f9n6W|Iht@@}G@Ciouq_kD-8}j$uB-Duz=G7a86&d}iQhL+6lD}-{KxQ(;Ss|Ph9eBy8I~|iVQ69~V2Ee% zVX$VHk~*kN@BIf6f18|EK+*^uOhQ)Bp1S<^S{k7yi%wU+_QofARm~ z|JC3$(fxnw|2hAc|6lii*Z)KR&;7st|Iz=~|G)nK_n)1CpFs?oLUkDo8B7>V8O#{W z7)%+A84MY87&IA_7~~kl7z7yD7#P82=%fF4{-67Q^8c>?TmLWrzv%yz{}cW<{jdLD z^uOSL>i?AgQU9a=2mcTG@BcsOzu*7h|3UvF{zv^!{Gawe?|=FKhW|bPr~Y3Go-a7_ z|Iz=q;GUWmgDFE0LkvR|Lo>r7hE)tF7|t`iW%$Iv&nU#G%c#fb#^}lz#Tdqz0iG32 zW6WnvX3SuWVvJ<;WOQLPV$@(1Wn^Xi$Z(h81UNs}F=R4$GMF+5G5r63NCbNmSQc!zS?TVHAT2gABu$|M$V;ilA1M-G8J19RL6SyZ-Ou zzt#U%{%iZ!{x9QS?!Ul)k^h|j`Teu`=l##_pZ`CPe=+}}{uO{vdYSxh&A*-hF8_P_ z@BcrM|9b!3{>T5X{y*vehX2R@Kl%UTzXXFOgC|2cLpehe!$O9Y49CDb(qA$#F@jEW z7iLsrlxNguRAn?`RAba`TyPjJO3~LKlC583U1l|ng6H#@A}{Qzu|x5|Em9W|EvDjgU{w} z`QP)u|NqSY^Zu{+zwZCe{|Ek`{(t%Z-T%-2fB667KO+Mtg8+j#gB*i0g9d{Rg8_pP zg9#Xd(x)DSHiH_25`#2@7y~Z@2e=G<^Z(iZTmP^8KmPyV|1JO5|6llj?*IP(Jz(1k z{}=pE`=9nd_J7>}@c&W&L;pwq5BnbtF3&+NVNiZ)`QQ70=Ksb2H~-)J|NQ@3|KI=r z^8>7Z~0!d|=>aYwXB<9`nSjQ=_Rv-#)$&+lK- zzx01~|2qFI{I}`fX>d&`@!#lw@c-QZlm4&%f93z{|6<^MvN_JKmC=Y% zf{}so8N(@t4Ga?*DjC8V92jI6*cl%FKlgw6|7rj8|0n&o`fu=`^FP!7d;f0!+x~Cc zzv=&G{;U1h{4e`o$-jhuIsan+<@`(fSNt#UU*o^Vf7AZW{i@_8-}?XkKPQ74xNL}LsAgzoSje!P;UL3NhPw>+7``xkVqj%t zWE5oNViafOW0YXzWfWm#XXIl1!|;dUEyF8@TMSnjjxg+DSk17IVFE)7LkU9~LpXyw zgC&C|gA90n{KNle|F8W&_y55EUH{koU-5tL|Jna1{-5~26WlWF`rrP)_kYj-$^WPR zpZkB||CRsO|KIU{|Nj&JFZ{m+El0Vj2Vm=3>owp zbQv@m)EML$q#1-6_!(Fk7#P0(|M36G|NH+h{XhHv!2jL<*Zp7hfA0U8|9k&;{jd99 z`@iUa;s1>Png0|2C;yNApYT8Cf5QLx|0(~|{^$HJ_+RnA9^48AwKg{VKk)w)IH&yi z&&D9jpw8gH;K=}5``6CU&#;PN1H&1H3kN46gS}?jY+A_K^ z+AumXnlYL&YBH)aiZSvr{srgha}0YJRxnIusAb4z@Mo}PkYnIwc=P}6|6TuA|8M_a z{Xgu#_kXqja{qt*`||JNzjOcA{9E&{_h0|N;(rzY68>fU3;7rSFW_J7zuIqW85y(~>=-f_DjAkAY+|^> z@Pa{*QIgSt(Umb1JPy&pxRh}|;||7kjE5PwF&<^y%6NcrE#oF|Kd+s!i7^{I4q?ft z0Pg4AWjMmHieVB%2}2x%9fKwVJHwa%7yj@6Kl^|0|BU~!|1JI-{OA48{{Pv($N!G} zJNj?szcv3R|C{x%^)(NYSO4Ar_w^t1f64z^ z{~iB_{?Gc~_=_~%QW!w1g6A_VVc5;EkKr=IWrk-Aj~Tu& zyl42!@Qs0y@dpC~<5z}X4DT2|Fg#)a-F1C|;UL3qhSdy<8Ky9FGE_6oO+^b*xf8qZH|7ZLMjR1ja{b~QF{-5=K_Wwoym;7Jz zf5ZQs|M&er4zA(v{eS-d-T!a@|NUoW;9(GEkOZetbp|a4T?Tyy0|rBI`qX340^jMO zzyP|-g9luOf=cd}|L^|4@&C;KyaV!8N-MqXMHmqaY&><6nj^3{M!Y zGaO^s#;}B80z(}`8bc_96@wN755wR8cmJRNzwZCS|MmY%{s;c|_^?uo{x|<$+rOTFW&i5`<^HSqm-DakU*W&Te|7&R{G0r5*}rxF4*$FG@7cc} z|M>r_{kQ!e`9J@E|No``kN>~(pOHb7!J5IBp_HM8VKu`JhWiXJ8Tc4Q87&yC8Dkh@ z7%Lge82cGJ80RrgVO$Cxjhf0hm$93%kFkoek};VvmeHNjhEau4l<^Tl#O#zg7R1{M+(x^S`72PW-$5@6o?+{}}#@{#X8Q@!#`*?EixQE&pfz zU-SR)|11CB{QvV`ltGEXn!$}Bjv<|)o}ratHp6^|Eeu;3PB0v2xWRCh;W5KKhF1)a z7+y0xVtC1LkKqx+HHPa9rx;E$>}J@`u##ah!&HV|hI)ol@G5tI1}6qH1`P&T27U%M zh9Cbw{D1WS_W$$$PyXNcf7kzw|JVOt_J7&`1>l-@!T)*x7yV!Ke>u4H-28vn|NZ}u z|3CZx>i^sSAOC;#|I`1U{}~wA8Tc4Pz$sLTL5)F+L5D$)K_3kD7<9nBB+y8cB!dtG z9|J2mSA6>a{Qsl>*Z*Jof9(IE|69SUL>K>`{(tKKuKykX>;BjMF9na#7W~itpZh=S ze=c|=q40nC|0-~Qt?&Qz|MUK@`oHP_f&VA}-vqC%`uksq0W|Vq$q>j8!BD_Z&M<*t z8pAqpeqa>p|qbQ>oBS`c&!&io940jpM zf_s(o82T8h7%~}x7@WXu_CNpM{lD=4@c+gCXZ)`K@9lR3&v-HZ|NHO$zkB~cyZu)G zTmNsyzxn@q{!RVY20nFa%D=vU^Zw2Kx9;Dje@FkF`}gSIhkva9CI0LFcljUnzx03C z{}uoD{J;1A<9`tbH3l#6cy15FMut5M4;WrBa53^T>N6TL`Z4-4rZXmk$8A~}s~9^O zYZ%)Y%Nc7Jvl(+3BN@XOofxecRT(82Ss1@C+-Eq$u#I5>Lk~kSLp*~EgCT|F8SM`Tw^6d;TBzfBgTM|Cj&Y{QnSKf`0w~=RY$8 zCj&o&2!j-Z90MqYYBFdu=rZVmp$>yKg9d{tgCc`0gE)9Jl!M{_|6l(<{D1TR;r~1T zFaAIK{~&mU)4KnQ|1bDI_5T!TJGkM0#sA9x#s5qH7yK{&U+};5f64#K|JDB+{RqPW3qeypZ9e;K$KSs4WwIT-~QIT(2u85x-vzcGAfc*by_;UdE^aIT)k(8*B6kjfCo zV9#L4AkM(a@b3S^|407s1h;~U{%8I70k36H{?GQG`TvW5FaDkXck$n@fBXKe{ERSfwI zX$;W}LEv?oMhxljGSo3lVVJ?NmSF?K5r$(7HyCa(ykvOJ@Ri{c!(WCU42+Dw82&SS zXZXqRp5YzCBZhkn7a2}5>|xjlu65eLbxtsYD}x?`A_FJGpZ|~kU;DrN|AzllzO|NDPm|2_Zr{@;^-@BTge_vzoef4~3z`p5B~`@htGh5rWN8b0KI3V23u;{S!< z8tl^l2me2VSCPvw=rdR_cryetB!gSjH4HuAl{~W;mNP72SkJJUVGF}LhArTh*D8iJ z42v0-FwA0@!O+Lh#n8Y|%}~IQ$q>g7#^A%?3|i=QzDCM62JO1zdzy1HN|2zNh`M>x7q5ntzpZtIJ|E2%e|KI)p=>NHvs8sKxC9=}0kw&q zF?;}z=)Y!o&+v@lIm2Cs+YA>NPBH9b*v7DuVJ^c2h9-s*hGd2?1}E?;24RNZ|383p z)TaL{|M&lI|DW?e?Z3}|_y4;8wf_tK7yAGE-=BXk{=NQp^WVLH=l@;%clzIzf2aRl z`*#W43V8GH!@vLk*#C?FSN?DE-}!&&|BU~Y|0n!k_gF!(YAf%jLGF_bg3F|;vEVwlJ!ywGS&A`O)=l`ew zZ~s5}|KR@(@J!^H|Hu9x{eKW#njZMS|Np`NhyEY^fBgUH|L6W+{(t@d9dHf*=KrVv z-~a#p&&0saz{?=WAOX|5yH>|9{?pQ17hwf8YQ1|DFF^z$5vs|C|4} z|8M`_{lEABr2o_Y&-uUT|BC+`|8M_)5WF_v&i_~cKmKQA;9!tsPynx8@nHx8@0F@! zXkwVcFr8r~!)k^-40{+(gJ+MgFx+Rj&G4AvF2iGn+YI*@t}$F=IKyy~VIRW|aPFSY z(9KZCP{5GP5X9idV9cP-0BR9^`2Q5_+BN?d|L^|a@<023+JE2wUjL2$8~m60FZG}G zKl}f$|9<>?@$cQghyPyuyZ7(KzlZ%VW{^^2fY*q~Jo-v6Wj=lrkx-~WHf z|1JN|{J;JGD|pU98$9Qb$56pAnPE1=R)$>+R~W7_ykmIBz|6?V2)cI{)P7cFlx0+7 zlx9?76lIiP%aMb>;InrL3SfSKJD&8rC)6=?GPE;HWawp>$}o{(I>SVUsSN!L6B)V~x*3`n8W}1X${2FN=@V4LgGx}) zUJTGIxio_a12+RJ!=L}({=fVG>i;8fYxL^>^Wc&6lmCzXKMqcr$Nr!Ef9n6){}=vW z0jJLg|DXPU_5c0G1lo?bR)WK;Kls?rN)WCJTEQ2(I zD1#6Ks4vgR@b~}M|DYA3PygTkf8+nT|EIyZVe9|R|5yEA@qa#e#pv|^6aG&Gr^t@~ z-Ty(j>wn+>zWHn_(2f=0NJ#cI2*MHEstO9sHpB+O0Ll{E_ zLk>e7LleVfhG`7T7?v|^XV}JYgy9gwIfhdVR~XJSTw}PvaE0M4!+C~d;8El43~L#d zG0bL|$k4`6#gNAk&k)Su%wW!-3O*s_H+bFkvHyGjFZ)0Lf5-nOa2gE&uL(2zuk>H( zzrcT?|BV0H{{Q&*@873?zy5vt_Z!>`{{N5rKQFkh)cbG#-|c@8czssG|H=Os{onro z*#Eo#U;k%j5MTh!IruX~G88jZGfZWe&9IqaJHt7K3k**go-ll8_{zY_$iT?Q$j&Il z$jK&~bk2Z#Kh75)X1}_G41}z4025tsWYwp_r zv;VjM-|&AHcwa;H|MdT<|AYPqg4aM8{Wk@#BUAjZ@n7M;`hTVW+W$5F8~r!>Z};B; zylyD&f9n6D|JDCH{!jWp|NrX$+re|r_rNQ385sB(#2M5WbQr7|92oo<0vY1KEtXt{ z3h?O(pi!u1hE9f7hAxH{@c#5>hH8cyh7yKihHQomhIoc(hF}Ii@LFOE21D>14`_T6 zw9@GR|L^}l|9}1e`TvLDGzzMDK|PqW|4;uv^Z)e!v*6Tu>HpRLH~!!G|KR_V|1bW( z1(%?I{xdMJGH@~Q!BeOLgEF`jRcC-eH3k(1Wd;QXSq4c4F>o2m4qkot^Zy5Mx&83} zjsI8vpZ!x4r93?~_mFq~vK%y5ihKf`{8?F?HPRx>PTn8PrYp$mHcL>xl^gDZn6gBF7% z10Tch{~!O~{eK1AQeX6c&i}UmjsLU%XZ#QTANb$)zs-NG|Jwhh{>%R7`!D>T{Xg%2 z=KtLPS^o3>=lU-K?g=aX*Z*(&-}%4q|ET|GMi%y5F?B*SfnTMVxmUNC@WVL)Sx{~5k9{9*XS@Ri{exb=CP z;T(AEW;4T5hS?0g3=Is$3@Hqtv2V}}w=8(<`_2Ey|IhzF{(tlTwcvSXPzx&if7buV z|B>MNWxM~5|4si}{Wtt?@!#;jC3roq&3`xWn%t283I9|77yhsK51TDn|9=;B=KJOU zPyZPhI2c43WWeh(Y#1ECdn}?EVj0pI(isXF@);@^N*QVxDj4b*Dj8}SDj2F5N*GER z@)+_M(iu`2VnH_nGWavNGdM9=Gnj(cae&4rK`V{ezo8f9d~){}=yX{C^o-dfxnh7o0wy|9|uU1GoeQ)$yQKC_jTRv^Ux<=DlronbG-4u-u9+Zc8*Y-HHTu!3P3 z!yJa`4806(@DnHk8Jrm`7&IB=8F(337(V@f37+5B^?&pK`TuABZ~fm09t{isANt?< zzw>{i|0e%c|7-l0{;%*~^uP3f;s4VA#lX9+)c>phH~w$_-}%4S|FHjw|MS3Q(e(dI z|8M($`2Y3)kN^Mp&%yv&VGG*l6~&MQj!n>51gH&t5Zs2o0B)PS0Iw{3%J7Ea33zPr zA;VRMs|+U@jxp?H*ut=!VLrnohIVibki-ze;K5+Qpvxe`AjH4`K7|Oh_vY~b4gXjF zpYean|K|S<{|o--|4;az@IUx}=zq`uzW<&7d;E9!?+#w;==R?qyq+xbfAatI|De&n zrvIJ)r~ID_UOTks|MCA9|AS^0KmGsxA2jAD#h}2T!(hl@%isXsV-dj+!H~oN8iC4S z$Y;o5C}PNCC}zlGC}hY1?@`YH%SJOqFa$C9F?cXIgV(|tGH5fXGRQN4#wU5fee2)< zLA|B7|6lxn`v1ZI`~PqMzxDt6|Lgy+{=fGBDwuWi|LyHk&W z@vj~K_x(Q#9&Nhy|H=PX|G)kJ^B*+wCCi}1V8~#`;L6~|5Xle=4vkuddT`%z7Q;-2 zB@Bxh)-bGO*vJ4H(O=83fngQHYOv@mhUpA_3>^&h4CM^D49N_U41NqQ3}y^E46+O& z3@i-4|G)hI=>PfuC;o5!zv2I!|1-d8H2;6@|LFfw;5CER|82o*S~dRb{#W_0^sys0cy2@PQKd7u#@2g!%>FI4CfebGF)M}!*G@1Hp3-w`}r)xVTOYY+rVRs z3mB#_^e{AlPaui|&(2seXfr4>2r_UmeE$zx5q$0c(f|AZuLJL{nD)Qzf6M>U|E2%a z|7ZM<`5zBnWf|~4^uPcA(Eow|BmRf|kN+S4KjVMa{}S-Z;P(HZk-o+M*Zkl1fB*lJ z|1X1A4}oT#85wvPgu$~mptVD`4E7A34BiYO3?U3L4ABfp3<(Tr3@Hp5459?|N8&Oe^4FI!ob1615Tl043Z4e z3~~$#3`*ddUWEY!LE}vF407PHCJ_c923`g(1{MZJhF||dtC-*ZfBOFsI5(XCfA;^; z|A+tY{tqghHvM1yfA#;R;CX{Z|L6T*08Xo*S=R;sm;7J)f93x*|2O{M`hVB|1OJcx zKMM|}hyUOF|M>s!e`W@L2G9(LI)f>L6@v$Z4?`3~ECZ+<1+{uQ89EuJFic^X3ts)S zfMGeqVut156VO1V>vZtUMkjdeqmUt!A%P)`!I#02!Gb}X0klVz8@zu7G{14`|B?Tj z{;&N%`~UR+ZU3A9gVwu%%AvsjLEtrmR{!n(oBX%@Z}i{dztMlI|K|T4{yY5l{Ot6v^#2R~FaN*s|IYu1z@@|8|4+eVeT)q33_=Xz42le@4EhX43^ojQ z3~t~NQh$aphERrRhDe4u@E-LThIoc3h8Tu$h6wOpOJ4?W23H1W23rOz1``GY@H!4f z@aT^q11|$R12eC3_J`1;96dSL7G7pT8e^FsWRx+6b1!w9WTuw&L9eIhjKEoF)%Rv z{r~O%=l`I3{?Y&Y;L;k@>OBHp!2(L7pqA3w|I7cc0H;XMjO)VxOa3ng&vvc)51M`5 z{C@{{4D1AWhU?b<$Nyja|NQ^Qe`audNQyz7L7Tx6JbL5D5X}(Bkj;?GP{~lm(8|!t z(9h7vFr8rv!)%5b409P~f@}7f;IWTxh7N`XaQi(E+&T(j@M3UeFk{e#?g3#_s=X|F8N#=l?A5%8SPTjsMI3SNzZaU-UowfByf> z|9Stj{ulhu1N*Gzf9?Oa|DFFQ{-5@L{{JQa*MjGpkATA-RLg_b2><=h#=rv}byQ|h z2d7ah1{?5BAa4dQhCl{?h7g7zhH!=uhH!>phERq;aL>q(!4q7&J2BWWSTUG_SHfv9 zfW{}Kz$pgQw`OMe{~uJ(fBygR|66d6>Dm7${~!N<2u`UF|3Ccy`2Umt&!Fk^{r^wk z5)@R&GcmA%d-9+ZD#{?iAjKdHE=55#y%M;7S7cBC*YT1J;tV1Tpd7))zzQDW`uYDe zwCxMZ30MDL{(t5_WGr^~|K0z${NM6_-T(FfSN&fNo!eURA2h!ODm~Z!-|&AEc%*j! z|0DlT{6GKy3U~wzG`{fj|G)p741C~`FKq^W25Sa;1|J50a2m~G$YubI(KRwOFmy6> zF!VF@GE8RZ2gm9pa0{TFp^c%Qp@yN9p#XZ0PauOQgFS;egARiVgE)f#0~5pV|8M_4 z1JBoj$|2C27f>st^MCvQ%KsJrv%x2%CH@ZspF$M^?g9J!5Bd*U+Zy&i4qRX6{4f1q z^S||f-~So^L9-4!{~!K;@&E1rul|4j&%(gNAj_b_UzT*K$1|M&d|rP0;@=l!1x?uCLz^XmSC=JKllm;SH(U-G}|f7$<<|JC5x6VOT`&^QXH zm9-jNf`aD1&x2=6p8kLL{|k8Jk()t)L4pCak{y&r4Z&-K9T*%K+!)*#ycs+h{206$ z{J}8-+6nB*;Kks|;L6|#-V1EWV8UR;pv#~I9-owDkYEsD5Mbb80QIf^|Nry<$Nz8t zKY?c>U;lpruI-=xe*%V2!KLSm|F8bP`Ty?!$NyjcfB*mM|KI-%3@i+63|tJn32IBGK7IgKkOJx8Fay`2l*K|7=Hi%3Lf>i3|;L5cbN(*{uf;j`AGDk1>HjbP|NQ4>5C*RUw`8zq@B{C#P6hXRn;DwHW&ccu z=?n`P<}fSHidF&7=z)3w zpZ>r3|KR`a|L6Xn0rbt;Hphz(J!Z7ysY*fB*lp|8M?(`Ty%bsKf$=J*cDtjXCNu7%-SIm^0Wi z*fKbQ_y4+p_x^f-cLKYB)20(x){?;jypkA{K0)J?pmw1+cr6?^0|&TW`1e0(6#dKp zkN-if(bxZ9{(t%ZIUK(D|MLH9X!-=z@IU_l{{QbkBLfQqJ2-`cdP<@UpuUncQW^!- z@1QoQ47e2vvJX^-ax<_ourU1l4;qF2{QvF$SO1@YSFYUrf9e0l|EK<+1kY*h{lDk` z_WwKnZ~njK{|4}C7SM_o$QVB~TYr7`{{`@RuwCHX16s4#_rLc)tgZyD!b<<2{XYe~HY?+Q>i_Kj znc&o00ZyMC{~_(UmH#(^cPm`{fAjyd|L^|)`Om^2#30F_!2sG_?a1KI5DXvtu490W zefKd;W0=G+1H9^B61e3#fuRH3KB;3UV<=+CWJqC%VF+dLWpHJ%WiVmT0p~9P1}+9r z$pGpR-vO^H1FaPT&El^Azx4l-|Fi$k{y!PK8)(9R(9B)uf6!{ke()IdeWLoq`kLpeh!Llr|g7=uobEMdq8kAb8yB!FYspTQG6K4%PW$$>^}co-NN ze*b?DUTbp`yw+yV|DFFqr8}&yZ2RB#zXrTEs{*{{CGUUH|2**8mojjjS@*y7f5-m` z;Ptkk^^9Bp@BM%L|9S9QvG@PK{byzXo!ue_9<#AyaApW#2xf?9NMy)m$YZEvs9c!y1V8vj}pv9mJ&R?7i%nU#O zfBFCN|C9f>{$B%+L+|^)2RweV;y-L0diMXR|EGgzMnJQ7Q~uBVKjZ)0|MS2-0#Gfs z1-wESRDyzPuiMZWMo=FB)H4Ci-+;y*L1`2;zX)2P0a}L%+HV2c6=cl-+QA0Gpj|?s z64sc(n8AQSpFtbE3J#P$<-jwLpjIIt0~b7n{`vp&|M&l2z^%g%|KEXA>6`x$`1b$1 z|L^~Q{Qv3ym;c}X|NQ?OT*osrurhE!%TOT(5e6}EOB7U!LeeNGb%N5TGy|v=3YwLN zl%ee4Rm`B>N}ziF-TxQRao8K+olBrJ3hF5z`M>}F{{OrG!`27v_`mJ{&i_09@A|*% z|K9)m{vZ5*1YCli2hYUb1&7n?|DXSV2e&>zxdv3%fci_IeSM(a`@Rf*;NBzXv@_6& ze0dBx;IZUlh609ShJ1zshHQpxhBSs$hIob;hA;+D2?{zb1vLHvDnmiDVmu6N48Or^ zZ6NDjPX9mtACgAb{9gcG_cHZ=_y3;%&Hq8CsMPfv^JXf;y{}%9iHqiLV{QnF8&-p+9|Lp(sz_WL-^_QSEZtMSpN(fNt09xY)TBC6X z+`aHr!+);}`3%t$^w4U9B0aRj`Fqnb&2^oRYrU8R4xb>;Y z02)09wF^NhMwCIAK>$4V18RpdGJsm5zrbx#P#XRC|HJ?H|KGtOsPu%SPtYjS&;NhG zbv!cz8v_ReHv=yNKZ78H2m{DfpxzQFjmj{{f=f?O4G$_oK|Ohpji5XMYF98Z{QVD# z=`a7^{Riy`c?upmxbgqm|BK+-^VI*N(6s@e)CfwayTGHRpxPglau0!Pcu)xnnu`U^ zb%Dyzx8OY~pqi3{L4ZMoL6$)QyjsAHbAq(yZ`U}4{FaX z{=W#k_8T-`3+h?4fM=ar{x^Vkf3^Q_1E=0e;Pg5F|FZvU{)1NBf>st>{D12|XuRyx z|G)oPz~f~K;5LdWG>rx_L^H%Nq%x#3JXpWJCfsa7| zTwa0Fs4@enXRpBkT1TYEpbIW7K|3!%`+q_EFLc2&8Vu^t*+)FT{|BWQP$~KiT#kPH4@#w=bo$}{hyNhn=l@^+fBpX*T7v!uw?aWF6x34!rBGo8 zP(3ffAju%b07|0}EX^RrAPKJHL9Gf(l>t;I%f_{$B>qhk@oAHv9**=a&AT^MCGt(ClnK zcwIr?|IYtC|3PiK?*9|OwL56t3us-zO7N`M9`L;FCGaZjm;XQi{|R1i3tA7Z!k_^z zaUkWtKLh9_!9<2c@QJLs4A~3?47m)247m&i3^@$B;9g)dLlQ#_Lli>@LjZ#pc!nD^ zz6cus28}L?Fz_?5Gr-pEyaLa0gWP}m|Ed3n{vQO_PM|TA_5U~gUjyEm0~(E53m)5B z5AG3wYB11BBG5{WbKq5Ox52YGFTw3lQ0osghXbltK`8=Mr-Di<(8vyG1_!iG15}fP z)(nHzwSzEd{{>`q5ol}@)Gh?27*Kg81TI0j89<|xpcD!kp#;_QpdS5Ka2oye{}VW6 zg38WM|6%F#$Nyj8UJ|Gk3hK#&QUa({2aPv@>UnVnNE!vDQfUT|ZJ_ii&LGAB8m|{% z0JVHM8CV%WBV2#MWi}`$fLgqu{t{@O0Fp*8{yzg=IeG$ImV!#mgZ~fz2Vu}EK+sAn zQ2GR|X1VeDIZ9wZ7K(oW3Ia|>9 z186=B(w|9k)M1-J6I{ND;5 zF#)X&-uNHX{s)z?pqlK!|3m*l=@S(4pmiFc*40z+O3Y74W8_@mF>=sqB5`nO1sdA{ zm8GC{ZlJY9pb`_5F4Y-8^Wz{HMexicWYhPc?>_?r6L_qN6Wk63mFl3DD5w-gq)|}m2}+-!`A$*rXego# zWd_e=LF#!>tM}#q7vK@tJK$B#h_x%ARaM9TgVHHzRur@<@c92@|4;ls30~21;XkMZ zz48C{|NG$9(5wF+{(lCKgn>pG_`s`0LFb^Tfp<=UcJPBv>H(d2<_8|h0gWGrGDI?j zGek0gPCEsqfFOoI2GAUbJA(^@J%bH6jzK5-C^N`_M?`rTSQr=>Kyz@QQMvozowBfX z1)x)?HveD!e+{@-G5`O9|Fgk61VJOFppkJivT97RYrG4B-s14AJ1TgOeEI7!tv))M$oi2GIN=DEE0WcrZAD z&%-o@&d?};*Omz|fJz6@{0w3&`p*BW|F8T%`yaIC5;O~O5WE_ACwRp!XnioK?GKvi z1NB@%CB<>@Dq&FD3e-OZ)$z~%L*~IjJrvLwBrA9(9OMH?8U>YBatv||pwUNANeNm@ z1i_$r9Z=eoXOIWC2|+0al0JnP_`#(as9(*>04YQNf$MotTlD+?Z~wpj2bH9tlnKIL z!6Kj<{@4FM|Ns8~|DS<@89demD$#iu_`oeuNE#Jq0Hsk-Nh%4h-z6A8B`B!f3(6Ic zG88o41j-Y?{)2j!pw-Nv5fISMCD6_g(8_yI8U?K!Jr7=A1xlx&R0#^56aP>BKlT62 z|Fi$k|G)VE%KvNsZ~njY|Nj5S|DXSV4PFxn>TQ7bPH}--$DnyG(6|R^Yz~x0?HTOB zqr{+j4u9}U#sG#8@cc#~LlA>MgFgdE#*M)Rd?K0!10;??r4VGE7&Q6;s^>u?bD%yR zXe{^A{|o<5{67wEg@DF!*Z*Jtf93yG|Cjt<1|FfA|9{bc&`9FK|4YGhwxAJ3(Arke zuCF8D79D8c7ibmsTX0DPTI~R9M{6?ZFo1d%b`16m?hNh>pu7btEVwufhOYp9^Y5bA$JZf#zpE{(lD^^#--AK&vlcYc4@$ zDWqKm+7Sd=aRzByfqJG#!Rs*3f!ASzYA#R-3R>v~3U^Q$3hJSNQU<8?2TB!!;Bj)$ z7!PR82B>!rniU7F-GE@ocqFLD0;+F8rI{E5D5ZdEcs>Rm22dRj>Q^%}fXYx%OZ3lw zP@4JiAC^YH{s%!&di@S=fr5JRpk5NF6$+a11i1uMqJ#QMpiz5JTNKn^f}~Li22ko0 zX8_qM!T>9`A+~~Y1!znZWEW_J3pSVa5WK1xlt!=rzx@C5{|o;w{67m`K?<62g5Wd% z&;1955@^j8sHVB~9~4rcSx-nvfktaVW3`|bGHBcbG!v`Fpw0kFqoC0Z(0NkM49*N5 z4DJj*3|AW%vSVu0i=NdEF?2w?DG z@L_OgaASa^PtbX8pfN_!E*#MK4QSRGl)pe@i=dH3(8xE){h%}oYFmNA`t*O$=mcoj z!%^_A3Q(I0w0<2lZUS1ve)|7;X!?Yd3!wh#%l~iwzyA*!CkLfaNc)ciJl6=S#e^6{ z89?)mpguHcED}^=fYK_cg$lx;v?&E1j|7#Tu=L5#z{|kR04hP*z->ZA3jGaFqo6bi zN}J#QgIGVnH9V*d`u9Jmj%P$FLqWZHP(3fmAj|+sqhbtV44|?UlsaMQQwZ9!0Hsin ztsp!9fM;7ltC>MN>p(k~Ks!S~EAK%g%AnB@P%G~eI8@I42dypv<@EF5(i2pEg4SJu zLh9as$f}mt|3NYQ4ct-&wUR+CWKigUaxQ4)KWGOk=qwZ)1{?59n=AMvUN;6%spG}q z4nB())cOR~kf2oz=HSr~(1|IaQ94ix6=vXN0F5ewM!vs+XE#7=UvB@0%r~3_pXPV) zKWN@|D|k+9)BknQe#aVcZ-2voP}>ePqPPp(>jRZakev&loi#81zX7i;0QEOOt8PK1 z4`{_LX!oo!g9(E*gAI6Y29ydxbBLhU2&hNr$Kb`_13s-4vfogtGx`frUpcDgACBy(K zKSAk}hk*;+j|bJah%yw^p9htqKmUVLBq(KqFo^y0|4(oq30B85F@Q!xL1ic>12+Sx zL7c^SS|6|!Cx6p~lK>jFV-P|&(7(7Kiv;94G1+kj@gK%oS36=<#tRI7ki z{)5ses5}DAWY{s-f#chS!3n&w#+AVdeA+3fhXGmz1}cM$7(gecfJ!0Ii4@}C{nVhg z9B6b7vi9W_xcv-T1qPZE1NDnR^(3g(25QS~{STRM0QEOE{RizL*#3Xp|6TuggLl^) z1@BA*wdp`>Z$a%8(7G|uxFTpI4>YC=ic!$M1yC9VjUIyLX6zY2Bl%7YZVavr9t>_! z44Ol9VsK&r`O1pHlEIV#SD4szp1wd&OG!F-w#js$oV6XwNECH>y1@%Pi863dz3p%sUg25cT zjzJ%M<`k$D0*%vwW*tB+X~^1_U;jaEQP2!AXe}5hjb8qL3A`)e*#BeTeO{m)h&%u9 z2Je&w?UqICO9Zv_KqC^M*uMlGjR3WzK|TQGFwl%0D1U)QNI)eHXgmV6e-<=1W6od> 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 NMnhmU1O`V4000Tan3weSh@7c5w?bm^*9tJbaC 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 D=J^(C literal 0 HcmV?d00001 diff --git a/modules/BlueskyNSE/BlueskyNSE.entitlements b/modules/BlueskyNSE/BlueskyNSE.entitlements new file mode 100644 index 000000000..4954bdb33 --- /dev/null +++ b/modules/BlueskyNSE/BlueskyNSE.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.app.bsky + + + \ No newline at end of file diff --git a/modules/BlueskyNSE/Info.plist b/modules/BlueskyNSE/Info.plist new file mode 100644 index 000000000..c2dd7eda6 --- /dev/null +++ b/modules/BlueskyNSE/Info.plist @@ -0,0 +1,29 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + MainAppScheme + bluesky + CFBundleName + $(PRODUCT_NAME) + CFBundleDisplayName + Bluesky Notifications + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + + \ No newline at end of file diff --git a/modules/BlueskyNSE/NotificationService.swift b/modules/BlueskyNSE/NotificationService.swift new file mode 100644 index 000000000..c6f391e00 --- /dev/null +++ b/modules/BlueskyNSE/NotificationService.swift @@ -0,0 +1,51 @@ +import UserNotifications + +let APP_GROUP = "group.app.bsky" + +class NotificationService: UNNotificationServiceExtension { + var prefs = UserDefaults(suiteName: APP_GROUP) + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + guard var bestAttempt = createCopy(request.content), + let reason = request.content.userInfo["reason"] as? String + else { + contentHandler(request.content) + return + } + + if reason == "chat-message" { + mutateWithChatMessage(bestAttempt) + } + + // The badge should always be incremented when in the background + mutateWithBadge(bestAttempt) + + contentHandler(bestAttempt) + } + + override func serviceExtensionTimeWillExpire() { + // If for some reason the alloted time expires, we don't actually want to display a notification + } + + func createCopy(_ content: UNNotificationContent) -> UNMutableNotificationContent? { + return content.mutableCopy() as? UNMutableNotificationContent + } + + func mutateWithBadge(_ content: UNMutableNotificationContent) { + content.badge = 1 + } + + func mutateWithChatMessage(_ content: UNMutableNotificationContent) { + if self.prefs?.bool(forKey: "playSoundChat") == true { + mutateWithDmSound(content) + } + } + + func mutateWithDefaultSound(_ content: UNMutableNotificationContent) { + content.sound = UNNotificationSound.default + } + + func mutateWithDmSound(_ content: UNMutableNotificationContent) { + content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "dm.aiff")) + } +} diff --git a/modules/Share-with-Bluesky/Info.plist b/modules/Share-with-Bluesky/Info.plist index 90fe92345..421abb3c4 100644 --- a/modules/Share-with-Bluesky/Info.plist +++ b/modules/Share-with-Bluesky/Info.plist @@ -38,4 +38,4 @@ CFBundleShortVersionString $(MARKETING_VERSION) - + \ No newline at end of file diff --git a/modules/Share-with-Bluesky/Share-with-Bluesky.entitlements b/modules/Share-with-Bluesky/Share-with-Bluesky.entitlements index d2253d31f..4954bdb33 100644 --- a/modules/Share-with-Bluesky/Share-with-Bluesky.entitlements +++ b/modules/Share-with-Bluesky/Share-with-Bluesky.entitlements @@ -7,4 +7,4 @@ group.app.bsky - + \ No newline at end of file diff --git a/modules/expo-background-notification-handler/android/build.gradle b/modules/expo-background-notification-handler/android/build.gradle new file mode 100644 index 000000000..e18eee934 --- /dev/null +++ b/modules/expo-background-notification-handler/android/build.gradle @@ -0,0 +1,93 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' + +group = 'expo.modules.backgroundnotificationhandler' +version = '0.5.0' + +buildscript { + def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") + if (expoModulesCorePlugin.exists()) { + apply from: expoModulesCorePlugin + applyKotlinExpoModulesCorePlugin() + } + + // Simple helper that allows the root project to override versions declared by this library. + ext.safeExtGet = { prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + + // Ensures backward compatibility + ext.getKotlinVersion = { + if (ext.has("kotlinVersion")) { + ext.kotlinVersion() + } else { + ext.safeExtGet("kotlinVersion", "1.8.10") + } + } + + repositories { + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}") + } +} + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + from components.release + } + } + repositories { + maven { + url = mavenLocal().url + } + } + } +} + +android { + compileSdkVersion safeExtGet("compileSdkVersion", 33) + + def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION + if (agpVersion.tokenize('.')[0].toInteger() < 8) { + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.majorVersion + } + } + + namespace "expo.modules.backgroundnotificationhandler" + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 21) + targetSdkVersion safeExtGet("targetSdkVersion", 34) + versionCode 1 + versionName "0.5.0" + } + lintOptions { + abortOnError false + } + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':expo-modules-core') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" + implementation 'com.google.firebase:firebase-messaging-ktx:24.0.0' +} diff --git a/modules/expo-background-notification-handler/android/src/main/AndroidManifest.xml b/modules/expo-background-notification-handler/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..bdae66c8f --- /dev/null +++ b/modules/expo-background-notification-handler/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt new file mode 100644 index 000000000..344508523 --- /dev/null +++ b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt @@ -0,0 +1,39 @@ +package expo.modules.backgroundnotificationhandler + +import android.content.Context +import com.google.firebase.messaging.RemoteMessage + +class BackgroundNotificationHandler( + private val context: Context, + private val notifInterface: BackgroundNotificationHandlerInterface +) { + fun handleMessage(remoteMessage: RemoteMessage) { + if (ExpoBackgroundNotificationHandlerModule.isForegrounded) { + // We'll let expo-notifications handle the notification if the app is foregrounded + return + } + + if (remoteMessage.data["reason"] == "chat-message") { + mutateWithChatMessage(remoteMessage) + } + + notifInterface.showMessage(remoteMessage) + } + + private fun mutateWithChatMessage(remoteMessage: RemoteMessage) { + if (NotificationPrefs(context).getBoolean("playSoundChat")) { + // If oreo or higher + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + remoteMessage.data["channelId"] = "chat-messages" + } else { + remoteMessage.data["sound"] = "dm.mp3" + } + } else { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + remoteMessage.data["channelId"] = "chat-messages-muted" + } else { + remoteMessage.data["sound"] = null + } + } + } +} diff --git a/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandlerInterface.kt b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandlerInterface.kt new file mode 100644 index 000000000..41fb65eb6 --- /dev/null +++ b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandlerInterface.kt @@ -0,0 +1,7 @@ +package expo.modules.backgroundnotificationhandler + +import com.google.firebase.messaging.RemoteMessage + +interface BackgroundNotificationHandlerInterface { + fun showMessage(remoteMessage: RemoteMessage) +} diff --git a/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/ExpoBackgroundNotificationHandlerModule.kt b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/ExpoBackgroundNotificationHandlerModule.kt new file mode 100644 index 000000000..083ff1223 --- /dev/null +++ b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/ExpoBackgroundNotificationHandlerModule.kt @@ -0,0 +1,70 @@ +package expo.modules.backgroundnotificationhandler + +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition + +class ExpoBackgroundNotificationHandlerModule : Module() { + companion object { + var isForegrounded = false + } + + override fun definition() = ModuleDefinition { + Name("ExpoBackgroundNotificationHandler") + + OnCreate { + NotificationPrefs(appContext.reactContext).initialize() + } + + OnActivityEntersForeground { + isForegrounded = true + } + + OnActivityEntersBackground { + isForegrounded = false + } + + AsyncFunction("getAllPrefsAsync") { + return@AsyncFunction NotificationPrefs(appContext.reactContext).getAllPrefs() + } + + AsyncFunction("getBoolAsync") { forKey: String -> + return@AsyncFunction NotificationPrefs(appContext.reactContext).getBoolean(forKey) + } + + AsyncFunction("getStringAsync") { forKey: String -> + return@AsyncFunction NotificationPrefs(appContext.reactContext).getString(forKey) + } + + AsyncFunction("getStringArrayAsync") { forKey: String -> + return@AsyncFunction NotificationPrefs(appContext.reactContext).getStringArray(forKey) + } + + AsyncFunction("setBoolAsync") { forKey: String, value: Boolean -> + NotificationPrefs(appContext.reactContext).setBoolean(forKey, value) + } + + AsyncFunction("setStringAsync") { forKey: String, value: String -> + NotificationPrefs(appContext.reactContext).setString(forKey, value) + } + + AsyncFunction("setStringArrayAsync") { forKey: String, value: Array -> + NotificationPrefs(appContext.reactContext).setStringArray(forKey, value) + } + + AsyncFunction("addToStringArrayAsync") { forKey: String, string: String -> + NotificationPrefs(appContext.reactContext).addToStringArray(forKey, string) + } + + AsyncFunction("removeFromStringArrayAsync") { forKey: String, string: String -> + NotificationPrefs(appContext.reactContext).removeFromStringArray(forKey, string) + } + + AsyncFunction("addManyToStringArrayAsync") { forKey: String, strings: Array -> + NotificationPrefs(appContext.reactContext).addManyToStringArray(forKey, strings) + } + + AsyncFunction("removeManyFromStringArrayAsync") { forKey: String, strings: Array -> + NotificationPrefs(appContext.reactContext).removeManyFromStringArray(forKey, strings) + } + } +} diff --git a/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/NotificationPrefs.kt b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/NotificationPrefs.kt new file mode 100644 index 000000000..17ef9205e --- /dev/null +++ b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/NotificationPrefs.kt @@ -0,0 +1,134 @@ +package expo.modules.backgroundnotificationhandler + +import android.content.Context + +val DEFAULTS = mapOf( + "playSoundChat" to true, + "playSoundFollow" to false, + "playSoundLike" to false, + "playSoundMention" to false, + "playSoundQuote" to false, + "playSoundReply" to false, + "playSoundRepost" to false, + "mutedThreads" to mapOf>() +) + +class NotificationPrefs (private val context: Context?) { + private val prefs = context?.getSharedPreferences("xyz.blueskyweb.app", Context.MODE_PRIVATE) + ?: throw Error("Context is null") + + fun initialize() { + prefs + .edit() + .apply { + DEFAULTS.forEach { (key, value) -> + if (prefs.contains(key)) { + return@forEach + } + + when (value) { + is Boolean -> { + putBoolean(key, value) + } + is String -> { + putString(key, value) + } + is Array<*> -> { + putStringSet(key, value.map { it.toString() }.toSet()) + } + is Map<*, *> -> { + putStringSet(key, value.map { it.toString() }.toSet()) + } + } + } + } + .apply() + } + + fun getAllPrefs(): MutableMap { + return prefs.all + } + + fun getBoolean(key: String): Boolean { + return prefs.getBoolean(key, false) + } + + fun getString(key: String): String? { + return prefs.getString(key, null) + } + + fun getStringArray(key: String): Array? { + return prefs.getStringSet(key, null)?.toTypedArray() + } + + fun setBoolean(key: String, value: Boolean) { + prefs + .edit() + .apply { + putBoolean(key, value) + } + .apply() + } + + fun setString(key: String, value: String) { + prefs + .edit() + .apply { + putString(key, value) + } + .apply() + } + + fun setStringArray(key: String, value: Array) { + prefs + .edit() + .apply { + putStringSet(key, value.toSet()) + } + .apply() + } + + fun addToStringArray(key: String, string: String) { + prefs + .edit() + .apply { + val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf() + set.add(string) + putStringSet(key, set) + } + .apply() + } + + fun removeFromStringArray(key: String, string: String) { + prefs + .edit() + .apply { + val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf() + set.remove(string) + putStringSet(key, set) + } + .apply() + } + + fun addManyToStringArray(key: String, strings: Array) { + prefs + .edit() + .apply { + val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf() + set.addAll(strings.toSet()) + putStringSet(key, set) + } + .apply() + } + + fun removeManyFromStringArray(key: String, strings: Array) { + prefs + .edit() + .apply { + val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf() + set.removeAll(strings.toSet()) + putStringSet(key, set) + } + .apply() + } +} \ No newline at end of file diff --git a/modules/expo-background-notification-handler/expo-module.config.json b/modules/expo-background-notification-handler/expo-module.config.json new file mode 100644 index 000000000..9e5c9d550 --- /dev/null +++ b/modules/expo-background-notification-handler/expo-module.config.json @@ -0,0 +1,9 @@ +{ + "platforms": ["ios", "android"], + "ios": { + "modules": ["ExpoBackgroundNotificationHandlerModule"] + }, + "android": { + "modules": ["expo.modules.backgroundnotificationhandler.ExpoBackgroundNotificationHandlerModule"] + } +} diff --git a/modules/expo-background-notification-handler/index.ts b/modules/expo-background-notification-handler/index.ts new file mode 100644 index 000000000..680c6c13f --- /dev/null +++ b/modules/expo-background-notification-handler/index.ts @@ -0,0 +1,2 @@ +import {BackgroundNotificationHandler} from './src/ExpoBackgroundNotificationHandlerModule' +export default BackgroundNotificationHandler diff --git a/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandler.podspec b/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandler.podspec new file mode 100644 index 000000000..363c7b5e6 --- /dev/null +++ b/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandler.podspec @@ -0,0 +1,21 @@ +Pod::Spec.new do |s| + s.name = 'ExpoBackgroundNotificationHandler' + s.version = '1.0.0' + s.summary = 'Interface for BlueskyNSE preferences' + s.description = 'Interface for BlueskyNSE preferenes' + s.author = '' + s.homepage = 'https://github.com/bluesky-social/social-app' + s.platforms = { :ios => '13.4', :tvos => '13.4' } + s.source = { git: '' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'SWIFT_COMPILATION_MODE' => 'wholemodule' + } + + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" +end diff --git a/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandlerModule.swift b/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandlerModule.swift new file mode 100644 index 000000000..08972a04c --- /dev/null +++ b/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandlerModule.swift @@ -0,0 +1,116 @@ +import ExpoModulesCore + +let APP_GROUP = "group.app.bsky" + +let DEFAULTS: [String:Any] = [ + "playSoundChat" : true, + "playSoundFollow": false, + "playSoundLike": false, + "playSoundMention": false, + "playSoundQuote": false, + "playSoundReply": false, + "playSoundRepost": false, + "mutedThreads": [:] as! [String:[String]] +] + +/* + * The purpose of this module is to store values that are needed by the notification service + * extension. Since we would rather get and store values such as age or user mute state + * while the app is foregrounded, we should use this module liberally. We should aim to keep + * background fetches to a minimum (two or three times per hour) while the app is backgrounded + * or killed + */ +public class ExpoBackgroundNotificationHandlerModule: Module { + let userDefaults = UserDefaults(suiteName: APP_GROUP) + + public func definition() -> ModuleDefinition { + Name("ExpoBackgroundNotificationHandler") + + OnCreate { + DEFAULTS.forEach { p in + if userDefaults?.value(forKey: p.key) == nil { + userDefaults?.setValue(p.value, forKey: p.key) + } + } + } + + AsyncFunction("getAllPrefsAsync") { () -> [String:Any]? in + var keys: [String] = [] + DEFAULTS.forEach { p in + keys.append(p.key) + } + return userDefaults?.dictionaryWithValues(forKeys: keys) + } + + AsyncFunction("getBoolAsync") { (forKey: String) -> Bool in + if let pref = userDefaults?.bool(forKey: forKey) { + return pref + } + return false + } + + AsyncFunction("getStringAsync") { (forKey: String) -> String? in + if let pref = userDefaults?.string(forKey: forKey) { + return pref + } + return nil + } + + AsyncFunction("getStringArrayAsync") { (forKey: String) -> [String]? in + if let pref = userDefaults?.stringArray(forKey: forKey) { + return pref + } + return nil + } + + AsyncFunction("setBoolAsync") { (forKey: String, value: Bool) -> Void in + userDefaults?.setValue(value, forKey: forKey) + } + + AsyncFunction("setStringAsync") { (forKey: String, value: String) -> Void in + userDefaults?.setValue(value, forKey: forKey) + } + + AsyncFunction("setStringArrayAsync") { (forKey: String, value: [String]) -> Void in + userDefaults?.setValue(value, forKey: forKey) + } + + AsyncFunction("addToStringArrayAsync") { (forKey: String, string: String) in + if var curr = userDefaults?.stringArray(forKey: forKey), + !curr.contains(string) + { + curr.append(string) + userDefaults?.setValue(curr, forKey: forKey) + } + } + + AsyncFunction("removeFromStringArrayAsync") { (forKey: String, string: String) in + if var curr = userDefaults?.stringArray(forKey: forKey) { + curr.removeAll { s in + return s == string + } + userDefaults?.setValue(curr, forKey: forKey) + } + } + + AsyncFunction("addManyToStringArrayAsync") { (forKey: String, strings: [String]) in + if var curr = userDefaults?.stringArray(forKey: forKey) { + strings.forEach { s in + if !curr.contains(s) { + curr.append(s) + } + } + userDefaults?.setValue(curr, forKey: forKey) + } + } + + AsyncFunction("removeManyFromStringArrayAsync") { (forKey: String, strings: [String]) in + if var curr = userDefaults?.stringArray(forKey: forKey) { + strings.forEach { s in + curr.removeAll(where: { $0 == s }) + } + userDefaults?.setValue(curr, forKey: forKey) + } + } + } +} diff --git a/modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx b/modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx new file mode 100644 index 000000000..6ecdd1d47 --- /dev/null +++ b/modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx @@ -0,0 +1,70 @@ +import React from 'react' + +import {BackgroundNotificationHandlerPreferences} from './ExpoBackgroundNotificationHandler.types' +import {BackgroundNotificationHandler} from './ExpoBackgroundNotificationHandlerModule' + +interface BackgroundNotificationPreferencesContext { + preferences: BackgroundNotificationHandlerPreferences + setPref: ( + key: Key, + value: BackgroundNotificationHandlerPreferences[Key], + ) => void +} + +const Context = React.createContext( + {} as BackgroundNotificationPreferencesContext, +) +export const useBackgroundNotificationPreferences = () => + React.useContext(Context) + +export function BackgroundNotificationPreferencesProvider({ + children, +}: { + children: React.ReactNode +}) { + const [preferences, setPreferences] = + React.useState({ + playSoundChat: true, + }) + + React.useEffect(() => { + ;(async () => { + const prefs = await BackgroundNotificationHandler.getAllPrefsAsync() + setPreferences(prefs) + })() + }, []) + + const value = React.useMemo( + () => ({ + preferences, + setPref: async < + Key extends keyof BackgroundNotificationHandlerPreferences, + >( + k: Key, + v: BackgroundNotificationHandlerPreferences[Key], + ) => { + switch (typeof v) { + case 'boolean': { + await BackgroundNotificationHandler.setBoolAsync(k, v) + break + } + case 'string': { + await BackgroundNotificationHandler.setStringAsync(k, v) + break + } + default: { + throw new Error(`Invalid type for value: ${typeof v}`) + } + } + + setPreferences(prev => ({ + ...prev, + [k]: v, + })) + }, + }), + [preferences], + ) + + return {children} +} diff --git a/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandler.types.ts b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandler.types.ts new file mode 100644 index 000000000..5fbd302da --- /dev/null +++ b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandler.types.ts @@ -0,0 +1,40 @@ +export type ExpoBackgroundNotificationHandlerModule = { + getAllPrefsAsync: () => Promise + getBoolAsync: (forKey: string) => Promise + getStringAsync: (forKey: string) => Promise + getStringArrayAsync: (forKey: string) => Promise + setBoolAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: boolean, + ) => Promise + setStringAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string, + ) => Promise + setStringArrayAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string[], + ) => Promise + addToStringArrayAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string, + ) => Promise + removeFromStringArrayAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string, + ) => Promise + addManyToStringArrayAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string[], + ) => Promise + removeManyFromStringArrayAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string[], + ) => Promise +} + +// TODO there are more preferences in the native code, however they have not been added here yet. +// Don't add them until the native logic also handles the notifications for those preference types. +export type BackgroundNotificationHandlerPreferences = { + playSoundChat: boolean +} diff --git a/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.ts b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.ts new file mode 100644 index 000000000..d6517893a --- /dev/null +++ b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.ts @@ -0,0 +1,8 @@ +import {requireNativeModule} from 'expo-modules-core' + +import {ExpoBackgroundNotificationHandlerModule} from './ExpoBackgroundNotificationHandler.types' + +export const BackgroundNotificationHandler = + requireNativeModule( + 'ExpoBackgroundNotificationHandler', + ) diff --git a/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts new file mode 100644 index 000000000..29e27fd0f --- /dev/null +++ b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts @@ -0,0 +1,27 @@ +import { + BackgroundNotificationHandlerPreferences, + ExpoBackgroundNotificationHandlerModule, +} from './ExpoBackgroundNotificationHandler.types' + +// Stub for web +export const BackgroundNotificationHandler = { + getAllPrefsAsync: async () => { + return {} as BackgroundNotificationHandlerPreferences + }, + getBoolAsync: async (_: string) => { + return false + }, + getStringAsync: async (_: string) => { + return '' + }, + getStringArrayAsync: async (_: string) => { + return [] + }, + setBoolAsync: async (_: string, __: boolean) => {}, + setStringAsync: async (_: string, __: string) => {}, + setStringArrayAsync: async (_: string, __: string[]) => {}, + addToStringArrayAsync: async (_: string, __: string) => {}, + removeFromStringArrayAsync: async (_: string, __: string) => {}, + addManyToStringArrayAsync: async (_: string, __: string[]) => {}, + removeManyFromStringArrayAsync: async (_: string, __: string[]) => {}, +} as ExpoBackgroundNotificationHandlerModule diff --git a/patches/expo-notifications+0.27.6.patch b/patches/expo-notifications+0.27.6.patch new file mode 100644 index 000000000..ba196eca0 --- /dev/null +++ b/patches/expo-notifications+0.27.6.patch @@ -0,0 +1,197 @@ +diff --git a/node_modules/expo-notifications/android/build.gradle b/node_modules/expo-notifications/android/build.gradle +index 97bf4f4..6e9d427 100644 +--- a/node_modules/expo-notifications/android/build.gradle ++++ b/node_modules/expo-notifications/android/build.gradle +@@ -118,6 +118,7 @@ dependencies { + api 'com.google.firebase:firebase-messaging:22.0.0' + + api 'me.leolin:ShortcutBadger:1.1.22@aar' ++ implementation project(':expo-background-notification-handler') + + if (project.findProject(':expo-modules-test-core')) { + testImplementation project(':expo-modules-test-core') +diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java +index 0af7fe0..8f2c8d8 100644 +--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java ++++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java +@@ -14,6 +14,7 @@ import expo.modules.notifications.notifications.enums.NotificationPriority; + import expo.modules.notifications.notifications.model.NotificationContent; + + public class JSONNotificationContentBuilder extends NotificationContent.Builder { ++ private static final String CHANNEL_ID_KEY = "channelId"; + private static final String TITLE_KEY = "title"; + private static final String TEXT_KEY = "message"; + private static final String SUBTITLE_KEY = "subtitle"; +@@ -36,6 +37,7 @@ public class JSONNotificationContentBuilder extends NotificationContent.Builder + + public NotificationContent.Builder setPayload(JSONObject payload) { + this.setTitle(getTitle(payload)) ++ .setChannelId(getChannelId(payload)) + .setSubtitle(getSubtitle(payload)) + .setText(getText(payload)) + .setBody(getBody(payload)) +@@ -60,6 +62,14 @@ public class JSONNotificationContentBuilder extends NotificationContent.Builder + return this; + } + ++ protected String getChannelId(JSONObject payload) { ++ try { ++ return payload.getString(CHANNEL_ID_KEY); ++ } catch (JSONException e) { ++ return null; ++ } ++ } ++ + protected String getTitle(JSONObject payload) { + try { + return payload.getString(TITLE_KEY); +diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java +index f1fed19..1619f59 100644 +--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java ++++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java +@@ -20,6 +20,7 @@ import expo.modules.notifications.notifications.enums.NotificationPriority; + * should be created using {@link NotificationContent.Builder}. + */ + public class NotificationContent implements Parcelable, Serializable { ++ private String mChannelId; + private String mTitle; + private String mText; + private String mSubtitle; +@@ -50,6 +51,9 @@ public class NotificationContent implements Parcelable, Serializable { + } + }; + ++ @Nullable ++ public String getChannelId() { return mChannelId; } ++ + @Nullable + public String getTitle() { + return mTitle; +@@ -121,6 +125,7 @@ public class NotificationContent implements Parcelable, Serializable { + } + + protected NotificationContent(Parcel in) { ++ mChannelId = in.readString(); + mTitle = in.readString(); + mText = in.readString(); + mSubtitle = in.readString(); +@@ -146,6 +151,7 @@ public class NotificationContent implements Parcelable, Serializable { + + @Override + public void writeToParcel(Parcel dest, int flags) { ++ dest.writeString(mChannelId); + dest.writeString(mTitle); + dest.writeString(mText); + dest.writeString(mSubtitle); +@@ -166,6 +172,7 @@ public class NotificationContent implements Parcelable, Serializable { + private static final long serialVersionUID = 397666843266836802L; + + private void writeObject(java.io.ObjectOutputStream out) throws IOException { ++ out.writeObject(mChannelId); + out.writeObject(mTitle); + out.writeObject(mText); + out.writeObject(mSubtitle); +@@ -190,6 +197,7 @@ public class NotificationContent implements Parcelable, Serializable { + } + + private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { ++ mChannelId = (String) in.readObject(); + mTitle = (String) in.readObject(); + mText = (String) in.readObject(); + mSubtitle = (String) in.readObject(); +@@ -240,6 +248,7 @@ public class NotificationContent implements Parcelable, Serializable { + } + + public static class Builder { ++ private String mChannelId; + private String mTitle; + private String mText; + private String mSubtitle; +@@ -260,6 +269,11 @@ public class NotificationContent implements Parcelable, Serializable { + useDefaultVibrationPattern(); + } + ++ public Builder setChannelId(String channelId) { ++ mChannelId = channelId; ++ return this; ++ } ++ + public Builder setTitle(String title) { + mTitle = title; + return this; +@@ -336,6 +350,7 @@ public class NotificationContent implements Parcelable, Serializable { + + public NotificationContent build() { + NotificationContent content = new NotificationContent(); ++ content.mChannelId = mChannelId; + content.mTitle = mTitle; + content.mSubtitle = mSubtitle; + content.mText = mText; +diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java +index 6bd9928..aab71ea 100644 +--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java ++++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java +@@ -7,7 +7,6 @@ import android.content.pm.PackageManager; + import android.content.res.Resources; + import android.graphics.Bitmap; + import android.graphics.BitmapFactory; +-import android.os.Build; + import android.os.Bundle; + import android.os.Parcel; + import android.provider.Settings; +@@ -48,6 +47,10 @@ public class ExpoNotificationBuilder extends ChannelAwareNotificationBuilder { + + NotificationContent content = getNotificationContent(); + ++ if (content.getChannelId() != null) { ++ builder.setChannelId(content.getChannelId()); ++ } ++ + builder.setAutoCancel(content.isAutoDismiss()); + builder.setOngoing(content.isSticky()); + +diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt +index 55b3a8d..1b99d5b 100644 +--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt ++++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt +@@ -12,11 +12,14 @@ import expo.modules.notifications.notifications.model.triggers.FirebaseNotificat + import expo.modules.notifications.service.NotificationsService + import expo.modules.notifications.service.interfaces.FirebaseMessagingDelegate + import expo.modules.notifications.tokens.interfaces.FirebaseTokenListener ++import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandler ++import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandlerInterface ++import expo.modules.backgroundnotificationhandler.ExpoBackgroundNotificationHandlerModule + import org.json.JSONObject + import java.lang.ref.WeakReference + import java.util.* + +-open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate { ++open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate, BackgroundNotificationHandlerInterface { + companion object { + // Unfortunately we cannot save state between instances of a service other way + // than by static properties. Fortunately, using weak references we can +@@ -89,12 +92,21 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM + fun getBackgroundTasks() = sBackgroundTaskConsumerReferences.values.mapNotNull { it.get() } + + override fun onMessageReceived(remoteMessage: RemoteMessage) { +- NotificationsService.receive(context, createNotification(remoteMessage)) +- getBackgroundTasks().forEach { +- it.scheduleJob(RemoteMessageSerializer.toBundle(remoteMessage)) ++ if (!ExpoBackgroundNotificationHandlerModule.isForegrounded) { ++ BackgroundNotificationHandler(context, this).handleMessage(remoteMessage) ++ return ++ } else { ++ showMessage(remoteMessage) ++ getBackgroundTasks().forEach { ++ it.scheduleJob(RemoteMessageSerializer.toBundle(remoteMessage)) ++ } + } + } + ++ override fun showMessage(remoteMessage: RemoteMessage) { ++ NotificationsService.receive(context, createNotification(remoteMessage)) ++ } ++ + protected fun createNotification(remoteMessage: RemoteMessage): Notification { + val identifier = getNotificationIdentifier(remoteMessage) + val payload = JSONObject(remoteMessage.data as Map<*, *>) diff --git a/patches/expo-notifications-0.27.6.patch.md b/patches/expo-notifications-0.27.6.patch.md new file mode 100644 index 000000000..59b2598f3 --- /dev/null +++ b/patches/expo-notifications-0.27.6.patch.md @@ -0,0 +1,9 @@ +## LOAD BEARING PATCH, DO NOT REMOVE + +## Expo-Notifications Patch + +This patch supports the Android background notification handling module. Incoming messages +in `onMessageReceived` are sent to the module for handling. + +It also allows us to set the Android notification channel ID from the notification `data`, rather +than the `notification` object in the payload. diff --git a/plugins/notificationsExtension/README.md b/plugins/notificationsExtension/README.md new file mode 100644 index 000000000..31b8bfe7d --- /dev/null +++ b/plugins/notificationsExtension/README.md @@ -0,0 +1,17 @@ +# Notifications extension plugin for Expo + +This plugin handles moving the necessary files into their respective iOS directories + +## Steps + +### ios + +1. Update entitlements +2. Set the app group to group. +3. Add the extension plist +4. Add the view controller +5. Update the xcode project's build phases + +## Credits + +Adapted from https://github.com/andrew-levy/react-native-safari-extension and https://github.com/timedtext/expo-config-plugin-ios-share-extension/blob/master/src/withShareExtensionXcodeTarget.ts diff --git a/plugins/notificationsExtension/withAppEntitlements.js b/plugins/notificationsExtension/withAppEntitlements.js new file mode 100644 index 000000000..4ce81ea61 --- /dev/null +++ b/plugins/notificationsExtension/withAppEntitlements.js @@ -0,0 +1,13 @@ +const {withEntitlementsPlist} = require('@expo/config-plugins') + +const withAppEntitlements = config => { + // eslint-disable-next-line no-shadow + return withEntitlementsPlist(config, async config => { + config.modResults['com.apple.security.application-groups'] = [ + `group.app.bsky`, + ] + return config + }) +} + +module.exports = {withAppEntitlements} diff --git a/plugins/notificationsExtension/withExtensionEntitlements.js b/plugins/notificationsExtension/withExtensionEntitlements.js new file mode 100644 index 000000000..0cc1c4ca8 --- /dev/null +++ b/plugins/notificationsExtension/withExtensionEntitlements.js @@ -0,0 +1,31 @@ +const {withInfoPlist} = require('@expo/config-plugins') +const plist = require('@expo/plist') +const path = require('path') +const fs = require('fs') + +const withExtensionEntitlements = (config, {extensionName}) => { + // eslint-disable-next-line no-shadow + return withInfoPlist(config, config => { + const extensionEntitlementsPath = path.join( + config.modRequest.platformProjectRoot, + extensionName, + `${extensionName}.entitlements`, + ) + + const notificationsExtensionEntitlements = { + 'com.apple.security.application-groups': [`group.app.bsky`], + } + + fs.mkdirSync(path.dirname(extensionEntitlementsPath), { + recursive: true, + }) + fs.writeFileSync( + extensionEntitlementsPath, + plist.default.build(notificationsExtensionEntitlements), + ) + + return config + }) +} + +module.exports = {withExtensionEntitlements} diff --git a/plugins/notificationsExtension/withExtensionInfoPlist.js b/plugins/notificationsExtension/withExtensionInfoPlist.js new file mode 100644 index 000000000..b0c6cfa89 --- /dev/null +++ b/plugins/notificationsExtension/withExtensionInfoPlist.js @@ -0,0 +1,39 @@ +const {withInfoPlist} = require('@expo/config-plugins') +const plist = require('@expo/plist') +const path = require('path') +const fs = require('fs') + +const withExtensionInfoPlist = (config, {extensionName}) => { + // eslint-disable-next-line no-shadow + return withInfoPlist(config, config => { + const plistPath = path.join( + config.modRequest.projectRoot, + 'modules', + extensionName, + 'Info.plist', + ) + const targetPath = path.join( + config.modRequest.platformProjectRoot, + extensionName, + 'Info.plist', + ) + + const extPlist = plist.default.parse(fs.readFileSync(plistPath).toString()) + + extPlist.MainAppScheme = config.scheme + extPlist.CFBundleName = '$(PRODUCT_NAME)' + extPlist.CFBundleDisplayName = 'Bluesky Notifications' + extPlist.CFBundleIdentifier = '$(PRODUCT_BUNDLE_IDENTIFIER)' + extPlist.CFBundleVersion = '$(CURRENT_PROJECT_VERSION)' + extPlist.CFBundleExecutable = '$(EXECUTABLE_NAME)' + extPlist.CFBundlePackageType = '$(PRODUCT_BUNDLE_PACKAGE_TYPE)' + extPlist.CFBundleShortVersionString = '$(MARKETING_VERSION)' + + fs.mkdirSync(path.dirname(targetPath), {recursive: true}) + fs.writeFileSync(targetPath, plist.default.build(extPlist)) + + return config + }) +} + +module.exports = {withExtensionInfoPlist} diff --git a/plugins/notificationsExtension/withExtensionViewController.js b/plugins/notificationsExtension/withExtensionViewController.js new file mode 100644 index 000000000..cd29bea7d --- /dev/null +++ b/plugins/notificationsExtension/withExtensionViewController.js @@ -0,0 +1,31 @@ +const {withXcodeProject} = require('@expo/config-plugins') +const path = require('path') +const fs = require('fs') + +const withExtensionViewController = ( + config, + {controllerName, extensionName}, +) => { + // eslint-disable-next-line no-shadow + return withXcodeProject(config, config => { + const controllerPath = path.join( + config.modRequest.projectRoot, + 'modules', + extensionName, + `${controllerName}.swift`, + ) + + const targetPath = path.join( + config.modRequest.platformProjectRoot, + extensionName, + `${controllerName}.swift`, + ) + + fs.mkdirSync(path.dirname(targetPath), {recursive: true}) + fs.copyFileSync(controllerPath, targetPath) + + return config + }) +} + +module.exports = {withExtensionViewController} diff --git a/plugins/notificationsExtension/withNotificationsExtension.js b/plugins/notificationsExtension/withNotificationsExtension.js new file mode 100644 index 000000000..6a00cfd23 --- /dev/null +++ b/plugins/notificationsExtension/withNotificationsExtension.js @@ -0,0 +1,55 @@ +const {withPlugins} = require('@expo/config-plugins') +const {withAppEntitlements} = require('./withAppEntitlements') +const {withXcodeTarget} = require('./withXcodeTarget') +const {withExtensionEntitlements} = require('./withExtensionEntitlements') +const {withExtensionInfoPlist} = require('./withExtensionInfoPlist') +const {withExtensionViewController} = require('./withExtensionViewController') +const {withSounds} = require('./withSounds') + +const EXTENSION_NAME = 'BlueskyNSE' +const EXTENSION_CONTROLLER_NAME = 'NotificationService' + +const withNotificationsExtension = config => { + const soundFiles = ['dm.aiff'] + + return withPlugins(config, [ + // IOS + withAppEntitlements, + [ + withExtensionEntitlements, + { + extensionName: EXTENSION_NAME, + }, + ], + [ + withExtensionInfoPlist, + { + extensionName: EXTENSION_NAME, + }, + ], + [ + withExtensionViewController, + { + extensionName: EXTENSION_NAME, + controllerName: EXTENSION_CONTROLLER_NAME, + }, + ], + [ + withSounds, + { + extensionName: EXTENSION_NAME, + soundFiles, + }, + ], + [ + withXcodeTarget, + { + extensionName: EXTENSION_NAME, + controllerName: EXTENSION_CONTROLLER_NAME, + soundFiles, + }, + ], + ]) +} + +module.exports = withNotificationsExtension diff --git a/plugins/notificationsExtension/withSounds.js b/plugins/notificationsExtension/withSounds.js new file mode 100644 index 000000000..652afd545 --- /dev/null +++ b/plugins/notificationsExtension/withSounds.js @@ -0,0 +1,27 @@ +const {withXcodeProject} = require('@expo/config-plugins') +const path = require('path') +const fs = require('fs') + +const withSounds = (config, {extensionName, soundFiles}) => { + // eslint-disable-next-line no-shadow + return withXcodeProject(config, config => { + for (const file of soundFiles) { + const soundPath = path.join(config.modRequest.projectRoot, 'assets', file) + + const targetPath = path.join( + config.modRequest.platformProjectRoot, + extensionName, + file, + ) + + if (!fs.existsSync(path.dirname(targetPath))) { + fs.mkdirSync(path.dirname(targetPath), {recursive: true}) + } + fs.copyFileSync(soundPath, targetPath) + } + + return config + }) +} + +module.exports = {withSounds} diff --git a/plugins/notificationsExtension/withXcodeTarget.js b/plugins/notificationsExtension/withXcodeTarget.js new file mode 100644 index 000000000..e9c7dae39 --- /dev/null +++ b/plugins/notificationsExtension/withXcodeTarget.js @@ -0,0 +1,76 @@ +const {withXcodeProject, IOSConfig} = require('@expo/config-plugins') +const path = require('path') +const PBXFile = require('xcode/lib/pbxFile') + +const withXcodeTarget = ( + config, + {extensionName, controllerName, soundFiles}, +) => { + // eslint-disable-next-line no-shadow + return withXcodeProject(config, config => { + let pbxProject = config.modResults + + const target = pbxProject.addTarget( + extensionName, + 'app_extension', + extensionName, + ) + pbxProject.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid) + pbxProject.addBuildPhase( + [], + 'PBXResourcesBuildPhase', + 'Resources', + target.uuid, + ) + const pbxGroupKey = pbxProject.pbxCreateGroup(extensionName, extensionName) + pbxProject.addFile(`${extensionName}/Info.plist`, pbxGroupKey) + pbxProject.addSourceFile( + `${extensionName}/${controllerName}.swift`, + {target: target.uuid}, + pbxGroupKey, + ) + + for (const file of soundFiles) { + pbxProject.addSourceFile( + `${extensionName}/${file}`, + {target: target.uuid}, + pbxGroupKey, + ) + } + + var configurations = pbxProject.pbxXCBuildConfigurationSection() + for (var key in configurations) { + if (typeof configurations[key].buildSettings !== 'undefined') { + var buildSettingsObj = configurations[key].buildSettings + if ( + typeof buildSettingsObj.PRODUCT_NAME !== 'undefined' && + buildSettingsObj.PRODUCT_NAME === `"${extensionName}"` + ) { + buildSettingsObj.CLANG_ENABLE_MODULES = 'YES' + buildSettingsObj.INFOPLIST_FILE = `"${extensionName}/Info.plist"` + buildSettingsObj.CODE_SIGN_ENTITLEMENTS = `"${extensionName}/${extensionName}.entitlements"` + buildSettingsObj.CODE_SIGN_STYLE = 'Automatic' + buildSettingsObj.CURRENT_PROJECT_VERSION = `"${config.ios?.buildNumber}"` + buildSettingsObj.GENERATE_INFOPLIST_FILE = 'YES' + buildSettingsObj.MARKETING_VERSION = `"${config.version}"` + buildSettingsObj.PRODUCT_BUNDLE_IDENTIFIER = `"${config.ios?.bundleIdentifier}.${extensionName}"` + buildSettingsObj.SWIFT_EMIT_LOC_STRINGS = 'YES' + buildSettingsObj.SWIFT_VERSION = '5.0' + buildSettingsObj.TARGETED_DEVICE_FAMILY = `"1,2"` + buildSettingsObj.DEVELOPMENT_TEAM = 'B3LX46C5HS' + } + } + } + + pbxProject.addTargetAttribute( + 'DevelopmentTeam', + 'B3LX46C5HS', + extensionName, + ) + pbxProject.addTargetAttribute('DevelopmentTeam', 'B3LX46C5HS') + + return config + }) +} + +module.exports = {withXcodeTarget} diff --git a/scripts/updateExtensions.sh b/scripts/updateExtensions.sh index f4e462b74..f3e972aa7 100755 --- a/scripts/updateExtensions.sh +++ b/scripts/updateExtensions.sh @@ -1,5 +1,6 @@ #!/bin/bash IOS_SHARE_EXTENSION_DIRECTORY="./ios/Share-with-Bluesky" +IOS_NOTIFICATION_EXTENSION_DIRECTORY="./ios/BlueskyNSE" MODULES_DIRECTORY="./modules" if [ ! -d $IOS_SHARE_EXTENSION_DIRECTORY ]; then @@ -8,3 +9,10 @@ if [ ! -d $IOS_SHARE_EXTENSION_DIRECTORY ]; then else cp -R $IOS_SHARE_EXTENSION_DIRECTORY $MODULES_DIRECTORY fi + +if [ ! -d $IOS_NOTIFICATION_EXTENSION_DIRECTORY ]; then + echo "$IOS_NOTIFICATION_EXTENSION_DIRECTORY not found inside of your iOS project." + exit 1 +else + cp -R $IOS_NOTIFICATION_EXTENSION_DIRECTORY $MODULES_DIRECTORY +fi diff --git a/src/App.native.tsx b/src/App.native.tsx index 9356be7a7..425d6ac6e 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -47,6 +47,7 @@ import {ThemeProvider as Alf} from '#/alf' import {useColorModeTheme} from '#/alf/util/useColorModeTheme' import {Provider as PortalProvider} from '#/components/Portal' import {Splash} from '#/Splash' +import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' import I18nProvider from './locale/i18nProvider' import {listenSessionDropped} from './state/events' @@ -102,10 +103,12 @@ function InnerApp() { - - - - + + + + + + diff --git a/src/App.web.tsx b/src/App.web.tsx index 40ceb6942..900ceefd7 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -39,6 +39,7 @@ import {Shell} from 'view/shell/index' import {ThemeProvider as Alf} from '#/alf' import {useColorModeTheme} from '#/alf/util/useColorModeTheme' import {Provider as PortalProvider} from '#/components/Portal' +import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' import I18nProvider from './locale/i18nProvider' import {listenSessionDropped} from './state/events' @@ -92,9 +93,11 @@ function InnerApp() { - - - + + + + + diff --git a/src/lib/hooks/useNotificationHandler.ts b/src/lib/hooks/useNotificationHandler.ts index 3240a4854..6f5fbd66b 100644 --- a/src/lib/hooks/useNotificationHandler.ts +++ b/src/lib/hooks/useNotificationHandler.ts @@ -8,6 +8,7 @@ import {track} from 'lib/analytics/analytics' import {useAccountSwitcher} from 'lib/hooks/useAccountSwitcher' import {NavigationProp} from 'lib/routes/types' import {logEvent} from 'lib/statsig/statsig' +import {isAndroid} from 'platform/detection' import {useCurrentConvoId} from 'state/messages/current-convo-id' import {RQKEY as RQKEY_NOTIFS} from 'state/queries/notifications/feed' import {invalidateCachedUnreadPage} from 'state/queries/notifications/unread' @@ -40,7 +41,7 @@ type NotificationPayload = } const DEFAULT_HANDLER_OPTIONS = { - shouldShowAlert: false, + shouldShowAlert: true, shouldPlaySound: false, shouldSetBadge: true, } @@ -60,6 +61,28 @@ export function useNotificationsHandler() { // Safety to prevent double handling of the same notification const prevDate = React.useRef(0) + React.useEffect(() => { + if (!isAndroid) return + + Notifications.setNotificationChannelAsync('chat-messages', { + name: 'Chat', + importance: Notifications.AndroidImportance.MAX, + sound: 'dm.mp3', + showBadge: true, + vibrationPattern: [250], + lockscreenVisibility: Notifications.AndroidNotificationVisibility.PRIVATE, + }) + + Notifications.setNotificationChannelAsync('chat-messages-muted', { + name: 'Chat - Muted', + importance: Notifications.AndroidImportance.MAX, + sound: null, + showBadge: true, + vibrationPattern: [250], + lockscreenVisibility: Notifications.AndroidNotificationVisibility.PRIVATE, + }) + }, []) + React.useEffect(() => { const handleNotification = (payload?: NotificationPayload) => { if (!payload) return diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx index e4fff1251..7ad21b400 100644 --- a/src/screens/Messages/Settings.tsx +++ b/src/screens/Messages/Settings.tsx @@ -15,8 +15,10 @@ import * as Toast from '#/view/com/util/Toast' import {ViewHeader} from '#/view/com/util/ViewHeader' import {CenteredView} from '#/view/com/util/Views' import {atoms as a} from '#/alf' +import * as Toggle from '#/components/forms/Toggle' import {RadioGroup} from '#/components/RadioGroup' import {Text} from '#/components/Typography' +import {useBackgroundNotificationPreferences} from '../../../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' import {ClipClopGate} from './gate' type AllowIncoming = 'all' | 'none' | 'following' @@ -28,6 +30,7 @@ export function MessagesSettingsScreen({}: Props) { const {data: profile} = useProfileQuery({ did: currentAccount!.did, }) as UseQueryResult + const {preferences, setPref} = useBackgroundNotificationPreferences() const {mutate: updateDeclaration} = useUpdateActorDeclaration({ onError: () => { @@ -65,6 +68,18 @@ export function MessagesSettingsScreen({}: Props) { onSelect={onSelectItem} /> + + { + setPref('playSoundChat', !preferences.playSoundChat) + }}> + + Notification Sounds + + ) } -- 2.51.2