4;W@3zKz%kZQCgHlzfX5PuB6*@wOE;Q6C^q)XIcjzB79y#R
z@FI7YJ-it_Q|U)IA~CV}naId*Fs2yWKyi#CeH}dJKmds^?tK?_mJO8It9_v`I-irVa4X%#HHjxwNTHcf>bR{PW;o)Rl
zxCkOWk?WSDl*&IAYBVfIbZJH2RQB+)M-LQX`wh9QH)CV7qJo0jU<~U91fvBJ`xBffv0O6El38T77kmXpL^=qfy_6HQIUyxU(6e2t;`Th3KCp
zBwT$eGIAuw3}eTCh_QT3YgR_^m`gSy8;aG>qCxmV0s{0f!w=M}*Y(?kgmth+*_<2M
za%&Y`_VBd30@wMSxDKq&TXAt4nudjS$5@e>(FclTIhv1UFdy5hic0>HcQn@Kgip!M
zFo2Z#dUW)lUz3xMBHWiEH6d?g0Z5f8PsAUybW4h`#!`6xky3g5u?7u3=24d9vXp%6
zYI~xi&|5$xk?VTGoNR?c9U_7#9m%jEadDG=OG&xBN^A`g_)(q(i&W8N4>uCzBK#1>
z6Go!cOoIgXY
zzL8Z5q+%mSl7Y$BqO%z?jsuGW;Gh)oMPlNO*J5H8HVzAW*o@VQ;$szD+4+v6)+mtY
zE)Yq?z`xU=84&Jc5Ifba`^4w*@r!>~tFNuZ197q39=aY)+?y}BGr5~WNIso74$2Up
zCnjEdBQ|zPtGacc<`;bRlZ$Ew<$4jgONs1EnXu+_#Hfn>M!^C~5T7WO$H-MpzN=219vnF7
zVWP{@@E)E>J|=#v;{Z1b;n4+5c04(IEuuBaBGnBJZt;4fMxT6>n7Cy|a(Q@EK@i7Rw
zletcLtx-gow$DgODM0iF%al_}tOX_F
z*AInqOJL+6F5oamut}LB!7_`334lSV;d_;e4OJ>nzY`a?u5VP-_pQUi
zo~CPT%C9w@d{H{?fpZ+m;z+;=@&m{9Knmi6TbK8_fLJEd8>##kELkNesIe*}jDS*WgDiX^zlR3=p8OWaO+X6ZY5Uqd
zGHJ+bK}iyrWR}SYD3W{hp`qP6M?}2RJ1Xk){*4;_`bJF5qW5BBH+~c!zmv%9L9!Uf
zh9@STA&zqHtHi_$qew>5-y`U~Ve}d8bAUjy6UV$s$Ko7MMnw(7IdSgl)vGt>-<8R~
zTe36~Xp;D~Hsse{hu=$pA2{v>QZ*itNLHzi3{0pECD2bWHMUtR=SPTGxODY#F
z2Zx#iB#9rBz|RrO&l$zPBb>Vb1Jnsf0BSzkGqx#PG}=J
z7~~SkKa1e|){_CHHa}+w|BgWZoz+d;lOHrb4k$UOBwz(_g$Hrv2Xm`oPNdI+_%Q)8
wpvb}E2h1G;DpPP(H36Dj%n{_Zu)4w{PFTmcieLbKqRk9hDBZ201{xp;D&Q;E?Stxu4
z!)Ljll0t*eX!^N1i6t^fN41vT6d?FNKt;jz2(`jv1Z%9bbNw@H#u$UojL&ZFJ4rKxbQmq8simiyyAhQcZ6G)3SV)0%(t_XrDi~1
zZj>Ev2zYA=!M{TlGSKgg?~Z$5Ix9%>$ctqTtlU+e
z^GtwWCIR1WB9xiT2=j1{rP;s@4l(d|u-t2EAx#OO)h>mZ0
z?m!%wlMW|Ho{d^<>!R%J&63r3``MsiT~^jjk|c7XD_4uT)4_bqz;2+8Vu-fV#9RG`<7{L9bp28jfAfPYnHW+b&L(@bi2B#?Q=
zDB~o^*XgFuRI8&U64(I#{EQ5hsFVRWQwnc-pd{i(i^`$=MQLei5(%tGZslvW&FGEW
zC+U6fh!U}9NH*$p`xjvXr%n{|SLzeJKeUgH;(FcO?tmYLa63X_V!
z&uh#n_`A@XbV&lg*OTDV(yXipc#y+qNg%TP^$PU*`&mOMDbnKu5h5ycix*{OJ>C$J4KwA`WOY^+nU$X2
z*DIW24*qqSnKx3THV&8Nx&VMzg4PG`o;1k
z-S0x+G;2n7_3llMj2cBw{Pr6;{>v}qz@R~7SC1YZM+SsPH&PNT%*@<8QYMS^^TZ}x
ztj4FFAboVYZzS+r59IBgI+0zydXYU(Jw^8Q?MwFc>qquH`6Su-;DcmK+qMRvj~-19
zj~GF=-E|k){m3Ka{OZ-D^zvm=URp}Z%gcpPR#HMrFJ2^P7cL|_yLa~}B7g*IsXs7V
zqZ#YRu;fwx0NZ?_UO`sYLz3>V;bG_IlI{%g}lu;y3){kK{GJ7b(!xst5*Fe$W&N};^;
z^us@^)!4+N-XM4P2Sn3U{Q|vyoFsiLk8K?~kh2RGkg{vnTn>JWD<@Bq1$W#*R#8xS
zQhoqZXqHAZQIsF3=k`&be7(MhXfrt}-j9)!L$=&}Gui#{!{nbg-xRbtbb4)oALA;O
zREy}fr)Ol4)gD3uzIVeiDut$`ras};Q6VmU^0KmGDd=}f(nn;;TD2mFM~=L%-|JwM
zP$zlQhaZxu)ES=YNhDxVp?Mh@dqyagiEb-}So;e|pOI1*2lnu8ype3Z?KUAaSyo(J
zXW-}K+Jy_`@S;UzQLkQPI<=~+Jck5jNRZ9YXubu;c&in6EOBlrTGm+Z%i+v@FTO}l
z&zdDfZiEnJ9fMyy&hOt(7TkZo$H*Z>g$fM0fy?8S%3Fc8@7yIS#N+*7=4PF4x+Hz{
z}(;vf2pX*?ZIC{<=LSH3&@OCttx?EJQKLH9705By}=jQ@XWQ_?KLu;`X`eqaH<2pm;~-R
z6BIFDV7gkpu?_PDYIPQ1nYj9Eq{t81)w3tLcKoT
zdGzy*HB^h%ueI#3DDK~2lU_f|*CRiNh7NV_aOdH}Wa(2+*$jSncZ`9#HFOr3n3lGX
zVlklB@&a7@FU!nqW89e9hqos^@$0W{3H-A=c92cOhm-mD-b*HDW!X&tcXf=B9mev>
zdFkmT-^k@1Yb_$gwAKyzdi__@8ed9)-+y;Y0u+uI;SlIOK@I_XD=4`qsMY@iCSC3a
zaP2=$sZ68{JW#|JGW#$A4h|gXeo3y~yUC2!t({5$40kJ=0QS&NXl7d4;r9~~)WGJm
zu8asV0^C5g*Rw|FmpLT9I(9$)xZ7lYQSy9r`Es(bTQ{eJ-yP);4~Z_KpXn#1@(p0q
zWzH}QePZ|*=yY?WvIjN|-*gi>^Y`C%9^*f`X%kuZ?z%6Z!zm-ZLI(H`5PMmP+>aB|h50WdVPLYyJmjug>fqH%{Z9KnkA33pM
z135s6SMc%2WPaDKWNJ*-x5{D%Egc)alOfASQoW%Va?Xdi~-e!-b^|Z>C8x3fOat7TLWP0M>oa
z@4u6oZQ9f+ux%OcP&NT)m>Om>Nv&R^ii`{uLZZ%;L0taKQLAs1_lJR#vWr^_Ig{A#dTt9_Npp9)`(
z{#dgR{>=z8=gV53eqLyG-d
z?-y8;B}QwM?caaz(cpIn#~4X5M1y`;s~7TUkW*w3mp|jxYQ<*O=}jU)%>&&aE^psn
zE$}ZMIz+a9{k2PyR=(yK1C!_^H$5%w$Xha*)@d?`%b=8Qnz5FG>H`v_|0WyUDdv
zrz(N|=!z9&@5G5@+c)0`F?&RMyao7mEr)nN(fR4=v~6+x$?4M#VNp7cuUjW{Vem%4yGz*wu!$~mW0lHpfLUPIp$bu6_(n%yzD}1X
zg+Fb4AXElz|MXK*G-?!?bLX8lC8|)t3Bm{oE=dm6Jm_^GgU~u`{HRo_B>ivSSVtesEE_Yllj%duA3T^GS+Ib}<
zke~;49r6R%oBii#G#%N-c0>w)UXS_jdHQK`sj!fooHK_M-g1k@*KVK>WwwAv)WvvvE8O|RxNQw?w9}Y!XMER$3c~4-sy90heJw||l6-!^4Qs}n{iJrlq0Ww^G)F)&!Coky@iL2y}r(^TSu;(I6;a}og&3&&IlbFu3frR
z<6Z$+;^@f^ajle1Fan@s#CWxOgDf;O($4_`!20{6C%fbTn^_4)bvXwofGjRxn{9>!
zD2=$ZWs5_VqO1D%m+}NwI>J+^Bm881d>SzPEFJbbzyT9Anw#iJFG>lp%HuR*{0x}?
z%$!-}A0*38zgzF8G%LD1p
zhI#4fJ*4H2Ci=g9dm;KG==0)Yvij9m%|7IrY$m{Fco){JamvcCwQs#8l}%U?2|`Br
zbGiIEVE7f8EXY%i=j~-Pv!9t-ZiTGBb_z@UpShkG-%?yr1@Vcz5-+kEu
z#&$fGzFXkyDc=TekRS+7DW*!%^0J
zZ1rlV&jQERtf}+XDDGG`A;@(@GVE~gn3!x}*%n8b6CiM;LZL0t>5lsX0rtQ8s!f|&
zZ~x&3n;s~7{9JOf6haBbs{j1Qt0F?qD4XE^>a?`9Q_m$Nv<8;@7{dD25#S{j0{tQ>
zsTn=dSzjQ)@t=RTX~&f%efm_j^etKlzn_iGC`>^~|6e$2l+*VQ#?J5F77=pPF$PwV
z4W6OVoPRMcu06197XiML%UjYDT`Kee0$kj*$)+%*Y4a**{r%IX34cGceY;TCZ~8Fd
zv>uv2-?=+JQbH|#;t8)_UTLdi%n2|{qq*{WY-|Uc3BX?5=zFE|ChAcadpiNP+s^xEu
z=IR?wo8AR1cef!MT#u!W8~u=y(vE_^#M=q5_vxpt*Z*Z@LNi*^C5&ikDDDgT&-CdQ
z*ZOYz_S-6pUL0zsd-g0@M!$o%FSNH-4sim^PERj=D=zLHVENhj_(s+epgt$S*9HPy
z^J)TQXA8+LYn$4lBGa@kAI)k@Oe#Im^XKo|Z#VlOmp${0&;i=15usJ91R_e^CMpr2
z-M})FK-Lj}y*PMOa&qg(GH*
z*}dE9?#+vO^r&q8@4c5?5G{=qkG+5WW!5Sqc7JB;R;T9$ur8sdO|X4I4sj=VCUt^e
zYSQFRV7ap`GXN*R$BBu}H|um~yqNR1UTx=d4NM7d|1V
zFB~{f?fWc6gEkHu=92A_uxpGbwpe$j9O8+vscC5^AB%};3oN&`MFMdGyc{2&wo#|s
z@680Tc7Lxc$B$R)<%wv{=1)GUarQX0U;&wGMwURY*R&Aco>6@IwAbeaYAT!Hr4SR<
z>VNNxj&5c%0Xq5lArTgQcX)VmzE-={TM2;p|K)AltO6DiSmIba*5;nuwX4SWq16fc
z{Zw&;J9QHF0dqyE^uf7vy|z5Unz9KHfEsQxJ~efBtHzDff#vpoet}jKfG>ujBWAr$
zx7J$;u&a0PDvMk!jST|_R=VNq-hSI^K%(bQm8@`^HbOC*OLmPcE*27QzC-}AV=Rl%WqF1v8G2Tc3p;Lnu4ltZc@xm84wkPxRtp+4YBTRD#
zkWhR6{L^Jyr|$XVj~dr1cx#?uEyXIDKwVnIv{u*@)R6#Eoid;f~^LzIiTC)2f0VY!d{Hj)ykL2>7fnkPan+R}2C&PA=k<&Dq*S)bV3LIc-
z%O3zz(;JO_y=(y_GQa2vS|yfEr4iaHqQt_PtHv0#tmLno3?yNI-+!$PN(^
zVQilZ>j}_4CI-_+#BVCq!;Ao4)fj7E-@Y~_m`oF3Sm07>OKm?To2AkSL~M{#*f?~k
zFte1}urQ~(>n(roIiDi{9Tk(&9a^QTcq%^rMPRs%VFs{`0J_*%M1jJG$Yi$^u)Tr3
zsWsv;8!b2r-b|M{p0L3Y=AqP@l2mZ8L4<@h;Lp}25=R8&`B
znE3!U5uko+!&Z}FU819r4BO{Tks!1xJhxQe
zftNJL!VZpC_U*F?OxVuU^ky%(|9&C!YW3|1XYW`wglciK#ZV=Gts
zoc^Dbo?bRyqag#6lh;%1CIQ288X@3Cp>9-Y32j|SG+Mk~S
z6g#@uavlJA#!WZb{BW_H5~bMZtgKw+ksxngFKmebW7E>e+et~2fZYfKh6C&&fWN}v
zJvW(>mi7jZ1bKrad}P!po8`pGO`FU{ajkN*hRWk%fJRsx7~0nwkQq;xT^4D$j<_-tOD%>)p@
zbk99z*Z9^{5o)$v;p$bvn(v)7iL7}3d7-0(Eo8u|vYI;1p(p7QA1aK41B?jJ=SpQs
zub7y}fnAkB#y9Y@wK=Yk4K{>ABYQPzqRZFm_IiUOjQD@C71q<7s~yu=2_RXp;C45@PC(Vp-c+bh6XEf3F8?Y?W=IHC0-@&l7C
zRCYo}SyRS=h?1}9|6i&8ACQ!U8lYrgmqmbXu!jK9ObyvaV@+qJr+?%Pe!w9LVrPXF
zp~Cp;BZwmfW
zPxb@Q>;DvMUS;ndUE&B3Hm$?rcfoQ~QhoU0hvXLOq)Maq$IUn2T#lbDTC^xjNlEDk
z%rXMl9095n2~rtqf@1Cq56@n&)q1oaP(&@o25p96k2tyRp-(CU;jxaP7^&+C^zY`Z{WK8ko(&%k>++p`J
zMAZGPdiiA|0hT}ie5G@(UAvatO{Ine#ySDNK!BTWx`}AD+B~NJqu8332KLAwqa%#Y
zy6TXSX!c_aIC(bb$EbNiAGeF7Qe4qBN3qP2vEV~4^gYti2uho
zZrm6PVFK-OfK46Y0C{*gB0+KJw41Nfo$y2%RM@;ZIWcLH-2})VJlMF-b`2%cHKzR~
z0@VGW(x)7=KyGgCnb_FaF2HJ-!FLU?*8#5N2gDev>A)!Y!pzM1o)`|@*RLNbv2(LJ
z^!MQl;!(|MZV`(_fVz`E^k-R{Hf%T0cbBBXP8nFe^aa9Mav0K
z3WsjJ{dTKYf0;{wp_wg@{J{Dn!DI^oZoBO^^3qE$k=I^(t;%@u#TUsl&pbnV_UuV+
zr=Qc7hkw^ycbPq>wd2k^?=+sPef#$0i6@>g{_Ux!o)Ug%?PuxTySMSZFTC&qx%Jjt
zYg7JIF!}QtFv{dlkR$rv)DPhKfcU4W3|gnvdd#jdh17B%|K%6EonNfv%Int6SgQ|T
zz-$AoY6#G!OBZt3LNCxuR7#zsGtAz-dr4ki9vL=l80plhlT{D@$tRz5DCyRtM-Rvc
zBLT*bA8+hhy<^7?fne4GaUu3kX6Ic+{Ud(f+LAvxIXS1P{J9qx4G9T}V)6eQ93_A;
z9}sD%slbkVDf2X%8Jj&M8nlmE*fIxJSfN%22`}R!b_>fW?Il2!8Hb*K_wLI+Y9^I#d{=MvW3IZ+?D0IYWIEZh_CAKTpPv9ZT-G;|{9`aFxok
zAAkIjeD&2=Ru2#iK!tpO#qQ9d1Nm3f?c(dNzb4I_H?NTd@4WMlkOk+T8^pqST}=X>
z`~r!UO67aNW^izDoTK4Si{Vh2pPwLunm1~cvmz^Nhr2h;J&C?M4lFds{#&Th6dGaC
zhitkBfIS32#`xWL-!%e+kG51^^`nmZYSC_zcv
zr%yMYbLGmF1kPo3B)~N%OqgIKF75+9ulU>`fvc=P?wek(-ya$p+7j4g@&7<)$)8Fx
zh>noNMvZWhl)qG}(QFoQKLTWD3o-vPGtR0l-PMrn;)$=_)Jj*|1lK_V@BzS0oH$W<
z9bSLo!UfX5e}A)9)R_dpfF*tN%{R#vM)V^`jtI^z{st1XZr!@dXSnB{d&u_f+l{l^
z=+UEv*P5QYWy_XMoe9{v%5mPbw6x!V$wmfjhB#CHRFOfkjT=K_B|aAubJv=ztaI)t
zgCKnzDR~gOxozEAX$9E&_ui|vTZ6L*fB_4-R^)6iS+d0CGXNj>th?E&vyze$^8Wkp
z8~f#
zOPjjM(2%rrvxD2)J2UEYYRVLc%aRK|{NfJG*M!kroYF`E=a
zf+5D>Xvha7Bs{uCt1WYWKHL8R0P_4!!pXuAGa*bJkV2!Qvk6shGxvDvgji3!3QmFZs%+QU;y;|{Oi`O
ztD?nSMF8X>POz<95f(*a%k(w)91lPIurX=`Uj?(4wb!tBCg7Q0+yiR;*HH{=7#5?P
zmHeu)HVBS}EGP(VrximJ6Q8HEK%Iv~i>O69O+o9dm08%!*L3e)bXY)lh}t3mvZ}`R
zjHgdm`WpZM{p|_@z`D{^T`E>~aDmlaSYN!xR1OsPjG^;k&@hos?=ulEtO_1Lv@WBU0=N35%o-+^+
z_y_#@fBf->+31fmNx-c?2uiL0CW^fbhQSzDIlmU2VWHzzP!KMz9FmyWcV$*qMO~Fi
z??8j9{rjE1)5=b2`Aj=CV7nu8ElFHKfcx&duhKP4)l@d<_)KIfDSmGgxzd
zv4i{Rr=M2(Ti?EYjb#%D4<4-Y*_=HSm}vji%=#xW>mTe&=htFp7-6SKgZ@&znvn3=
zvW$#tn`_Y*D9p_jc6oHgW^+6lwy0-Mvn>%#_xExI0bqs2*TA_f5dg`nfddB$5uBO^
zDo?GsK0wbiyvvp?GfFV{11Qtrf15UKn&6W->U&g20#V%mS|as3fw>@q0tt6T=GW?w
zC}shAbvb;238|@*ur&?erO(v_*!j>yq}UZ3&51_Q7hjm&==vWMhuakd=+mc9rLqXq
zzl#arOea|EgPb~b%CP!eIWMZ4an(QpoVnfC7j*4fDJ1F&0=)O$d&cu0
zJ$kg(2_VizV20tJ8#4$menSgeUn}$S%
zy~JVz0~PdV+E)t2sO6cNh8<;Gf&qy
zAfhr}RDZN|>C##!0LGXxV~p3BJbAM4yH7s(#Q3|#ix=A!{jpXK@dYoqhSJdQC#_+_
zhA4ZEX4ng;ZR>B&7YK_oY)7dK_V?FMRjW6yqf*FKB@&Q6*VxqT>H$B%(#|gE6^iAO
zt{?zbYZVj}7^5sGo30B2V9^ZM`Y4Wrbp{#v=Nd9(h|{lW5*;dqf03D)xz*p_zZt9f
zm$ArCt!saCvjE>yIEv2#)FJMZmyuB-NFgUX#9Q09C+BT!N$$wNZji_TTk7Axm0Cyu
z!Px|WuI5n|$RA#nS=$6ab?R2phJcV|hYlSwl#A2v1cJB%{M6aSD2L-srB4ZUe9`PG
zEzp3mD3i!foe-c(UO-rlZ(wNpcPS~qEQ1tsrbA3+$C1&a9V&CSXFYIrU7IlrV8?i8
z5&-AfxpSxSbD%~a*03%JfQSz=!F(0~8F;xgB12e6<01l7fB@9;6O~H!7cj+&-r{OI
z?r&{G2${IZ2t#Z(HQL`lcd}Z&WsN}!IVCrMo!v`Wg=B3Ebbpv_W={aqS)gF}aHkQV
zbLY-NgoOhRwKkT*mbF0u3@qF~Z|1(E2db;AKmA#zqnM%K-$kWQ3t);hyozL4tF^el
zwX;9~tH)>()370!s_7dS*Jo~edhtdlM1>IbIU`EFyLw<}$Q6D28hZg%yL$`$YzxMX
zvx@+5?qEe9c;EptoDvBo#T@)7gT?|Cb9FeT1i<2YEMkXbv36KGueoT@Zj+s9P$to}
zYgd;^A1Zr@Mx(jfq)C$(fFqAm>qa_>+Kg1UCK$5?vauK_Izy;&
z|Ni~L-?(!L-9LEn;A%lCCIE`k(2W7I#M)s^O%NZ50Er2(dGltW9Ku!7CxhPCHe(g1vjzv^z@lFud
zj*-+qxx+#h)0VCfu;epm&Lj^#^iYjpv9}b04`hXHkwJW!CWwmgsI$YLT>50ud#_X~
zQR=K`7?Qh_<7TnqSXc+@4;)-gznG&P<wuwfdevDXq3o}a1FT-iXKVn_V}kYJV-+T2|T
zfGSRCeAM=s4v>Sl_OKN+h>x2)r?tbD2oA!LSSW+{!BIz|$`!glEiJ7$AtB*4CVkRG
zOC7oNsqg2;M-?fAq(w+T0BSd~K9b8nrcN>0=!E=0J{_~&hy>LUfDdOBjCtGO;K$E&
zijAbCq%VP$00U-Zo*;dylR`pN2*q*%h5{z-Xt{j+eELNTs6*`N+5vZxL)H)gseOqA
zRR)~i3dH;6a`_ZsgcZA`h@w7jFMX;jmJf>bwn!l)Eh2=brOY3Y2L-hnt5W4J=8+)>
z6J#JkUC#uc)6a0H4eMJm6S$&?%)N1+_KN{ShV!FOMRCxr$N2ABZw&yUey>
zVIBUbQtevmBoaUl)um7H6Ls!8w{2_F0&gFeLuFce&r~YazKDp3PQV1!{pljzU#FFx
zuJQ+jC6ti?0a(tK)j2BazHutmA(kU3b(948bu5PlP>bHd#x4iGMgkV@EJc}vTCF}3
z85!A)IlbZ~jvlG|t?>tVi}mE#fPgGGJUt>KyN^pvJ>o19)TLwmC;blJ)9ZhflS77E
zIXI>QTn<@LmrmPkL5;0JPZs(1O#YVolg^nkw8xfdD<$4czz&^
zB{t-o1P{f=_WDh!JhH?~t_zQ92Y&3^zn~V^~;+ZxxFCIfjgJg`*i`cPyLm74Xxa<*@!=
zD3#0)v2{T9h0(U8!@M6g$IJ-Zo)nu?-Ui@fm>?k}#Jj=#@epw>T@AKfdL;~&@%jpfv
z2w)_5HZHCob&e0fFW5v0;K=r@?oc-2&3Yfs?%PR8hdM_`_s6rtGYl~F@sx`kUJ3lw
zIL16gn%vOOFAWY8y^;Fd(9n)wq@*ktOCoz`11V47e5*f!N}f-t^~Sc!A}LQ`RYn)UFaM2NeZ=-(lgSD?MnpW1XTbHo-}Md-Jkryc
zJwplnc9K9GAqr<&QjkOltMS=@j^^*kWMBTMR34j2AJ963EGn~QMv&xCHPIdF-Qx40d!{+i#{YXS(FiO
zAc3Ae#M~ay(N7OeN}55PW8nk)I&s5=2Dsrm~5N;>;}rK7Ri{dLK}(JV-7-
z{X%^F%npWley&L8=de-+6}z`h*}V>xtbW~+z%*RSBSkT6YDPFNmH7vih;U1I`2Fw7
zWZ!+6lCt9uwYnS*Gn*BR5{Z*xJ^j6>`UF)#PbChu_5{`*GUro;qU^2Y*tM8_fq5u|#3laGTGCm{tF&`m}J9dl=Eg~ZB
z{ZG@TLx;=d3pp9!IIp4-2r?-jj&WwDU`2W5
zWtnVY&)C=xGebhUh^&1!?nSVn6?wYI;>$$94`KH-K#K5qjw~`I0-q6*nVwAxHZ0G}
z5jo5NMQ*F`@GgDh;sy;#N}BzZLa}>%YU<_bln^M#rXVATluQ;yPVf`!UMHrfS0Tp4
zs$jJV+?7UpdIhdAk!yGpz(d-g79R<#k5f`Eznhe_yI(@W?1!2(8KMmd>B^j10r5b5p=3Z<2+pKO`6QDuv4&y#`pMTv1v|GuK!ANLCFM8N
z!hcRb%W#F_!T<_*YUT4Di;Ej`XGFvcae;xi^6$YxPrcS0|LZbG&F(=GyC*Rc@V%c1
zjal3}iY0KkJQ8LF!B?OFfh-7b)F{WlG9j8#P_@51DymQKxVRzDG;KQJRhew*JJgXJ
zDwm)7SfRM`IU@#~OppUn77zr0{e#UO=4OM54A>wN^g86ruk^hDIkY>@iD(Ta-xXYI
z0KF!zjn4oJPaXRwqUmQtS%_HPz{(49&j9)ik+tVCCyw2VFm`Vw;Cnd{xUYbimt
zz&gJ#mmi?u{dZ_e%CTVz#mV6*DW^W71OFaIe}}&frmuOAzV_YZB^7#!SD6B5!{7Z&zlc34>V+{TT2
z+!z+t1OLYF@He~$uVuw%%|*IB*A(o+XTVP?Ymfm4;8|gshJ9wG$kI1u-!0ODwZB0E
zzVAhi#Ir&}Gh~Qi5+Q-^Kb(|b4>_h(`dxbfpg)U
zIJZ#hVF0_9>3DA8#eh#@pE-_w2UE~DG_m$lR7aW>jI)B648cqygfpGY2@%hvL^6w&
zE1ASlGcalxFtM;dixEVR5zGdxws`R0^Vcze!i>2k7A!W
zlzj(N&`Z`{I;K9-WYbSD0c2}cC8S0?M5OCZw~Mh=%vFcWH2R!IZ^~NFb0WcjK(68go|i002ovPDHLkV1jac+?N0V
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7835db902..3773093b2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,7 +8,8 @@
-
+
+
@@ -36,13 +37,14 @@
+ tools:ignore="DataExtractionRules,UnusedAttribute">
(ARGUMENT_KEY)
val binding = DialogErrorBinding.inflate(layoutInflater)
binding.bindErrorDetails(error)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt
index 2d83bbbf9..e1c234575 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt
@@ -1,6 +1,9 @@
package io.github.wulkanowy.ui.base
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
import android.content.pm.PackageManager.GET_ACTIVITIES
+import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
@@ -41,9 +44,8 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
)
}
- private fun isThemeApplicable(activity: AppCompatActivity) =
- activity.packageManager
- .getPackageInfo(activity.packageName, GET_ACTIVITIES)
+ private fun isThemeApplicable(activity: AppCompatActivity): Boolean =
+ getPackageInfo(activity)
.activities
.singleOrNull { it.name == activity::class.java.canonicalName }
?.theme
@@ -52,4 +54,14 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|| it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
|| it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
}
+
+ @Suppress("DEPRECATION")
+ private fun getPackageInfo(activity: AppCompatActivity): PackageInfo {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ activity.packageManager.getPackageInfo(
+ activity.packageName,
+ PackageManager.PackageInfoFlags.of(GET_ACTIVITIES.toLong())
+ )
+ } else activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES)
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt
index c6fe8a69b..41b97b075 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt
@@ -6,6 +6,7 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AlertDialog
+import androidx.core.os.bundleOf
import androidx.core.view.get
import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
@@ -21,6 +22,7 @@ import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
import io.github.wulkanowy.utils.createNameInitialsDrawable
import io.github.wulkanowy.utils.nickOrName
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -37,10 +39,9 @@ class AccountDetailsFragment :
private const val ARGUMENT_KEY = "Data"
- fun newInstance(student: Student) =
- AccountDetailsFragment().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, student) }
- }
+ fun newInstance(student: Student) = AccountDetailsFragment().apply {
+ arguments = bundleOf(ARGUMENT_KEY to student)
+ }
}
@Suppress("DEPRECATION")
@@ -52,7 +53,7 @@ class AccountDetailsFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentAccountDetailsBinding.bind(view)
- presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
+ presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY))
}
override fun initView() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt
index 21a7a492d..6e2bc8c44 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt
@@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.recyclerview.widget.GridLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.DialogAccountEditBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -24,12 +26,9 @@ class AccountEditDialog : BaseDialogFragment(), Accoun
private const val ARGUMENT_KEY = "student_with_semesters"
- fun newInstance(student: Student) =
- AccountEditDialog().apply {
- arguments = Bundle().apply {
- putSerializable(ARGUMENT_KEY, student)
- }
- }
+ fun newInstance(student: Student) = AccountEditDialog().apply {
+ arguments = bundleOf(ARGUMENT_KEY to student)
+ }
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -45,7 +44,7 @@ class AccountEditDialog : BaseDialogFragment(), Accoun
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
+ presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY))
}
override fun initView() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt
index 4279102e1..d23978f5f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt
@@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
@@ -13,6 +14,7 @@ import io.github.wulkanowy.ui.modules.account.AccountAdapter
import io.github.wulkanowy.ui.modules.account.AccountFragment
import io.github.wulkanowy.ui.modules.account.AccountItem
import io.github.wulkanowy.ui.modules.main.MainActivity
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -30,9 +32,7 @@ class AccountQuickDialog : BaseDialogFragment(), Acco
fun newInstance(studentsWithSemesters: List) =
AccountQuickDialog().apply {
- arguments = Bundle().apply {
- putSerializable(STUDENTS_ARGUMENT_KEY, studentsWithSemesters.toTypedArray())
- }
+ arguments = bundleOf(STUDENTS_ARGUMENT_KEY to studentsWithSemesters.toTypedArray())
}
}
@@ -49,8 +49,8 @@ class AccountQuickDialog : BaseDialogFragment(), Acco
@Suppress("UNCHECKED_CAST")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- val studentsWithSemesters =
- (requireArguments()[STUDENTS_ARGUMENT_KEY] as Array).toList()
+ val studentsWithSemesters = requireArguments()
+ .serializable>(STUDENTS_ARGUMENT_KEY).toList()
presenter.onAttachView(this, studentsWithSemesters)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
index 9b5c63e4c..eab24f91d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
@@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.databinding.DialogAttendanceBinding
import io.github.wulkanowy.utils.descriptionRes
import io.github.wulkanowy.utils.lifecycleAwareVariable
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
class AttendanceDialog : DialogFragment() {
@@ -22,16 +24,14 @@ class AttendanceDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Attendance) = AttendanceDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ arguments = bundleOf(ARGUMENT_KEY to exam)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- attendance = getSerializable(ARGUMENT_KEY) as Attendance
- }
+ attendance = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt
index 477b762b9..7834b6e8b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt
@@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.databinding.DialogConferenceBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
class ConferenceDialog : DialogFragment() {
@@ -22,16 +24,14 @@ class ConferenceDialog : DialogFragment() {
private const val ARGUMENT_KEY = "item"
fun newInstance(conference: Conference) = ConferenceDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, conference) }
+ arguments = bundleOf(ARGUMENT_KEY to conference)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.let {
- conference = it.getSerializable(ARGUMENT_KEY) as Conference
- }
+ conference = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
@@ -57,4 +57,4 @@ class ConferenceDialog : DialogFragment() {
conferenceDialogAgendaTitle.isVisible = conference.agenda.isNotBlank()
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt
index 41adc008a..876b563f9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt
@@ -4,12 +4,14 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.databinding.DialogExamBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.openCalendarEventAdd
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalTime
@@ -24,16 +26,14 @@ class ExamDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Exam) = ExamDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ arguments = bundleOf(ARGUMENT_KEY to exam)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- exam = getSerializable(ARGUMENT_KEY) as Exam
- }
+ exam = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt
index 34594111f..a1ef2ec5a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt
@@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
@@ -27,22 +28,19 @@ class GradeDetailsDialog : DialogFragment() {
private const val COLOR_THEME_KEY = "Theme"
- fun newInstance(grade: Grade, colorTheme: GradeColorTheme) =
- GradeDetailsDialog().apply {
- arguments = Bundle().apply {
- putSerializable(ARGUMENT_KEY, grade)
- putSerializable(COLOR_THEME_KEY, colorTheme)
- }
- }
+ fun newInstance(grade: Grade, colorTheme: GradeColorTheme) = GradeDetailsDialog().apply {
+ arguments = bundleOf(
+ ARGUMENT_KEY to grade,
+ COLOR_THEME_KEY to colorTheme
+ )
+ }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- grade = getSerializable(ARGUMENT_KEY) as Grade
- gradeColorTheme = getSerializable(COLOR_THEME_KEY) as GradeColorTheme
- }
+ grade = requireArguments().serializable(ARGUMENT_KEY)
+ gradeColorTheme = requireArguments().serializable(COLOR_THEME_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt
index 2af59c011..edc384c5e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt
@@ -15,6 +15,7 @@ import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeView
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.setOnItemSelectedListener
import javax.inject.Inject
@@ -48,8 +49,8 @@ class GradeStatisticsFragment :
messageContainer = binding.gradeStatisticsRecycler
presenter.onAttachView(
view = this,
- type = savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? GradeStatisticsItem.DataType,
- subjectName = savedInstanceState?.getSerializable(SAVED_SUBJECT_NAME) as? String,
+ type = savedInstanceState?.serializable(SAVED_CHART_TYPE),
+ subjectName = savedInstanceState?.serializable(SAVED_SUBJECT_NAME),
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt
index f9d463510..5e2cc65dc 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt
@@ -7,6 +7,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
@@ -14,6 +15,7 @@ import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.databinding.DialogHomeworkBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.openInternetBrowser
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -35,16 +37,14 @@ class HomeworkDetailsDialog : BaseDialogFragment(), Homew
private const val ARGUMENT_KEY = "Item"
fun newInstance(homework: Homework) = HomeworkDetailsDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, homework) }
+ arguments = bundleOf(ARGUMENT_KEY to homework)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- homework = getSerializable(ARGUMENT_KEY) as Homework
- }
+ homework = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt
index aac60b56d..8f237e537 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt
@@ -2,8 +2,11 @@ package io.github.wulkanowy.ui.modules.login
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Bundle
import android.view.MenuItem
+import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import dagger.hilt.android.AndroidEntryPoint
@@ -16,6 +19,9 @@ import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment
import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
+import io.github.wulkanowy.ui.modules.main.MainActivity
+import io.github.wulkanowy.ui.modules.notifications.NotificationsFragment
+import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.UpdateHelper
import javax.inject.Inject
@@ -28,6 +34,9 @@ class LoginActivity : BaseActivity(), Logi
@Inject
lateinit var updateHelper: UpdateHelper
+ @Inject
+ lateinit var appInfo: AppInfo
+
companion object {
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
}
@@ -55,7 +64,7 @@ class LoginActivity : BaseActivity(), Logi
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == android.R.id.home) onBackPressed()
+ if (item.itemId == android.R.id.home) onBackPressedDispatcher.onBackPressed()
return true
}
@@ -71,6 +80,22 @@ class LoginActivity : BaseActivity(), Logi
openFragment(LoginStudentSelectFragment.newInstance(studentsWithSemesters))
}
+ fun navigateToNotifications() {
+ val isNotificationsPermissionRequired = appInfo.systemVersion >= TIRAMISU
+ val isPermissionGranted = ContextCompat.checkSelfPermission(
+ this, "android.permission.POST_NOTIFICATIONS"
+ ) == PackageManager.PERMISSION_GRANTED
+
+ if (isNotificationsPermissionRequired && !isPermissionGranted) {
+ openFragment(NotificationsFragment.newInstance(), clearBackStack = true)
+ } else navigateToFinish()
+ }
+
+ fun navigateToFinish() {
+ startActivity(MainActivity.getStartIntent(this))
+ finish()
+ }
+
fun onAdvancedLoginClick() {
openFragment(LoginAdvancedFragment.newInstance())
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt
index 786bbfce8..b9afba986 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt
@@ -98,7 +98,7 @@ class LoginRecoverFragment :
loginRecoverButton.setOnClickListener { presenter.onRecoverClick() }
loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() }
loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() }
- loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressed() }
+ loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressedDispatcher.onBackPressed() }
}
with(bindingLocal.loginRecoverHost) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
index c42a4e9d1..03aced14e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
@@ -13,10 +13,10 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
-import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -51,7 +51,7 @@ class LoginStudentSelectFragment :
binding = FragmentLoginStudentSelectBinding.bind(view)
presenter.onAttachView(
view = this,
- students = requireArguments().getSerializable(ARG_STUDENTS) as List,
+ students = requireArguments().serializable(ARG_STUDENTS),
)
}
@@ -79,9 +79,8 @@ class LoginStudentSelectFragment :
}
}
- override fun openMainView() {
- startActivity(MainActivity.getStartIntent(requireContext()))
- requireActivity().finish()
+ override fun navigateToNext() {
+ (requireActivity() as LoginActivity).navigateToNotifications()
}
override fun showProgress(show: Boolean) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt
index 3455b3cf1..5a40a6bc3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt
@@ -100,7 +100,7 @@ class LoginStudentSelectPresenter @Inject constructor(
}
is Resource.Success -> {
syncManager.startOneTimeSyncWorker(quiet = true)
- view?.openMainView()
+ view?.navigateToNext()
logRegisterEvent(studentsWithSemesters)
}
is Resource.Error -> {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt
index f2acd76c5..8d403271b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt
@@ -9,7 +9,7 @@ interface LoginStudentSelectView : BaseView {
fun updateData(data: List>)
- fun openMainView()
+ fun navigateToNext()
fun showProgress(show: Boolean)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt
index 36c40d156..ab27ecf3f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt
@@ -18,11 +18,7 @@ import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData
-import io.github.wulkanowy.utils.AppInfo
-import io.github.wulkanowy.utils.hideSoftInput
-import io.github.wulkanowy.utils.openEmailClient
-import io.github.wulkanowy.utils.openInternetBrowser
-import io.github.wulkanowy.utils.showSoftInput
+import io.github.wulkanowy.utils.*
import javax.inject.Inject
@AndroidEntryPoint
@@ -54,7 +50,7 @@ class LoginSymbolFragment :
binding = FragmentLoginSymbolBinding.bind(view)
presenter.onAttachView(
view = this,
- loginData = requireArguments().getSerializable(SAVED_LOGIN_DATA) as LoginData,
+ loginData = requireArguments().serializable(SAVED_LOGIN_DATA),
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
index 5cd6fa103..d332ee350 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
@@ -6,6 +6,8 @@ import android.os.Build.VERSION_CODES.P
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.addCallback
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
@@ -50,6 +52,8 @@ class MainActivity : BaseActivity(), MainVie
@Inject
lateinit var appInfo: AppInfo
+ private var onBackCallback: OnBackPressedCallback? = null
+
private var accountMenu: MenuItem? = null
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
@@ -88,6 +92,9 @@ class MainActivity : BaseActivity(), MainVie
this.savedInstanceState = savedInstanceState
messageContainer = binding.mainMessageContainer
updateHelper.messageContainer = binding.mainFragmentContainer
+ onBackCallback = onBackPressedDispatcher.addCallback(this, enabled = false) {
+ presenter.onBackPressed()
+ }
val destination = intent.getStringExtra(EXTRA_START_DESTINATION)
?.takeIf { savedInstanceState == null }
@@ -266,6 +273,7 @@ class MainActivity : BaseActivity(), MainVie
analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName)
navController.pushFragment(fragment)
+ onBackCallback?.isEnabled = !isRootView
}
override fun popView(depth: Int) {
@@ -273,10 +281,7 @@ class MainActivity : BaseActivity(), MainVie
analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName)
navController.safelyPopFragments(depth)
- }
-
- override fun onBackPressed() {
- presenter.onBackPressed { super.onBackPressed() }
+ onBackCallback?.isEnabled = !isRootView
}
override fun showStudentAvatar(student: Student) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt
index 9c32d8583..458e966d4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt
@@ -139,12 +139,9 @@ class MainPresenter @Inject constructor(
return true
}
- fun onBackPressed(default: () -> Unit) {
+ fun onBackPressed() {
Timber.i("Back pressed in main view")
- view?.run {
- if (isRootView) default()
- else popView()
- }
+ view?.popView()
}
fun onTabSelected(index: Int, wasSelected: Boolean): Boolean {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt
index 222412ef1..37f9a19b5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt
@@ -10,6 +10,7 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.databinding.DialogMailboxChooserBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
+import io.github.wulkanowy.utils.parcelableArray
import javax.inject.Inject
@AndroidEntryPoint
@@ -52,8 +53,7 @@ class MailboxChooserDialog : BaseDialogFragment(),
presenter.onAttachView(
view = this,
requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false),
- mailboxes = requireArguments().getParcelableArray(MAILBOX_KEY).orEmpty()
- .toList() as List,
+ mailboxes = requireArguments().parcelableArray(MAILBOX_KEY).orEmpty().toList(),
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
index 8c6b0402b..6c54d9fcb 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
@@ -13,6 +13,7 @@ import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.content.getSystemService
+import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
@@ -23,6 +24,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.shareText
import javax.inject.Inject
@@ -66,10 +68,8 @@ class MessagePreviewFragment :
companion object {
const val MESSAGE_ID_KEY = "message_id"
- fun newInstance(message: Message): MessagePreviewFragment {
- return MessagePreviewFragment().apply {
- arguments = Bundle().apply { putSerializable(MESSAGE_ID_KEY, message) }
- }
+ fun newInstance(message: Message) = MessagePreviewFragment().apply {
+ arguments = bundleOf(MESSAGE_ID_KEY to message)
}
}
@@ -84,8 +84,8 @@ class MessagePreviewFragment :
binding = FragmentMessagePreviewBinding.bind(view)
messageContainer = binding.messagePreviewContainer
presenter.onAttachView(
- this,
- (savedInstanceState ?: arguments)?.getSerializable(MESSAGE_ID_KEY) as? Message
+ view = this,
+ message = (savedInstanceState ?: arguments)?.serializable(MESSAGE_ID_KEY),
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt
index b5f687bd4..14f3d718d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt
@@ -28,6 +28,7 @@ import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialo
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.LISTENER_KEY
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.hideSoftInput
+import io.github.wulkanowy.utils.nullableSerializable
import io.github.wulkanowy.utils.showSoftInput
import javax.inject.Inject
@@ -108,12 +109,12 @@ class SendMessageActivity : BaseActivity
- presenter.onMailboxSelected(bundle.getSerializable(MAILBOX_KEY) as? Mailbox)
+ presenter.onMailboxSelected(bundle.nullableSerializable(MAILBOX_KEY))
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt
index eddb43243..c78ccc6ec 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt
@@ -9,6 +9,7 @@ import android.view.View.*
import android.widget.CompoundButton
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
+import androidx.core.os.bundleOf
import androidx.core.view.updatePadding
import androidx.fragment.app.setFragmentResultListener
import androidx.recyclerview.widget.LinearLayoutManager
@@ -27,6 +28,7 @@ import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.hideSoftInput
+import io.github.wulkanowy.utils.nullableSerializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -43,12 +45,8 @@ class MessageTabFragment : BaseFragment(R.layout.frag
const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id"
- fun newInstance(folder: MessageFolder): MessageTabFragment {
- return MessageTabFragment().apply {
- arguments = Bundle().apply {
- putString(MESSAGE_TAB_FOLDER_ID, folder.name)
- }
- }
+ fun newInstance(folder: MessageFolder) = MessageTabFragment().apply {
+ arguments = bundleOf(MESSAGE_TAB_FOLDER_ID to folder.name)
}
}
@@ -131,7 +129,7 @@ class MessageTabFragment : BaseFragment(R.layout.frag
setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle ->
presenter.onMailboxSelected(
- mailbox = bundle.getSerializable(MailboxChooserDialog.MAILBOX_KEY) as? Mailbox,
+ mailbox = bundle.nullableSerializable(MailboxChooserDialog.MAILBOX_KEY),
)
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt
index 5811456b6..e46ab42cc 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt
@@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Note
@@ -13,6 +14,7 @@ import io.github.wulkanowy.databinding.DialogNoteBinding
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.lifecycleAwareVariable
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
class NoteDialog : DialogFragment() {
@@ -25,17 +27,15 @@ class NoteDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item"
- fun newInstance(exam: Note) = NoteDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ fun newInstance(note: Note) = NoteDialog().apply {
+ arguments = bundleOf(ARGUMENT_KEY to note)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- note = getSerializable(ARGUMENT_KEY) as Note
- }
+ note = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt
new file mode 100644
index 000000000..163ba8cdf
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt
@@ -0,0 +1,64 @@
+package io.github.wulkanowy.ui.modules.notifications
+
+import android.os.Bundle
+import android.view.View
+import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
+import androidx.appcompat.app.AlertDialog
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
+import io.github.wulkanowy.databinding.FragmentNotificationsBinding
+import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.login.LoginActivity
+import io.github.wulkanowy.utils.openNotificationSettings
+
+@AndroidEntryPoint
+class NotificationsFragment :
+ BaseFragment(R.layout.fragment_notifications) {
+
+ private val permission = "android.permission.POST_NOTIFICATIONS"
+
+ private val requestPermissionLauncher = registerForActivityResult(RequestPermission()) {
+ if (it) {
+ navigateToFinish()
+ } else showSettingsDialog()
+ }
+
+ companion object {
+ fun newInstance() = NotificationsFragment()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding = FragmentNotificationsBinding.bind(view)
+ initView()
+ }
+
+ private fun initView() {
+ with(binding) {
+ notificationsSkip.setOnClickListener { navigateToFinish() }
+ notificationsEnable.setOnClickListener { requestPermission() }
+ }
+ }
+
+ private fun showSettingsDialog() {
+ AlertDialog.Builder(requireContext())
+ .setTitle(R.string.notifications_header_title)
+ .setMessage(R.string.notifications_header_description)
+ .setNegativeButton(R.string.notifications_skip) { dialog, _ ->
+ dialog.dismiss()
+ navigateToFinish()
+ }
+ .setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ ->
+ requireActivity().openNotificationSettings()
+ }
+ .show()
+ }
+
+ private fun requestPermission() {
+ requestPermissionLauncher.launch(permission)
+ }
+
+ private fun navigateToFinish() {
+ (requireActivity() as LoginActivity).navigateToFinish()
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt
index 7dcd51cea..0a71afef1 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt
@@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.core.text.parseAsHtml
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.databinding.DialogSchoolAnnouncementBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
class SchoolAnnouncementDialog : DialogFragment() {
@@ -21,17 +23,15 @@ class SchoolAnnouncementDialog : DialogFragment() {
private const val ARGUMENT_KEY = "item"
- fun newInstance(exam: SchoolAnnouncement) = SchoolAnnouncementDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ fun newInstance(announcement: SchoolAnnouncement) = SchoolAnnouncementDialog().apply {
+ arguments = bundleOf(ARGUMENT_KEY to announcement)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- announcement = getSerializable(ARGUMENT_KEY) as SchoolAnnouncement
- }
+ announcement = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
index 364ad2137..77a3c6cf4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
@@ -1,18 +1,16 @@
package io.github.wulkanowy.ui.modules.settings.notifications
-import android.annotation.SuppressLint
import android.content.Intent
import android.content.SharedPreferences
-import android.net.Uri
-import android.os.Build
+import android.content.pm.PackageManager
import android.os.Bundle
-import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.app.NotificationManagerCompat
+import androidx.core.content.ContextCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
@@ -26,7 +24,7 @@ import io.github.wulkanowy.ui.base.ErrorDialog
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openInternetBrowser
-import timber.log.Timber
+import io.github.wulkanowy.utils.openNotificationSettings
import javax.inject.Inject
@AndroidEntryPoint
@@ -42,7 +40,14 @@ class NotificationsFragment : PreferenceFragmentCompat(),
override val titleStringId get() = R.string.pref_settings_notifications_title
+ private val notificationsPermission = "android.permission.POST_NOTIFICATIONS"
+
override val isNotificationPermissionGranted: Boolean
+ get() = ContextCompat.checkSelfPermission(
+ requireContext(), notificationsPermission
+ ) == PackageManager.PERMISSION_GRANTED
+
+ override val isNotificationPiggybackPermissionGranted: Boolean
get() {
val packageNameList =
NotificationManagerCompat.getEnabledListenerPackages(requireContext())
@@ -51,6 +56,13 @@ class NotificationsFragment : PreferenceFragmentCompat(),
return appPackageName in packageNameList
}
+ private val requestPermissionLauncher =
+ registerForActivityResult(ActivityResultContracts.RequestPermission()) {
+ if (it) {
+ presenter.onNotificationsPermissionResult()
+ } else openNotificationsPermissionDialog()
+ }
+
private val notificationSettingsPiggybackContract =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
presenter.onNotificationPiggybackPermissionResult()
@@ -156,25 +168,29 @@ class NotificationsFragment : PreferenceFragmentCompat(),
.show()
}
- @SuppressLint("InlinedApi")
override fun openSystemSettings() {
- val intent = if (appInfo.systemVersion >= Build.VERSION_CODES.O) {
- Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
- putExtra("android.provider.extra.APP_PACKAGE", requireActivity().packageName)
- }
- } else {
- Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
- data = Uri.fromParts("package", requireActivity().packageName, null)
- }
- }
- try {
- requireActivity().startActivity(intent)
- } catch (e: Exception) {
- Timber.e(e)
- }
+ requireActivity().openNotificationSettings()
}
- override fun openNotificationPermissionDialog() {
+ override fun requestNotificationPermissions() {
+ requestPermissionLauncher.launch(notificationsPermission)
+ }
+
+ override fun openNotificationsPermissionDialog() {
+ AlertDialog.Builder(requireContext())
+ .setTitle(R.string.notifications_header_title)
+ .setMessage(R.string.notifications_header_description)
+ .setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ ->
+ requireActivity().openNotificationSettings()
+ }
+ .setNegativeButton(android.R.string.cancel) { _, _ ->
+ setNotificationPreferencesChecked(false)
+ }
+ .setOnDismissListener { setNotificationPreferencesChecked(false) }
+ .show()
+ }
+
+ override fun openNotificationPiggyBackPermissionDialog() {
AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.pref_notification_piggyback_popup_title))
.setMessage(getString(R.string.pref_notification_piggyback_popup_description))
@@ -202,6 +218,11 @@ class NotificationsFragment : PreferenceFragmentCompat(),
.show()
}
+ override fun setNotificationPreferencesChecked(isChecked: Boolean) {
+ findPreference(getString(R.string.pref_key_notifications_enable))?.isChecked =
+ isChecked
+ }
+
override fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) {
findPreference(getString(R.string.pref_key_notifications_piggyback))?.isChecked =
isChecked
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt
index 4cbdac945..232b03480 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt
@@ -26,12 +26,13 @@ class NotificationsPresenter @Inject constructor(
with(view) {
enableNotification(
- preferencesRepository.notificationsEnableKey,
- preferencesRepository.isServiceEnabled
+ notificationKey = preferencesRepository.notificationsEnableKey,
+ enable = preferencesRepository.isServiceEnabled
)
initView(appInfo.isDebug)
}
+ checkNotificationsPermissionState()
checkNotificationPiggybackState()
Timber.i("Settings notifications view was initialized")
@@ -49,12 +50,17 @@ class NotificationsPresenter @Inject constructor(
view?.openNotificationExactAlarmSettings()
}
}
+ notificationsEnableKey -> {
+ if (isNotificationsEnable && view?.isNotificationPermissionGranted == false) {
+ view?.requestNotificationPermissions()
+ }
+ }
isDebugNotificationEnableKey -> {
chuckerCollector.showNotification = isDebugNotificationEnable
}
isNotificationPiggybackEnabledKey -> {
- if (isNotificationPiggybackEnabled && view?.isNotificationPermissionGranted == false) {
- view?.openNotificationPermissionDialog()
+ if (isNotificationPiggybackEnabled && view?.isNotificationPiggybackPermissionGranted == false) {
+ view?.openNotificationPiggyBackPermissionDialog()
}
}
}
@@ -70,9 +76,15 @@ class NotificationsPresenter @Inject constructor(
view?.openSystemSettings()
}
+ fun onNotificationsPermissionResult() {
+ view?.run {
+ setNotificationPreferencesChecked(isNotificationPermissionGranted)
+ }
+ }
+
fun onNotificationPiggybackPermissionResult() {
view?.run {
- setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted)
+ setNotificationPiggybackPreferenceChecked(isNotificationPiggybackPermissionGranted)
}
}
@@ -80,10 +92,18 @@ class NotificationsPresenter @Inject constructor(
view?.setUpcomingLessonsNotificationPreferenceChecked(timetableNotificationHelper.canScheduleExactAlarms())
}
+ private fun checkNotificationsPermissionState() {
+ if (preferencesRepository.isNotificationsEnable) {
+ view?.run {
+ setNotificationPreferencesChecked(isNotificationPermissionGranted)
+ }
+ }
+ }
+
private fun checkNotificationPiggybackState() {
if (preferencesRepository.isNotificationPiggybackEnabled) {
view?.run {
- setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted)
+ setNotificationPiggybackPreferenceChecked(isNotificationPiggybackPermissionGranted)
}
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt
index 2bf8e31f4..a391681cb 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt
@@ -6,6 +6,8 @@ interface NotificationsView : BaseView {
val isNotificationPermissionGranted: Boolean
+ val isNotificationPiggybackPermissionGranted: Boolean
+
fun initView(showDebugNotificationSwitch: Boolean)
fun showFixSyncDialog()
@@ -14,10 +16,16 @@ interface NotificationsView : BaseView {
fun enableNotification(notificationKey: String, enable: Boolean)
- fun openNotificationPermissionDialog()
+ fun requestNotificationPermissions()
+
+ fun openNotificationsPermissionDialog()
+
+ fun openNotificationPiggyBackPermissionDialog()
fun openNotificationExactAlarmSettings()
+ fun setNotificationPreferencesChecked(isChecked: Boolean)
+
fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean)
fun setUpcomingLessonsNotificationPreferenceChecked(isChecked: Boolean)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt
index 6ff7a39b7..598046a2d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt
@@ -8,6 +8,7 @@ import android.view.MenuInflater
import android.view.View
import android.widget.Toast
import androidx.core.content.getSystemService
+import androidx.core.os.bundleOf
import androidx.core.view.get
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
@@ -24,6 +25,8 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.capitalise
import io.github.wulkanowy.utils.getThemeAttrColor
+import io.github.wulkanowy.utils.nullableSerializable
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -38,7 +41,9 @@ class StudentInfoFragment :
lateinit var studentInfoAdapter: StudentInfoAdapter
override val titleStringId: Int
- get() = when (requireArguments().getSerializable(INFO_TYPE_ARGUMENT_KEY) as? StudentInfoView.Type) {
+ get() = when (
+ requireArguments().nullableSerializable(INFO_TYPE_ARGUMENT_KEY)
+ ) {
StudentInfoView.Type.PERSONAL -> R.string.account_personal_data
StudentInfoView.Type.CONTACT -> R.string.account_contact
StudentInfoView.Type.ADDRESS -> R.string.account_address
@@ -58,10 +63,10 @@ class StudentInfoFragment :
fun newInstance(type: StudentInfoView.Type, studentWithSemesters: StudentWithSemesters) =
StudentInfoFragment().apply {
- arguments = Bundle().apply {
- putSerializable(INFO_TYPE_ARGUMENT_KEY, type)
- putSerializable(STUDENT_ARGUMENT_KEY, studentWithSemesters)
- }
+ arguments = bundleOf(
+ INFO_TYPE_ARGUMENT_KEY to type,
+ STUDENT_ARGUMENT_KEY to studentWithSemesters
+ )
}
}
@@ -75,9 +80,9 @@ class StudentInfoFragment :
super.onViewCreated(view, savedInstanceState)
binding = FragmentStudentInfoBinding.bind(view)
presenter.onAttachView(
- this,
- requireArguments().getSerializable(INFO_TYPE_ARGUMENT_KEY) as StudentInfoView.Type,
- requireArguments().getSerializable(STUDENT_ARGUMENT_KEY) as StudentWithSemesters
+ view = this,
+ type = requireArguments().serializable(INFO_TYPE_ARGUMENT_KEY),
+ studentWithSemesters = requireArguments().serializable(STUDENT_ARGUMENT_KEY),
)
}
@@ -154,7 +159,6 @@ class StudentInfoFragment :
)
}
- @OptIn(ExperimentalStdlibApi::class)
override fun showFamilyTypeData(studentInfo: StudentInfo) {
val items = buildList {
add(studentInfo.firstGuardian?.let {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt
index c9243b12e..4f5547d20 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt
@@ -8,14 +8,12 @@ import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.databinding.DialogTimetableBinding
-import io.github.wulkanowy.utils.capitalise
-import io.github.wulkanowy.utils.getThemeAttrColor
-import io.github.wulkanowy.utils.lifecycleAwareVariable
-import io.github.wulkanowy.utils.toFormattedString
+import io.github.wulkanowy.utils.*
import java.time.Instant
class TimetableDialog : DialogFragment() {
@@ -28,17 +26,15 @@ class TimetableDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item"
- fun newInstance(exam: Timetable) = TimetableDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ fun newInstance(lesson: Timetable) = TimetableDialog().apply {
+ arguments = bundleOf(ARGUMENT_KEY to lesson)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- lesson = getSerializable(ARGUMENT_KEY) as Timetable
- }
+ lesson = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt
index 6fd126326..e95d6f827 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt
@@ -7,6 +7,7 @@ import android.view.MenuItem
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
+import androidx.core.os.bundleOf
import androidx.core.text.parseAsHtml
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
@@ -39,9 +40,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme
private const val ARGUMENT_DATE_KEY = "ARGUMENT_DATE"
fun newInstance(date: LocalDate? = null) = TimetableFragment().apply {
- arguments = Bundle().apply {
- date?.let { putLong(ARGUMENT_DATE_KEY, it.toEpochDay()) }
- }
+ arguments = date?.let { bundleOf(ARGUMENT_DATE_KEY to it.toEpochDay()) }
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt
index 7d32278f0..ddd7488e4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt
@@ -4,10 +4,12 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.databinding.DialogLessonCompletedBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable
+import io.github.wulkanowy.utils.serializable
class CompletedLessonDialog : DialogFragment() {
@@ -19,17 +21,15 @@ class CompletedLessonDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item"
- fun newInstance(exam: CompletedLesson) = CompletedLessonDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ fun newInstance(lesson: CompletedLesson) = CompletedLessonDialog().apply {
+ arguments = bundleOf(ARGUMENT_KEY to lesson)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- completedLesson = getSerializable(ARGUMENT_KEY) as CompletedLesson
- }
+ completedLesson = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt
new file mode 100644
index 000000000..d0d47025e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt
@@ -0,0 +1,32 @@
+package io.github.wulkanowy.utils
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import java.io.Serializable
+
+inline fun Bundle.serializable(key: String): T = when {
+ Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java)!!
+ else -> @Suppress("DEPRECATION") getSerializable(key) as T
+}
+
+inline fun Bundle.nullableSerializable(key: String): T? = when {
+ Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java)
+ else -> @Suppress("DEPRECATION") getSerializable(key) as T?
+}
+
+@Suppress("DEPRECATION", "UNCHECKED_CAST")
+inline fun Bundle.parcelableArray(key: String): Array? = when {
+ Build.VERSION.SDK_INT >= 33 -> getParcelableArray(key, T::class.java)
+ else -> getParcelableArray(key) as Array?
+}
+
+inline fun Intent.serializable(key: String): T = when {
+ Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java)!!
+ else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T
+}
+
+inline fun Intent.nullableSerializable(key: String): T? = when {
+ Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java)
+ else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T?
+}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt
index 1ef03f2e6..62b85af4d 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt
@@ -1,11 +1,15 @@
package io.github.wulkanowy.utils
+import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
+import android.os.Build
import android.provider.CalendarContract
+import android.provider.Settings
import io.github.wulkanowy.BuildConfig
+import timber.log.Timber
import java.time.LocalDateTime
import java.time.ZoneId
@@ -86,6 +90,23 @@ fun Context.openDialer(phone: String) {
}
}
+fun Activity.openNotificationSettings() {
+ val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
+ putExtra("android.provider.extra.APP_PACKAGE", packageName)
+ }
+ } else {
+ Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
+ data = Uri.fromParts("package", packageName, null)
+ }
+ }
+ try {
+ startActivity(intent)
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
+}
+
fun Context.shareText(text: String, subject: String?) {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
diff --git a/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml b/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml
new file mode 100644
index 000000000..b1b01a0b6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml
@@ -0,0 +1,20 @@
+