From d0031929ab3eb6042a0240f07d79c6b55697ce5e Mon Sep 17 00:00:00 2001 From: Obolrom Date: Wed, 21 Dec 2022 18:14:29 +0200 Subject: [PATCH 1/9] Updated versions of gradle and libraries --- app/build.gradle | 44 ++++++-------- build.gradle | 8 ++- gradle.properties | 3 + gradle/wrapper/gradle-wrapper.jar | Bin 53636 -> 54413 bytes gradle/wrapper/gradle-wrapper.properties | 6 +- gradlew | 72 +++++++++++++---------- gradlew.bat | 14 ++--- 7 files changed, 76 insertions(+), 71 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 91e0db9..349e355 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,50 +1,44 @@ buildscript { ext { - rx_version = "2.1.10" + rx_version = "2.2.20" rx_android_version = "2.1.1" - firebase_auth_version = '19.2.0' - firebase_database_version = '19.2.1' - firebase_storage_version = '19.1.1' - firebase_firestore_version = '21.4.1' - firebase_functions_version = '19.0.2' - firebase_remote_version = '19.1.2' + firebase_auth_version = "21.1.0" + firebase_database_version = "20.1.0" + firebase_storage_version = "20.1.0" + firebase_firestore_version = "24.4.1" + firebase_functions_version = "20.2.1" + firebase_remote_version = "21.2.0" support_version = "28.0.3" } } + apply plugin: 'com.android.library' android { - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE-FIREBASE.txt' - exclude 'META-INF/NOTICE' - } - - compileSdkVersion 28 - buildToolsVersion "28.0.3" + compileSdkVersion 31 defaultConfig { minSdkVersion 14 - targetSdkVersion 28 + targetSdkVersion 31 versionCode 6 versionName "1.5.5" consumerProguardFiles 'consumer-proguard-rules.pro' } } -repositories{ +repositories { google() } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - compileOnly "com.google.firebase:firebase-auth:$firebase_auth_version" - compileOnly "com.google.firebase:firebase-database:$firebase_database_version" - compileOnly "com.google.firebase:firebase-storage:$firebase_storage_version" - compileOnly "com.google.firebase:firebase-firestore:$firebase_firestore_version" - compileOnly "com.google.firebase:firebase-functions:$firebase_functions_version" - compileOnly "com.google.firebase:firebase-config:$firebase_remote_version" + implementation "com.google.firebase:firebase-auth:$firebase_auth_version" + implementation "com.google.firebase:firebase-database:$firebase_database_version" + implementation "com.google.firebase:firebase-storage:$firebase_storage_version" + implementation "com.google.firebase:firebase-firestore:$firebase_firestore_version" + implementation "com.google.firebase:firebase-functions:$firebase_functions_version" + implementation "com.google.firebase:firebase-config:$firebase_remote_version" implementation "io.reactivex.rxjava2:rxjava:$rx_version" implementation "io.reactivex.rxjava2:rxandroid:$rx_android_version" @@ -54,6 +48,6 @@ dependencies { testImplementation "com.google.firebase:firebase-storage:$firebase_storage_version" testImplementation "com.google.firebase:firebase-firestore:$firebase_firestore_version" testImplementation "com.google.firebase:firebase-config:$firebase_remote_version" - testImplementation 'junit:junit:4.13' - testImplementation "org.mockito:mockito-core:3.3.1" + testImplementation 'junit:junit:4.13.2' + testImplementation "org.mockito:mockito-core:4.0.0" } diff --git a/build.gradle b/build.gradle index fae5e0d..f7c23ca 100644 --- a/build.gradle +++ b/build.gradle @@ -3,10 +3,12 @@ buildscript { repositories { google() - jcenter() + mavenCentral() } + dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath("com.android.tools.build:gradle:7.1.3") + classpath("com.google.gms:google-services:4.3.14") classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -16,7 +18,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/gradle.properties b/gradle.properties index aac7c9b..143455e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,6 +11,9 @@ # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m +android.useAndroidX=true + +android.enableJetifier=true # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 13372aef5e24af05341d49695ee84e5f9b594659..1948b9074f1016d15d505d185bc3f73deb82d8c8 100644 GIT binary patch delta 41671 zcmZ5{V{oQHw{2{5V%xUuiEX~&#Lkms!igrf?POxk#I|kQ&fK}T&aHF4(^b2x8oPgV zb*=8b_Ue#A$e40Sl&=bqP*`AKaByH?U}9kLC?uHw-IBX|lo9#YuMsb*Dv)ukd+l=L z0uJ_{9{+~HApURluR#4XCl_lL$p1Abew+;9zgLZaqQm{~)|HQ5>eWBT1$Zzpro?zC zoJ4^Ed>}?k*K(-u0L9;r_a|#GqaGD@aWDNr z({TlzpWnm=Nts7=`l;|z<9EjrkVsN24SF=YI|0mogwmq#t<*d1A#`{fI?3N820Mmj zTiakibP~TYL_D>&t@(N9vUmo6wXC9GAj5^)8|sXfpp{)lL*p&P%MhaZgBw$gfR<<# z3xBK(g3)KjT}pRXGF*zpkWbguG}dH_*|L+I*_@V#Fr%BJr$k7}nA6=2Dt2^aXK>!+ zyZ{oEQmHgKW}9tS#xtV?mTvDAjh*v1Lr0D2jgdP9ZNT)I}JkWWlA=6Y02Y;POH;J%;Ovc?`6@KTYVD`(Y6ny9vR;8?lp|je#CW zdvsPGY3CMO=dLi`fS$HH9W47ji61$oIVN0|Dm^mKI-*m|FQc!96(J!iSbjgI%3_W1 z6WVpnI^ht6hstnWm_+8`+nXo*-o$%SpXGZb{FDMWT1&cGOZG5ZXc2L{ zE&Mo!XJV1-=>RO&PZ8hP3~Qdc_K5dpZ~ssuQ58EbZn{&~xjAN1FG~5`64UZ;lo#g&*Iu`G z+~)O(504`SXKQ)9Sv6&aXUbD};7keM986UrnomjA6pbb4BD*~$#P6zG_zj(~Y`V=k zMT+bKTXl(_13?~i-EyLLC4iK;lIYrg1M)P{ltWDItn~}4f9^{zDnzb#BXO~Qut$)` zUrFppO!5=s+^d1L@v53CH-^bEAQ8~HP zYzL&H=J?Ly7=MW5C9>?}$Rqde^`$B8XpaACicdJ?^xILoSEL}OKJnjdlKzlQnzIP) zCT&X68Zi%i98+OSO9zlHD$M}so#Jw9*KYOQK(Ax#Xhq}*8ZQ|7F9<|s+uIaSy`RP; zKYCgIe%@h)v*(+#psKK{N$pQ%_aY_e=LEumXHd%yo%sDAbuM$==ME;$B4Z73Lw`~# zaL-HS-0IbZSXN0Ek)eqXZ%IrTQzm~F;Jtd5`-J^3Y=!+VI8LMoU;Q6gIqJmkiuw<1 zMgQR?=l{dX8f`dm%0%YgFF>-op%aD}mJnppfF;OTvu;t|!7j9d@fjO=FvLVrF#@FK zVD`!x3SYHy`P200ZIq(lEbf}kF6y-m-W8apIQ)Wtn(tAL>y?nF)#FjElL%PS&9XEG z9|4LB<!D$ef4(-k!S7eqEot`P-T_mN()W&LwMq?n{1cE{Eh@C?FK3qr?r}r?!DQ zaJIP<^wsHEOccF^nLu!PZb8*)vt**Bxm{I5-co*luw z#qAnQwZ&c>Xhz02$}+T9q37X2`x6rl(DX=SO!=O)T`Zce5OG ziS#&^?1832A}yY6W&lhaVe%K3BQ(5o!xeSpn^~T{Qsw-D_+8RasS4*<#;}3r#cZ>o z==WxZq)=a!AJ@l(e<4cSVCZyd1zO`^OZp@bzRg>E>ckvxX^~lQ4}5tj;g5#y}s#eY8Vb z!DD)~JM_^$jE>}bwIfVp`4Q13<-@7acsD$Y*xUQaMs7SDNvdS$_vJkzs*#@p!oYxx zKmumP*(JFEW-LJFKAS+GCz!yq8k4enx0mgFO8r1sDQPTr6g{oC$+@l6g2J51zBy$) zf_2fX6Wf5Kq?#G!h38v(m4Xolv_Wu8rA55*JIsGx)IV>~Cs9$rz^Dnqz{vjV@Sp(M zx`-xNOYTA{s~dPR(j;24btczle{f#$3BEHSNn=1jC7|h1{xY$TYoq{~a#2i8Lb&Mj z#7gXPDy=s<3oDz^DC<2LpNC_c))sfVE1is)+WV(0$In?o#{Ddy zXG=&J$1h&kzsfi8)4Fdo^I1ymC zeRciR^bFhSxJ`1SNyOoDn7glRoXBB3cRi(+4#ZFV9zEQ$xV!rGg1X{o$o-cN`;~?0 zufsOfr_2zK+k0)86aI2|s+(VwanBxg$j>hNRap%JUUGGSVc@ggm?wLFK%m7T=m9#Cn=kSroJ5%z#!~54V#2Qh#+us}dN*Vcy=d5ilxWdeJEAHSfy8bZ_;pyLOYPo^s^_ z@la?!gc^t3Z-)9vf=9wfO$q~j`f5mXp&u(^TFmaz)L1C$u=SiT0STC+4#KFQYCu+J zAr!Bt<7IZ(6S{PzU*b*47oA^5<#35O`EE$qI@bmc-b8R&tB2;hE<#rdSn_dfux%;u za~N>93Yz+!6rI6wPWoO(TbIsBj5In4xZF+3XOg(F@Y5~HN2kQQ{+{lVqdB?4J+?EH zs!qin-U*(oTA}9M#HO7+);iv`~zm5*b(+LpSVTraL2>}KvOLn6Q`z5kp3)ZvE zO9JLcrgAbWRwboW;uVwksw6ZVJXQ)DXI@%*LnObl@qm^1Ws)GL9V{0nb zvVqkK(ZS;04Um{&Q%tGXIwen2I@;A#Sm3Hv!MI_eh&|+1$GMZoaly`~s0Z?WssXo_ z-+5bHL})F?s*d4k&NaUca{v~L54e-2y&v6v%(lDQ74bMo8RRXvNH}>j^9p;%D$WkM zT-NMu1|AeABviD8RKR6I?Brnd{=B6SIFskqxFk?R1on0Nys}4&7`KR7GIml%H+2H6 zEV*SYS^aYajNF8XsXFZipcR9fsnOfiXPsr^{HFjEwBd>9!hR$1?p~~f;Fs`sO*wfiFME}eCI$v>`fz`M`TA@ z0D|gFsraID(jdzB8cvBMW;yf@%LW-t&&*xo8S`BVufcvqulTT^r=Ij@n zW$=;+I1xsTvojcj%;mT%`zzr}Y~-u&M2yyR7IsF?i#SX$O@BGGsyTEQ>Ux-d>JjLsg|%0K$=Q7Q_FFYr&KhJ0v%$G ztVz|E*9a%abF!KL(CtwKF&5)f-~1}$1_tVM zo^RURBf=& zc#?2roiy`B8IX&q&sXgKg;7y_i-0a?9=(EUt;5T_^3;qVC3kS!oP$FL(6y!|$SeNp z#7Klz{Cf)#&yCNh^tYG*T2NCRMt|yFt^h+ZE9>F*z-lnDn>IQ%6|7Bnv|6DAHkCPU zR^C~oB%&Zd3A=>O_yFBSvtq7jsQbb>{G9)wKnRH(#ldZml(O(HQ+TX+Kakw)q-@OWjhj<02on9Ah4p_;J_oLi&ZU&wWdRM;L@e zipbt@{9f$j6pArNJ+0PGmrXl6a5b;XtUc}i+6pI?8Tdqnlo&cq-j2n7?NP&y{aMwDvW>e-4qhqZmD-w=E8{>R z=eBdFW~9v@OQ16*%gI{gPdeLUE-hRoJ`K*kikExUzEAMQ(sbHDM$WW*$llTnR_S(A zlnrrY$S<1{_{Js~h$tNyh1#nb@}O^&1(x+>7{g}fD&LeCjAnh^_}W1&stu*hRw{;+ zUuGJXv9=7=9~cj7OkHt!0JI4fa{SGN0j8yTF5=DR2;hRHWu9xvp!spWX}-eALE%Dg zonQNI(|UUIQBEUVzhT)~UTM|WmJhQ%sP4Uyhagq|R5-L5ji18D-Bk(aVhT9R{t^Co z77gVj8VA*IeA*e*JjvLeJG7V+HhPv}5Xag19^AWX=ao(EOJuCMm30E7*+5(#mW5+I z&Az`y08t8(Rc;GzJ+P;UH8j>oW|VjZ9>fx#Tn}5qmI=Goec2 zDJE}FZi!t$&U`M#gI&ZPB5w@K4pMh?!!rVL9*(nLw(Hg(yq=aoxLt75UbRbrhpT*u z!3#DZ5Ty%}WKbYdGX{fY5B7#x{D2JRxdU$(;7WR=J;<@P7#x0+@ItkvyTO=xB%Z&a ztlgElHo_||r~#vSy!~dz0$WP28dZ8NtbrK0Pbl;C*h@rMbc``bDV60z@K>B_d&qvu zS*y@y%7AjuADy?Gl$q6eb{GA$m1-Io(^tKzdOw!E~1?e!Z*Y$2SBO{qwiXQIM z%DF74bYM!PL{Lod#3sd9-1srDyk;ET_2HEgR!;u3@mwZP0bSkVlA^L{fRFx0OVyuD z>OW5E8k8s57TM>mHpb;Af%^R!DV@f-?k|SD5+Vc&ELa0ZU6l4GN zTW7nFpJ0?KMs#N|ruHy?VinDp4h6VsWGBrxqP?5zkmx&g7InGoVZy!NY=@mg8n<4O zL@Oq5S~cCsXWnIwB}v2uPSMX%=*~wGZ9@?)Ly?@Ig13{GhoWcRcq66L>SxtQ03u5_ zj->C-ZIv&Bi|2`#=wt60{?RPDF4sb(q?sn_&|fV*0hj&Y6RR*xC+T@kD@&oM=L+4ao?;UAg`V#YA%~E&X`PilF_tA$`DayhT^J_{>nPx6YTrj z35M|%BYlH1>W%B^D?6Szpq*4(xzD2N-PAoSM3pcZc2z1avC|;m23zE^1H1AF z)r&f!!fHRFLMymZ#_)tz7o;QjM}-xqG}Zj(YlsjN#*cxAN1aZ#`5x)BXv4nwWcFoY ziKd)lagu|k54KIeDfDKNBcf+`_aq9w&|4Y!9ju19#!9(3K#hoMJff>YIu@fx%wpjJ zm+BmM1<@sUqjk_IA}LobK_o_3lU&0zV+AFq{r$I*Z11zke}WX2`7F%-cQEGa+-4dNrJbpf4 zHk@vEcs%XzVgc%LA$d&m#=wpv%F#oBAb1t)^|tphCUt#9l)(sZo4cM5b?#?@Gh^W${MXe1r<4k)glKU z`PDF&xy(cIxpXUPbhN1ugC>qH$I{<8@qx3(4;bxf&Xx)v9RF0?c$nWb%4k1x$Fe)tBUA{ zJBfb{rna+EpF1)lemC8;x?r}5H>m2Xpa*F8eOYG*I%snj`>Z0_ozWBE7C?V)77$>Z zPJUX~GimEdI+!mOl`+vS(ncM$gD4=aD?jB^?m;dB7F^~{UHUyPwj)Hz4QVY(pmx1$ z6$dU0E=JWKMx*a(s_|s#0izL@62>j9w?k9Gv<48B{{8#Pd6iQ#QF_;*{zAol!%$6X zmd`{t{gAy(Y>NCZEdrp$z)tHGDx5|6+D4QKtmf8SKPtH=V8RiZnOflir zugy-DayS(VmMbl6r|}~rI~4Ovy5zdZ_AX%>>2=nI3}tWgBqZtM)k^pAQyLY+a$yt= zLJZ&K*qzrs2nSM|-NKSr4eD{MEH-a!Z|yfyg8JMZgznLeB&BFr1mqR;gN7F4O*t`* zHdwgM+TLc)3Dkg5n6Phse*y?uStB4Jr0r~Axbzfn+zhB`$6C07K zgk{Zk25B$!pmjTK!?uF9xwziZcy!OhxhEG49|%(unZL1KEoA>X@d?tsEyk8@uq~&| zIG}<^PZiqIXk=%>Oa*Pr#1}3hXZ?{2q9K-|3Q7Ye{ty7q^Ec_ z1&~ei;Wfd`b=X(v1dLeQV@QIOQzq|??df8mNmI*XE6^YD^Sup)Ln}BWC=4ItASqkSZzuP zn^F2x$iX#L4tpDoJq~Ygi2CK?Bx#(*J?gXgnEM!4fCtN@1_H?c;xT0y*SL6|%S4r) zMPZlrTXG`)*C=khaQ^g#EH3xx<|J|^Y;|1&Uu6`~ynfRngiqLaT})A(VG!hEmX9-x zP*P^ zID4V%m%{qKU2fkPkSE)0M;fpv+mm7=2tNb9^xn8@YVatWGah%$&A;EaF)hvN79LPu zHJI_2S3083J{U|0Tf;XX_=E-^IXGoC@|7U`j!BFE(k-=hD;|Ah&5+xyTYvtIRFUFw zU1s90#Ir=lO#k-Vcw)SP!z(EC97J4mX8xc;;e7rbO1 z^=(%NhGCwt&eZ$E(Cj5YeDAN3IDH(ZRbs98doY|@@k?aTYr2Ce_9uu}{ZgzQWTS}5 z@l4NgKE(uOMhe(J!vu4Vre@4Fcv1kap)&7IxvQz2N-_ZP{Ya)%?xD0#An$|uwZ%l` zAa0CMCG6-(VqBVXuD`TW<*WK%#v`3)jnC+sAm3%0Yv$d7CvU=16Wk5y!`Gj1##)3d zH}B+5%-1WO>t@{qrQh_VrX=amtpEOh^8dfMf2eYw4TFDVziNXgbP~k>$jY8o*(R1C z{ND^fodhS~hq;9JSLCPv?Th_)dscEJF)$YU(D;z$kokJ#FBSy$=4P_uC@*zVl-cph z(N^x1L~@zUZqyqbax`-KD=~>bCmT?e`L;TJ^R^sz?x6Mib5+qx7D=x5HT27il=pLz;Cq6_ zeG-XrN_6+`kT$lT(yB{~LHFel(d`pKfS>w6%e4da*3Di=!m}@ZcO1#^K{$Ot2ubBL zd_cvvH9gFYYtSmu$3=F0jl>&F`zz|j`Pm7;Ki^_q*HNoYipEz#vef4+#D`B@r&hmj zlPM|{p023xGmAgFhbKX*A-TE2wY*7mayouf?xFg4d3K3EE|xkGjDw!+7(->g$Q|_Z zfGh#gjp7Xu|3qM{5z(>##bl@wlVIF5R3CWivRPaTMNbr+hPO!^ zMIcyBsFE(&XeO;E4waMy(#vMsO^H1#Z|sR%c=3_mtN#|BE_*zJ4mu|$2#+2uu)5{N zlM7ZrgojZsW}>L4p`nix8{)+Crac5&vCZEFC0AXfZ;vCoNe%y`FuRC}69!7yLA zd0iC#5}1IxV2sHuEL2;G>MTI3N0rk)5N}t;pNY1_0Ag@IKUZ{SFAQl42OIeoUWaA# zQK7UJr(y@svyrM!Bb+&nR0B7^ei;j`;eKJqna9n4*;AA&*I_0~2*ci7+L{13#Q4Vk z=>wFhm_95fznsWxkN=F9Uo)^RvBXN+b%yOJhpk9ppS7aK=31TWXS)yiQ;_AjF>(`q z5GYqcG}*sP@gY4Q5u?w?nd#q6O>oANMJ;t`%tcdhcM!sFlDRWKwp$SF5D&p+5Ou~G zy{m=XvTcPdy>*lo*y2s;1 z9bbM2%4Jn`jH}-U#}M=0q{b6DDb20%H+Xe!YdU28f+czZKGO!_zbs3b8uP#B18gHVN}7=s3`S+$IQAmBxJCSamZTtKz&6@SBg&%g`ay zl8+=)s!G@mHtk5rR9B{(x}C9gW|LU63$su98HJH?iJ?K3NSF^K;n$|rL~@x`Q!hsH z2aOH)U2x?WZP<&SI8)#YU~ou_*YEQ^x6l?CVIsH2UaS;$vAPghg}F5dab93*8%1$8 ztD-Aq=&)qy#s=H>Wwb)IFqnO>iqk&{aplI8>rypBy=nC}Grk?99LsZhoz+p_Voi-| znRk2WT6Y!XMU4WwPlSDPn&*$$SxDyQF3M*Vdu8=-)tH8~C!u7_3e&L=?Hp9sn7E~l zyr~}N*|hBhhk^y!J5Fxs@wuwr^_@rVV>AV6X)g&WzC92!{}5z8k9I(Y_T`hbtnyfS zfOrUP<@9(6m4T-p(_1(8Y7p44b)AuAEnTr_wuFAtuU`VX!Y4yRf5CTrc1CwGhn@^x zdGl8etwCV*H3yfF+>fHFpig$~c^|s7EW0p25>6l(P9OLDy3W}c(TE(oDb3*X+QF;i z{eHf_pk5_Fb7JJ58fRepNnE`I!YO4_3T`5`_Fl|$FQxO zmcNF29bNXiyY}thMWeX>Jqlf-?J!>y;5N(Pu^Pk+e}zPr&BBc;Mvz}OBCb-LOm1|ja%RX4_Sk549tq$5V51R$( z)yf)7=8f`L^W2LCB0T$1HYxnLV`p+L(?p|<*-24v!1=;nlJ3A9C9`yVP052O{R1HX za*Pxw5u)sVQTs|;nca8vic90n;W8YD51lIOA571EsvkOq2$wn2}1gZ;8=6uj^IPfOuR|U8B|P4lxW$ZTHqx z_E4Jq%>li#kezCH&}=M<)3UGA*x}XG+^bu4WbB$pg*LgH0B=ugv6MJe!#xyR>z2Cl z;g&t`tIfr2RZkZ+p=*#aVA*jcA!UJ}AC^h|Z9sR_2D#C){=tYb{?DJ}Zkc+(!7E_z z4`ofpACAfP(MFZ+Q;y~;==+I-8fpew$SbrwCmODmFn+#4nxc`HAVN>i}YWGAe5P32)D5{I&dnp!^*t~u|QD7|X17JXM@NMsx zOTptYT9S?L_abxzQhFkKwyn3ct}~Rf%9da!u?G2v;<>96r^=^Bnas#l;r>PaPc|tr z1Zo@WzeRyYk>AZFV8OsHP!nUJ@DnflVG_rb5r8*O4Q=dCPJT2+Y3MI5iKK52Of+RY**= z0>tqdVkda>cPt3mmhq|gBKAU%2wz;xX=VSS#Cy$R72g!o4Y0+kJ7*21f0_KTPZr#kXgp|H8>yg%f(jI2)ygXEX6rpA5dK)#DG4jll^?v_wP;{iN zf(O?j&!q~ERRhVWT!Ma;awq{LASJz}jQUFLx#OduJ+H@SXMX*sn?twZ>x7=)?6q=+ zk%?`&c}ClApzmtEA;nOr%t%gw^E#o8McidaXteie;|S|zarLBgSbOlMtD&*!!h2d4 z)BNu2Or6EiyV50&eS_$~tsTEuAXgn34!Xg#(h_9CtG|uiNfp%F==hosOrox2GkNEn zJf8#%Y6Wt-3hbtIR^f$6)>;+G%%|P^D=&IP74-(p<$iO;?=IM@e9nm7@JftjTCOn9 zdG9S^i!-r`5$kwDl(w+tIdY*^p{%GCe2OX;+`WJLZbDPv}yP=*Y4Y zc5C5A$j^8Pwa8n|LZm+q;1n$0gUHc}xW_`8TDpl={QCv>q$x!O{|YT?4u6!N*;IPS zQm}I8M{MqPDSuAi9e+SruHAEJEbg@e^6%N3g5?*ke_AET5{Fl_kPved!z;##518Kz z*9oy(JC?}5mu_I+RsW?cl3$hJWJ>Nks05aFS_5Q8yoc%Ce$o+j17pe$wEba9g~$B< zobNO%tGPEBvA^L$#NZb*29%K;cn*zBFA7F#gQ^(IA-(SwK7JDGPI11E#(!PjY^3C3 zqDEA9rlni30Bzo*;zKZyW)0r_WCL-}`q$Y1a0oAL5LhV^WYS>Mhfn*I9 zzL?v0j8BeQMG6VRwrP`Ibp{N7Z;H5R9aYVUvSH+T^R|<< zqWPlToIaV27HE_>1fl1DN8Oq5lirB5(a&6O1bcH>$a!)oK1$X<@jlb?F&cXhMce&L ze134+@$8PqG`(D_;E)z3zrhxhqVdEjUM}f{zoS9U9wSSSo-NJk4!=4#Z!adYM!N(_ z!1hP0)~~itcpcGu{U)FQr@G}G8NI&7s3UkeeqO3E38>#k*p*?<=lXJD$>?A_SQ34Z z`s`M~sWJG2u4?0}^c$9zYM$g5#dX&21T0VtYq^0q!g1ehaEEsAstENsLKCB2b-B(; zS+d|#o#)bYa(m7yDlXvZ-BO)7l1Fuj6l>7$BCjp0kjcGzB{wlc5IL^H0Oi>p9eIvGF_7azmN?i_3&C_^o*%xpx7X-tW0 zOeXU=nf$jmP%p~gR_IcTZoTr=ITwFWOmDJ`?OZ#~Ojalq@)QrL5|_2WXproI9!0y= zGwZkD)Xbv)en46Z_w#{XI3NTQt?396rZt2q{xrNO;-yn4)%RLM+u+2Z zTpKXMe`QJ?oni-#7MFh+NH!?`)|7ihCupq22sdqDM7tOD-l@~AAPKA&vwM03Qs1$Q zEDd{lgV%1mLUblfA?JDK^frO(AsD6UQz>P0K8WXw(N;a)y|Dk1-jNPvxxNbxa7+kl z0_Ts*EY`){e(;O$CBSlwzZ?YPx-9{#-kCsXfw8InYTSqW)}|C-A73KAQw?srGezqs z7sx&GGz->rYa`;6cm;TX=pm|{J97I1Vk7E&z&*c}mdzhnOLPlJCS)VXKo<2y+n5#d zPQruQvAY>X1zL^p)p4k?o3WIkY02#NIu*FjahQA|*2jGJkBCnUnNd~>udx;Ti@5*A zbh@|;335UsSXE9sh8^Xgz2?`!lo$ehZHLw6)*A1%*VP!iGyoyf!=|J0v<&B}%CTON zVzVs^p&c7~(D|%3foc~w;bP)Q zlb%6?Wu-?UF#W9;ne<5Cv>(}yYOa_nH*bl?&c&r(frrEU3l&N0tv9Vre zswVgo&TeKR12lYfpSN_%zWq-flr_LJLtBIzqGZ@`??VA%V{%={TX^ZsCz7@vooA## zH#R!u-H_FYQtHRvS@$lPRi}oIAvbZtzo2{R8i{5HVw{JE+$(+N!lW7LMh$0YiR>8@ky{3&RnUnyD|Ed`ohz3vgb!#rOzR(rFfSJthCC+vNbbba0>MU`cnQxZZfT* zxoP(>r6H9TusX&5X;27LqjQfUR!QCL>xcQo-x)#4^fN*tW9!q+neU^UtMjQ`f`|e$ z>zXMCXqZ-3$dp+|Z*8JIOBtsZn!Z@YlMOHBoF1c<9XS|=+Spx%K5;3S(}StYK$Or? z6zN%BLByP7w`&r_*)J*;7V<1<5n#ZZ*le(|osmCRhUjuWscmT^q3 z1ijd}Cz_t|foS)AJ2>i4k@Y*wXj_thd8SeTzWlGJ?f2J{MbB2wBeEcs3S_E>h`!4F`Q6DI2kJ3)kXI+8-0_Qur`#VoC_4aoL1mieG%95Hu1NhLf%Pd}NlxrXqadIue4| za}ANJ=1~VR$1ZxbzU+}sx3ZU@h*)Hrc3)Q)G;-YhzBs(w;>&yfssjF&7dvsC7WZCE{9c%LT!_F~H_T#ca4 z?XO(y?^Z3JTiS(STzGrrT|4^rtQYz;7I&uc31M7&i|i*9dxXirW!;VTRxoP3%OVkK zfQ&xDWM|8uT15Mfo$Rzp&00{Eh|Yca1n`{X`Ge zUQm*M_jp$L;chWa2+;%ToUPgiZ^uKt&@ensvI9&MCDsjGbB}ipq2g_!;x>-XE{?Pb z(~0?By$Jd5jhqz3v%6h7R=agdvfPyjTtzCIKa=6yNu$E=i3w-_{l9uYZ{MV!|MLBP zsp;xZ-RRRDq*)!TrtmP}$!_Q(PBz<_Xc7-MAe;?FFVc@%>4w|GzqKo)Aq-XdE!EHp z&r|?B9D|6A2alhVm)1qvxs!JjCc3>Q64aH??2t-qyY%2SJZrY$ViN8_prx(pyv{s4 zp)-6s%gE_tQ)AaA#53XVWHH8C&zi}Oilw`RHQ=C@ z+I8Tssghz=G$A&kW6}fq)6Zq2aW{JiBw8=<&hMVja)b-tK@E$XO-3Wx^d$KtmFq(v z^d!lf>eqN(46*=9Bf*Uu6oOyQ;E7)Wgf-kbdy{8CD?^GX`mS!fSN$lYU-egS=>zK* zzR+Cl7ijQ3%BNLC9{1F_g>?0oj`xCTAGwUw@Z;p_35|fz+5vPHtt2?!6kYDdLA4j( zU-pMIsk-Te?37eR*esO_MFr>rZmPcIlNb(79K}G!rl0OB&R&eP8cyK>u0@NApZwSdX46X=la*`_-1foeY{+!eNQPnb=*jA8Ll`CY}^lOcB(=+rSgk!01Fd!;T zo(EzKa{UF%dFyK&$3C>kDTdPWn*@^B6Oij25mQBs!?U|C2u9&f^<~Ulu-&3Olh7hk220B0S8}pbS2&P{<&$)I@}@Q_v0S91m8Ae zR1geH1*SomX{_T5cCrjb*HE3%xfkK@b6{SZG>#d<=1r;4t>12xmO?kTe2_F4Cx~N8 z@WmWQC;GdxyPhDrOg4^tQijH^OIOPs)mNw0lyR>8?aIMt^zSaLps-L(e9}5d8rYje zSC_Hr(r&uTC;NEQ-ffN70cq(E3_+#GhVdVBy)zL*L?ezXEs3M*l75FhG+w=SjllX} z%xvXHo!lCU^htiI*D(%WR|r|P7C{jQ+mX*FsmXp90a#8d4+`gk=`Z{79fQf?!3g44 zx=YT%-)tL6x)WmG47Y$_sy1n&*JrhT{-B?yvk7*XC7Wmq=ELJ)<7KUz=EVSt2x|jd z<)7$Df)tkKId5xZ6bSQv4n_AGWFSnCEgLQfbHhf?n?NZ-_0E%`;mi%@;VT{bf%;G8 zQih0=hUa7%ZMA%ZtQnRh{eKd?kEPFD_UJ39stuK;{11o#Lu3RazoVoFb+HvN=v4%n zm{RRGIU(`FvC+7nYFf@AewN8HHshw5eGfLJ38)wL=SVY>{Fv4 z#=X=lM*Ie5`toI@hPQ{x{;kRV*gbZ11g8w0C_*>G*qhC{9_L=hSWXe!adhnsOqaig zP9jS#JeP2E9Lg(gi?TCT2}FNJyH?_sU4M4A-$~dREld% z9}~loe0zL1LgXz7Z$pyjJ0Ks3Gs9F@3+p>}to6#znF%9=I(?zTm02=Jbo_EmuzWEv zDNqlaswzX`*slY|@rF=JSiHRop0TR6sWO)#q|muHG{fL7dg1=$Nj<2Udlupu)SvA+`Hq%VukT#~VFhQY?mm}_5}f{pu)t?^psHO*KF5?oaveJtioX$Hq%+Yyhk zQWcmeW<|ozZz#t+WIgs<6(g{r(UQDjkWN(%_F?nDPH%=6$jdwLhLxtzL$qP@PrRi1 zO`FYiCftrHE#7ak1bp0s6@5|YI2PlFYrs)UAnrwBv5`D6eZ?7X$1orfn+m^@=a<|H z%XC40rxi+p8RtktFXKw>sd<@5R+-U7+ET>JYjX&t+O#fjoKT-}NeaBYJz~=j(s1$g zX?J;UJff=@E#wRooJfW+U2{@S4ZTJ$N-PXAx6P3`2 zUI~2BbYK`BPW&>ma02&G?CyhugI0r_JXBUwRgha@*O?sr>fJIY4rUf$w*xVN)74o# z5fv25x%WACBPbQzJrxxi*2bllE>*i9YT(f zXdt~>l_*_<&9@VEw|$rY<3qg6Eb-SMj}>#9O7zX&1Nf<2Y(LXZ5tMtK(1i%tI zz>}=EGqty|Z}fQrmww3lhQ{_3tT{LO#Th}jFlUHu!H&OgHwGCMF75Q-xBuMCteRG#FA{l_S?YI1HkEI$gS{$fMCm<|14XC^jyq$LAVD^vU(9xoDPVECpDr+Q z1TIV0s;}p0-i44mtT#1^hb?<;owPDto>mXGd_q%8h{Ee482;$3X(&dEeqpZ{@RA2nIYs0h+>i-%l~@?@i7A+ z1WKgs7_kuNNT+_l|L}Ud-9lV@LgfA29=Ld8L+F4g*ur~-r~9YN6BZC=_Z*l1;5+;J zGi-}c=#;eW%24QBaWv-(Mg|~~a#L?V2VXaW*O49&^YOCE4$Bq*)fzica7+9FW(TWl zPVa+C#XB+}|Dee6qj=qE83GBfrxXAHVr0Or2ff7g{vFHbG?MF8;oFWHFKF*XK^NUX zxyENJDV(pt^uXu)l^bD)`C_1rN6nrvny3*&|1t z#wm#_)M-DqUw{}9YdRsQ<@9V9v|tA$wuAAay;W%y14UN=JIAKdK_PJUx90+bI(j+T`0VP59jk?@@!3e@+~gL`F~#A{}WE%U||3Z zaaWMP7^HNxtL*7ad5xsZ*VdwuVIBk+jVfptVHVbzdzdKgj(dm$vuGTKobYs+?Ir-n zH}zBk1Ot>eV^{*AnBuQZku#(3Ygh{Vt}K2AL-y!~;d@ccWH%~khA4-O6F2D059$fs z7(*-OpAu+)qxW=~{c!&xA2UkcZc%kpQW|R9`lAafIoWkI)nHP}k2Fs;N7@2X=^%8L z8{m8k%XnjVskq#|1MFOUY}~DlZ36y{+c|ujXPpIjUA2Y%UV#CsGidIW@!I(yl}r^o zH)3NhdRs1O|4fFIoXi{o9wW*lhF;4_A#`UjW5`G9YLhv{2RKbrS>m$9gDFVjGE+|t zRAzJW^fZt%sSe9M+}4|-GsXc&nZ}G~w&J4iu+QdO9On3Tz)kq1vbR!M590klA!5+L@nI11y?WrbLF8#Xa3_=(`X)Bb;{a z{A$_JV30L9~kdJyyKja5b*FGqlju3#=*GlS~Y zA=-{c;Rw9fc~Sb#CO$u}1t+{NBe_h70cZ4w7n#_4gL-*S-lUB;&VK{88#QW{8_muT zFs6Lj6w4eOvYavH^ec6EaYylo^}plc$z`X5Cf~CEL5{p^OK^7DskDj8f408^XtoLc{;| zQX7DBRvx0op|}x+U%6=lmNHcmM_C&jbfvPt|MX6tT^UhoARDVm0-m}aUti64uQsRbx!v zu8s+{M7^dBx`$0-@Ub#Fz5nlxC%oskQM^LTPjl0qgHs5eebe+VczQTW)0h=^EUP6F zVCAKc!W5~$oNEU7GzltBVagF3^4X@@B1s?ARhG(&F|t^3z&h_lM(mlI7#kStJF?;1 zUR)aP!a0{|sXDVbaaJjHv)LzJNm~B&`fXn2{0w2q$a&oR0XG{4}Ukzj~QkIu9^xC8o_qbp^-q?2qB4Rb{W-uCwHFQRRwV@VYois}xGBlm*}7K?;<|NO`;PU`kBk|qlzC!%1& zUVUfeFVA)?omX#KK^iXx+Wed;0xqzNF#tUZYDNKub2tc>BrPhUP?|dEVf&I?Zed(% z&P~ASsNJ`Yk;l{Fr&FJKrF|tc%n?nMt=%grczxb~PCiJx>1I^HTrv`h#PYMEtNnGD z>*$1T{7oC_B>YwA0eJ=%4C`KJ6wM6rsj79)KlF*%QH$=NRgg+@2D}ngLNfibL0QK2 zLb(8lMd5W_k*Mt1+16rv!Gw=}*KFiBc&MJ+1F3iR-%?sHE;Pl=yTr0xkOMwD`#VU8 z?dzNDY`loPjxZ<}w^e>tF-QJCTwN$6h1)o@&%NID+&l>;D z7-u|5DAOymhHn=Voj+{%6Z7X!ALg*Q8!-UXOU4LjeRgqYI5}fx0?#Wlo$txU0Xbs4 zfUG@rYmM8x9XeRtzinZzt|W)+>b)&^gFOQ_MHbbCrfkyv#@w$EK+9DDEa5-CvPG>v z_1e*wQ>5pHa(WM+(Gk&hV(Gr#VgVk!b?h@(e$Al)_&7>zY%JMyXRGLv1k+YvbNG)+C*{x^%BwKx z)s84cW{`Ve%c_o@x6kMF-h4mF(1Y9oMvQCLx<7sL{G=Gw(JC?`fug04on${noVRf5{BN+RpUNg=@fU%oqnsYcp`* zF&!_m%<8&?45Iyp4o~$)!K%aXTq}ls6za$lE5LYKQ76Iu{{Tk*!t%HfeDf z6wE@EX_3giPUd+haLn{%J@r=)94_;McIgg@98>)?DZtFwhq9&=e}LuGb!)*BfGyRC zx<=X}LD_v+JTNx#gEZE`HkLd|FhyWo+d*E`dmS`Je2}3rRZfFoAdP|h z-zmxyGp_L|)xIz12jTJWLlDYu`<8wC@Gp2~Qezv1WsB%jOIQEB^zQVO=6#>Q@Z3^0 zKB}~ews1^vow%orB^K$cVxk%o0GQri0#iOx!rz#6&p>d5f4upaZO4dW-$U)t17Uh2!`<$H_T=jqbJnk&F5e#g%k|N?Bpe=uHu_UR+6(*b8Bjqtg%xjODmNa zT`!QaWux)NV1y>!ix+fBhy>EsOvyxBVSTyfBFpdd-^FM8)#!fOwpZrCfR;skt$h9I zT9o-s;z)k=I!!)Vvz*#rfbq}P48n0$K_7D+8dOWcTl1Ej$%V4vKi-Lx*(Dw&!X#a+c2-)U+&Agbd)~$fe13;1tsu;aJ(RZ` z;pt!%vWK-K_%{h+PZ#X}$N`;80L#Yk3OQHn03Y58@I5WkuBav5|1c-hkV*i8sG=71o-O-FblGZb65^zI7KVUF^ zK=RC37j;8jlk~Cgj7+;>p$9Z`r6m08y22#Oq)eyK=b5tJ z><404`Y2)GdvEwZSf3VQ{y)L}`ezu($aj=p^7kU0>i@vK0s>T0&JY41MKi(|^&8_? zC(wCo!;NCqF-QCW6G>$DY)eGLRo#45qQ|va7}l1x^^K@PuSDNrvlj6wI@MoqBJNW3 z8EG<_YlG%5kiUKt_=*q6@%~e#VN&pLfBdxFnemjhKQ*b_b(e=}2w8{!g|J(XM1Z(I z#d3Bd_ET~E=Uz4(wBjvbT35u&7RwQK_mU-dnAePL2OB%enIXh;Tr1KQp6rEzV0&u7 z_j$+H+l$ckFX1qhm%t1!;lWZ^zgOxp;{8nmQ||r?Xk*^-lH|ZM)(qkCE~>F#;<4iG z%_))q>agY7ubKMiWAlqa%+bWeey(2P1z{kpOqE>%u26ME2CwdWzg`TXF0w_d5=r0BKd%~_SZ`G zOoDQOOgWGFF6lKngx(*>y~J%^v6j8;J*)=nR&(FR?+KfcSgzBNP%0{)1eEqsu)#jB zbC|bEZ$$AJs_458S#{NzNIt;|gy@K&({5=}oUsKf&VU9?;b~B=E=%PlD7%iRsameD znf2gzp{X_ufXFRz=>U{w&X>VZI99X{2`lJF$fVq=){?;aEm?K?Zn=!)&(TgE|F~M4 z_btLyl;bZ)B}<`_Lt-<#Lr<9K+Cg|vo#A1h9l}!Kkh7|`XTnH&z*7NlNDd3j%e6tQ zqtm&0;#mQUcH>Eq#{7yAmF288ga>Yp-AWA?mte$nO8KR16Si2|;=Jmbbi~MSfIH$M z=)YqTsP(9=sR@KCjA`l&#}nr)a8VbkupczOMVYA)H59*ygn}?SI6_ZIW!L zJsTx2|IijO850Q+iXQwQXcwZ*!Dy#m~#Ww7J z{nBSS2x^reAGA5JZx|qM)#%6!V(XT#_=wE(X1ryp+A-Jr{i)~dRhHcPfIU^s1T8&M zVg&)<MO|G?!7g#p1%i5Pby%1?RIpd?jL_Y}ux52l zJ~YWVDs-^@-l61>knU5|mh)C)EIK7&(;WcJq`%$M0O8+ZE#4EcBnVYg;>SV(KaU>^Dk|0zjZ$SdpEc*JO4RSPX*{lFEY|E3lL10E?r~DvM=#5XRE7ye@JzC zalu`kpVK|M=9gPb>x5DA)+rsHU4mG1#J>5qViq){(u^hc^ zMpfPzBUw;b)|FPb0;tCB;h=T*NWVRJTLFlhdl`m|558JsHHGAU4o@G^WzGP?^OJJ` z^T)Wg5G+A&uvk}Q8IK+5YkZi2xLaD7fv8(+Sa;aT0D%wx96x~%DamHOHU2IEy~Wu& z8zb?S{|V(7NMVPvB5l4o$ovWDnZ$mAxH&im5+42U?&#UB&3IHS73tJAz}2+v93{84cq6 z)B6Il2S9kfeh}jgLTtuio~)~qxPTODnt+dpksUi_PYYFX1te-Yn=H-|@~^SoRhysc z;ZI$n<%5t~P)5Ss}W6uJG7 zFKS4nIL9kSS7!m_a#iH!yV~8YLtJ2!YhIr#k{#EleLm@j@bv;*p|%w8&D}^drRy^M ztUZ=mRN#O0;!t(O6j6vE+m*D(_!*-X_E$AKl+>Unl+?&Pm74@9Ib^030DNY0GS`Gz zaS2=QEdlmZ{1?tl7hF`r14XXg3*3YU_swCYd||;?yVP>L%wq|2!rhZa)gQf|LXw4{ zHLp9xnO=e-^^*W&ZgL{hs|Lk(XpF$vBoWyVGFK7^60)q)MwSeFB_&XYA6&Pmu%pg9 z`AGr0ULr6`DNCy%#a8g6#1&+@>FTauK(C-XcZG@*T}6OaulAjPs?4i$-@cZ- z_9~Dv;wzal_Ogcl|2?tvojftDB(c>Y&-vlYRk&Sf&BCcRs9Q&$`3v?EV0xXLx(@Pq zxsPH^Q%hS%C#jD>^Aj+SbC zMn;~2x<#H*Ms7@cNq&0lfQDv@c5G6<8REYO!XHUq#^nD&qBfQq2ZH{GLtxi?m`Uz? zTs($J+A0+TXySfPiwZ2pM(HjQMd$_S@rxlvgRsTT(sRiwxbT?r5Z1OAWZ3<)W)#zM zOz+Tt^(Q0kPB?<=Y?9T0b5$jkxc_W~*$JsaVVr}sf0wDX zpsVh?MNzN5Wez!go6--(*3@Ls*_ErTBHa2K`#+SPd46(96B}Ai4G*B*7d7$AOrY_I4fr5 zqO@yr5?jkO5$?sQ(Yb}?F6_aPy)0@)rDt=17M1c!+nCI9om5!rRoW~8`&^bSV;WHi zWxHS~QjXwyAF+M$5u&Aa^&`w!&zh$=Oj; zL(Qs#JHYGG@9~twN1^WN4f4Ro0Rv&NFelna8?N~4tihlwg$L7svy1z;1ICgsPOUPa zsz<@#pv9~+LK;}V;9=ewgN@yoj(i&v?U{giTOCbF6vO>l^s6u&1yC5dnxs73{ZoDz zBsa2bv`i$;IA6l^y(RiuNFEv+!w%aW@9xkS2&;cP9&Qp?v)X=y68eFY@hIadRXpX= zRdMF=HqJ8HX_Gz67sGd_LAgF-#ytRd9`j0hLO%z`V88GyVjLhXT4GmCBmF`ZQxs$> zdab{lv-NFT&m)sZ@Ud^(PrO5lc5Cs{X!M$hadX9jY+g<;YF{p%WfllFV3<-H{|j8N z+j_bX^>IC8nER-)ZS5%go=)o9N081^BH{90+*Nw8Z*88q<$+Z^J&R^>VTpjT2ft$= zN!N2foxhMC=of0A*UG{xhBEYeIVZB#GsFcqzF&Hwd#^c=34Xh^<_E(*4AD{=2O;!r4ZH{AV``ZO{Puk8ad-xd1W%@#6<4LJ~+Y zdXiHdY?6X6BES>%yDL$^XRu+fqgXAtV8RqZ+b`+}=?GJ`+AvDl(u#*zvy=oc$fe7* zg-1S}Gwuv=9u7e{L066IFy#5&SU!s`UdxpvNmC>+5c*i;O?*CjK7Kx0f4;x1{Qc4S z>)K>B5CYqA2&rF$w`Jdi7sFXv%Q0%#1@C%C22Zmf5@0MX!4S*GhJ8IwH8`vMPX{BF zp~_ad6LWtBDJg|2(qsfpR2pt2w3Qs#0H-S<9%Pqlw?&sq?xtX%>R7fGS&h48)VtcuntN&a4u!BwoKVkC{Qi%;sTK>xjaycoD z6hx4?!oB^mblF|*_Vyr!*Xv6)pwB`3k7Jh^X{tsIR;RY1(UX}t=UwLaeyv$;KJtaS z8la~ETcyLXba!LI6iy{s6xr)wpbd6Pw95kw3o1LGPzv(oH^w@W2Y{}tfZ6p{Z!e=c zmZ!l@d5XE4J(Gz}TbeT`e*XZj z01vGO0p%QASS!v|nwo|!-luSp=&agD54cdTlY|2^Icy1fa+Vy1f)N-@4vKTq6wx$g zHuJ06LG>G-U&^Qa$1;>;M+^n=R%h79?N>q+jF#k6{+O$;;RI=kj#uWexUJye7wZgR zxFfs!mJ(EZ8x!OxezG+qqlSN*QEaO-?J>yIBeAiT&grhf$LD$bZkXI z1Xrzvgkpg~L1w2I^Oa^A!nVrnhxCqv&j}x_7J>wU+l@ne1P?!RoA-?HsS-{^+yt>4 z)`+wsDq^tRnFmk{GHu*^rwF<01bnc4u}u45t{|Ai-xE&R);w;q4k2BjR! z8<8hQ=`$B>f6xW{`@jvj46evGgKC|NC7#^_d|Y51%E4ro3or{!V*=$`1}NZ>+z&LN z2UOBZcD{AVol_}Sw2r$_OxqzX5^zB^D4-1O2~b+0n+4|lDYdTFrcn2E_izrx% zQ4n}NMRHGoxhe1}@h_PYXs%Vu#Z+pYKD7~e_IHjehj34#JD2cxVc#|j5gXv9VEo#pZsjzSTqvc_(jmc56@{FQP zJt%#wEC@)oVOvwQAz@L?rFpay4Gi2FU3tW6n$6(h4T(4vl%qU^_b>xIH7I1WwT3xup3aH9$G%bBlTY*n=Ui;RsE?b=Y^D z728L@lgCl(e<_*XvWS_Ukk1xpW^GWaLzichNYGmJR=1K1Tc7xt8^x{T$g}+ZKmP+3L-V+jZ+Lt1eW;S= z+{ltl&FcSS$G?iev&jec9gg;WsRi%<(QG5r z1{$nli1m`_C^4w$NTq;((VJJpny1-e-lF^c_zQ*#PZM#Q7ZJB7igkQ}L5I;18vlGe znVrG?HzOk_gI^E;_<$q|G>5K^&$5n?wF;(7up13C-b7_4*%MfamPTS`*b5AYh8B>M ziNBE=1ehXI10~LzHDHFYzs(KW@vtQg25N_X%1Na$)%4Aa*6*!N^FAk6R0Yx_|T$J1;=zlO%31I zS)7oRV6WqEw?`&p7Wc_EQk@MQZOvRQCU7v|N2W zP!qRWYp7ao^|VVQ$z}Rwd@mx<>FWjkhbyGjxtr{nWe}<27%w=P)nl@g=l5+Sw8k3T z9pK@4E>JcAe3oIaoRBpgo%wi#FPHhD%{S>IIH2`IC>FL#y`!q-!x2X>DWC+Ke|-7oL1$s~l6 z6?U%1zk&B8=**7=7syGUjU(}v{zHb{}WgXfRG zHe@m+fD5Iy2G0W-C@8ZiD@$y*dO8uiH4n-QJ+}h|nASO22d66pXHW=y$)BFP&`!yp z4N=-KF?nd0>1%(}abJu%R6!UB0@b6ih`20Vjd^XR#gUpDaT=K+okLI1%=1$W^tQ;_ zr5{o?MF^F_Gf%`ofA1((gC`a}A^ZRQl#(?Xr!@++TBrT^pMlfs?R>o`38YXPfb92k zObpcw*&to7g{NYx`ZV%8Li5kF2ug|=s^BleaW4l=NpoG8VYXBL<4sqc{8 z5ljeK7Dr9aM#{7zjZA;22}owao}R9%tj0VOU*7hQ=i4nsPY0Y!JYSm39I1>o*TCEG z8}9vKP7n@DZdhhF|Q$I6Qac;?Ztl4)+z^tsO0wdnqB>9q9QAf7N9t9B}#XrgGi8cI? z?d{sX9MS>9=(aV_s`5v*aH+gY7=(;C(nIc$OjQK?>HA|t`RK`A+%GgN(fF3o!7W!; zKIrEK^tz^PQW^dR>ire^Z!TX+o6yw56u|GMQ#M5!RqNN63i14WPLjd_!dWJ0{gKB}*dwzqX z>6)cOQYDGN?sV(XN5W)&aTP?gHA}No(o1A(m>BFFe4)7*MyZRWiu}Co52@Ml;}heo z*;Nb|GhBxb(l}KiB5Atc$81266VRA>S!qAJdX@OJ8}X2WCM+0$_B41jayBA%oHc~F zUITl(bjo2OXQfZm-_YymKHi8Rr;Vn}*drp{gkXTU3dZpr8nJO?d*fxDcE zNyxy`nlCL`Pza1D&5CX|+%NMp5Yi(5AE099GPcza? ztasc-yiXyJ1YnMKkL`>`;^2b8YsH4Vync{yi!~;3)LBwnQfal-sZd!^wYBJ~!P)TM znsunu5N&h`F<6G*>YjA+U&sBV=x28H+;L&ef}785uV&6h0>)T$RcU)Jy2Q{GbZRTx zW>pkNc~4M5cfjqcUlv$fueenjV`d$uDncHX7+Wpt0Oal2*n;T26Qdv9#3a5Z1ml|Ld?Y7vsx{a`$M;z#Mn#NX^-98guyD}@vCD_%Q=(oy_I_O!q7ytO7 zLiO0Fhq~#1o@`_wB+_Z9{m9vxh6~L)Gp0& zyXVhB1<32L-NVfVJTi}pH(IMx$%`SxdKg=TZo()S%k@!hAGZy}^Cd=`-0MSRJtywd zLXg`ri*=HzIUzn_v*adR;l#n;m(P)6E8;g5?a)zxS2|+tK|yKNS4?$StZL?|H!YV6 zZn$7u7mGJYw1sE6CX|m2FVcxRF$%&SO6m+90iHDiBOAiCjN4AUi~fNju&w(FKNr6` z?OvV<3x;gQ(<-XVZYZAQhrawabn4LDKJ~HMYa;^w>yA|FNt4l_MX#OlZsy+Z6#^M)!e z1vE-LRa)Q3Q>gV(v!Bz9C7$-(Z|}-9SGu>27$9HgwNecb^gvz%dcVjW_NWYy3!v1~ zw~gtr&<=AX0-nX1OM6zJ*!~E+3jUL!`-JtY_cDR{%(J>02wNn;w6y~PvqJ{)ARtVz zPhS2R=X?imDYleq2S>QCH*x+KtN+KpbpS(=3SXZGQyuafb~h%LTqpR`E+U+HG4rWn zk^X%>A@X{tUgQEB5^3k1ALc;utn(?lLh}3A9xS*g)7=|`2zfmzH$|63_fx0*6C2mu z6w(siur>m!9X7NFG`C0Sc2p(8hj^M+RyUAZ4%MDHvmgNx#EuvUj_;p+AZ>!70ze|u z?r?7dt(IDJm85SeUCwtJILcvS#+*kOU{*Y1wQSlsA(x2ay7r5Mjg5=q68)gD_Dswj zC>=v74p2dvIi%Zrpixkc+d&$1^@iVL&K{!g4=w-FyW0i%XDE9I_;1#rrvr=~O~^1{6TFg|j!b^LGN->Vl`^=PNF*AF zN=3J0V$9ljJ?%#MGx{enV<%b!)F3M$q-}a#14*7pifVlN;(0QId;hld`daWu**+B* z{t%qTVnRQ^`{qi0Z9h3aI=(tSJJ3y)hsBDSOy&?3Xtse518lBw0}S4ewJ~;@Znsh= zUUz^w`gY$$%2MINy%KdQO6XDF9hI&Edz`~fYUDC?o zs=kwBV(xCgx+njxk_yA&MxilZ(C1+c_;lioIE=XHDVDv$0kUA1#ps95;HK*SMCXlylEb=7tt`v$MFp?XMJ%39JXQpYIOSUXzh$g~<4-;82DTs6KSMqHIV;(o_=*4GQaDMs=X zV&gExPDmmws7It*9w1gB@daB{3b5wGO&JYTa~vUy8?~voY0+{g2AzDB-R1B&@do`v z{E6@=;)}V`pUaNpoKa!7i=lm>5diw%mFB3&-ShWbQ(TAqEui|pc>`@653r?m`u`ZH zuP%;@!$j88)t`;GE#JTXW7l7P_xQggq9K(a%pu`7K!1|b z5M%s8q){a}Af6WGM8rYd0Azd-LqiMtkez_g0g-=28|3m|glU7raJk)yUATkxBZ{AC0Rdl`v zHb)MrY~u2qE5M^!QJU-}dFXN@MW9%~+BV|VW2(X0QKA%M1)bMAaj!voqjJk#WN!c8 zc6*j~&Q`UyIXtFdJK!9|wRYKU$^nDRFeA%9Q+YJT&mq4Q%3{d^#IO>Xw28qqRjR=< z%f>;ag*+U^jy#!)@jI@?q1j&+vWm8kw7_Mrwsa36F2F%>kR$h4^h$ePHrzlf9 zsb~->(xRsGGB9q~EveZBvsRAQ{ZV%f)&#oLqOLEpkglCb0cJqsJ+I>4eMWLCB1@*rYOLci36e1$7-3 zSu@ETN9eZn0@&t7pVz5Ku%DT;Zsn z`MVxto3zRd>qWXs=1PYawN8Ww;KUh?Iu!bevBR9j021Km=6K>^(Vnx!yZV8L+{D+j zbWsiCQ+PZLo`7Vt;>MZlgynE&>ez8F!Vxk*qJ2@kv|D5Z87|^|Q$Lb@RRjrFNfu*U zwMy$tjWR>_CF=7pxIEcskzwiK9klN3UtPKaEZrunJ;U)K#(_#TCW*{J3!t;=p znI`Uv0Hn;EPz~N{TakPV9(DzLPEU6jwS(^I&Og5#3~^wojrd{ohiMy|5~?qgV1s#N zQ?HZ~t0C&|LMlcs9Y!>8*?&dMz>(0_uacc6W81(9pJCERgv{kRU@t0?Pw7m!d(90x z%~035!x))8u9Smn6^Z?>Fi&A;u+7(Pb{}t<2k`U2CMF@K%we4zO5_)JZ24>dn%VMa z;du)6A?q@sv!`6sV_^qzi8X0i^L2-$tz`L|13ND)uTQ=obiUHbpy+QNbklZ4I9a5k z;YN!gC_iev^)gvqH7-YkpiRf|_!&QzrYldn_D6i5EQi0(taKcdl@dv6P|Y{G`@wmF z5rFZ4ww{GtNGNFh>Z~44#L1Hh(Z0fMXyZAZ#*=$r>>yQ}cy65=ram;ZDS|)7cU9Kp zidy|hoJS=j-{$kv+n71%{RaEFLqdruq~B=@smSRP>3vt_FOo%wYy=M1p_N^MGcuXa zGoFypg=7R*IK&=O4N90YLu|ed-Vn~7HUJ2^9H&tQk&XS9b3D0v?`JvO{g#dGYbdn6 z6{Nc$AcFuPNr2z4S#{ zlhD7IWFVEtA0?O-jgVMpi~=x&eP6~RN-S@0hcE}-QHXDgO%*)Qy^p|q^!Z5TW0jTI z^honc{-LqtJj9K?E!-s}Cz+svj1hhH2_Y1kWQQ@>6l*JI^_?$h?da&QUPbB)BUTve zoyrm1;LGOtDbs*zn#c80<)GrT1AezReGvM>4&LKKh%j{4c>fmL zdcKoq5z&v5sKAlY}Q6*9B?2O~DjuMl{1 zVX%3eeE$&}3Z-riXgtB*AryYbt7fc&L}P*C&gZIK?!Z-%M*8N86BG{JWejC0m=+6fjB>6It+_4 z(}<~06LWbo^9sWecJ6tY^VQmqI3jisGvJ3{^6h5JBz!7dD#ayt>+$=v>H7ciZc6j@vNC6#Ns4j&cj zQ^>D2^F)fS9enXI4Zz=9euJj=GRzfzX$RVNc_3Aqd&=MXG`Qf{6CpbfVYk4?m5*f7 zWjtprnkDAHP+Kn7_$91D>&W&LabhtxGe;bIZBsvhoc{1#t%_rY(W7D9?zGHuIfJ3X zhMpdWEibDoQ7j&l78?HsdXeVOTC9Xdu))luakcvK!P1=8dI2mHdqm470N+6-*Gvr7 z$-M4BxTLO)J|_R5UGNwMt*c#x)6ZT{JJryPiCiCJw;uFA@Ty*L83xm%vru%<%`&7m zIAqr0WzchDOxB80QM0TmmQdjbu!6{2l$J0%Qxgr zO2RZ~INi2Bq*Ah+qMu`$OvpjkKxhi|beCwt?|h#L=CvRSU2Y2}da)FLelg|P+OJkc z&8rJol#9wZR?vYTX^`^9G$DLs6IQczV09m)V{Gd*c4CgJ?mf3M9f8(8g;m!XzcT}+ z2d%isv6%m%(i)?kVQTcN&r_a8xa|@CH?#|WMJmtuuH=euc=!LH9ZeF>3xUft`Ek1}Or8R319CJis1!1D}2BP!k2MUB3 zxxlYDkx6-ZsT88L$h2t7qe<>0Ue`-P!5)98y6`{HWX$Elk_rOy)(@`o18jFVFidKb zR!YpJCI+IQurwIz$ykZ*txJGCh2M7E6rwE`k>5F)UIQFa?i(}$d;P4qSpX(mIOea6 z!$SOI%IyVsWebd!anTuT%O@}`MRBZ}ABQw#t=m30jDlNe}B zXSlu3=skC&(5Q7AddRi{qli_g$7Vj@FO3s%e$h7V7<7FVT1@w!J>Ql&k-0NNWy__Hnrnv-132b{;j zyNKO=JSCWDH1VWL59d|MU_u|QLMVKJoh-zKW(&iU0_lC-L;#b?E7DiyGKeLWT=Rl@ z=^`xzQ?v=CSewyYC>})>Qju&-CIY6~L`G$1(<9d8C;e`$1D!)(FBAP-EQURlLu>|9 zlAW~yB`9?k0b&_7QL{D8!EF-PAMTnyETTChe~I0H4La=D5O!o{ni;upQ?7-oQ4KqnCCl5L-xCB%OH&v3FhECcBKuHfpV zDK`ng)$*J&2UhL4pRn^dOf-uSQsmERt5iB87SX*CmP~KymBbd(PdLC&!*@^#lmW}H z808+E8GYPrI~BQ>ZPyv^Y1ip*{`T>_cE<1r+O3E%4lB&-&~L;>mLoK&bj{HgB2(qz z#Gs;8%6d9e@!q^qt(1BSQOgtUV~!_ItUYgldzvr}_Z!84Q0T^yM`OKZhhsE<6q_h| zsfE@b;KY!Cn#?Ef6|=7+}7$tq*oM|9;`j_N~A&FUVy zZ`@=RI-~%|7a!V-Kd2vl+~_&Nb;^0EJ#ebs+T+siA((9CcP zjgnMroYC>wFh8({M2)XNbDYOQrloX1EV7*ASL6-GD`_PZ-0+ zs8l1EbFavhd)HVNf=ojJErkpalg4VqTm6?B6ccTti8p?~{-hKArRG`XPb`|mF!vOk zbO@yzjCy#OPWgUc~t zy>m9%YBy}zdtm;K+e3ZELA=?PLCe$$i>~ ziNwKH6@?^6o-R^eW`dO+%7#QIH>T;IEOl1Q)>pa}8WIJ9BjUiVrQm-?=rTOv0G55w zxC~4a3T7O|W3Lk$54;qbCYkh4_*OEg9hk@}1GX%%^ql#8-CNtup{}1(ZKD(dV*cRr zIf>y>Ce5{=vYgm982^C_t>vFVs9-=8&GM*57qrrf`L&|IQPCc&{x3`ZYDqk*mG9>z z5dcRKlm(vG=QM;q4WNA}f5xgu1@J)4jD2sZ-9rY$+SZe@bQD16hCRH`Wu0{5a*$mj*f({X?!wjB9%VE18IzO@1i|~ ziXWwAg2ik2I#jd2d?_@dN_XBC0S&3K>u|bm%$Uf_1rU+pHPnoOtlgCPcC?sJV8v5lad(i1w%#M9g!I zO+->JWG1!~`?wCE*{dYyyU+58^wZ%JSx{hLJ=fjL)UbR87QwOOIUG1@k zo$%vxtp<8@y0OMF%EIp(d^7w_O?V=2z}x+pKvrB| z%IsG$`VtuepFa+h!4aiMPfb?nzSQ?fuikY5FxfW2@WdRL!7EY(&|x;I7P0vkd7=Ap zAz7ECl&nh3*fT;y$=QK74y17*3B&lCmqrqMj~%H3797;FwCSlWbibUv$zisx{Plu? zx=sC3U;pD@Z%>uv4frlx_3y&{{}G4M7)Fx46*RzOBeXa)QVE@&M8k@@0S!q+CcG#@ zI!Qu~iOdIgya-O)W9%yRxwdyK3(EHvG=gHd5s-X4)l#U^G(0f<#{Ad!I*Zfs@w_!I z4&tpyGVweGC@JbRzB z?>_gQId`9Z)DOeniBW-s^dyD^={DI`Qv(mu57t)lT3-Z1i5e|vTKgU-E zx9BrUoX=Hy!@ot=*I3C{-6D?^L0`j|mJ5pHVvp4M2yz0s2h4g=`WEm+ zHS&~T#^u(M4F7_dcu5bN6E=-HO&LIAx1jeP5fvQ3`-IOG%_=j0wCF`7!DS(w&#(tj zMndTMZ{-`Ye95Zn)J`1+ zZ4&VIyc|j1<65Mv_cY_Gi17NE=*K8X9a`4Pui(eMd}YrM9_>oO>1OwL%r z@5~Oba4<`eL`qT7#dPW&o_PF3h(_r*@Q3xd5rb=I`qXXrKA4krN&%N}4<+{(pT$N6 zobV`kRJ=pvZBYeO#(i??Gcx(%w>n`Nm-_R?I}>l?YgAaKZXa_H?#{~S&US)ON*kf7 zcYhS?t>W(B*%U98EQV6moZW2YR>2%S?>1-PWh~N>xv5rggB~o*gv?wjBRrm9Y_@8` zyHCD6a-YNUPSmUxuqg?mmrDx*1K4r$fKwmR!4}dij zv4l!$f;)QflQ#&GStwH8rwhu>SJ=ld(LeN1Yzj7qJjZfYw<-38{l-9i*J|H>GzD9C z_Pz6fyPnWyva(ON)Y&ENdPsP!maRG*m;f(i{1b#p0(I3>8mjv38Mh>7!$ysABLw{! zkj55?PGl4lhfC@PF-j9?nwGoc3&%ZEdJ(O4o!s*AYQnssev6!;VJ_;-{`%qkCz@wE zXGz;meMhYIEgAXRR{8SN+mrm@I7v48qp(TH7Vpe)qV3PAZ5f?kt8r;agkND*qdpab z3)Y7&;?Oo%7u60vXA;Ct<#kw)E&lYy;`%C)AeE~{i$WNa1)UJp%j~!K zC`NF4KR&5lHu>l^cvtTWf9IQu%`Z%H+E`jRH3gw>q`usd+6r2?f8Rf_91mlF7*kN5 zD8LNJog7N&_nlZ^x8RXxiaCaU38`s1gv^}}WjmenQi#$;#C^uIVN$X=j(J&6v!h*+ zvr|;lRW@1BsdD7h^C+Ypd6nf}mS(^Qm-N#TD8h~5EUE);PK&{>^OW2Q(c3K!>z~8G zj%84$2CS2Cu=-aV#?o=4XnJ7QUT19RqhUotwu$MjMXRMmdhMPFh|?HVH?nyOeMo@S zpfSwqZo8{RsWOc923km_M00RHM*MCi0a4fFC=eh{lOLqU*k8w1T{sdF7i1;_+rPCx z9A}@)gjWIDRRB2#aYaig%-WgmSrc?U5rUR*<4sZ8Kjw}rt|YLOjlClcvliH$T-ea2 zid`<5q-V6a+p-`qK4u;0G@)6Rk@7&lC1I$Ne&QB*BMK~9?2=kunCP|%rb)yv8eR`> z&ExzI-;m({p#Pbqw4`T>sr{?QD6rvK9bIg_{XlePC~J)-Dg3^(t;J|b#%?X4upP9C zVjr*c6FV;YNNb+W_otgc|H%hEKzW(=uEg@}<-<%91@8E|^bTt*C-JUlx*>#*O|}ah zb(&1lYld-)3`8FdYfq4a`I}U%#d(zC%dABi4R|{X)MVoZy6kwWKl3_%S(mAn{&3DU zrSmPA$T>6S<3`|^?RQc8QbDV}pkGnY6bZ&+jajA#rjd2uQxl{6lNrhnHZ3a*H1ShD z*ol@kVcK#TQXeL19niapL<;;i9CS*MHTJBtpy#QxYIAY_+9p*r#s^+h5YcH{eb8-> zE2?H8(5s)JxFXU}Qi7R7-zv@Yq-p_Eb1vQVR+g-pQAedf7XD{~H6t4^hpfySs2TG> zq^R~_O!!ksAR&w!E-+!pe1|s$Z&>8{*62~u6V>#X(rzRFgLhnAbQ(6T-ya*6fGp`B z3r?)Yjdk?T#7hxZm{q2KmfFICWhN>;S_vISF9yX)?)tZzEk#C#-dl`rt7bK$!6>#Y z>DZuewQM!O%;L5ZB2dRNRMgGw|XE2Xl3uykad z!Ai@(|IFp%w_sX>^}0TiwHRnwHCr}%r+}D~Z@l>B|Cz%D3d@5n_foeY@`^2{q zSd&qPp>`Vhi@7~c;#09FwjbkUDfhyfLWrPGN}%RibkofA$WVsCL-$Ex%T018t(YIZ zrqCeqTLpOv{`vM564rA4ALXH1eQSz?-S6%`pkP<8HjDUBW@UiGxRS-U@+I{}k8$w+ zV29m1OLj$2^aEV)C?iPyx|?Mg`)~t=WXg7z{d2D^#EM=d4EYiP< zYZ|{xL6O#BBg%%?4{S|Hh%fJ3y7;-p2hzkK;N8LGPvIjD8WCOUB~; zNaiB3#knZ$P(go8dQ$8$7Jk-Rl+`dEex}^&D!q^qk=G3Q&>LPTF&N&or%a(}A--lQ zlkQ?)Y{0RItZWIM5|)Rd68uDR_YP4B{4i|oKlbuybAg+4YP+PxUWQ#qjZ{~SESM;M zF4p+f5s~wr=KW$mOK2pauBwe!dw75{*I*%-Cc-RrbYp|%oyiV-F*IF;j|}e87(y3( z9Dpa-f{{x!Ok9ba+_!k&UjP(lu_vpHdxpYK&s5AqdgqwP`5Da}Fn&sEouG;DaPYd69Qs*Ip+g76?Pd_uoKK+I+`Mmle3FDKV49~U2S;t9ouaZN7 zBYO6aKWLrbcg%Vo6(U8oWjQm|z2U4BaXgH-_hZW;80D-NW~Q9kmg~4dgkHq2D+Sk9 z@v5a@c1gG^R}P&ooh+=M9ODlnG@R!jYL01aIWqTKDA#+7CpE2o$4hQAR9SfFI)!vQbP38R}qke~fjIVd@|Ak1r^{%7$o zZDY!;qRYZcOFe&|VHOj4I8kWXGn#B9!qG@3 zFlm%mlFbl5G;u!K-6kjznef%X(i~At#~}IJqeB~vYzPK8MR@9`@-%BgRemrcoE5=uiuG|XzspKV>~QXEv){^4%zLXty`S2 zJe=6-pq#RL9Odl6OidE=`e*4I*?Y6Qnu(P6&lC987NF-^9B#@zGTwLe1os*IV3W-5 zkH$Q-=jwUN;d9HfUhDL6H63sF_THrs)lie7lIrg!kNBqVONwsGJ!C!2t#;rav;%aXy6I*FYT@s^9UF z>8m*p4}(FAQCE<6=1_*F9ir20T5vpulV-Yvbiy}J^DS6n^KI-sl0`_T;it8Uww7~q zArl0mJ1QYxMjoU59%y%+T(0%`=AnorJ=gM3rh9i*uAbyB_EzH*S>ZC>8C7;vtuh&e zXIVvw!uxUH7ejMlulVdfLXH7yDRhNW(aL}`-T6uA*Ik`oyURUkOoI@j?yfZAZ8V38 z=N~GR1^g297w`D%Vd_imqwP&F=NO^A)MO5L@#Xs)?p5;EM-9TRV%_MsW?By1be(#+ z14&Sq`aTpEnc;x3Q}s|v$ML^!L?c(ld~Fa;u)|>pf4EAs8|1^2JxzIlZE*Jv8$*Lu z{0I8g2T(PxKvyTPfRaG`mS7bO3*mZK_7Ycl`T=z#xGgx*YQW7?QZX3o$GLg6tGr$V zHGV|3?_fYU_Zvn3g~;u2zh{SD#;42kbA&~t92NN^(2Nk-HcVv~)EpBM`5}Z(1+vY`qgZidEOPaFB%U1*= z-^W0Q=@NphC7DarS|mJ2lSsn~L>)@pRYI@(gANp*Rg;&90bA6v=1?@xGe^l&@Fyod zB}k6vjQ1--$~XH$nBcR2I*ER7qKJs-1EqB(-h}#H{#a-JU8GlYOj?1Tn{3XBiROsX zJ#KD7T`0+U{N>_qNSmZcFMrSs?xR^6rO7fZECaFd7s(${G1sj|J+LdD^o>F~=o?mQo z4g{UI5Rhe`05wZ@a}{hVRdV{$bw{xdiGQz6;?s>NY9$mE+%+PrqNZ90#*yW{lW@oEf`=fjwj@<2m!!=gCO z>DE|lQ_QG(ev-nfuA2!eJuzCcsCa;#xnm!Y(T z{)$}4=qQQaGW7npesHz~?*nmb2Co3A35s2-bb-|Db32r;mOAt3&`yk|QKQm{fMhl0%CR#jOONIL$50jF?^K_9KP)LTx%Xr-iJ{cx`%RFg zd8jX1Pp3Z%*d6VbX~Lk8&zjAUVJUKfsMiZe+wi`1zVp)Hp7gL$&~vraGj)CEVu>xo zI5OkLpCN09(wUv{GRe-Bd&mPzG#>Jf9?9V+3HYa;;7Usr-v$QbgkL3mLD*rmKFJj28e=c_Z+A^N0Pv@m%A~QyhPU{SK39`gn z`-ZXv?ady(*E1{uHPG>_a$|8!G*nQ!PB|_+l{YjkZAB>k@Lc>&4rExqa=D!c9=-cA zI-|fVRQ5>d^Sp$hT@Ttgao1+|^iULgqpnMjf3W^WyOoRr+)8HM6LOcjBG`MnDqqTT z<5hN?>EV~+5#linAm8ruDAsbc9i>EuZeeV54wIbQOlC`JXf#H4x`JvL#q9Juk$J)!Z;skQMy*hDVYv=gkmnzB_ za?;#uD{R5EyeArCT27ATSv{JIi4fwm+~8X2b!L0z zZF(U#9WlO5R`VN$Wht&e*pU({)qfC@QPm^;rQROJ$vWqdOypx#=j~*f^CZngHsiwl zmcpI;E=tdl?YDHqyCWXRP5g=DDO{&)EyS$Agzzrwz_^Vxs}Mw8^OZEOa-NlNHkZJR}p6tqwh*(b-%1C4@NpQ+oWS2le^_+>pHU>p50SzXf+9EYM}D z>_8^I+E5Vtcatjc3keT6D3M^YebiUU2$uevf&lH$G$VlhQ(#_t6yOKatjs-KKu}$` ztQ2brS6?X-kg@<3ZR;KjK9KDR37B~Tek0$&N4ioAv-v{E|Bnz^F(qMENxA3j|3yje zABhVAC?(*ysN_|Fef~lYy(aJC3NSN&S%9G8YXuIR+d7#3CHn&M4+G;G9to-b595C+ z8vcLcg~=}3cU9Id5?itSw-wuN8Zp~IrG|htZ>(2f;EW6z=G{U7<8Ps4xYic4H%2CD zz||q(xJVUoRa9pC)AIjU7IycPKJM2Ud!leold6Jq{lC<`i!@G@j1Bo$!2 zB@Y70NcxhrLyQGWX+`h|gdEHW92?m`oO^#My2?T9>n2RM=czEG)tPOX5|^1)yUiRDsCuk|gK; zx33_q+eIKzyaGrxu;6yg>t;q+$BTeRcZTWL{@u>Y<%mEVnSjk}5xaV_8$e)$MRp^2 z{Kdksb--~wfJcJ>f`UlT%L)yDqq<(9|1K%HqKtY2c!|Hvgs?K;3JsjO`oeZQo?RDy z88r!1{uppr#LASuy{yb^J3?g;Lemgr7r%q;_8`dqHwXxj&5eM~hybumuro)Tzbbzb zUy%iPP3B)+yC(9Il=~HM>Z{i+LdXSzlFH~y{6z%Dc{j#&4#L-N1WsbyAI|;j_=|6k zsDP1b0QY=|fPa;6X(ZNaTo^~^t?QhNZ&z~w#|H3S>=}v+*bUwe1z50svU%$pk z1>}QZeBBrbJ*DgGMNe5Pg~9VcF!U~j#=#P632FhwRKNq32nfS z1sYlcil8J53I+oN1O){|)0`%mgg}V;Kdbe$Ocg;OARzT5A!S~i3v7^=M;BnA|FOXM zXYp?v2;{$pe+}$kcXY921pPl}l4giu{=e7%vPgQP{$HyUGl(tB|JfYf3tL0`=g;Y% zFR>J72tt6Wx2v1Ey^@QglevqVwYlrRnU}Y)yPK7{gPXOfv74g{gQ=adt81C6y(5Y+ zQUC^3#Q5S-l~G;V)|!vnNwnjl5e{@*6g0DZwY;35y)md|y{DOD(qqGOj6K@}=KfF_w&rE7%sN6`a+Be8+nV%cukzVkX+GKqg;tjFu`8fbhuCg7I zxt8#mlw9G}d;?j1g?TQGg`SghjWyD(>(R|uodha=GJEd1t z4dBUm4IVI&lk)_tft9W+a--U1K$T;fi3tIRtI+fl1o-Vhl*-FrWpW?@D%H=bD%({I ziORccOf@5I#)LRjZx^YVPoQ`TLSs#h!J<`)<7&oC*PeEn%JXO9j)WMVuTd#j*7F{+ z^T5VzTxfGhly3>20M~1%@e?p?KJl%=4FIm4%LNkk5PJ4raEb88TKbb8zWWDGSHYi7 zT$<5JJsJ>!l`k*-C28ZqB0-|2W$UV)mYfn9s>)Z*{JWx+tNP-~Aj?^+IYF`cQ`aD{g&>pSzvqjL;$SjK!0FT9~wyLaPqzbk{r$rs`18364}^G}LeyT=0)kvR zvUDQpiDZEHb}WV@;1)d%kaoU=F4VlX7Um{T?n1D-zzWDjSu2gFxlIZIN@I!(uEX*uD8^KWMsMe+&KBUP<2|u^lgj*1%D-p<3;Qk@=LbXVbW~ZK2>WL`{sZP^?a94k4WsTme(8})2h&1rk zDoN9tYISMKEi-dx=Ww9@=(QQ$Vl;a;5yn;JWz)BxHd_%Xl4VZKwLz{_bcRCNDt5rj z#zu9azpb;f^`SEb;A9n>SB2xtQgt)%wX@B{{CPj{W;b2VEFjkP$eG1TKu&S6kRV&> zN``>93`ZYUdI>%bGLDUut9m+QW`#A5Tu23(UHOYh1Py#R&)z(y5?jlC32~LoF~51E zJ%TzYJ8`;0C!FKNm|CZw`8Vpz4xT^{-`JLC*Cq3h@S5l>U>gTF)PJ$&1a6c;XXVaD zemukC<=%gF)O~4CeP~PB+(b$K+(MXNdqqvRfau+K4KG|*wmm0-OD@P{m=DTT%DRq( zm6p+Xmqh|gt3kV!rHo~*VaMG!tT~qIk%eGL0lTMBHJDFO2Agp(xzYK-44>M@N2ARQ z&1jm+B=)8WpgA8qfrS}L#W0aM7!%M-c5L2DV!SLPo^jALX0A6<6QGy;fMbx6G^iTT zOMEOZ*KBzEaZ8P3;&;|eT*Pt{?>d4P4Tb0E{7jNwZ5F<2XQsDkXA%dOrD0azMS7bn zZpX?Z_U2sOcOw!op5STT8}au2E;JS~@%fMxn~-D)$j_h-3oZZ5c19RJ%1>6pKxc25 zs(OVMe^#`%DTa@Um{&}4Fi)L%SqwiR;3>FwG>f9?PVG}GQ4?aWYrjY)yVhzS#l%9v z=iy0StqlhKnF+4+p@eDYnF?o=51~@XD8I_4ovEOjU_xmj?YHMhRuA{Pss@>fv6nB+ zJ-Z14px31lbb1Rd4__mnR`(Xx&|F=`5uhfmil@|=SBD>LL%^0Bw63B?^Uq-GGpzf! zRBHO*o+lt?^mnGZHoTpt4I2%nSJ%h{Nvtts`9u8XL-^8?d^iu;`#N{Tqod|iUJ>vj2FlmR@+3VJtqOPxAD$jJmRm=Hv*78+PI0*Fid6qjLZD|r;l*met=RZT!TP~n?i`cV9k!q6PZ%(SfA-VS zlEIxd5b0CjlleyFx-lrnsnQG$nfVX`;46FQtRwvVaJ)o@OX3M0Pn2Zc9|`FU)sQ{0? zyTtbpT>P-60C&hR*5KTP=U=K9PtKy*m@NyR+rBgR`Hwu;`Ro0^Umuu(-|yPP46&77 zqN1o0Y`G(q(VXn>eiF#=1IT;s)koYR1Ia@E> z<)v#8*u7t)73J!TvZ|f4K0VZjLEENk_(kRv;IOO*{~30O|EcT+3lCE z6F2h}Gd0WF!m`??iBNN78{`ES^g#PMqy`{r)=6vz$2J^umu|9X*{AAYhSPiu_rgD0 zMLvy}+>#eg`a^Qs;kr)erd}^5^_vyxjj#$TsRx%n60$F{s`x#^=bUvru1gbu3h#_) zS{@doWjpK8jNo|7*5qfai^im7XZ1gOJB1Z!ZuMXRUXK}@y?lC$i1!n?Gv+YMq? z(8}uoNFRzU0%pt31s(&YR_<1PBB&%qgVA<$qUVOJ-%D=^Jy=h8n z-1d2z(VhLf&^ANME=#J9guNJ^r1BtFp7*uaPtM>Af8Hy^uTq*f=jk4xOu&npA)U>vq`ETZR*VQsmjl^5@ukSUR!> z+?ZTE1W!Z48lT}<5wg*7F4g_D0zes&<{Xo!6E-mn`Y zaGsKrcsI!(N||1$$XJxNb+yfb)|AA+fxfu*hLls#YVRHT8np)?X%~=)2o^S+c*EL8 z+xZ3E1)Fqo2m29oPG|CIM1)3Fe1-5D3er!KkE@6B+s?&oy;{h9yf1kN2BIO2*PBFG(8 z!GmxZq3$CRRSrL)lG|)IYeE@~aUKCEC*tdpQ9@{*s^BT_q~7SV)JimAj0^jb#V1Sw zEFVfF{csABMlHp=@kh_he*Eyp@0;NaBadQ~{iwqk#}~sIM()*> zf3o2WF?05jE^?UtWKSK(@AI(wamU~8{eL-Ah7*q5i*kEYM)$xV#A?OpI8Oka^tQFA zy28zAlw8ZJwnM~co!umVCCR3Ii;@mMv8oQIJY|eaMx0fYQ@WaFppi*9SaG)5DYL?E zQsXeeo}pjkx5|yL%6V$grn`ZEQc#Q+;1T5R@w7D<*?trY9G}0M+iJ7+#eVZEZMgc-5ar8GT@4D@tUqh z);!bt%x!6DyYuoEcFF=I+T`SO;t@(cx-lF-VY_=+37@Kb+VdYQC46_OQtjm3<~;}M z)=?d$JAd0b_n?Ej&#t&v615j{|H-6?iaoJj!_2XpHoN*Bsa7gk}%^B0w@&I?n!8Z!)z4pA#kE8h%RZ$|yZI*d0X2>!7 zgMjcd35|~Fq&(`)M=+Y826sG8lRwOk?&_s8s)}_izUQDls(2qA4NmtcvtEovg+1Eq zQ1f1O{skv!DbY+5KwJa+UKVW_^=#W)d@$2v*}3s9k2avr98>|&?oLs7C6is(6KC#s z5;b%ydC!a9KSu0*FA+_Uc#uQ~3>B!?9;>4Rei@>-dP$~VFz_0oxs=yng1y)lI{`Pq z9yJGtxMvm?)^+o0v*?>@@4s4p7OK;nrj(yhWJv9l8rP@plq%*4H0c{lQ0)xwtL==3 zM-RwK+{W~rZb1O5j^KNxUBL1ZFk40})-yP-sR%0HBhb!4exw~>Sm(&4`$`YVWKUts z-D8ZB9WzY8EA|fM$-C7&>Ddw2T04Ogoan2%(HhkA@2&8WH7yK;?|8cpfCozTyVlg4 zhrah++cn#&Eg^v994gXv`kYYJ`yieq1N^EJ zr*DCWg}>Go0mqZx38y5ce0{zWo;Tsl7c2pyaAnpyuK?hHNF5lgmwIO+*y2q*M?OqRutMvQ_9&Ny` zmkjBw#VY_~dltbB*R{*p$CuLqD3o?FXin=Mz$`JxUwb@Ljy)2sN8es>wKSUW6HfjP z+ua$09vV~3w@r&KUqqS-P8wVh%ji@MKGlRrSZ8oua|(}h9~H$H zB{NXSv!%MjH7RiVE8W9Z;vwcyrS2v|Ydqt0$i)zK3r&%p>ySN|8x98N1ifFf6HOTK zeC_~LMHQRh+9`@#rgF1HFOr$TOgGn~RAo@?A3NZaoxcDgxd~-ajZLwl=y(LOc+dku zC9ZK#4VFvL0iiX|IZoMLeg(N_^7y>`JUb&WP$Ey_P}yJ$#gupD6Zk z_X|`hYXWP_x)S3M@v~u(C;aGSIf|*ye*OSAP<_`Z)Y`>K$o!LII*+sX+80+mAyj6z z6rMaf56o%$_c@k+s4eCw>U=8`i*jaBI{j43hgeMceT+W>ejB3uM26tNOSUT*#cgIN zh#Ah8BA@!9tXElMuPiF7y=1*s`6h7^R^F*y1aDuI-z~ZXI!>ccNM&Q{d{>J(1;l_L z#+)sainxz+f*a)1-0ltG2>vU6{GzjIDNTL?3o|1%V~J9ha*MGPq8}Uet@hq_V}ki* zNvw!MZ#`vi|B(T;uzjwezZu>NEG((gNW$~@BTEfM#{4PgFB-EVZ({rWX0%>+-KpdU zen)H(x$Pg>&ERJ zQ-A+YHI!?weHRxM2xXvICP>+{xk)!ni!?N4D@NiS!HI(eFP*r7lz7iX zbZO?99D7ZAJC`h0rxPQy+OMR$s8O3>La(kYy+E~L8%AG>_iCYGyYS3B#?vcoXmKZ%54 zPR5P`XV1%)0;&o!CS3ttk-q7%dH97U^)F8_-t}|`u3qYa^CwRrUNx#-?t$qcm}8e^ z)`{iHRJF)3XP@46&ur9wK|=+Pt}O-Y(vq@)bH^`Bc2M_Wy~>AcKy@b%0efWgvrB5= zUa*ep?<8T{W=rirb!RugHit3jd&q?vhucFL?;k23opRYUVJ`rnHs_w9A|U%S55b6U zyZ0{9-#2sz3>@M$)8DE-Q?_9U1w58Hixo`^#4yj|wQPg3c)H|q! z4q3Qo2HFdh(FWKDHSabby*hgW^a5Qvhqjr)@Nu@69Gs5btwmMt%VXuMIh^cuj$Lk| zyX9?iVY3-mJoydNT37Jp_di~A))&mSF$B`WkG5HUNnVx}Nk-X=c#3Xo zZWPZ>Bb67@=exTS#pRm>BQgN4J=Bwd1gMiP2Uw2Z zbH9q0WA~KE(%3w)ZD%5tF=4PLfAjE{Z#S=YDqHa#f+PZ->ex)n+oCU$ml77Qj@h}p zHq3Q1dtV+Jf3Y=f#$wY~S@k4sl$t8>At7EQ(fbUg-nmt*U$UggvFdZjIFcYs5kir} z?Xuv^l#aT!*FfmzQQ~bGdz7Pen~#~&PT^JLJm54%b&{m=&Z&7!l@=;vU87p~%Jg$m zlE|UrJu(A^t2cX_pjD$?Dq1}*V$vCG<;AloB*k9BbA`hiL_MD-94OkTF+lGk5lV)b zWEE_ly-Om6ki@~PnNYQ{BjgIVn(bJ7PH@Y??P49Y)79Qt?@=H`tDtK0NdN9E9jZ<3 z1WzuTKeIGN8KUBC8>h_2kce1tFQ-UQr7_j@XS)LeDV#QqJaD9CH9VjoqMP}$O?Iha z-QfA7mBZEjZmQxDgah_oSE2e}1pq*rSW=mK@DY86EkG{iTr`aMRF>_wd=zjAfcA7d4aVRH_&xUl8G%cQ@wl>jL?fM9+ z@^14r^d6K7&beC$DgPOkyC@06q=)-dELUn{(PHW%s@Vuu_|wbgfo>9bP+u`MM0%7O zXKVp4`2ksQ+XK~ktf#=`@nR)`Xlj)fvSRJD&#IgQyXi)GVOvG39WGidrLLQ{HWGk? zB)oJvG^UJ6S-SlC2K4c0n(#8%1R8W9zj>GaeprM`5WQz9^vG4ulzJWcLt;ke+1%fF z8(Y>kF$Kq5EEx?iX%-lT3!CK1^-oya>=G`AnW<7}J+*cVSA>fx9Xflnv|blW%6qh= ztD>lz3Dw?iMoLI#wXD8tN{<@Cnh}6LA&x>(j1F_bK4wn0(FjC zZ4@(q=&0#*wRdk+R4*Mm1Krfa)1lQ&ik`5gy}5H$;SAwu^|{w3t9?lp00|L9we zSAj|_+Y^QENas04y6|!M%RcRvIP;tcAbBEbgnrvhOV4Unp-;b8)HS(KG%S3$ENepE z#4)(yuAp|e-2JQ{zUF|%JiAn~+iYpeU%slYkJ2LhET-1d?FJiHH0wz}cxJmB#f72c zk3+Xh*^AexUiM2Pj{p#{mAz7zofYbgQcQ}6nogdbA82(E3K6?;y)@kxFUD7Wk)0+* zK5C!VlBJvytrVVnTRSRx@mRm+fi_{~?h(gfBXlAO9+yz% z7fGo34xLtjHcogNaJ?p))xwap@JZ%h%_k(N`T-?a_PG4W0R<>n%nAN2)PpSxSu}Wt zZMQ&K?HvA#K{enkqPy66g*g92HlzBB?hctoQe9wdp888aZGh5{WQ%OV+zBEW?;%! zNi|ZCEZtyQ4i{k1SoH;(#qoJ;%TfOL$BPD~U6kJ&1-wbuF%rv1#i@LVDM=$^{t0a%9|xH^d=$Z6 zS_f~%GQmYjn7nOQDG5#J3mZGD9!8TZ;>bB8A`XdAZvb^6(2h7nr zOD|cDAV2Hcci36O?`FYYTG={ z!ACkCdWbHN%90Yd=m0AzW*Cu|e&aerb-69sGp=4h!a4c7oWE>87`{gFvpCL2K0R~gr4LL#t5_&n`chJ`68UV`-GN6 zQx+i6VC}o{z}0`fqDiF53S#wnT4J}7H_gSmnMl1s6ig7#I?wfyhN&eNtnlJPM<7a# z>71Iaap?kWbS7IUV64lso+#=`m?G&uHCCE~(Qfc*<|=bPxzhxu=;e||>ZxaB9$}&Y zhkj;ObEp}+ygUC^yqa26^emtB{D&Mb=LT?9m}V++S}99DnX$=>J4t}MR7SI$RbiHl zj-AU5Ojf1KP{q+<@%Jo=wQ*<;c=mySOaTo?l+Wd?^HE7j7_0A!R{=>u{`Ey`$$8+4 zUb&HN9cY;fxE`3!XsWY=p03u6|aD^&i8nUtg_=QY&bY}gSCV_u8%!yar zn+UhTYmQW! zhhekbdFp zEe(Qa@F!Z@YKY3pf^vSz(S*3f7C4^t5EpW@x?*fK_({wSmwNY3^AkOB!%_gFaIqgx zPQRS5Fu9QIImgC&NZW}U?=JMLh0wge8>Yo~HyfNXtO`mliDNHej6XgP|L?2!`^HR?zV7&^Z$P zQ>eucwDLH!1@jeQ3il;+tIq{bdT~2c520VvjeiWVssb4b%bwx86TSe&Zfmui3*SsA zBoH+Og^^1uLrp2~b5*TB8x)z(o6h;7G&UnJgIUFism$AxcTljf)uzWHK` z^$HPHf_2L+je$<~wLVFg)3lJ4>fq6XfJK&bQ z?fWgO>St9uaq4MnBaROS!q=V~ICcof8-4225Q{@(;!EvXOSevd^5{1&3h}oFTf<2leoUPq)mLOhSS zZ#GL?zmPoVwHx@}2!D;xo(DixMxRwI;6AJ3ZixfVy@$g+^7SmrIOpu&Q8o%Rye^Qt zubr1ZtNOZ%nN}1Jdm*XzeHUDraDBo!5=sW=nX@5U= zv*$w+RT{f`hvFk__+s7j)mvzqpmPlzhHG5bNDgcRdz*udbRY3{Zj!Wqt%Qk+a6SAb zfqMp^X!XS!d4W^-qElb9lYM1aBd?$pr3dOmTlC2fgl?cB5hO40Yq?T$r><^u9vqAen7JcxYYeCb`7riAi??aaDhpZ7& zDf|VJ0SGyKlUn(Q@>~%LUy!OH0p=)*yfJ7buaav#!PE7gdzQbc*e>-W;-(w9Pe9 z_#xx}VjG={qg6;?I0J+~9hF`isLX#*Pj>c+E&=92K*id~vW08Fx+5r5ul;=;vrV3M zh(;=Mf6dO8Ya>&>p63g1BR3+s3HShMk1%#tKSe4dmZ-VLtShP$I1bV)l3~#`? z7;Dnz7h&9fa(>3+;@=LiF1{GQ4Yy`+jqs(}12VB+8qX4G$t$cLEU7<)CpR@`zZ+Xx zay~EahCN;Hx#xag&hdG25Z=x8xa>gJGP&mGFmL3&LdObw)A2oR#PA3`IuPvs60d}H zs%<`7{0sV@oYn52T-3-v4nGY#rH~LiC13;x5T~K7yRCu5Khz9fjWTG*4CVxamNTlL zDuY|d1|8Ojm28yGQySCAC?S?!Nr!r#&S3qC`)4bopMx>H!B2kwlkv|AV9}%AEQIrM z711**i`R8-rqA^IW8RSwxHpK<{M}hog3*Gkm(f^5g7(yGXSg@^AA?{l9S+HiJ*{;J z827;t zW36*zv#+Ag6ED!Y{1Ip=_LN_q+iKMYXt0wy)TGbWN(1lF8N!?++K;}jfU;NDNbulv z(9cRKcB9P3&RO&1xZPK3WRtY6Fkuk&5TDJ$8F&5D*$IWz=&R9-6tKbP;BT>ZXU~qw zv5!h&IVobz#Md{B$*6rfg)8xr0MC*);Ugo?zD&>Tu~SFnkwai?FKREvck?s@>=+ey z76X{Q>(U%5!ppnPHiyZ!JJ^>eWDd$U7Gu@qX0a#rEx65mM9{sf%@}U26x`LOFDW5) z)qV1?OOY_ZR;*hSk1gh8nz_%^{uS>?c0z)Q>Z!dTJHXuZ5_y1Ssw93PETMRyTWzG= ziv`8Uf`lLnXQI5K`@zTxW4KWQe9m#icdEsoL5?T*E$5r4CJqA^o>oxnqOYmFqD6t_ z&yGH~ebu0$C1C`H&&18b*cQUp+w9|yV8IQKXu;?2*&ken6EKDoB*F<$y=8|uVfsfs z)vlj_YPg5jSgvc0KrAe0a`Rzu%JEmKt~p@0dg#topfLO)Fz;$1*h{DY3f8Ii_Ppc# zV33f_A;{qK5Bw3YA%VaCezcy)<$}@*|8ARoQKxZJrPA}+#iW>~ZBuHDRo5PyXs#n! zAJyq7&%zg++M-kaX^bYIQRHe!-b7T3!B)F-YJx-~)pv^K3{LK@ztNE^v-G6SYUwOF z>Tjc~r9C-lnaQtu_~I1>G(fEvE_dJmK#Hu+Rzw>G=`@k=)H_?$T*|$x#~n|J^Vn=< zI~xfU@%q)U#rp0~521-2+9U<`M{}rzl%_y+=j+Ym8dK@5%94@v6+c4g<^Qx$?5a_h zuwt*4M+gyY+myW`BkwTNvs4plktVQ1<%`!}_<^pU|6N1;c@jDR(8rBkwZ<&bEnMUC zgx?2axSy9SoWm=l_qwX#h({@-J;h<8V7E$ZoJ*- z5_)rAV`J=1-saEotx+9as=f6dARo`ap!);g*3XLXrw0J$dBwDc13F}W!MCQ*U(Omq z9x$*lsHMA9$i2c>&QtsqJo3gNV4lGfUd~yU{|FMEb-CaB5J^q2tNMtJJjf90De9XOUl9g^Frsp-h=muAJM zchgCwRdFU(?Uu5O*Cub-v^TCpH#Z5OV5q2Qit$L$#Kh}5gfAc>1B)?6aqm6_9)>_2 z@84$SI9j&t;Q`kv9<2X#ZJe&q%1po?K6KokKE z9JIT}6FR`18VDT`kMO-Lk&N-qw^A`uT@z--GB;^3FF@%Lhk{1NqL`UuiAnFm?N89Sc55#-+n-bPzN!@99z>?S%W##hCl(1=N-MOSt&hw>LQW7x;J^{5(4yzE)mNm6l#+a>A8&EcOj+Nh>O-_{xB3xl#%a zYON*v(xvqDW)G{$*m@)L{J{D-s{~1rpHro?36A;G=-%1$_PJf$IX+uYw^qYnkNSP0 zJtd?e!~9_={Jh{t!OP{$bvYU$inLm%nfXm9lkr76JhKft2V2=vOFuLbtj2Co^a!`h zGid(!Gg`~ow3Jg4MXo+Nilcxw=bR=!i_%vz3bfiQ)|seyXw?|Xmw5kj35pieil>`) z?|&Q^c`O>@>NT?*j$0N%OFNlde7LvJuhH2L4Kz(}K7Rhs7p?H2%As}pkqa$%Ly|H? zv+^-_SFw0mzPQZvouVx0qFwQz*|u=vBKP{iB9PoEPD&N%D?jiv^fo{n(uJv4qiB$b zv3vea{x`G#miV}(JOx(Y*eG|m-PTG4yXt?%FH33PmDs@zslY*FFQniaG<<(dRY#4g&kS@ z9HXkRxSz0=`qILYXg)y9UI#5ZSeGct1o?7HO#yZOxjeIE?vX+;;8&&Mc{(Oy@UOI9 zI<{}($0R#X|l7=o35DT)|$%7z$(KeqH*cw{<^Gnv2vvdKs?SIg8S98CGnpWk-(ju{r8|w03~Yw5SWJk|JK5ABLX46WdN; ze^oht$Z^@leb|z&Ianq;@vYp7;OO10GAClfT@?Mz}%xVeMMK($fnW^ zsMOI=QpuvbAz&HzbS>c7viNlc3@Yd3ubG-S0evt?4)TvBG7W@PWO4uzwD_fBv9@3Uq$a?sTt-m^j2nY zoCyNtLu<+pcruR6MeP`>t10wjyOcsxKNfrEE~7#A^mq?7{$%P3=WnHDdaQsi8w_na zhxYIyAann)bFf(Be7Y!(!9+)mQMgwfle-W;EoL<5PLsW@>3aQIKiMcz=pXe8G^T5T zJDTdWd5uvM-Y#}+7&FH*v*^B-Y}dz*3Xmnhs5Z&AZp6fT zl+p7B#)|= z8^om?Jmy4Qy0#7X?p`z21qReB1H+y3Ba?81xD|nE&kAE_i_GXyX!61w5zI|ns2sDx z0AuO6XIgWtLac7_)48e}rdNqsR>tZ^B$T|s1GB3blO#@<8Z$*EbZF1~|6YhoQqRJb zEn1J6=W%H2nqeyOb!bZxSUh|-+j@X?v(w73-Y&ehDUW=r<0(5c40SYp`9(^+u_dW1 z<4_X%2xU}j-q^vB8K01Vm|u0?HIr!|dDTS{(Q zN?u!I6Au}Tn!Hifyiti9=eU;kHErAG^=!l<2F^_d^GOKRnqx56t9w3aQQvYju; zi^nNnU^d-W*U~SzsiW4c^e})?xShRj)i+6Hs~bDR16b=~TXF0Me;ln+p z!kdRLJko|}(TlbnS8$mtSDtoH0EXqv(G0eZyM{3Wyz+@h`FUiWin~}RO6_`HHjTb;I@KNMz~(HXJY>-MePHg$emq-twJXu5?9byIp61V#Wf=;|=i zOH1c5onwk=_(@-efu6kM=swiA#()``2Bz9A48E$NY#h0% zHtq;|hygmxu~OqA-ZwQTctPIg#a-X5?>W<*E!cmv^V1w;2yH%CRrfQbkoFMDd(d6I zkY&5`gn#?N!fojFPBVYlymM&loEIaVEFqf#-Nv&8$?<1P=Ms?h8uEr|w$DEDsFQy0 z-UX8ClF8VEWFY>G?(4x%s$w#45VHPgmuRssrR2vAME3K=dBoGWNk1x7DsFtoRkjdu z#ZQhgVGVZx?@=qhBwt@zm#TItWkXMKA)#0ppH9}o>7rpW-Mg9Q4O_+1qui*qiOym7 zy!shm*t^HM>;nKZuVbyb6%!Br=(Cs5Ug*ef7U~I3)sojH?#&|D8H#z}pqRPY85j;R zHJwWz%cYO2r)Tj+RfLVFr8eG>xLU*W5xsp`G2%QbV(CCm__)4wYp4LhyL~0vN?hJO zxYV#Jx!?a0{7(oRic?zC{0{<)VEy}qNb}!R!Uzt09{6a295-xOfS8Kz$UU zSN_qjM{kcf!?U^*@lNI50sQXaH~`wGA^OCtAo#6DD-5wxE4&4UKuMXjYbOGc<~@-R zvAS|JLcNrkc!1*MF;S1;)pRN$q4NG1xeK5M_{EJ*IBH5mFg~v4hgYEVAk9~Fpb8^U zXTqAV_JHlTmM|}!J^%Rt4sMuQV3-`CI62HHEAbB-qfegK1o+ASS}&vboIJq|*kJ$+ zT#R?9`Qh#>-|GwZQ$`Im96`Pp=>?P?jlV}l7aUo_1k@KJ4j@%-c^9_z)f_tk9LIwI z!#zITya;dCS6A1Yv`FVXTlqD;vn9ixq@X z*ecSj#p4kFXx>}>!g-uH3h*_Ltf(Zwy_5)HJoaG z5V?EAqkMbWRTT1L%{>=}lKx!JOQ=v)2r~OHZ!vR7McFZQN`%IWluTq`GLw7_zs$~% zGk+4>dlQfDyc8Xd1MB$M^LrzkM?svZgg7#5KyWPCFY_+2zKWD|=5|I>niyfgHXF7# z)oCZX`4^wqmP>fcd89%9jIk^x_iI_L6&)XFh6hjYpQ>vH*H{mXv6&>~s%vor<@07l zY0Xm+9tG^ARCAPpF<5{TN4lHucvU2`y+W7(U+xuXP?~#739~OHXbq1EUYzFd!u7K; zm!ul`_Sna6J~m;4X?&xd#-VQjB#*D+IQ_Spgz?e4shsXdo-{V5diN4Ks}Wf;hr~&1 zY;KL$kg(G&sJb+kG;3NUR-N<_)}PG5qgaQ$hlkXYKrqG$k<3^{0QGJ?{NNobhVK>LV&M$u!G0ARl8?||2qZ%OZVw)P+dFG zip88eZrtU=PRRPhWEOMDmLp;LKcumixiQkimkj?WmgW}){>|O3 zH93RAm(MgUg38%J=C=;DStqm2u`IKmPcoGlSsH<9QjvkPHc3OngxluqqCV6+(H795 zXRxq(%O=Jp|6LpkD6auiml}Ly5M!_^zK%OaKH)seamHnL|M_6s6Iosbyt&Lj7$IQrFT16d%MHv&yJpYc&EYP3FusV?ieVPErIFsbAIeo?|eQ3i?@9oUEL@^c}X- z&!q>q+Ld%EbzF$53KOfFbC{1O2AISNmltMor1 zVn)OOtYz$RTE1ZCiI6ec#29|78(uTYME1+jE1|tggFA80{Dp%6k+bGDhhk`~aU?Wac@-jLyZ&fUL!yJMMlFKXW+y{z~yH(yL2 zL?y#Jf0yoYoIK}zc0ICA;j5g-if)a*fi#Q(pzXl-#F765)MEYFJ>XPF0aUgPE&wK5 z!=dk@T7PQlP`@>&zW79@-<|MiY;r<;MkRQsI#!^X2R`-LAwc+&@4BH-;Zfoy&f!PV z(7t{su-!P6O^rD*j(2D<;S@?6N3egElpsR-gC!3Lb+D)|@gKog_SmAA7c2(JQS@j4 zzd}^0NX@-vg&T#I7-0V3EJ3xRDHIorL(_y}C=?}A#Vl+6Kd#<6IJ2nf+l_78o@8Qc zf{8J)GqG*ov3d(gjt{Fta-2uoZ9crVoo8iZ?T^W-1 z+I%r~-ChjsZ32@hSU0Y|ZBT1-X4~^O5K>m&JOq?3OwO(@?2160D8DH-7uV|ap%nl* z^U~#^g4+BlmU~E>f0v`@MSW+-3IFkkH)YdI@7$>M6Jt&0==S9G=2NxdG}IgV2VLF~ zB|WH$OhKZ|AYPFpxi@}Ma<$$UGmAz7`VIg%LGECq3L z>RjZUjz01ku)-8qViYrJe9a230>l`X%Bn@53vnMvK#nnSl+k$I9@V3!!1EUCaaR2` z_q)Ku^p}?dQIFV%H*TA~`hcsbm)gLyC?5%+FEkh05Y@;qlnmPsR|LV3#qE#BE6q@0 zqbMusUMP&#fDuBzU7nrspV;2#{F{L`oPd~kv!XRSjNyTu76f98e&m~ejt>|?tXn&b zn9#Rb93TH-12^rx$h|O{tjImu(=3$Rh6wFAxw6P?hQPt*iOUxK*Nx7+aVJ?i=T6p`SZcgJ0PP$YND>{ zJ|~gUJ$QxI$yl2)d&*O6iep}!KmQL?=Yzz|({j3lPagBcP0MV%`MsUFA}J9>JtB2B zQIQ|;I8YU^LFZ!g=fn)lc!u^Z*g68R$uVC0K?oN^oy?-05@?dYrpf4`5TW2os66ww zNl@R#V#s~;$J}A!;R)YRF~m)5%wgt+9#<_ftwIqL?k#7Z$=@0yf{K0lySH{KO1oLi zlwR4huDrI38BoWh_W4IHe8a$*PMZja<=w?&hO-rJ8U0?epVz>{@6}ZhgjaLx_@i~$7EHU2CA>vS<3c>1203cU z?2ir8)Kn$qbB&+QRF7u9T9fFC(0wp_J~asCZ2z4i7`>MnmVahd0xQs1(QiI5p47Yb zVDIKP7(%;pscX_Q(tl3**D?-p+$rYq3C4D|>QyDguTc=M4%Mo8E-+ZK6N9sJ#kYS(8}|~zorTF zgbO}Ce<>^h1pQ^ztTb?HljHlO@^^La2vOqM8iq#wP#wO$@gyYPL*@be3!O*xD%h8Q zafBc~PeBzlzBtxr(pxKHl#{@xT6m0}-^85|vr`?W=ECm%>-2$noZ1RLC8C;3(nQh2 zvt77b{30`a{31NOJ#gqt-)2Zl1eA?pS5~h#LWOnk86z;qsOqvK@SpT3+D!*P$m&Zf zg8n_v&}C*D6kj)-d@Kt9RFl?#?TK;`u~1EltxXY|?5*$moXK%;vtyN(b?gk3Ttm?X zodYN3Du?!qC+UzDq}2Sz5jQL_!83k@IUcLeHcEH3F5e8N;*h7aNAQ_)-V{t~-@&dK zeamSUld!21uO#@C689I&{%oC!EH(mvM~D0`RGL2&SEaRQaFqat?J{SWlV|pJL=M%z zwp>nQp)K+2uEBWq{)I*z9kp2k5!Gh>BjKuOB*pVCUERq!Z-nS{0=&n+f;XAmZ*#j0 za)WCt&kB2i0;`K3EMbgG4xW+tgQWL3I%aZ(rM(?0dMsr?C{VJNe4v9(6+J%R|M~OMjQTP+ya2Sl~c|%rmS|6Hm=T3CSYoByvXE(DX3JK7?-1hs|LN@P1<%< ze*%-sYu5xYDF`;fT1L2?*9@xE6y}B3Et_BY*=dO>AmU`%LOSFe83*GC3YuUME9!=| z9Fy^OU98g0c0#`o6*ly>q@ik@o1P9ygQaUUacvor8X{DOnWE<;Z_!z!=Fmn%d3` zBio{QsGumjOXQ=%RpGL$YhbS50nkSdM#(!?X^HvE?tz4Z2aX&^=fCXL|Kw@J5v{uhoR-cSl37X}1G8W{wH^!q^~!2gSR3Q`LL zV3^|inAEqk@*;3!p{div79m<)k-^0`EKtO{th4pf!|MF8uj5^%-<+^AHGx&O?ksz> zX(Uo9(8bh>Noum9nb}iO=F_ZH)%w8xz+N~`-@HIoonU;8etzHD_sl-_JRVO3WCI|3 zTt1YH4TEaHxo0;R@c>%J`!sec2lg^Ud2o=!Dj7ZCbOM3M5l7?((*blued5MmGDE{E zZ2RbRX*ZIX$oonLuk&z33g3E*DqFVFt-F`dAoeZgEFWIp1QhH{OPOcz6B23oulT$b_;A>_8o4S*kVTkQrMQhxL$H+=pj zF0$B|k9>a^jYKFSwMak4#Ce#R0G99;J=&7~qPa3dza6)K_v9sPw}aO<#fGZ1J&7UL z{%hfYQ0aZ|mOS58PJV+yC|pvV-y$`-JpC#%YZ4!kObWJKtY~=XNtQ5d80j+KOshUn zZ%L{cQFE2c2C#bS*HBqpH`J3?d3<88x1mK4KQrL2$~B>W6xgLF0TV;K7;_4)u_=Q* zpu=le)XJdUXgyRz=;CB=IW*DYNf9Ypy}z&NxGdFXLh&MAiLuEUWh!Gx*3D$O0{yUB zfgPK;P|wD_|G%j>I;r=yiv zL1Tb2#k=ZxP_DqR7{bFDH$r5T(U32+>d(C%TB{T#HRiGzqF=SCSWL{h;>}`nFqnf^0ATgJ?G`?i=FuBOCasD9a~k^ z6y>xy1OzE49{tp8ysHXqa^M5Hb0)i{dWihZaTatOa9dt>qa2qZr}aNX`Wt*+Y5bX@ zQAtF_HR`468g+U|Ls=2!vX~zy>wx3_bW=7onxRN zO?XkdulM(C2mL-KV)Bh6BJIsd8JBo=h=1f?vlmLA!rf&zl_6uq?p|Bt54nEK58ZxZ zQ2-Nh4{_>yB&Dg8bJqU}B%CZHOeNx!rnubketjfl62v3TlUE3vK2EF8gr zXD(Q4Zx$9-E8m&Uu+wrup?OP{>;_In>y@YarJ^Ky_lo`K!7B;d*-)joeD!#BrtEdt zld^Bo`igccJ=xa1a0C+jO4`_J_(x?(5@Lc>L7=Z?ZId#ss)e@n!o(g=i#ia6|f8H=(O$MOPd zJ=VG|A+jiPjMJb6jiX*yO9M}8GbGsf{hE|k!raGMBzn2_)Y(~Q_UsaDzLd-tH*>x! zzZjg}zvJU91QaFUPuo&eqRi#X7Jx)WDw~A_|HUO1v9nlx6VV2O>FiX?LHb0)fB~5= zRF@5nya~f_NvDQxY^1?d9kvsZzH25q0nuL#oU={VjgjYlm#)pNFc1zsPAG1oz4n<9 zE$;40i!NyWTkl;idI4r#!LvuutJMf^t6?t3*5zp*?`O1vtE~DP=jW{7A^_KIjTc(D zjN6LMUKenGD^B}olfW9tD6?Tau?8f<7T}M~-@Ku_Kd&p>w(c3-$x;2L@xyTNxQ>Ra zFnV#04VKsJZU_#>%{o9RNf}6;kSqp0$z4G+y&P-{mrITnew-!pNwq+uZ!lQNIX16s zFKG3Yfi(nd@^?zBcg|@ePXZntHjYy*8OsuoX96WJp0(L_J5%yZG7JSNp}DejU8p`I zXmutN%jW{wa877*?3vs^Wy9djUkTOjyzLPO_M721XItv@o@y((&)`YTFC`A(*trp71P)7Jk+-f zn@TP1_k~YxmCP=EP`hqqAve5c(DB+Ax@aS^x9{^cTs?At63}`I~lxQ?0Yp%GP z-jGh2L$U#ypUxT;;tkTKZVMoCcR?WkbROOe$cRbs2LLhxEUdn+`j{>TkG;13yjh$jXsy83EyuUJ`uKTN)X0B`+UaL ziY`HQ%QT(=bV8I@d=ofD zs!#%e)ofHXXJjtVQb@WXzb76+lH={AF+FhI``~s&-?_fC%4erQ1OcHRv@7FSdxTL_ z(&`Vba%fb=_JOWm-g8xlR%M$@%~0JX9XAK2f-Oo&8*lb|l378KsWN+`T|_IyT?K` zzz@h;Q{ZN*^gs?8@9qO-dGrAI#oU059e7!LAe`EWM)P;)lxya{;9CFr1i%?%%KyPE z{>QBUGWG-eFB?ejviCo+-hXZ@3d0hXd_zBKd*-|{58$d zR(WoeL(;{9ozO{Q80zaw<O#Z-ROw(45z^{_E z8E<6>W@;{H=_Y5yjZZQYvU2|AnU0NM@fa}Cd=lv=QM}`*78-;_ zgvXh$3B|_)Ozn0AQ$~slRlNRt3e_)5#$ug#+WHkN^X$STLuAP1lW!R8*dkKKd84D0 ztZ!1s&lq{uXi!}QpG>+riEIAmJ4mW#t}Yiq0ZGbD%7aUGDPT_OL3MWllIyig-S=J) zJVIbZL^bV-qj#NKhs-5un%~7O7Y899uP(N19tk-EC6s(rubSRX{`p8l&c z^FI#VQ6b9`_wCRDkifV=YT!*Y6p(2aizZWfS{_{p?qBU+O0 zTw_5PY%MA}LfBT(Wf71h4IAK6DQs!M<+PRl+~t0~ZoBm`Jk7Wbf^j?1gK~t3V=-Hs zV>s&@Oxc*ilcbVnr7#>GtrG0Va5|!wPMVZ0s5diavuS`+DlzP_$XBJgg|E;^%c5y- z*(ZRKrTk7)0vR%ADsi0&G3WdQF3%HH=jlbphuT6UVz6vaUu^}y-3)+f*0ApkUcC|B zY??@8knJOaFRn`K#LBXDZ{13iLhxYNP?&G^I+VzOo5zytw<4F`jCyUF0X4_eLq(&xfFjHIdLco~*I&H*Zs{V8H5kV{F7Bdg_Kv!&!I0 z>(ze=Hp4S{%j{+07Y-2H!{XOK)P|LA*I!FDGS@`;+XC?=g9I3csRXARQId$uHUqcA z-~Z$I`%W}OkW|cGuh{OpMijK|302d|^Y(hPEr~yXIX9DhN^8~eFpIRRu}j!<{3j|S zibu|(xmWf-ksfj8iQ(A8{ggxWk$XvuR}58mDvE|B8$49SEs7%Hc-|HfB5H^7k)5Bj z<76S82j-=OWLZX0q1^>CkyZ}5H-v9wpFf#a&R!o`t@VmE;%VeUCgT&*Rhih?6?SFo z_~L<&NvxYtPH;n|deL{xd`P$TDe%@rhDNLy#KNHY2mB-v3nZI*pz^V=kpKU#_|H0q>#}>pk`x^endxfBX`|WRlr5nZ}rcbvKP=g9QI0(c~wKy%-$PpPdVy1yK zuORprzD#7pn!c+H6esI?>MF#=pp@3-rtS=fs%I_CnunhhnsCR0ZcX0fQl9+RllvQ= z_uF=MCBLbOOMUugO_<>Oar%Ac^+uQD`_$FoR~7*D7w=YLEu?Ez4kc+mx*-}s2A5Pl zQu6N3UDryNKv%m+UJ?h{P`D2jcX_7-0n(NEA&99<-H&m1T*j$cX0N+%-IKygWyr&e zKho3Ks^6RCyG{OXwAtvpdO^SMI{214fT`k!zyu}+G3Wsz?abtG zh!?gb&~JZgHR#K1U|};x)yoba?fUi}XcFB3^?YSue^s)((4h10s0&i2bDjyYS$ap< z+y2g2EVt!2g4e@+$_fVq=26}LZV6LnE*p=xU>&+a1h!A~ts*4}XTd7q8I)yU3r*b> zXfB)5rSh$I=x+G-RU81cR$1CFzq7VBZD8$Z_x+1C8_KYjxbAUY_F&~J&uN!W=i=Im z3^S_TR)pnZPOdRW6I!~O+jixyxO*~GPES&)$#r0qVo^P76&)AO&RUKPQx$OIgo~Hn zLb!C2j18yOx^;)Y1x8R{W8i=7Y=0r2YGkx+O7F&#TX!_v&-DVvv2=OdSI4~dU=3*W zz*WbhPdRZij?VAQG`Y;+o^sTB5nJMNsUemoLh9C=8uK?yUrv_cTyn*GRjt(Q2wiAa z!eVGogq!_^5MbGU+9>e=U5LX@g4ELU_rAxIyEn0}R<;ww1n!1c$8F!!2{^jh%Tm%U2 z3P)dm-YML$w0};!{qFtUL098LI#`tu7kbX|xB{NvR<^_b5tO_P$x*O~8;!RNY$77h z9#}LX^8l}wa8v5lqW`e|Q0d!-STIN|+dFl*T7>`SMl<>t@36i*1Glvd6i{oYW>Em~vvj8l(4j7x(m1a*==C>Xw23Xi%4N z%}9Hi;;>ldmtIQ51%Y#O*e$%%CB%atH9swWXaWO7GyZEhjDn2d?@{iGw#Po*=wO3l zi8kQCiEM~_3;xJ;F{COv6-dO~s6mZHc`!Rjh>9~#T2CJrSzkH{*v{C~| zu(XKlhgfT?J2S-RN;zQ?ed1l_hr1A`)3TFY_fx)JKID+oX<}nJ)?lJmi&t@=GMayA- zWZzP3iJm9Mnho%QYVd{A-QsYB16!mrge%<$qV`}PP-Usd)CHalhlvDqQ1P2}7S4w+ZT5sL%v zVqkyTW(=mGA;f)CTUeV{Mu<@4h0x@IkHxhb8A8c!Se<(nt^v+nPZNl5x8?}3)?mjF1l0!cY6(?N0twm!x@U}~7rJ@lLCp6MpHsH$4Odlvmosx9B}(~A`x&prXK z1{RDt0XgxGD|{h5Qp0Z&{cL&k)_75IRKzC2{zXC^4F9^Ev8*-VI0`Tup6>y&p;h|} z?_U&uvXJ|nF`z=>)yZ{>Dmo=<0hrAZ%S@!Y!}hmORDK#GAJ!@^We(jAn==9?V#Kl*TcLP4}M0dPuoA!_;v zQF;sK(80yKOaf<=8r)60I8{NBG4s#pr2p_QaE!fSZHgJ``qkyPtNp&rd4q?A@BW)b z_MZ)(z!Ix=@;gvn|2}zf{-2ZAe`3|MI2?e@@~i?6j;+!Sq$mQut+6i&0&0c<7B*8V z>8&B|B>k!8oXbG=F7j@~-yR8rmx~y(taT2@8mV0o&uRW+?&;R&js`%L&nMKMp6E!n zBcq2qHRSIM`v^)cjUntfny!?3T-@F}hOVUg{*5#2Z&-XZT~;S+oTOcA77B+~Zzw>m z5%C#ZlEYW%SW1Qh;I; z{U&W{+WA?adfAGcP9^VJZfQCD{2$o$nF8>>4oe}rW5gAnp9wmpdChJJ|r z&UmlEyS0Z2^?JsiaZU#x>KU(uz#KrGW?`YKQ|S?{zZl^cMWWQovA@+Dr+XisiRT0N z%@A`gn;Bw&gjGg*ajMn$f#JSLbq!{8QibO%eV@jcpZG=Cc#2!}R*isvtFcC5(Q3h& znbu`^(=_4|n0>qe$8w@}1Eq!5gsXbJf+m!-p8f;hdgnt4ZHta(m(cleQW(H&H5g29 zUZvZlz3G5B%{9ww7&AzM5xK+P@20)PWs{I=cir0JQk&y|Afiy1j}>DJPp6~v>`%g0 z^4aR8g|F01bG!+LLk#B8^W3@zv*p$o@sAaPpBk65miY zkwIz?dJE4hen;K*lk{@RrDSOkzTlwk}~6yMYWy z79}NEr|>3Jsy=w~_kV}H2L_AO!0oI+*sq2<2{MmS&G_#b z5UGej&<0H4P6Rwq-VPbyIIoKGF9Bz~_BR^Bz%CJquOa#d7Fa^TADHG}621kJB00Zn zRm7F!v^9?A-a9|xJ02Bb2)_lZOjaKWVi-2KMS&Lblwyxd$ zcf^3NH+IR89t4x1ha$HLN;~j-*o^@I!>yb2&^qi!FrA^0 zm_YQ@S*d?48)+wbL7p4^P|mQOIL&rcFG?Au{SpSUw3y_OCz?_D!AX)v(hR$gcxU+$ zU9`A8w8A2NK`Qjzq^s^%=86O-Oq-cj z;#|cQy%g65%vxPUYhEQ50z)$A!$wJCLqS@Q{pJGI=7E}}xi-kMDyz%tY2UPn{mC|q>F8dz+ z*+pL#wqZqeZj7|BytiFb5BLmy3>M);)vD?OsqR9?s0x_11acrvL$$&(nU#S8L-tb( zxjqdc*;J3_nFF7bT&$(6`1`yiixL=Dssr`CAU?%a(W2LRDSl(4!=Rptmay%#vQvHz zER@rnYi2zl|Fou>Jy!;8$-KYj3bMAT7@CVn>r+h)MuHJnvxlOKM_*H@&xEu2W`K6# zZ^`7!pO${hv^t|UKYD+8@%gX*Wa$Cga)aZ>`?efU`H{2Oo3b4etGNcO-8@ZQBMeC1 zT4^y2zkp7-r%-V*Gdj4dy{yQ0I3hin-uIVZ!mfq@c_Xiq9dfS;q$TZ`bK@r7#k_U6 zC;e`J`I}OIaN#$E?1)S32j;()!rOYpjE!1wb2xgt$Cj>y&VLP*d(~bvdpXHmwj=0N zLcAPyqmx{<6(=h8T>eg|^LP@cX0>}n^TYjxL^Eh}HLsmC_~I=3=NR%>{eU4{`wTyL)ph*wIN}+ftZPI7sr^ci{N}2_BxNs zlU@~d%c*#(xu?&ySq}XjQ^hcjk$C#^ajT&N7CBA7azyyD;PtLo$5|?_A7Ls9ceTGwLcB}>6Z^23+l&LUI_PAueyw_(k7`mBV7Av zPT@w0ra2x_I-{C8t43Lx7C^E{D0@|hRtA1vd8~%$(9ig|7{uZd>1S1=9%=m38RFKa zc0aO<>lS8&9IJMR9kh#ZIo1Zt7=~nQRO;1r2`OXk)s<^HDNLc;y&!wlbav?j{BYrP zjC7Fe3L$V0lz5Lp6itU)XkV{>Eq~*>Z5A-taVDBBGmIJi8E#3`j8e`f-svQvvij?GS~pLej4G&dcW zL1^L+YJ}S+-UI}Eox>5=c}c)ukKsI9C5mll=sp3|thsj@9Ap8y>|G(Pm%9Czxm%T- zDPzW)1Xi1NAnR^e&*L|hVPB%!8h$%D;im1}!_e{~ESI6uuadq`E|7IWMG$5>S=SuV z7}$#w$lErg+v-U-6=V!=YBd=*c_>jR!hNNYqg|Z)t^r0Lja*uv`wh>)Q9p)zfB#oX z05N$OYI2;7^b=j&^sPX@$KaPlbJMGs(sbSfO7Fwh2Tw`lTCPax8JZ?C8*DDO?b=sqIq62IyK)Sh$VGMikz)*T{h>nfKdOf7)tPz90&D z@_tsPR@^$(<5bl0rzQoMABXlpJ~>w=d-!J5@y@(7*Ss`a)O;i2*vcgz5{2R~$oTq1 zY{n_S2sDk2!Oj{{EZ&1#0*NpK(GP$6m;}A@M4Aau_&x|!fr5J+7m*M01mSla&OQOI zQC*SfE1zt7htlo;?WIA54cMk=ARt3@|0l@!Glvbx)PQ$WUToC;anA(=f>XqS1n`j| zj~d2<2>af!h$<&QFu??exJk&x`;E$^q=G1vrNFU}o zK2`1smV4zB-jCzKye;9II`-%LdBA$Ws<}^=iFAWQ9J6T77c;+7Jx@V&z0Z314Dl+1 z5iZH2(+~94rYAQYH4TrHdAwya8=d#|Y8nEtI>9kT9H2!>JC_+e*{3!f-ocK9!@VlQ z1<>vCDhbq!$~W4$5M}o}9w_D9YG-0OHV)l8X^!2q>*m-;$&I_^$~AUKgko8Ch!)=u z!bv-~{V~D1Z^c_abE;+19&+^3rdH*aH%WRoE0*B?D}a6CMOq!Z^&9VRhWNQ#Vaz#z z-6r;m4z+b{*i!Ub+G~TRd)dOPR89?M-&vca^KmcS_W=y=@6kTh>M~TOrhEPa$E#bt z`msZ1^9?3XddK4MWFG9tY+n6frGO1^)FmoFX27TdCZy6#XC!j40%tx&CG%E~Vgn}z zs^8iYApabE{WEXVIw9Uap#MZ-%Km!bWHCqrHN6)KD4ExpNgZ zZDUAJYbDb4cWwV+YJJ2-HYw|dsxhm23k#ZcxkW@818I2~H%5ijg|#u0)#}(!ZLlmz z6B>+F72*x+Mv95S0sIx7=4_)djKUA{NfWb7T*R^K zfgIi~qCh+)0e#yhQ&BHTsp>!}GDbnq*wJ}f>6D{HD*~&*IV**5fOHz*m)(dZ(F<%Z zk`+kixGSgDsP6#LQF4Jor7u-o|85v-LLV%Ec@lRHZyw4Rxy7y04xR<-O4n~DoMK;% z{yL}2H_!xqvTsd7xM>^%iM!W)q^2QM`CVvpgzDdTR9tU*vp+t9uXY!*P~c(s*t%J zQSPDGv#6;a9o5DZ6Ky-xh~(i!qG2$c3OGW9*$84=IfgkJ_rV5eRE09kE46H2L|j=h z4{jq_FbvvPc zsDEXWmS!#VJuM@tF?T+e!D%gco_W8;<7#FYxX<(4B!Pn3R6=3xtwVuPmzFr3DZB<+ z!N)~4FnTJ{WN{AQU_#wc0@fz{s59=tWd5Nt;@0fa*c2{5goxH4Ga1^VN=wW#{R|o$ zF=k{ao#{#e?B7@@o;p#cJ2P>t$Bd9o=XIo-e~8Z&%E5sAHFcE={^NLj%rUs$;m>Tm zQSC4F14^F>#WH#vfqtREAY0UvOJQN#NT=WF3jMIrF}MSOJVhKFC+tn@8KJiM9L5=I zs7)bPmMHl<-9^mni}3!Uq*eWh87`X{ASlorZn?OY!<ryeBm9 zuI3%(PHY|h6&CWQ_A{{bd?jBYfKAC7N zG0w&%tC~KZKP$1Y)1UXS(yM`w&?xcUvEqyj7~Sy%yb1(dV~g1_Cukytey5<@si7(^y`uhdz91?X36B;PdU81Hs^Eidcj7B`hBKs(OCkCIQ9G0X z!I6~ONK=IIW+TS|kRo+lo6>!Ddz-JBe&$cb-QtMQ;qv!l%&u;BJSU?DEw34$cD)!c zY{`YYJKjh6;;*Ntgo=!x9jDQQmC-`Tuut|Kb~zQ(JS8d`f*vyrfPl6m(_E}E{%TzG zi+DJ*{hhWNC%aTZGVr#ZUzi^|Y*ht-2dBdPFSela6Sk$-p_tUmewx7GE-55$?5x59 zOr#~SM`b2^W9SAGE5=K@zg%t@EgLpNcom!Wk2wKtvxKUGV#x_SnA@*}CeNy^o#OkW z>qZJ;A{y;NThuO2sk!P{LUTGK>dYe+Umdb+8MBj@dm9_-%S-BHc@a{oTuFNXa*C7r zrkY-T_^*k}_1jDnBjFSrjn3r>f+5FWOZ6dG2+iv4hdMn`43#Lv)plEHRU}fBKjvAe zm%TJs-NRUaxYTZ>br!WffawspXBTM#{RN36H#emTRESqi1{EF=N~+f}l}6Cm?t?4r zv^sQxXc1zuVL3(nB%zOfj2&3AIXlEaA(nY0zOv0qh84UU>lv6q* zqeGnZ1ujL}sWjiqOL&9;y2EEQ8UoTnN2K=)-NYEhZLS9%elK z3>}J54vk*H0TiE=qdzayu9;8wpuNUvoncQK+w5f9!?^|77nYy`Xa-+{)s*MMI%T0T z4VlEFkaV`6kcO^^BhM!@n_6b(mpF`%#mtWo;XNvcSE`*h(?zM+gsc4I7mhLb2lG~U zS-tbs|A5mPSb9$KFb$6HDtO5Gs3fGcT7PhsFI=R{lbVxHHyG1#Co-d%%;A!23*^b!VBjPzn ziTZ{;g74Atvo#K#KR?Z|pnPt^DgH7@XKimlZmWB@&#>LHOokk_qIGF)~MaYH8 zD5qn__ju6&FTVP`kRxb{EN!~1Xho3iD7I*hS>*vY1|2!=hB~~3- z1}Ah4iCR@r4Sas-%Lg>b)AoiaAE>ft8c||J=*&E(n4ItNH*#4Lye%nt+EVse;yN1= zyzbc1bG zL&Wo!-0}a=-uWeJY>fKVPAy-yB!Q>)9$=l~u~sZaLahB+K|eV%q0E&|&L1dF2}Gs@ zgD_iXEXva!g=}o{)TFBC=X2(B0y>vMGCx6=JkYu=U%5S?_>mLud20rkB_9b~S&(Nv zA>MWYOFiAcZ3xAaCUVD{s2WG2I{PHZJDvI2pz z+G)d=B9H?A$pEPhovCqvU|`RvzH!`RKpg)`nGV^W+ZtRe>yO{rkki$XyC>SXu5w9x zhtWzbEQRH6vg`&vx=}XT>wSdyki=x&My|b)lK4b)dxHT>5K1Sp&f_p3b)?|&kOm_ng zNW0VeY-lL$NS=c9S^`r|W(S2l_c-kc4#((vnsrFY%bp53{aIqKMq{}E(vSAUMcWxD z)WMMsA2bP9qaYC)*-bNgV8EmiHnC*&v-RHwM6&ao^sk1WB#M_lzC|zIayawik9sbF zRLC;&IDFn`3f>hWT(lfBD5>bL&RvT~g_vgdGK}jYgU3xiL%Shf*)8etqK`jDM7XU}g(# zQTz?4_?jpZSElGqiV!pF+HS3#wDM5yK;GV2UAHr;z}=)`B~mv^FzH`Z*svGk0Jvn~ z_Em|UMHqN=b9}M1E8#S$q)p^XIg*Eekf2YFfwS*0$=jrX0>U$1GSnKy%^+dhyIvmY z$1|?4+HXY5v(K$jTe~_BKewd>57bVEKX^U^<^846wvh4+xM=l$FN35|8aa}_Gkd^y zuuF7jc8S=M^KBQKgS1mKzw-t@0iGAa7iS)6`ZlH6jYJZl>kuJ*@Z=TE?Ga{Hpjy5J zIwNY?cS?pRN`U5mTi;+`m{)C|R3A}6F|?OgjW<{}yIHF)=(Q~|n~Mv`w^Auq$uw>= zYkok|-2E|#iq;Y%lgYgs`QSc(LE%rv@vyh!WV2joodtQdo>Y@gPsvwHzyX7HeO*ON zQxP^58^1!zf*Tc4(FVxW$I&Aw_l0J`F<9LPOjaXQPod(R>VqE@nIlC3)=V`h(HByV zJMpjkY#PWf_{4QvGj8vkj#`K{h(9);KR)rFj>H8)-?-=iQO*S(*Zoak*yAUHJ><|E zLDQM1In^hCfTiF!WRN;>z-cpVtaXU7(K7YeIXHDvOWEe{t1#r*s$X%QE>Ys7<+$#N^~W)a67=)4BD?*tX%ZZhKAa%$Lq3`t6osaY z&P&i3F9rM01>wqc?TNZqztBLp;1LKI7umqJ1!v9PSV=xb(&Pbt0BeD~@e^AbvVU@( zOs))(4)Cn}Q(Vj|zo++0B!%D25^%&9nbQLhP|T)<0M-63*cV=f{w|zIfv9vozJ5nP+j>m<7WZ6+A<1l7RV=6C|KHXZQqyCRJez!qv#Cp<}!!?sl=Lu@<1}jVFZPOr9$MM^Y9ghA3r#is9Od@IL znHB`2ME{#hy-7rI$5Fg-%ieOU)?r_qhmj1lmRtlW;MiC%V%QdJ__?6%$h}G3*}dEt zXh9T75P?fYO0{=Rdu{;iu3p6l#THs0VPes7AKZ4BKh*8tfC=1ZS2f|lRn%rPa?k-H zfOlbwl(h6QEhMcgn6u>IaR5AHJqM(I=0z|(Z^ztCzizc%f0j}`t3kPXE+!E$5=joK zKrvMi?xp}o4{I1G`g*ds~)d09fZE_Da_jHu6 z%w8vp=U=({dzCNMr!Iqq_!A80|Kv#A65H3GNQwREq#gz*gqyx2&UCp2E-Z>_liF)5Rk=TndxTrg?tWuzJu&_btZ zilBr07M%a5v8#ZJs*Bc$G(+c50@5Mff`pU^h;)}TbR&(!pdt!Lj5LxmNJt1GEieov zASoqMk`kf_h=T79|L-rO>%H~PT6f)h)8nZ>NzEQ+C z%mq_Q-g3Bk_!`hFvF&Un#>eIi#VJob#(wZibjMT+JcqMiSz6iUw?iqV~}X>)uqp zj7Z}tVF>>MZ*(UU&G|DlAcCJ{n`SBQftfSt)q(@O z75ugoR|fjFFI~EI=IO#DdwK379!F%Nig}bDztM2oy+V5qGBSuu$*>U8+cevH*xMF$ zHcECs6IA~~@i@_vJTMdX7P>m^>6dQb>UOVoC8^q>F`%wj)N(^{Jd@u;jLX$VP4vdN zyJ^|>%YZvSQ?_MQ13s_6n_OrSUgS$B3fzoPnY~7)0O1Q;YG|*JyGVq$Y2lVVTa%&W zCEH%2P;30fLU>F1MvTlmiaWNlalB{Jkwmr=D%0~h%kelmB zl9IbGcL#4otote7YrCw^-^f8Ubp(9+wf4P;IEN1-9+y3oo-)GB#9p0G)M2xqBSqd) zomSGg5_S8LQwEbku-*n8LgyyIYk#T5WqYE=cqnZGJZWGhY!GHkECH_#9kb z1v|9|{hD)RNt*3>=}YyF9pnvj8IhTf&+|$Qin8`Aqhd4QyPkOwb&2gB@7C-!78=rB zoYHhlhsF8?EIXbEG$b+3Y$;l)Gs(EzO*|yUJ_QKkSunB#;*~Rz;@~JCKu{)x+G{Qt z;Fuc7WK4TZ4!SCyz8sk+#q&%XM2AcEtky+X^2*4lU5|S4eHX!i2;y{Fs5dnF^3U7b zmq(}@d(>CjZby7<2e4}hpPLbIs{8WfXQA<#%c*iFU&8yH9 z%)Y=e`%|l9oYNO{`~>N?M}1p{WzvB%B`&{LG-=5HX0u68>2f%?Esr+4BkiNlDGsh- z@2N@lvY}#DZy2HcHsZ++=5Y+Y78jFWx*PIOWFifWbuA5_K?<%L>~hL9x~~#)!HoLx zP-fbSCi3r|PG#gt#NIof9=@b}Kb5YT_RSO$nW}ZOy)Z=c1>LhUdN4Ai%pBj)&74Ju z@vz*aBppwVR*AUw>=e?$;!X_Be1*JY)a3X_MD398SAk6)@nL#WT@psm#z9%07}iE= zU2)f1lZO@jW0ya4WQm(f@evE@YQsYE**y7|G8(Q|WRyVU5^$R*cAfn?{O=V@a$l|@ zV~d>tb*@vzF|*z_@^DH&mxWjz2soF8THl$kfBP)MALXx;Quiu3M8$)Xc83fiQ=06L z4tV~3k@?GH(R4R|TlpSY_hfzItSM;#=K{S!?|N#mU!g+5=F$Spy4}W9Bp6oW&9Yam z(J%BgP%69K8O9I}pH2dT=P_#NPbKeMKtlTLEIJ&)+iFyasRqghokF$-Rf72)rhAv2 z#rJkDUpCjkZy-|BpquR#ZL4S%21~nw5_}@IZ;bkMXKsTB{LzBvBZKAPMFN(zd(5Jn zZ?^`oq!(HMTL*jhp=r0QU%>KwnQyHQ=sn2+UQ1o<8Z&)wSmtPUdCjU$1cj<8E&XsI z>L+h>`N#HzjjmcZ+i4W~ShBcsuyn!5aDjtzs|*L+zhLTFXW1^A`=W#|xw)K-bxdk5 z<$4eI@!PS6&5J0CRr|5W+WUqRE$jJ6hOxuW3u=`NbS}qSqt4&sDnns`IcVr$$&Q-t zUJ&1!{~KO8#w~6J=;}|j+^2Y=PA|n5+k--SiZi+g%Q?4?RL@fI6n|A{XGgYfKP!m% zQinr?D?+AsX=L3gSYLkIz8q3>Wy)q8_-z9RialeG->Tgo9Jqsu1 zSfyc;`DEg@w#`uG^r7uzZj17x$O$4U8q$vTdmU;IK5bGy*Sr&>39GyuO;1_q6+3v> zn=aM)f*oj|4?p$sf$!#l@2=OEv-iHTm^WV8v36U5>(FcaKJLWT_MAI^?^R|2|L5df zsujCrA>Yj5Fv&gYMKZjcuYDc}^Wsf&)#pp*?ZT=vYh5Fscg2)^4o6RgQp8M|h{sKI zO~4|Ly?UbY%jx%_YFjJ)(twN-f`6S^*kGG_GzOH4FRdFk1FGoqZp{%oP&Lq!NOc#H zP>XSqWsFL;!Cr1!FuZJB-g+CaxkLP8kwW)!9G6lA1tiNd#bP)^eIaLHpfBAI8MwT4 z{Nw8pVMK5lZ$bwdnf&w5E~*7NnM^!lg&c%x=o$h#VE?k+%HpiYZRuF z;wT=QYBj_siB~ZV&4XIr?Xn7jhJT@eCUY!bdfBvLbq_qCp9!1q7zav!lj?G6Bo$J-pHd~|PG=Da5O{asO0 z%cd-HqNaW&{f;I1rucNu{bRR=o>>!^?&mwOXnV$Vfz2J$COf|(1HSL=zD%X{MIeI< zOwZ@Dx#Bu*23V~`vj>+=em`1mS5SJ7GzqZGhkBZS4iZ;fH80LRf4Amgwb`Ipw1m8; zqY*m)A-7Pe*-+I3si?cxmg1aR_-lpczb%^*G6lF>jp*X zUo84}n>N~6tl!Y`2RnMX^K7*NXz3@KC;JxWB~(MfLn^ z&hb&s&sVwysW`7ldtBYV4c-vs)VKvZH>9s3{50jW5Jg-+I~ppcS_N8q8nvOGd&DKh z*%3@!aOMliH)=WwcGY)azi_zTVI^lJxR*b{6u+SBA$R0)ics}0dFuLGec!hi|)A5X)% zerCj0I5liS>$b8DkxHpDB`H+`^?i>=(i1Or%|^yvcWw(rf*j?9jdAuiIWCI8S_RJI(tZ=R<8Y82wnlU}}1Z z(3}O1PTj!H{WKxJGNK7Gu(*lbTY|~~AHz-prDQQ!wC=;KSjt-2&nz#&hnw49L;R!c zg`6ysjnLNvOD#&Q#thA?O$_5^vOJw%8kd%vgH=V=qdY~#%Jf4}wwkibGtyR2*G!e( zj7BCD*nYxw^h(A}O=066>fUt7i-ufN$U%0@V>(P2leCL!Kqep`2qf|!tCQ2YH=O!Cmyu$`WKRlc3V|w{ z5alroe@%HwzV*4W%cvyECj9!_jGSZD5y+Et`W}IKPR{{#p3aXnBcQh1Kl)PHoFSQu z3NUiq58Ml)@*`{K33gd zjAWD!6;N%q5il1{eG#H4FQb&Fwi$uF;kOu51QD>4XdomA+{`G=x{2LG8G=F0=2{9^ zF{6H^;;sk!7JM6gNb{yG{NU_a(&6Y`-r@3ZqxwL2whNsuOwm*}!42_k4y!f03Inu9 zJ?#>B`=YzP=koF=5?K;$IA(*V@ckdXIY8KNH zMSPXoLo6ddk>bn-dlVjP_@fWn4ouf6VO-`7!F3m(X^r}63J2txyz)lTKNb~Z7$!l9 z>8wjQ`S%Rz#9ml*TnV-E39~}q8?8tPa9WcK%N83Zv6;)^(J4WyX&E2Tk5Z5B)5t z|MJ~C@2allm4+lnPo)23FD#XBAyP}Fu$)nj%SxnHo4rZUvf>}PW7eHlp76oAXn6Wg zyII&;qkHk{s^JIxq)MA@Sayug{DJDjESo16X-h|NWsNVJKihJ~CKhy>wTs@0V` zd_0F_?~D8%^zk>=JqEikCy;)yXRqjPc|87R*pK9cQMZCz%^+9AwNjBNdFPKF%sDwU zB|m9&D?{7vGSzJ>HXp>Lr=@uEQIMKP8pCL$_bhcuLxVR8ob%KtEMZ8+t*zOJhleX$ z?3_(zw?5x_hVGx?OL4y)${6G9jTG^QP(_;0zY;uqWG1>x6hs_2;cNC;BjNqgnMGzn z0{3HR4WhTZDqA1dtQ?m|LPd-~pz5mfR2IZ}70UmWb$Q%UG*hfkgF30TV~S}D&gjib z;V^fPPtEqB=J-ZNRsYp(52%7+A0J%Exw~I`K_{j2$+PqipJ{V;rE(;*bE%6I;}&0f zr$WH|O2zi*8o7d~??UIQFLQinz@9BMYKit05v+mQuPER&$6?>e*`w5{`lBU>sp*CyqqUx^h(vkov{pt;TS*A0wx&fW;C8A9`RLz? z{*KE=qy-Zl&GZ^oW$zt)JRE1m<mx-`J4;MnNv4&vQ0Ysj;Rm z(Xo&2#}#+EZG@|0_JzfQk`7DS2ix(#STYMf*fFx{L3HE{SNq=5mNse;AyIs19aY=m zW+=vETwn*SHF=N#8jeNsYlw?5kdT_RDohboj%V$M#Y$;=R+8lbYewrgWwheV$tC6*WCgLfr_+;;+bc% z*@$7cvJi7>r4gFcJYw8-7VRK534b}rZTr5VM5n0+q?cRQBV{M<B8_YD*&+}N~ijy2ZeF#@w+OkqNj%k(7 zmq%2a7hWTZ)hNDl&(%3rsZrwPRjG672$^rNQCZGh@&SGxHT|^&t%@>!bHAVN#agYn zaV6*TnlE`aWvM8n45V9R2G$}COa-U54MExKbT<7>vR{u)*V}nj)ESPJELRmUY^5nv-&W7uQO~!^hg(PdGQMD! z-ukMc^N%NX=st4C@|Am&s>F5PI2UrQ;^)%P_|KloF{n=Ty1u$A7D0NMm+x%yo^KO=C6U;8@(#}ZcDjWf8kXa5cmUoBE4?mS-cyx{ zR~#~91&B5PL<|3t3*2kqcK86E8S&~I?O*z`ELcyY0r#9kpxGs`hzN;JgvfVtW3`xf z4@Lu4@{`O`bEdyI8icdDECDKj0gj?r9KTVX5WO~HMcDP=pH{svShT?e)6Re<=dfsm z=uc=ac2k|k5BPq3KnHl%dy?be@L=IzF`nSJyBSa8W#8A%Fau_=!4EuXMVAQs-w_ynJ$iwP0PdRhinct9+gaMKf-STxOP{D1L(04#R* z7_&vdAlz6+fms0l8N}z8j2bx94q-jYBJjK0W8j!_u_ERCk^Q??5CtIEq+!YG(k?4}>LQK8;^JPxe_8SQzU7Wf8-|AG-p07DUBsmecs(0VDq8 z|H%DBpOyiR(|9a@Q!M_q{s3MM;WhAIH^Y<{$2?5EC!1N=I#b~Skgi-Pk{b~Hk6AEBBy};^50Sm9*bb^QV<0JF} p=@6p*SbYL}1@@b;20r}?SdX2j#RnX~F \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec9973..e95643d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line From 1b9051273e3f0281f698a85131253b9e9bfdf535 Mon Sep 17 00:00:00 2001 From: Obolrom Date: Wed, 21 Dec 2022 19:14:12 +0200 Subject: [PATCH 2/9] Improved RxFirestore, added throwing error if main thread, updated doc --- .../java/durdinapps/rxfirebase2/Plugins.java | 14 ++ .../durdinapps/rxfirebase2/RxFirestore.java | 189 +++++++++--------- .../rxfirebase2/RxFirestoreTest.java | 2 + 3 files changed, 106 insertions(+), 99 deletions(-) create mode 100644 app/src/main/java/durdinapps/rxfirebase2/Plugins.java diff --git a/app/src/main/java/durdinapps/rxfirebase2/Plugins.java b/app/src/main/java/durdinapps/rxfirebase2/Plugins.java new file mode 100644 index 0000000..da94426 --- /dev/null +++ b/app/src/main/java/durdinapps/rxfirebase2/Plugins.java @@ -0,0 +1,14 @@ +package durdinapps.rxfirebase2; + +import android.os.Looper; + +public class Plugins { + + public static boolean allowMainThreadQueries = false; + + public static void throwExceptionIfMainThread() { + if (!allowMainThreadQueries && Looper.myLooper() == Looper.getMainLooper()) { + throw new IllegalStateException("Network on main thread is not permitted"); + } + } +} diff --git a/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java b/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java index 9864776..16bd84f 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java +++ b/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java @@ -1,6 +1,5 @@ package durdinapps.rxfirebase2; - import android.app.Activity; import androidx.annotation.NonNull; @@ -48,6 +47,7 @@ import static durdinapps.rxfirebase2.DocumentSnapshotMapper.DOCUMENT_EXISTENCE_PREDICATE; import static durdinapps.rxfirebase2.DocumentSnapshotMapper.QUERY_EXISTENCE_PREDICATE; +import static durdinapps.rxfirebase2.Plugins.throwExceptionIfMainThread; public class RxFirestore { @@ -242,15 +242,16 @@ public void subscribe(CompletableEmitter emitter) { * * @param ref The given Document reference. * @param updateFieldsMap A map of field / value pairs to update. Fields can contain dots to reference nested fields within the document. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Completable updateDocument(@NonNull final DocumentReference ref, @NonNull final Map updateFieldsMap) { - return Completable.create(new CompletableOnSubscribe() { - @Override - public void subscribe(CompletableEmitter emitter) { - RxCompletableHandler.assignOnTask(emitter, ref.update(updateFieldsMap)); - } + return Completable.create(emitter -> { + throwExceptionIfMainThread(); + + RxCompletableHandler.assignOnTask(emitter, ref.update(updateFieldsMap)); }); } @@ -469,14 +470,15 @@ public static Completable setDocumentOffline(@NonNull final DocumentReference re * Deletes the document referred to by this DocumentReference. * * @param ref The given Document reference. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Completable deleteDocument(@NonNull final DocumentReference ref) { - return Completable.create(new CompletableOnSubscribe() { - @Override - public void subscribe(CompletableEmitter emitter) { - RxCompletableHandler.assignOnTask(emitter, ref.delete()); - } + return Completable.create(emitter -> { + throwExceptionIfMainThread(); + + RxCompletableHandler.assignOnTask(emitter, ref.delete()); }); } @@ -495,29 +497,24 @@ public static Completable deleteDocumentOffline(@NonNull final DocumentReference * Reads the document referenced by this DocumentReference. * * @param ref The given Document reference. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Maybe getDocument(@NonNull final DocumentReference ref) { - return Maybe.create(new MaybeOnSubscribe() { - @Override - public void subscribe(final MaybeEmitter emitter) { - ref.get().addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(DocumentSnapshot documentSnapshot) { - if (documentSnapshot.exists()) { - emitter.onSuccess(documentSnapshot); - } else { - emitter.onComplete(); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { - if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - } + return Maybe.create(emitter -> { + throwExceptionIfMainThread(); + + ref.get().addOnSuccessListener(documentSnapshot -> { + if (documentSnapshot.exists()) { + emitter.onSuccess(documentSnapshot); + } else { + emitter.onComplete(); + } + }).addOnFailureListener(e -> { + if (!emitter.isDisposed()) + emitter.onError(e); + }); }); } @@ -525,29 +522,24 @@ public void onFailure(@NonNull Exception e) { * Reads the collection referenced by this DocumentReference * * @param ref The given Collection reference. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Maybe getCollection(@NonNull final CollectionReference ref) { - return Maybe.create(new MaybeOnSubscribe() { - @Override - public void subscribe(final MaybeEmitter emitter) throws Exception { - ref.get().addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(QuerySnapshot documentSnapshots) { - if (documentSnapshots.isEmpty()) { - emitter.onComplete(); - } else { - emitter.onSuccess(documentSnapshots); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { - if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - } + return Maybe.create(emitter -> { + throwExceptionIfMainThread(); + + ref.get().addOnSuccessListener(documentSnapshots -> { + if (documentSnapshots.isEmpty()) { + emitter.onComplete(); + } else { + emitter.onSuccess(documentSnapshots); + } + }).addOnFailureListener(e -> { + if (!emitter.isDisposed()) + emitter.onError(e); + }); }); } @@ -556,29 +548,24 @@ public void onFailure(@NonNull Exception e) { * Reads the collection referenced by this DocumentReference * * @param query The given Collection query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Maybe getCollection(@NonNull final Query query) { - return Maybe.create(new MaybeOnSubscribe() { - @Override - public void subscribe(final MaybeEmitter emitter) { - query.get().addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(QuerySnapshot documentSnapshots) { - if (documentSnapshots.isEmpty()) { - emitter.onComplete(); - } else { - emitter.onSuccess(documentSnapshots); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { - if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - } + return Maybe.create(emitter -> { + throwExceptionIfMainThread(); + + query.get().addOnSuccessListener(documentSnapshots -> { + if (documentSnapshots.isEmpty()) { + emitter.onComplete(); + } else { + emitter.onSuccess(documentSnapshots); + } + }).addOnFailureListener(e -> { + if (!emitter.isDisposed()) + emitter.onError(e); + }); }); } @@ -588,31 +575,24 @@ public void onFailure(@NonNull Exception e) { * @param ref The given Document reference. * @param metadataChanges Listen for metadata changes * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable observeDocumentRef(@NonNull final DocumentReference ref, @NonNull final MetadataChanges metadataChanges, @NonNull BackpressureStrategy strategy) { - return Flowable.create(new FlowableOnSubscribe() { - @Override - public void subscribe(final FlowableEmitter emitter) throws Exception { - final ListenerRegistration registration = ref.addSnapshotListener(metadataChanges, new EventListener() { - @Override - public void onEvent(DocumentSnapshot documentSnapshot, FirebaseFirestoreException e) { - if (e != null && !emitter.isCancelled()) { - emitter.onError(e); - return; - } - emitter.onNext(documentSnapshot); - } - }); - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - registration.remove(); - } - }); - } + return Flowable.create(emitter -> { + throwExceptionIfMainThread(); + + final ListenerRegistration registration = ref.addSnapshotListener(metadataChanges, (documentSnapshot, e) -> { + if (e != null && !emitter.isCancelled()) { + emitter.onError(e); + return; + } + emitter.onNext(documentSnapshot); + }); + emitter.setCancellable(registration::remove); }, strategy); } @@ -1227,6 +1207,8 @@ public static Flowable observeQueryRef(@NonNull final Query ref, * * @param ref The given Collection reference. * @param clazz class type for the {@link DocumentSnapshot} items. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Maybe> getCollection(@NonNull final CollectionReference ref, @@ -1239,11 +1221,13 @@ public static Maybe> getCollection(@NonNull final CollectionReferenc * * @param ref The given Collection reference. * @param mapper specific function to map the dispatched events. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull - public static Maybe> getCollection(CollectionReference ref, - DocumentSnapshotMapper> mapper) { + public static Maybe> getCollection( + CollectionReference ref, + DocumentSnapshotMapper> mapper) { return getCollection(ref) .filter(QUERY_EXISTENCE_PREDICATE) .map(mapper); @@ -1266,11 +1250,13 @@ public static Maybe> getCollection(@NonNull final Query query, * * @param query The given Collection query. * @param mapper specific function to map the dispatched events. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull - public static Maybe> getCollection(@NonNull Query query, - @NonNull DocumentSnapshotMapper> mapper) { + public static Maybe> getCollection( + @NonNull Query query, + @NonNull DocumentSnapshotMapper> mapper) { return getCollection(query) .filter(QUERY_EXISTENCE_PREDICATE) .map(mapper); @@ -1281,6 +1267,8 @@ public static Maybe> getCollection(@NonNull Query query, * * @param ref The given Document reference. * @param clazz class type for the {@link DocumentSnapshot} items. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Maybe getDocument(@NonNull final DocumentReference ref, @@ -1293,10 +1281,13 @@ public static Maybe getDocument(@NonNull final DocumentReference ref, * * @param ref The given Document reference. * @param mapper specific function to map the dispatched events. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull - public static Maybe getDocument(@NonNull final DocumentReference ref, - @NonNull final Function mapper) { + public static Maybe getDocument( + @NonNull final DocumentReference ref, + @NonNull final Function mapper) { return getDocument(ref) .filter(DOCUMENT_EXISTENCE_PREDICATE) .map(mapper); diff --git a/app/src/test/java/durdinapps/rxfirebase2/RxFirestoreTest.java b/app/src/test/java/durdinapps/rxfirebase2/RxFirestoreTest.java index 55083b9..b2bcb9e 100644 --- a/app/src/test/java/durdinapps/rxfirebase2/RxFirestoreTest.java +++ b/app/src/test/java/durdinapps/rxfirebase2/RxFirestoreTest.java @@ -93,6 +93,8 @@ public class RxFirestoreTest { public void setup() { MockitoAnnotations.initMocks(this); + Plugins.allowMainThreadQueries = true; + setupTask(documentSnapshotTask); setupTask(emptyDocumentSnapshotTask); setupTask(queryResultTask); From 2b76f1d5121f47033744c208ac4c40d29ebee59b Mon Sep 17 00:00:00 2001 From: Obolrom Date: Wed, 21 Dec 2022 22:21:46 +0200 Subject: [PATCH 3/9] Added checking for main thread in RxFirebaseDatabase --- .../rxfirebase2/RxFirebaseDatabase.java | 284 +++++++++--------- .../durdinapps/rxfirebase2/RxFirestore.java | 11 +- .../rxfirebase2/RxFirebaseDatabaseTest.java | 19 +- 3 files changed, 161 insertions(+), 153 deletions(-) diff --git a/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseDatabase.java b/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseDatabase.java index 3b476cd..17b569f 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseDatabase.java +++ b/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseDatabase.java @@ -1,5 +1,8 @@ package durdinapps.rxfirebase2; +import static durdinapps.rxfirebase2.DataSnapshotMapper.DATA_SNAPSHOT_EXISTENCE_PREDICATE; +import static durdinapps.rxfirebase2.Plugins.throwExceptionIfMainThread; + import androidx.annotation.NonNull; import com.google.android.gms.tasks.OnFailureListener; @@ -15,6 +18,7 @@ import java.util.Iterator; import java.util.Map; +import java.util.Objects; import durdinapps.rxfirebase2.exceptions.RxFirebaseDataException; import io.reactivex.BackpressureStrategy; @@ -22,20 +26,10 @@ import io.reactivex.CompletableEmitter; import io.reactivex.CompletableOnSubscribe; import io.reactivex.Flowable; -import io.reactivex.FlowableEmitter; -import io.reactivex.FlowableOnSubscribe; import io.reactivex.Maybe; -import io.reactivex.MaybeEmitter; -import io.reactivex.MaybeOnSubscribe; -import io.reactivex.MaybeSource; import io.reactivex.Single; -import io.reactivex.SingleEmitter; -import io.reactivex.SingleOnSubscribe; -import io.reactivex.functions.Cancellable; import io.reactivex.functions.Function; -import static durdinapps.rxfirebase2.DataSnapshotMapper.DATA_SNAPSHOT_EXISTENCE_PREDICATE; - public class RxFirebaseDatabase { /** @@ -44,33 +38,30 @@ public class RxFirebaseDatabase { * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} * @return a {@link Flowable} which emits when a value of the database change in the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable observeValueEvent(@NonNull final Query query, @NonNull BackpressureStrategy strategy) { - return Flowable.create(new FlowableOnSubscribe() { - @Override - public void subscribe(final FlowableEmitter emitter) throws Exception { - final ValueEventListener valueEventListener = new ValueEventListener() { - @Override - public void onDataChange(DataSnapshot dataSnapshot) { - emitter.onNext(dataSnapshot); - } + return Flowable.create(emitter -> { + throwExceptionIfMainThread(); - @Override - public void onCancelled(final DatabaseError error) { - if (!emitter.isCancelled()) - emitter.onError(new RxFirebaseDataException(error)); - } - }; - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - query.removeEventListener(valueEventListener); - } - }); - query.addValueEventListener(valueEventListener); - } + final ValueEventListener valueEventListener = new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot dataSnapshot) { + emitter.onNext(dataSnapshot); + } + + @Override + public void onCancelled(@NonNull final DatabaseError error) { + if (!emitter.isCancelled()) + emitter.onError(new RxFirebaseDataException(error)); + } + }; + + emitter.setCancellable(() -> query.removeEventListener(valueEventListener)); + query.addValueEventListener(valueEventListener); }, strategy); } @@ -80,29 +71,30 @@ public void cancel() throws Exception { * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. * @return a {@link Maybe} which emits the actual state of the database for the given query. onSuccess will be only call when * the given {@link DataSnapshot} exists onComplete will only called when the data doesn't exist. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Maybe observeSingleValueEvent(@NonNull final Query query) { - return Maybe.create(new MaybeOnSubscribe() { - @Override - public void subscribe(final MaybeEmitter emitter) throws Exception { - query.addListenerForSingleValueEvent(new ValueEventListener() { - @Override - public void onDataChange(DataSnapshot dataSnapshot) { - if (dataSnapshot.exists()) { - emitter.onSuccess(dataSnapshot); - } else { - emitter.onComplete(); - } - } + return Maybe.create(emitter -> { + throwExceptionIfMainThread(); - @Override - public void onCancelled(DatabaseError error) { - if (!emitter.isDisposed()) - emitter.onError(new RxFirebaseDataException(error)); + query.addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + if (dataSnapshot.exists()) { + emitter.onSuccess(dataSnapshot); + } else { + emitter.onComplete(); } - }); - } + } + + @Override + public void onCancelled(DatabaseError error) { + if (!emitter.isDisposed()) + emitter.onError(new RxFirebaseDataException(error)); + } + }); }); } @@ -113,36 +105,38 @@ public void onCancelled(DatabaseError error) { * @param fireLocalEvents boolean which allow to receive calls of your transaction in your local device. * @param transactionValue value of the transaction. * @return a {@link Single} which emits the final {@link DataSnapshot} value if the transaction success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single runTransaction(@NonNull final DatabaseReference ref, - @NonNull final boolean fireLocalEvents, - @NonNull final long transactionValue) { - return Single.create(new SingleOnSubscribe() { - @Override - public void subscribe(final SingleEmitter emitter) throws Exception { - ref.runTransaction(new Transaction.Handler() { - @Override - public Transaction.Result doTransaction(MutableData mutableData) { - Integer currentValue = mutableData.getValue(Integer.class); - if (currentValue == null) { - mutableData.setValue(transactionValue); - } else { - mutableData.setValue(currentValue + transactionValue); - } - return Transaction.success(mutableData); + final boolean fireLocalEvents, + final long transactionValue) { + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + ref.runTransaction(new Transaction.Handler() { + @NonNull + @Override + public Transaction.Result doTransaction(@NonNull MutableData mutableData) { + Integer currentValue = mutableData.getValue(Integer.class); + if (currentValue == null) { + mutableData.setValue(transactionValue); + } else { + mutableData.setValue(currentValue + transactionValue); } + return Transaction.success(mutableData); + } - @Override - public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) { - if (databaseError != null && !emitter.isDisposed()) { - emitter.onError(new RxFirebaseDataException(databaseError)); - } else { - emitter.onSuccess(dataSnapshot); - } + @Override + public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) { + if (databaseError != null && !emitter.isDisposed()) { + emitter.onError(new RxFirebaseDataException(databaseError)); + } else { + emitter.onSuccess(dataSnapshot); } - }, fireLocalEvents); - } + } + }, fireLocalEvents); }); } @@ -209,57 +203,54 @@ public void onComplete(DatabaseError error, DatabaseReference databaseReference) * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable> observeChildEvent( @NonNull final Query query, @NonNull BackpressureStrategy strategy) { - return Flowable.create(new FlowableOnSubscribe>() { - @Override - public void subscribe(final FlowableEmitter> emitter) throws Exception { - final ChildEventListener childEventListener = new ChildEventListener() { + return Flowable.create(emitter -> { + throwExceptionIfMainThread(); - @Override - public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { - emitter.onNext( - new RxFirebaseChildEvent<>(dataSnapshot.getKey(), dataSnapshot, previousChildName, - RxFirebaseChildEvent.EventType.ADDED)); - } + final ChildEventListener childEventListener = new ChildEventListener() { - @Override - public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { - emitter.onNext( - new RxFirebaseChildEvent<>(dataSnapshot.getKey(), dataSnapshot, previousChildName, - RxFirebaseChildEvent.EventType.CHANGED)); - } + @Override + public void onChildAdded(@NonNull DataSnapshot dataSnapshot, String previousChildName) { + emitter.onNext( + new RxFirebaseChildEvent<>(Objects.requireNonNull(dataSnapshot.getKey()), dataSnapshot, previousChildName, + RxFirebaseChildEvent.EventType.ADDED)); + } - @Override - public void onChildRemoved(DataSnapshot dataSnapshot) { - emitter.onNext(new RxFirebaseChildEvent<>(dataSnapshot.getKey(), dataSnapshot, - RxFirebaseChildEvent.EventType.REMOVED)); - } + @Override + public void onChildChanged(@NonNull DataSnapshot dataSnapshot, String previousChildName) { + emitter.onNext( + new RxFirebaseChildEvent<>(Objects.requireNonNull(dataSnapshot.getKey()), dataSnapshot, previousChildName, + RxFirebaseChildEvent.EventType.CHANGED)); + } - @Override - public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { - emitter.onNext( - new RxFirebaseChildEvent<>(dataSnapshot.getKey(), dataSnapshot, previousChildName, - RxFirebaseChildEvent.EventType.MOVED)); - } + @Override + public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) { + emitter.onNext(new RxFirebaseChildEvent<>(Objects.requireNonNull(dataSnapshot.getKey()), dataSnapshot, + RxFirebaseChildEvent.EventType.REMOVED)); + } - @Override - public void onCancelled(DatabaseError error) { - if (!emitter.isCancelled()) - emitter.onError(new RxFirebaseDataException(error)); - } - }; - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - query.removeEventListener(childEventListener); - } - }); - query.addChildEventListener(childEventListener); + @Override + public void onChildMoved(@NonNull DataSnapshot dataSnapshot, String previousChildName) { + emitter.onNext( + new RxFirebaseChildEvent<>(Objects.requireNonNull(dataSnapshot.getKey()), dataSnapshot, previousChildName, + RxFirebaseChildEvent.EventType.MOVED)); + } - } + @Override + public void onCancelled(@NonNull DatabaseError error) { + if (!emitter.isCancelled()) + emitter.onError(new RxFirebaseDataException(error)); + } + }; + + emitter.setCancellable(() -> query.removeEventListener(childEventListener)); + + query.addChildEventListener(childEventListener); }, strategy); } @@ -268,17 +259,13 @@ public void cancel() throws Exception { * * @param whereRefs array of {@link DatabaseReference references.} * @return a {@link Flowable} which emmit {@link DataSnapshot} from the given queries. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable observeMultipleSingleValueEvent(@NonNull DatabaseReference... whereRefs) { return Maybe.merge(Flowable.fromArray(whereRefs) - .map(new Function>() { - @Override - public MaybeSource apply(@NonNull DatabaseReference databaseReference) throws - Exception { - return observeSingleValueEvent(databaseReference); - } - }) + .map(RxFirebaseDatabase::observeSingleValueEvent) ); } @@ -302,17 +289,14 @@ public MaybeSource apply(@NonNull DatabaseReference data @NonNull public static Maybe requestFilteredReferenceKeys(@NonNull final DatabaseReference from, @NonNull Query whereRef) { - return observeSingleValueEvent(whereRef, new Function() { - @Override - public DatabaseReference[] apply(@NonNull DataSnapshot dataSnapshot) throws Exception { - int childrenCount = (int) dataSnapshot.getChildrenCount(); - DatabaseReference[] filterRefs = new DatabaseReference[childrenCount]; - final Iterator iterator = dataSnapshot.getChildren().iterator(); - for (int i = 0; i < childrenCount; i++) { - filterRefs[i] = from.child(iterator.next().getKey()); - } - return filterRefs; + return observeSingleValueEvent(whereRef, dataSnapshot -> { + int childrenCount = (int) dataSnapshot.getChildrenCount(); + DatabaseReference[] filterRefs = new DatabaseReference[childrenCount]; + final Iterator iterator = dataSnapshot.getChildren().iterator(); + for (int i = 0; i < childrenCount; i++) { + filterRefs[i] = from.child(Objects.requireNonNull(iterator.next().getKey())); } + return filterRefs; }); } @@ -323,6 +307,8 @@ public DatabaseReference[] apply(@NonNull DataSnapshot dataSnapshot) throws Exce * @param clazz class type for the {@link DataSnapshot} items. * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} * @return a {@link Flowable} which emits when a value of the database change in the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable observeValueEvent(@NonNull final Query query, @@ -351,6 +337,8 @@ public static Maybe observeSingleValueEvent(@NonNull final Query query, * @param clazz class type for the {@link DataSnapshot} items. * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable> observeChildEvent( @@ -366,6 +354,8 @@ public static Flowable> observeChildEvent( * @param mapper specific function to map the dispatched events. * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} * @return a {@link Flowable} which emits when a value of the database change in the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable observeValueEvent(@NonNull final Query query, @@ -380,10 +370,13 @@ public static Flowable observeValueEvent(@NonNull final Query query, * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. * @param mapper specific function to map the dispatched events. * @return a {@link Maybe} which emits the actual state of the database for the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull - public static Maybe observeSingleValueEvent(@NonNull final Query query, - @NonNull final Function mapper) { + public static Maybe observeSingleValueEvent( + @NonNull final Query query, + @NonNull final Function mapper) { return observeSingleValueEvent(query) .filter(DATA_SNAPSHOT_EXISTENCE_PREDICATE) .map(mapper); @@ -396,6 +389,8 @@ public static Maybe observeSingleValueEvent(@NonNull final Query query, * @param mapper specific function to map the dispatched events. * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable> observeChildEvent( @@ -409,6 +404,8 @@ public static Flowable> observeChildEvent( * * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. * @return a {@link Flowable} which emits when a value of the database change in the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable observeValueEvent(@NonNull final Query query) { @@ -421,6 +418,8 @@ public static Flowable observeValueEvent(@NonNull final Query quer * * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable> observeChildEvent( @@ -434,10 +433,12 @@ public static Flowable> observeChildEvent( * @param ref reference represents a particular location in your database. * @param transactionValue value of the transaction. * @return a {@link Single} which emits the final {@link DataSnapshot} value if the transaction success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single runTransaction(@NonNull final DatabaseReference ref, - @NonNull final long transactionValue) { + final long transactionValue) { return runTransaction(ref, true, transactionValue); } @@ -447,6 +448,8 @@ public static Single runTransaction(@NonNull final DatabaseReferen * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. * @param clazz class type for the {@link DataSnapshot} items. * @return a {@link Flowable} which emits when a value of the database change in the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable observeValueEvent(@NonNull final Query query, @@ -460,6 +463,8 @@ public static Flowable observeValueEvent(@NonNull final Query query, * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. * @param clazz class type for the {@link DataSnapshot} items. * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable> observeChildEvent( @@ -472,10 +477,13 @@ public static Flowable> observeChildEvent( * * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. * @return a {@link Flowable} which emits when a value of the database change in the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull - public static Flowable observeValueEvent(@NonNull final Query query, - @NonNull final Function mapper) { + public static Flowable observeValueEvent( + @NonNull final Query query, + @NonNull final Function mapper) { return observeValueEvent(query, BackpressureStrategy.DROP).map(mapper); } @@ -485,6 +493,8 @@ public static Flowable observeValueEvent(@NonNull final Query query, * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. * @param mapper specific function to map the dispatched events. * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Flowable> observeChildEvent( diff --git a/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java b/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java index 16bd84f..a173b8d 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java +++ b/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java @@ -1,6 +1,11 @@ package durdinapps.rxfirebase2; +import static durdinapps.rxfirebase2.DocumentSnapshotMapper.DOCUMENT_EXISTENCE_PREDICATE; +import static durdinapps.rxfirebase2.DocumentSnapshotMapper.QUERY_EXISTENCE_PREDICATE; +import static durdinapps.rxfirebase2.Plugins.throwExceptionIfMainThread; + import android.app.Activity; + import androidx.annotation.NonNull; import com.google.android.gms.tasks.OnCompleteListener; @@ -36,8 +41,6 @@ import io.reactivex.FlowableEmitter; import io.reactivex.FlowableOnSubscribe; import io.reactivex.Maybe; -import io.reactivex.MaybeEmitter; -import io.reactivex.MaybeOnSubscribe; import io.reactivex.Single; import io.reactivex.SingleEmitter; import io.reactivex.SingleOnSubscribe; @@ -45,10 +48,6 @@ import io.reactivex.functions.Function; import io.reactivex.schedulers.Schedulers; -import static durdinapps.rxfirebase2.DocumentSnapshotMapper.DOCUMENT_EXISTENCE_PREDICATE; -import static durdinapps.rxfirebase2.DocumentSnapshotMapper.QUERY_EXISTENCE_PREDICATE; -import static durdinapps.rxfirebase2.Plugins.throwExceptionIfMainThread; - public class RxFirestore { /** diff --git a/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseDatabaseTest.java b/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseDatabaseTest.java index eed8dd0..c363f58 100644 --- a/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseDatabaseTest.java +++ b/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseDatabaseTest.java @@ -1,7 +1,12 @@ package durdinapps.rxfirebase2; - -import android.app.DownloadManager; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static durdinapps.rxfirebase2.RxTestUtil.ANY_KEY; +import static durdinapps.rxfirebase2.RxTestUtil.PREVIOUS_CHILD_NAME; import com.google.android.gms.tasks.Task; import com.google.firebase.database.ChildEventListener; @@ -30,14 +35,6 @@ import io.reactivex.observers.TestObserver; import io.reactivex.subscribers.TestSubscriber; -import static durdinapps.rxfirebase2.RxTestUtil.ANY_KEY; -import static durdinapps.rxfirebase2.RxTestUtil.PREVIOUS_CHILD_NAME; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - public class RxFirebaseDatabaseTest { @Mock @@ -70,6 +67,8 @@ public class RxFirebaseDatabaseTest { public void setup() { MockitoAnnotations.initMocks(this); + Plugins.allowMainThreadQueries = true; + childDataList.add(childData); childDataMap.put(ANY_KEY, childData); updatedData.put(databaseReference.toString(), childData); From 2a749cb388f9fa68dff358acab868a4ef1db3508 Mon Sep 17 00:00:00 2001 From: Obolrom Date: Wed, 21 Dec 2022 22:51:30 +0200 Subject: [PATCH 4/9] Added checking for main thread in RxFirebaseStorage --- .../rxfirebase2/RxFirebaseStorage.java | 393 +++++++----------- .../rxfirebase2/RxFirebaseStorageTest.java | 2 + 2 files changed, 147 insertions(+), 248 deletions(-) diff --git a/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseStorage.java b/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseStorage.java index 12be138..417108a 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseStorage.java +++ b/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseStorage.java @@ -1,10 +1,11 @@ package durdinapps.rxfirebase2; +import static durdinapps.rxfirebase2.Plugins.throwExceptionIfMainThread; + import android.net.Uri; + import androidx.annotation.NonNull; -import com.google.android.gms.tasks.OnFailureListener; -import com.google.android.gms.tasks.OnSuccessListener; import com.google.firebase.storage.FileDownloadTask; import com.google.firebase.storage.StorageMetadata; import com.google.firebase.storage.StorageReference; @@ -22,9 +23,6 @@ import io.reactivex.MaybeEmitter; import io.reactivex.MaybeOnSubscribe; import io.reactivex.Single; -import io.reactivex.SingleEmitter; -import io.reactivex.SingleOnSubscribe; -import io.reactivex.functions.Cancellable; public class RxFirebaseStorage { @@ -69,33 +67,24 @@ public void subscribe(MaybeEmitter emitter) throws Exception { * @param storageRef represents a reference to a Google Cloud Storage object. * @param destinationFile a File representing the path the object should be downloaded to. * @return a {@link Single} which emits an {@link FileDownloadTask.TaskSnapshot} if success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single getFile(@NonNull final StorageReference storageRef, @NonNull final File destinationFile) { - return Single.create(new SingleOnSubscribe() { - public void subscribe(final SingleEmitter emitter) throws Exception { - final StorageTask taskSnapshotStorageTask = - storageRef.getFile(destinationFile).addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) { - emitter.onSuccess(taskSnapshot); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + final StorageTask taskSnapshotStorageTask = + storageRef.getFile(destinationFile) + .addOnSuccessListener(emitter::onSuccess) + .addOnFailureListener(e -> { if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - taskSnapshotStorageTask.cancel(); - } - }); - } + emitter.onError(e); + }); + + emitter.setCancellable(taskSnapshotStorageTask::cancel); }); } @@ -105,33 +94,24 @@ public void cancel() throws Exception { * @param storageRef represents a reference to a Google Cloud Storage object. * @param destinationUri a file system URI representing the path the object should be downloaded to. * @return a {@link Single} which emits an {@link FileDownloadTask.TaskSnapshot} if success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single getFile(@NonNull final StorageReference storageRef, @NonNull final Uri destinationUri) { - return Single.create(new SingleOnSubscribe() { - public void subscribe(final SingleEmitter emitter) throws Exception { - final StorageTask taskSnapshotStorageTask = - storageRef.getFile(destinationUri).addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) { - emitter.onSuccess(taskSnapshot); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + final StorageTask taskSnapshotStorageTask = + storageRef.getFile(destinationUri) + .addOnSuccessListener(emitter::onSuccess) + .addOnFailureListener(e -> { if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - taskSnapshotStorageTask.cancel(); - } - }); - } + emitter.onError(e); + }); + + emitter.setCancellable(taskSnapshotStorageTask::cancel); }); } @@ -156,32 +136,23 @@ public void subscribe(MaybeEmitter emitter) throws Exception { * * @param storageRef represents a reference to a Google Cloud Storage object. * @return a {@link Single} which emits an {@link StreamDownloadTask.TaskSnapshot} if success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single getStream(@NonNull final StorageReference storageRef) { - return Single.create(new SingleOnSubscribe() { - public void subscribe(final SingleEmitter emitter) throws Exception { - final StorageTask taskSnapshotStorageTask = - storageRef.getStream().addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) { - emitter.onSuccess(taskSnapshot); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + final StorageTask taskSnapshotStorageTask = + storageRef.getStream() + .addOnSuccessListener(emitter::onSuccess) + .addOnFailureListener(e -> { if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - taskSnapshotStorageTask.cancel(); - } - }); - } + emitter.onError(e); + }); + + emitter.setCancellable(taskSnapshotStorageTask::cancel); }); } @@ -193,33 +164,24 @@ public void cancel() throws Exception { * The StreamDownloadTask.StreamProcessor is called on a background thread and checked exceptions thrown * from this object will be returned as a failure to the OnFailureListener registered on the StreamDownloadTask. * @return a {@link Single} which emits an {@link StreamDownloadTask.TaskSnapshot} if success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single getStream(@NonNull final StorageReference storageRef, @NonNull final StreamDownloadTask.StreamProcessor processor) { - return Single.create(new SingleOnSubscribe() { - public void subscribe(final SingleEmitter emitter) throws Exception { - final StorageTask taskSnapshotStorageTask = - storageRef.getStream(processor).addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) { - emitter.onSuccess(taskSnapshot); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + final StorageTask taskSnapshotStorageTask = + storageRef.getStream(processor) + .addOnSuccessListener(emitter::onSuccess) + .addOnFailureListener(e -> { if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - taskSnapshotStorageTask.cancel(); - } - }); - } + emitter.onError(e); + }); + + emitter.setCancellable(taskSnapshotStorageTask::cancel); }); } @@ -229,32 +191,24 @@ public void cancel() throws Exception { * @param storageRef represents a reference to a Google Cloud Storage object. * @param bytes The byte[] to upload. * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single putBytes(@NonNull final StorageReference storageRef, @NonNull final byte[] bytes) { - return Single.create(new SingleOnSubscribe() { - public void subscribe(final SingleEmitter emitter) throws Exception { - final StorageTask taskSnapshotStorageTask = - storageRef.putBytes(bytes).addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { - emitter.onSuccess(taskSnapshot); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + final StorageTask taskSnapshotStorageTask = + storageRef.putBytes(bytes) + .addOnSuccessListener(emitter::onSuccess) + .addOnFailureListener(e -> { if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - taskSnapshotStorageTask.cancel(); - } - }); - } + emitter.onError(e); + }); + + emitter.setCancellable(taskSnapshotStorageTask::cancel); }); } @@ -265,34 +219,25 @@ public void cancel() throws Exception { * @param bytes The byte[] to upload. * @param metadata {@link StorageMetadata} containing additional information (MIME type, etc.) about the object being uploaded. * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single putBytes(@NonNull final StorageReference storageRef, @NonNull final byte[] bytes, @NonNull final StorageMetadata metadata) { - return Single.create(new SingleOnSubscribe() { - public void subscribe(final SingleEmitter emitter) throws Exception { - final StorageTask taskSnapshotStorageTask = - storageRef.putBytes(bytes, metadata).addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { - emitter.onSuccess(taskSnapshot); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + final StorageTask taskSnapshotStorageTask = + storageRef.putBytes(bytes, metadata) + .addOnSuccessListener(emitter::onSuccess) + .addOnFailureListener(e -> { if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - taskSnapshotStorageTask.cancel(); - } - }); - } + emitter.onError(e); + }); + + emitter.setCancellable(taskSnapshotStorageTask::cancel); }); } @@ -302,33 +247,24 @@ public void cancel() throws Exception { * @param storageRef represents a reference to a Google Cloud Storage object. * @param uri The source of the upload. This can be a file:// scheme or any content URI. A content resolver will be used to load the data. * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single putFile(@NonNull final StorageReference storageRef, @NonNull final Uri uri) { - return Single.create(new SingleOnSubscribe() { - public void subscribe(final SingleEmitter emitter) throws Exception { - final StorageTask taskSnapshotStorageTask = - storageRef.putFile(uri).addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { - emitter.onSuccess(taskSnapshot); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + final StorageTask taskSnapshotStorageTask = + storageRef.putFile(uri) + .addOnSuccessListener(emitter::onSuccess) + .addOnFailureListener(e -> { if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - taskSnapshotStorageTask.cancel(); - } - }); - } + emitter.onError(e); + }); + + emitter.setCancellable(taskSnapshotStorageTask::cancel); }); } @@ -339,35 +275,25 @@ public void cancel() throws Exception { * @param uri The source of the upload. This can be a file:// scheme or any content URI. A content resolver will be used to load the data. * @param metadata {@link StorageMetadata} containing additional information (MIME type, etc.) about the object being uploaded. * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single putFile(@NonNull final StorageReference storageRef, @NonNull final Uri uri, @NonNull final StorageMetadata metadata) { - return Single.create(new SingleOnSubscribe() { - public void subscribe(final SingleEmitter emitter) throws Exception { - final StorageTask taskSnapshotStorageTask = + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + final StorageTask taskSnapshotStorageTask = storageRef.putFile(uri, metadata) - .addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { - emitter.onSuccess(taskSnapshot); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { + .addOnSuccessListener(emitter::onSuccess) + .addOnFailureListener(e -> { if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - taskSnapshotStorageTask.cancel(); - } - }); - } + emitter.onError(e); + }); + + emitter.setCancellable(taskSnapshotStorageTask::cancel); }); } @@ -379,36 +305,26 @@ public void cancel() throws Exception { * @param metadata {@link StorageMetadata} containing additional information (MIME type, etc.) about the object being uploaded. * @param existingUploadUri If set, an attempt is made to resume an existing upload session as defined by getUploadSessionUri(). * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single putFile(@NonNull final StorageReference storageRef, @NonNull final Uri uri, @NonNull final StorageMetadata metadata, @NonNull final Uri existingUploadUri) { - return Single.create(new SingleOnSubscribe() { - public void subscribe(final SingleEmitter emitter) throws Exception { - final StorageTask taskSnapshotStorageTask = - storageRef.putFile(uri, metadata, existingUploadUri) - .addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { - emitter.onSuccess(taskSnapshot); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + final StorageTask taskSnapshotStorageTask = + storageRef.putFile(uri, metadata, existingUploadUri) + .addOnSuccessListener(emitter::onSuccess) + .addOnFailureListener(e -> { if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - taskSnapshotStorageTask.cancel(); - } - }); - } + emitter.onError(e); + }); + + emitter.setCancellable(taskSnapshotStorageTask::cancel); }); } @@ -417,35 +333,25 @@ public void cancel() throws Exception { * @param stream The InputStream to upload. * @param metadata {@link StorageMetadata} containing additional information (MIME type, etc.) about the object being uploaded. * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single putStream(@NonNull final StorageReference storageRef, @NonNull final InputStream stream, @NonNull final StorageMetadata metadata) { - return Single.create(new SingleOnSubscribe() { - public void subscribe(final SingleEmitter emitter) throws Exception { - final StorageTask taskSnapshotStorageTask = - storageRef.putStream(stream, metadata) - .addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { - emitter.onSuccess(taskSnapshot); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + final StorageTask taskSnapshotStorageTask = + storageRef.putStream(stream, metadata) + .addOnSuccessListener(emitter::onSuccess) + .addOnFailureListener(e -> { if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - taskSnapshotStorageTask.cancel(); - } - }); - } + emitter.onError(e); + }); + + emitter.setCancellable(taskSnapshotStorageTask::cancel); }); } @@ -455,33 +361,24 @@ public void cancel() throws Exception { * @param storageRef represents a reference to a Google Cloud Storage object. * @param stream The InputStream to upload. * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Single putStream(@NonNull final StorageReference storageRef, @NonNull final InputStream stream) { - return Single.create(new SingleOnSubscribe() { - public void subscribe(final SingleEmitter emitter) throws Exception { - final StorageTask taskSnapshotStorageTask = - storageRef.putStream(stream).addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { - emitter.onSuccess(taskSnapshot); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception e) { + return Single.create(emitter -> { + throwExceptionIfMainThread(); + + final StorageTask taskSnapshotStorageTask = + storageRef.putStream(stream) + .addOnSuccessListener(emitter::onSuccess) + .addOnFailureListener(e -> { if (!emitter.isDisposed()) - emitter.onError(e); - } - }); - - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - taskSnapshotStorageTask.cancel(); - } - }); - } + emitter.onError(e); + }); + + emitter.setCancellable(taskSnapshotStorageTask::cancel); }); } diff --git a/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseStorageTest.java b/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseStorageTest.java index fff5490..8ea8445 100644 --- a/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseStorageTest.java +++ b/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseStorageTest.java @@ -90,6 +90,8 @@ public class RxFirebaseStorageTest { public void setup() { MockitoAnnotations.initMocks(this); + Plugins.allowMainThreadQueries = true; + setupTask(mockBytesTask); setupTask(mockVoidTask); setupTask(mockUriTask); From 976bc9ff35a3b6019a596bf009c6b64a4651a29c Mon Sep 17 00:00:00 2001 From: Obolrom Date: Wed, 21 Dec 2022 23:06:50 +0200 Subject: [PATCH 5/9] Added checking for main thread in RxFirestoreOfflineHandler --- .../durdinapps/rxfirebase2/RxFirestore.java | 14 +++++ .../RxFirestoreOfflineHandler.java | 52 ++++++++----------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java b/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java index a173b8d..a377623 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java +++ b/app/src/main/java/durdinapps/rxfirebase2/RxFirestore.java @@ -262,6 +262,8 @@ public static Completable updateDocument(@NonNull final DocumentReference ref, * * @param ref The given Document reference. * @param updateFieldsMap A map of field / value pairs to update. Fields can contain dots to reference nested fields within the document. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Completable updateDocumentOffline(@NonNull final DocumentReference ref, @@ -301,6 +303,8 @@ public void subscribe(CompletableEmitter emitter) { * @param field The first field to update. Fields can contain dots to reference a nested field within the document. * @param value The first value * @param moreFieldsAndValues Additional field/value pairs. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Completable updateDocumentOffline(@NonNull final DocumentReference ref, @@ -343,6 +347,8 @@ public void subscribe(CompletableEmitter emitter) { * @param fieldPath The first field to update. Fields can contain dots to reference a nested field within the document. * @param value The first value * @param moreFieldsAndValues Additional field/value pairs. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Completable updateDocumentOffline(@NonNull final DocumentReference ref, @@ -400,6 +406,8 @@ public void subscribe(CompletableEmitter emitter) { * @param ref The given Document reference. * @param pojo The POJO that will be used to populate the document contents. * @param options An object to configure the set behavior. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Completable setDocumentOffline(@NonNull final DocumentReference ref, @@ -429,6 +437,8 @@ public static Completable setDocument(@NonNull final DocumentReference ref, * * @param ref The given Document reference. * @param setFieldsMap A map of the fields and values for the document. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Completable setDocumentOffline(@NonNull final DocumentReference ref, @@ -457,6 +467,8 @@ public static Completable setDocument(@NonNull final DocumentReference ref, * * @param ref The given Document reference. * @param pojo The POJO that will be used to populate the document contents. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Completable setDocumentOffline(@NonNull final DocumentReference ref, @@ -485,6 +497,8 @@ public static Completable deleteDocument(@NonNull final DocumentReference ref) { * Deletes the document referred to by this DocumentReference. * * @param ref The given Document reference. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Completable deleteDocumentOffline(@NonNull final DocumentReference ref) { diff --git a/app/src/main/java/durdinapps/rxfirebase2/RxFirestoreOfflineHandler.java b/app/src/main/java/durdinapps/rxfirebase2/RxFirestoreOfflineHandler.java index dbd5c67..78b1ddf 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/RxFirestoreOfflineHandler.java +++ b/app/src/main/java/durdinapps/rxfirebase2/RxFirestoreOfflineHandler.java @@ -1,15 +1,11 @@ package durdinapps.rxfirebase2; +import static durdinapps.rxfirebase2.Plugins.throwExceptionIfMainThread; + import com.google.firebase.firestore.DocumentReference; -import com.google.firebase.firestore.DocumentSnapshot; -import com.google.firebase.firestore.EventListener; -import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.ListenerRegistration; -import androidx.annotation.Nullable; + import io.reactivex.Completable; -import io.reactivex.CompletableEmitter; -import io.reactivex.CompletableOnSubscribe; -import io.reactivex.functions.Cancellable; public class RxFirestoreOfflineHandler { @@ -20,36 +16,30 @@ public class RxFirestoreOfflineHandler { * * @param ref Document reference to be listened. * @return A Completable which emits when the given reference receives an offline update. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ public static Completable listenOfflineListener(final DocumentReference ref) { - return Completable.create(new CompletableOnSubscribe() { - @Override - public void subscribe(final CompletableEmitter emitter) { - try { - final ListenerRegistration listener = ref.addSnapshotListener(new EventListener() { - @Override - public void onEvent(@Nullable DocumentSnapshot documentSnapshot, @Nullable FirebaseFirestoreException e) { - if (e != null) { - emitter.onError(e); + return Completable.create(emitter -> { + throwExceptionIfMainThread(); + + try { + final ListenerRegistration listener = + ref.addSnapshotListener((documentSnapshot, firebaseError) -> { + if (firebaseError != null) { + emitter.onError(firebaseError); + } else { + if (documentSnapshot != null) { + emitter.onComplete(); } else { - if (documentSnapshot != null) { - emitter.onComplete(); - } else { - emitter.onError(new NullPointerException("Empty Snapshot")); - } + emitter.onError(new NullPointerException("Empty Snapshot")); } } - }); + }); - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() { - listener.remove(); - } - }); - } catch (Exception e) { - emitter.onError(e); - } + emitter.setCancellable(listener::remove); + } catch (Exception e) { + emitter.onError(e); } }); } From 52d44ddf4c53297d4ddd7ce39b904d7b81fc0e8b Mon Sep 17 00:00:00 2001 From: Obolrom Date: Wed, 21 Dec 2022 23:17:18 +0200 Subject: [PATCH 6/9] Minor code improvements --- .../rxfirebase2/DataSnapshotMapper.java | 8 +------- .../rxfirebase2/DocumentSnapshotMapper.java | 16 ++-------------- .../rxfirebase2/RxCompletableHandler.java | 6 ++---- .../rxfirebase2/RxFirebaseChildEvent.java | 7 +++---- .../java/durdinapps/rxfirebase2/RxHandler.java | 2 +- .../exceptions/RxFirebaseDataException.java | 1 + 6 files changed, 10 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/durdinapps/rxfirebase2/DataSnapshotMapper.java b/app/src/main/java/durdinapps/rxfirebase2/DataSnapshotMapper.java index 6009501..180f879 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/DataSnapshotMapper.java +++ b/app/src/main/java/durdinapps/rxfirebase2/DataSnapshotMapper.java @@ -7,7 +7,6 @@ import java.util.LinkedHashMap; import java.util.List; -import androidx.annotation.NonNull; import durdinapps.rxfirebase2.exceptions.RxFirebaseDataCastException; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Function; @@ -158,10 +157,5 @@ public RxFirebaseChildEvent apply(final RxFirebaseChildEvent rx } } - static final Predicate DATA_SNAPSHOT_EXISTENCE_PREDICATE = new Predicate() { - @Override - public boolean test(@NonNull DataSnapshot dataSnapshot) throws Exception { - return dataSnapshot.exists(); - } - }; + static final Predicate DATA_SNAPSHOT_EXISTENCE_PREDICATE = DataSnapshot::exists; } \ No newline at end of file diff --git a/app/src/main/java/durdinapps/rxfirebase2/DocumentSnapshotMapper.java b/app/src/main/java/durdinapps/rxfirebase2/DocumentSnapshotMapper.java index 90d7c2a..5afef45 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/DocumentSnapshotMapper.java +++ b/app/src/main/java/durdinapps/rxfirebase2/DocumentSnapshotMapper.java @@ -1,6 +1,5 @@ package durdinapps.rxfirebase2; - import com.google.firebase.firestore.DocumentSnapshot; import com.google.firebase.firestore.QuerySnapshot; @@ -8,7 +7,6 @@ import java.util.LinkedHashMap; import java.util.List; -import androidx.annotation.NonNull; import io.reactivex.functions.Function; import io.reactivex.functions.Predicate; @@ -96,17 +94,7 @@ public LinkedHashMap apply(final QuerySnapshot querySnapshot) { } } - static final Predicate QUERY_EXISTENCE_PREDICATE = new Predicate() { - @Override - public boolean test(@NonNull QuerySnapshot querySnapshot) throws Exception { - return !querySnapshot.isEmpty(); - } - }; + static final Predicate QUERY_EXISTENCE_PREDICATE = querySnapshot -> !querySnapshot.isEmpty(); - static final Predicate DOCUMENT_EXISTENCE_PREDICATE = new Predicate() { - @Override - public boolean test(@NonNull DocumentSnapshot documentSnapshot) throws Exception { - return documentSnapshot.exists(); - } - }; + static final Predicate DOCUMENT_EXISTENCE_PREDICATE = DocumentSnapshot::exists; } diff --git a/app/src/main/java/durdinapps/rxfirebase2/RxCompletableHandler.java b/app/src/main/java/durdinapps/rxfirebase2/RxCompletableHandler.java index 3127275..1890b4e 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/RxCompletableHandler.java +++ b/app/src/main/java/durdinapps/rxfirebase2/RxCompletableHandler.java @@ -9,8 +9,7 @@ import io.reactivex.CompletableEmitter; - -public class RxCompletableHandler implements OnFailureListener, OnSuccessListener, OnCompleteListener { +public class RxCompletableHandler implements OnFailureListener, OnSuccessListener, OnCompleteListener { private final CompletableEmitter completableEmitter; @@ -19,7 +18,7 @@ private RxCompletableHandler(CompletableEmitter completableEmitter) { } public static void assignOnTask(CompletableEmitter completableEmitter, Task task) { - RxCompletableHandler handler = new RxCompletableHandler(completableEmitter); + RxCompletableHandler handler = new RxCompletableHandler<>(completableEmitter); task.addOnFailureListener(handler); task.addOnSuccessListener(handler); try { @@ -29,7 +28,6 @@ public static void assignOnTask(CompletableEmitter completableEmitter, Task< } } - @Override public void onFailure(@NonNull Exception e) { if (!completableEmitter.isDisposed()) diff --git a/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseChildEvent.java b/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseChildEvent.java index 4427be4..d26c784 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseChildEvent.java +++ b/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseChildEvent.java @@ -1,13 +1,12 @@ package durdinapps.rxfirebase2; - import androidx.annotation.NonNull; public class RxFirebaseChildEvent { - private EventType eventType; - private String key; - private T value; + private final EventType eventType; + private final String key; + private final T value; private String previousChildName; public RxFirebaseChildEvent(@NonNull String key, diff --git a/app/src/main/java/durdinapps/rxfirebase2/RxHandler.java b/app/src/main/java/durdinapps/rxfirebase2/RxHandler.java index 4881dd8..364f56a 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/RxHandler.java +++ b/app/src/main/java/durdinapps/rxfirebase2/RxHandler.java @@ -20,7 +20,7 @@ private RxHandler(MaybeEmitter emitter) { } public static void assignOnTask(MaybeEmitter emitter, Task task) { - RxHandler handler = new RxHandler(emitter); + RxHandler handler = new RxHandler<>(emitter); task.addOnSuccessListener(handler); task.addOnFailureListener(handler); try { diff --git a/app/src/main/java/durdinapps/rxfirebase2/exceptions/RxFirebaseDataException.java b/app/src/main/java/durdinapps/rxfirebase2/exceptions/RxFirebaseDataException.java index 2f4f36a..2f49a34 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/exceptions/RxFirebaseDataException.java +++ b/app/src/main/java/durdinapps/rxfirebase2/exceptions/RxFirebaseDataException.java @@ -16,6 +16,7 @@ public DatabaseError getError() { return error; } + @NonNull @Override public String toString() { return "RxFirebaseDataException{" + From af5086bd2553d4fb71d34abd63fcc9e35ba9953b Mon Sep 17 00:00:00 2001 From: Obolrom Date: Wed, 21 Dec 2022 23:32:27 +0200 Subject: [PATCH 7/9] Added check for main thread in RxFirebaseAuth --- .../rxfirebase2/RxFirebaseAuth.java | 31 +++++++------------ .../rxfirebase2/RxFirebaseAuthTest.java | 2 ++ 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseAuth.java b/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseAuth.java index 8cd023a..c4b9d7e 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseAuth.java +++ b/app/src/main/java/durdinapps/rxfirebase2/RxFirebaseAuth.java @@ -1,5 +1,7 @@ package durdinapps.rxfirebase2; +import static durdinapps.rxfirebase2.Plugins.throwExceptionIfMainThread; + import androidx.annotation.NonNull; import com.google.firebase.auth.ActionCodeResult; @@ -16,9 +18,6 @@ import io.reactivex.MaybeEmitter; import io.reactivex.MaybeOnSubscribe; import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; -import io.reactivex.functions.Cancellable; public class RxFirebaseAuth { @@ -205,27 +204,19 @@ public void subscribe(CompletableEmitter emitter) throws Exception { * * @param firebaseAuth firebaseAuth instance. * @return an {@link Observable} which emits every time that the {@link FirebaseAuth} state change. + * @throws IllegalStateException if operation is happening on the main thread + * @see Plugins */ @NonNull public static Observable observeAuthState(@NonNull final FirebaseAuth firebaseAuth) { + return Observable.create(emitter -> { + throwExceptionIfMainThread(); - return Observable.create(new ObservableOnSubscribe() { - @Override - public void subscribe(final ObservableEmitter emitter) throws Exception { - final FirebaseAuth.AuthStateListener authStateListener = new FirebaseAuth.AuthStateListener() { - @Override - public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { - emitter.onNext(firebaseAuth); - } - }; - firebaseAuth.addAuthStateListener(authStateListener); - emitter.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - firebaseAuth.removeAuthStateListener(authStateListener); - } - }); - } + final FirebaseAuth.AuthStateListener authStateListener = emitter::onNext; + + firebaseAuth.addAuthStateListener(authStateListener); + + emitter.setCancellable(() -> firebaseAuth.removeAuthStateListener(authStateListener)); }); } diff --git a/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseAuthTest.java b/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseAuthTest.java index 60b7825..4a2ee92 100644 --- a/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseAuthTest.java +++ b/app/src/test/java/durdinapps/rxfirebase2/RxFirebaseAuthTest.java @@ -77,6 +77,8 @@ public class RxFirebaseAuthTest { public void setup() { MockitoAnnotations.initMocks(this); + Plugins.allowMainThreadQueries = true; + setupTask(authResultTask); setupTask(providerQueryResultTask); setupTask(actionCodeResultTask); From 4171d664420368ff428372748015a740e0567f5a Mon Sep 17 00:00:00 2001 From: Obolrom Date: Wed, 21 Dec 2022 23:34:30 +0200 Subject: [PATCH 8/9] Fixed readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd86d5e..719bc2c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This repository started as a personal usage of [Nick Moskalenko](https://github. ```groovy dependencies { - compile 'com.github.FrangSierra:RxFirebase:1.5.6' + implementation "com.github.FrangSierra:RxFirebase:1.5.6" } ``` ``` From ba4798200fcb3facdda526ce54af84d0ecff684f Mon Sep 17 00:00:00 2001 From: Obolrom Date: Fri, 23 Dec 2022 14:19:43 +0200 Subject: [PATCH 9/9] Added documentation for Plugins --- README.md | 9 +++++++++ app/src/main/java/durdinapps/rxfirebase2/Plugins.java | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/README.md b/README.md index 719bc2c..8e634c9 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,15 @@ public static Completable updateEmail(@NonNull final FirebaseUser firebaseUser, `RxCompletableHandler` manages the CompletableEmitters in the same way that `RxHandler` manages the `Subscriber`. You can check all the differences between RxJava and RxJava 2.0 in the next [Link](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0) +### Plugins + +```java + public static boolean allowMainThreadQueries = false; +``` +The flag, that controls can be extensions be called on the main thread or not. +By default it doesn't permit the main thread queries + + ## License MIT License diff --git a/app/src/main/java/durdinapps/rxfirebase2/Plugins.java b/app/src/main/java/durdinapps/rxfirebase2/Plugins.java index da94426..f128c69 100644 --- a/app/src/main/java/durdinapps/rxfirebase2/Plugins.java +++ b/app/src/main/java/durdinapps/rxfirebase2/Plugins.java @@ -2,10 +2,21 @@ import android.os.Looper; +/** + * Utility class to make behavior of extensions more flexible and testable + */ public class Plugins { + /** + * The flag, that controls can be extensions be called on the main thread or not + * @apiNote some extensions not supported this feature, so see documentation + */ public static boolean allowMainThreadQueries = false; + /** + * Throws an error if you make network call to Firebase on the main thread + * @throws IllegalStateException in case of calling on the main thread + */ public static void throwExceptionIfMainThread() { if (!allowMainThreadQueries && Looper.myLooper() == Looper.getMainLooper()) { throw new IllegalStateException("Network on main thread is not permitted");