From ce09a5264a69f18ffa9ecc391effe9ebef9d7af9 Mon Sep 17 00:00:00 2001 From: Alberto Ponces Date: Mon, 15 Aug 2022 18:42:09 +0100 Subject: [PATCH] fix: save patched and installed apps on prefs, improve installer log, improve dashboard with real data (wip) --- .github/workflows/release-build.yml | 2 + .gitignore | 2 + .vscode/tasks.json | 8 +- .../app/revanced/manager/MainActivity.kt | 35 ++- assets/images/reddit.png | Bin 22247 -> 0 bytes assets/images/revanced.svg | 6 - lib/app/app.dart | 2 + lib/app/app.locator.dart | 39 --- lib/app/app.router.dart | 231 ------------------ lib/models/patched_application.dart | 28 ++- .../app_selector/app_selector_viewmodel.dart | 36 +-- lib/ui/views/home/home_view.dart | 4 +- lib/ui/views/home/home_viewmodel.dart | 14 ++ lib/ui/views/installer/installer_view.dart | 6 +- .../views/installer/installer_viewmodel.dart | 52 ++-- lib/ui/widgets/application_item.dart | 26 +- lib/ui/widgets/available_updates_card.dart | 27 +- lib/ui/widgets/installed_apps_card.dart | 21 +- pubspec.yaml | 6 +- 19 files changed, 180 insertions(+), 365 deletions(-) delete mode 100644 assets/images/reddit.png delete mode 100644 assets/images/revanced.svg delete mode 100644 lib/app/app.locator.dart delete mode 100644 lib/app/app.router.dart diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 201a79ff..261a84bb 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -19,6 +19,8 @@ jobs: channel: 'stable' - name: Set up Flutter run: flutter pub get + - name: Generate files with Builder + run: flutter packages pub run build_runner build --delete-conflicting-outputs - name: Build with Flutter env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index ab6de429..a78395b8 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,8 @@ version # Flutter/Dart/Pub related **/doc/api/ **/*.g.dart +**/*.locator.dart +**/*.router.dart .dart_tool/ .flutter-plugins .flutter-plugins-dependencies diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 490ed99c..0fa4713f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,7 +2,7 @@ "version": "2.0.0", "tasks": [ { - "label": "Build (Serializer)", + "label": "Generate (Builder)", "type": "shell", "command": "flutter packages pub run build_runner build --delete-conflicting-outputs", "problemMatcher": [] @@ -30,7 +30,7 @@ "problemMatcher": [] }, { - "label": "Clean (Serializer)", + "label": "Clean (Builder)", "type": "shell", "command": "flutter packages pub run build_runner clean", "problemMatcher": [] @@ -39,7 +39,7 @@ "label": "Build all (Android)", "dependsOrder": "sequence", "dependsOn": [ - "Build (Serializer)", + "Generate (Builder)", "Build (Android)" ], "problemMatcher": [] @@ -49,7 +49,7 @@ "dependsOrder": "sequence", "dependsOn": [ "Clean (Flutter)", - "Clean (Serializer)" + "Clean (Builder)" ], "problemMatcher": [] }, diff --git a/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt b/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt index 971836df..ba725783 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt @@ -179,10 +179,41 @@ class MainActivity : FlutterActivity() { return true } - fun createPatcher(inputFilePath: String, cacheDirPath: String, resourcePatching: Boolean): Boolean { + fun createPatcher( + inputFilePath: String, + cacheDirPath: String, + resourcePatching: Boolean + ): Boolean { val inputFile = File(inputFilePath) val aaptPath = Aapt.binary(applicationContext).absolutePath - patcher = Patcher(PatcherOptions(inputFile, cacheDirPath, resourcePatching, aaptPath, cacheDirPath)) + patcher = + Patcher( + PatcherOptions( + inputFile, + cacheDirPath, + resourcePatching, + aaptPath, + cacheDirPath, + logger = + object : app.revanced.patcher.logging.Logger { + override fun error(msg: String) { + methodChannel.invokeMethod("updateInstallerLog", msg) + } + + override fun warn(msg: String) { + methodChannel.invokeMethod("updateInstallerLog", msg) + } + + override fun info(msg: String) { + methodChannel.invokeMethod("updateInstallerLog", msg) + } + + override fun trace(msg: String) { + methodChannel.invokeMethod("updateInstallerLog", msg) + } + } + ) + ) return true } diff --git a/assets/images/reddit.png b/assets/images/reddit.png deleted file mode 100644 index 941a12e6c3a588bee3fe6e097300b1a4018249a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22247 zcmeAS@N?(olHy`uVBq!ia0y~yU}6Aa4mJh`hA$OYelajGuoOFahH!9jaMW<5bTBY5 za29w(7BevL9RXp+soH$f3=9mCC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2 zNHH)dFnGE+hE&XXbGN)AB=q0$kKbpie2H^$J=hfF61Z`}qD2!F%KqwRmzTa>_j=d0 zt9M1;?0viI)vfBoTNkbt2+-+hTCre(!U6?_y=+b%miz7fBBGp(CRabdWBs|(_PkA_ z!S{Ron#KEy-)XW5cADItY!h`+>;~5jE^lt3Ee9?MIdkjiHav4k=IL!X7V#>yPH+3C z$8ED1DreWM6VbT!r1*40QG#XL)&p8Ps~bZlrX@zQ>8$?qIf&=9fzE=a)+w6~^eoaj zvA(&3<@66eg-K`MFx(0{RVK0R_$9`}Wf9vBUy!^0VEfvdBNv?#9G@P)c|b;Iv!{XZ zY5gfiw|kN%-tSPl>CM{U(ecKiD8i3@f$+4s|G!_nz zXShl3ZEW5k!|Nxlm?C)6-xcWsRrKXPqYGEMs!m)MeYyX0p^=^hxpw_NoQZGR4izI?<3@Gypc!wp!J%i0ehJzWri?#xNWG-^yNFwcs%K4`?GuA z8^Rb5H*WX*uubxq(EUc2S6-|QN2a}XmM*dslIU1yFSSSgWguS-ACKa})oBv@fAOhI z;u2*L(%z>3*5HrW;rvGC#+LU!ldhg>ym(;slTWKE`&;)hcV3ys#n5R}&A^*^=wigV zE7rB02G@-fDyFgRSz^kr@+ySEL*=I3lc0X(4RaV}n$6Bmtcc{@)BChC>eIe&dJ{B) z7!@Wa^)E0MozwI*$D}IW=H&A-Lfdft?E8UGhwA&4E=D(Mc=|U?`+Zhp+D|^^kIR@Gg3^9p?kf7%^RVBc_f|(+di=(h*80F^_{Bk{Sp6IwtSfDuF@^a_+RPZ+*^wxCM{)h$U6IN zA@A26ET>an$nN);BEsk&bZ~8&%4d+)07eDVO|=Ncse(Y<#cWP z#Ef7@g{zyGR~|1qcc@7D!ntxe!IfK&9*CXtsUeNmqcnu!L|Tq~(aEPdJlC0T|2(Ix zq&d6wA=|9aJ=;{K`L1M`(0ilq%i#k34ZGj#C<)DwH;Bn=`#kTxf{?5h0}I3L>3J$U zxGgS~iwSaeK9$wlai-=#$^F11_M!}t=j8mVi*`s_2z&fEr>wM4n^8_-di0s#^G3OVr@kmd(;cKNrCw$6W_s45*t>7f}z@YLrj93!J%>i z&S_i?JW0PVG{w9-P$2i~UYEy)wt$1KX)4{d>=P4KOgtg5ts!}X4bPVEb6s7GuGQ#- z&la?KZQXHYE?2{h&Fz=xvGgZc+$t9nJSo(+-5@5V?c?hA8k4L<84S}tTQXMgWW2TR z?ns%NDxqg|xbkMXfM+jPgTyMChxOJ4E8puVP5L@-@s#ef8eKK)6Q8VL=#ZR!-tGW* zM$7&Vm*f5+1+yeiO5Ar`@-2knNDHg8-E^jMrmr>61qE-qNVY|1ob$aEqahc%YBR84qnjdCg`O)nm6Tx@j(^lOFCieqVPG4ab z2rOebtMSkJlae3o^)a>1qPt+Vu}3c(YgKvYSQ4?O4zO8D$2%d{A5Hvhdjo9iF*n+Mk29UimJ z9Qz<`JV(EV{bZUH^Mi?RkIxMGpb4^WcTW3L<=g$AK{BrxKOEp&VE6Qo@M`%w!IM?z z1COw#@f_XPoFXmCkeT-RrdWmWjqBxNf)hJ_GzImUFP`z-+QTf6ae~{nhIbM7Sgn4X zQ&JK#EN)ort9bth-y~fVHU-}t`(KGaMNI!GDTQYCKTVE4z;NGjQ!7`4$%elc(ueCC zpY7Y+(cv;}q2{#hxpv||`X=#)FtnU#+Q`at?1RdL=hpol9z0K&`ft?8ZP?{2t5sX* zoWve5KWN^840j#Di6XoUrx>5rxb}l@QfCN5%YkppMMd}YZTlc9xN`M*ji(mIU9S6@ zQ=~*07&rgD!FhPMgE%N&jE96IP@(Df*nG_hdH6$DCVK@0a*VRR(*>qu3 zd5#_HzUCArQHIi#&z7>MUpp{^tlH$DoUr2TbC&y#kNj92WN$G217~) z#Zj?>N$(X3moX{0ZfjV*;ZAp2P)Em)3GGi=qYqf_Yd$iSt3l*O&ANj{%iqf=DMjkB zpHAK;Km8}4kh>Pcq!exmo;&ZX+dDcK5)AI7|28~PK4C}T_fnx8`@BOR%8+_13z$7@f`HVLnq}_f1wsD}*6r)8Cu@ zTy9LhpxiStKpbau-YWzyU}D(7`ljP)ae_7|xK%>BJqj8mkD1(etU3K_Dno94 zRN%pCkgrd&<*rCrVcRurU-OS?S6LfmJ#4iknig(;ucM?i$t*VF^z1T*ueIzSHM6-H zxZ6@~_+;K$cXxCwxvMzk! zz2>=qpx}F*w#{b?=6#e^xw~l{!-l^vniC}g&XA24l?D-(8GI!G|h7GkZ z-I^{saD!bO6qfKZ*X_gF_X;m3nX)##J#QBB!ScdEu(l=hHJ>sVcUA6dUg8|a!?dL~ z!+|w_;jwrZmsQ)DEB8#)z0ACM5raZMdt+_waRI@JCtkZs^8etQ$R`=kaD(X_`?-&E zon2g#9y3Kx`1#h&t)Fqlod?g8PxQT$5ftP+BlRF5_{8*m%`K|Q+zhv;UsL%ZbOU1V z(#R>7F83r&e6Nsr;zTq(2Hgi0I_oDl{x=sC{CFto$6CpA+54JVG#5*GRX>8 z*SDs%Rt}fLUdv}+KQJqFLlNhhpMq)rjq~?6PfEJqxw$!2by{4S%H&#iPOoDk44}d_ zRj2LTzRevebARzJEG&-L$8qX(&GOFaRlE-x9zR&{c;W5Fbr09Cy(IMGU-6GDmYyof zXMWuJ%Ncx@P5BG15ICKli7@=M>E8BT)N}qF8;eIW7N2ZxRO%bBX`M=ZmNLUgtnLR> zodmbB`bv!zr~Il91d1O@RCk^{t<--0R)e3$62Am`s*cv`tMK^Ga60!@R*6G0kl_YX zU2|&3dl{w3#i`a3U)V~l7d(5uE|T@~zM`WCb{$Iicjkk!+to1H2Ww_8e4m-%B-L(P zE@e}%+*5N;lubzI@JUd%=?e_ssL$|*VcX&!P6tqO-sH5Gt>C@-hSzp0=O@4Xb1G@U zCXE?JVqMc%ryF(^eSYx8@xyHGJJ$_l_x%?6S84cXimCLSrKgQ_Y|n6{{p1T0->Ap% zCPAp?6bDcH&vQzXO#S|G|M|Bq;e4jn>jEt>#?T)0)ct=jqCm50@Xjdfq`viTkAQ9}YHI_SMsV`%TjH^1N`H zCwa|#g_Rwtj0xNsKCF*TpZcqu%OPaQ4_D5+I_*yk0TkLe73mtC|u>QUadsmz6<4Dy4w21+)r{M zM9%!O+k5#DpB3Y}dfh#N|4P4xD*k!<;*4bS!~4q1CUn=ddxX8=VwioN^T2CRC@gK? z*W_3I({JLQM@lmc8S7-;%LO0b{gw03g*x$yf;s=I|Cvhu+Av#D;ot_De76tN-bV`QwL|T+Ew!cUQ&*iGM#?OYZ2- z=tvzo@_45Tjd?Mx_lWExGf}4dxN~O_Wy1f zy&cEPyS`{?x6W7Fb+55>FY9Kta9h!h&fyg)PJgOAxaPO+u6u6n`fS6TG&h0nyr~f) z4BPD2u1GMtaIRcz;+oRC-WQ&y+}HdQy|3W$R?a$!*Lkvc%y_EAe)vtd$$oe#?%jVS z#|R(R-7_b;?2A^FV|B3eXVwaIt*J`0%Go^my~stA|LGaqo8OD9{9U6FQ@`fD$jrkZ zrb(XrxG(vN$|q2vm>6O(Ng_q`WW?FqhbD-K-I@7gqIAynN_$cMz%<>M4ehh-G!Gi^ zm>jM=Ti&C4@~dou_Zvf#MJ>kz9_v2}pZ+)4I`75dJkF?s%a6+L$d*}i?D}5zJm;R- zj-E+Y$qWA9%sbD(+4j`2T15HmZk?~PORXm6MdaFjXPEx`oZ_Ufb>+%)zI}hvZ(XcD z<9qPe|M#k*Z!CTOYte$s_wsfvTylRCYk}AACuz1GzDj!?0#ZPQX|1OLTgMHnSxS*F zrhQMiuRO^*?WdE(j?kjxPo5n-SG_BmZSMDGz267R=H9gq;k+-}erUV=Z@-D2S_U#X zZJ%epR|xHVqRVi*Jx1t{wCO*kf0E2|zx)2%A-~V#-;7N`+r$1&DRg-HZ7c8WL+5PG zUT9iKMCH!?vvX-$wcIi$g$Pca|J!VD-tJj`rF@33>m~+;{?&~V>&&$#HEw&$ayBTU zrufv3gmv+Ig!cWuVtcRRKyBV?zC50TyW*N+t9FI{Sd#bc1@oN;)~?H5`f2O2zxyDY z5-e8q4eq>3Rc`=HME9~B+HtCiTdy=U6}t>owdJ=@szuItLU z{ZIAn{_Qtm<+|BJ`ahG`w62V2o4?db^l4u4 z{F#&QoGN=z#bWh#>pq1WOVvx>Oxx#EcxBDj>NOE>tUZ@yy6}}VGuN`;3|PX&Fx#K8 zpy$1e(x%3ccmL1NeYRgPa&y+-waukbxsUTC*GQ+_sJ#E;{jKSF(eGdUubw`$F8TMx z`#lLW-YdLzS;nw|dFAn-e}Z=_Ih7(cw#Q}N{@-@F5E2V5xjZeT(wi&-zWXumv?>Ye!goX z%j;vpPo6pcjy2cgvMM<{Z)HdC?4N!UPVTzYWzY2e7ax-F;l0mllT8{-~H}$Dbwb;-y-5gO78JTsq8$~*kOF)+tXH!9I+`MWEmP{@)&1< zs@vH&Ok02dPt8s|*79KUk1bWL(%v6V$$t2r{9yar2W$Kc?1P$jHE*n1`krOxI>|Qa z+V%zxP>!{2Fs^wnFj1?-H<9bX7tRf3z8@kNrM)YOn9#|3fa^Y|(fo%pxB1@}>+Kb1 zhd|^44^};%P8A^Xp)FQ-W^%63vE}#XH`**Ux9%Bg(Lie`mzIFQv7DD;>2y|5x0h z+VD8xR`@<)me0JE`MEYVw6`Z=Yy4!*A%X$oLq14H~YS3 z)90`3`-D>#A1q_g)Y%qTURT9Xw3Fq=z0+G=m!!S5aynghQeOP|&;Kib{gJ)T*|af5 zuEzdYc~{PriA8!J3LkC}XZRK|D`|(;-Rq(gRa^?$53G>-sB?qszUHfUhs77}IaA*C zCgtaB+i6?YADr_~uHkA7Zw9CCL=Ep$Mumdy44cm0dHM0Z)poIa^_R-Ka;{A?{iAtT z+&7ys;r9i$OaJGKOjKFAiQz(1AiLH-?Nk&Kg-?zW#fprgzQ` zJ^gUm{UgcdN4EcGoxkUqb!*N#OVyZJecY{a3^y1}4u5LjuwGe-G2;e{Rhjp{9XWCg zJ65iYKg2BVQf+hK$HNaNp6(0wDCMc&RGT66E%nfQMvbRQ3tV4Z7o523_EL3~pc}JQ zZnHDwt@;pI@#Orz;E>!?7CS!vUy!r*y-LvDX-|sEZisVkW!S*HqTka$ysd*p;i@P@ zC_~P>UiKJ4fu*5L>OD>SpEwsze6JEDe0YAYe9_@OE<0=)Qs=Pd2){CQQL$Kg=fA#Q zQ8GhH@%sDx=JQ!pkM5g%V8!%@yOi%(uy!`fo_yS5pf3=`7_jsBN72N&@h(miPH{EZ z_myvESduk=58L!N%*JZJcGhKb+SEwisp|T@`H1I=-_KgEGjEt9+z{(zy}yHH^;NSA zX}h2F2Y;_Wo95SM|F!A*yT(nIzQlgN-T&~>ymw_SM=tK$CcpQvRppHB3?@fHRQ!VO zF$SN<%i~~8a#L3@wa5hrv7U``uAU`y5y|Mki%%ZQAXg{PFEF` zmFpN*$Td7U^Gohu9Z$>_^_~houQ2xFxa`DB89UyFSS;7B`QWr=xsumgiN4K$Ur3!> z>72k_!FYOQ;+$v~r=sH=H&*8t9oL!3uAV(7f38Um6Z>3--}1cGGF-a1ul~DnNoYIg z#qib-7q#=UlTNzy&v4S?`n9A{oWX21~8AASjC+_BmD_}Cua=00xq z;BB|^!e3nAz4e}H?e;=xW7Eu+VNp7IO;f!SzJ<&%7OnfICuPi|l9Rn6f!%o0Pse#C zJ(g|t4!!$5_LRJsRr8oXiqU{)^YUHoC#rZ2VmR&yJn>|fWlmrZkhjo3QQYMrwPX+T z0xpJ$TMS}iZXRP(yW!Z7%ENMtt;$c3GhU0EA#$}WgX^Vq?Nb(pCxqE-xfMP*aw$z> zTlt0I&nIRPU2ePXj*#bv6#T^^7!!VPVlPMtcrHBAWaWKkE0${nmD8&y)VyMtVPVls2WB?I93~U)CoxKs;#S`IAHSQe?w8pQ9i^bj&m!2^3Ku1CF&uu+Y}wzj zBwnkU!RNQsscUTWU0t#szftfDdd_q}&M{r#XsnCNk=4&~6JN_tw)eaHU&&53=lA86 z>(p0P^E*gB7q~Q!NlC~kmqA6yo$YjFsjcT+E>IJLXZdy0mX3-^uNWLe8Ia?PE-DoKm~q?h!kW@g$Nw&PS3l9iKqh`CpTAeo zd&Y>voeqbm^otAb-@-7#>E#Yc7HLqv<(I#!pZH?am#yv> zjb=Jedd+Ac)9v=Z`$+WjzXz5Si05}b``_&nA(uTldan6RrUyTk%+pu8xLB0I)hIOR zF+)}?!wtqO?%n@8j>Kzy|KA;cNI$sgypocm)uK&n*G;|kongvF@u2%94lYVcQ{@`u zk2S63u!U z)`ngGU5%WzzW>i<{qrOAL!^sJ%+`$E*?J6XxELn(Oz`4AaDMU~tp+6_^HObwocbun z-RxrSE`OlaiN)Rj;+?DKZR6M7_N7JlJ3|ED%8!;xLbbc!Jx*g(@it~~d8Fm|ZW{ZC z{GEmCA53s~+|eN~#h@{%_RO@~A7!>+$$N$hozZwx;QZKh0To$u;$Y3Xv?Y0IwuP0rieKJ}us$HQd{JC^uPo_1l8 zu1WS=v7eb&cwU*7)aTvd`6T6Vc^atHKYw?H^W$=Vp3@V%T$b`gAPFPRQp z;ua25ny52&Dwc&?xEZp&DYj!t-i4J*YhdU4=%FYP#|_@ zCfnxa2i~kSC^kAXdtn^Q#1A3W|JR>VyT_+?Fk>3iS*LE5NxVIUynlCrSNd%NG?;D`;WPQu~{SBUr@9MO^w<}~8 zc3o0s`GrZ8L22dc`y9Kzx7qTS_s4$o%PqC_j6J+;L%IET2J>wRzsx_JW&iNiA%+`IeTN~%is^|HsF zHEUxYbv5eWYdn9i=rNbeCV$PPCdP{+Ha}E1*Jt>(Sza`QvEZa!^m`8WxeuiNzGFY0 z%{A{z+|8&N23*JX@XqHieJ$LP(#>Lf)?nYQ55cp=8Ggnv*i70Mpk%k@u3Xi8j&`1w zJoa6JemX_&nq8b&E!^{q?dc^u;Tt!=la+0 zp0Bz;TY5JWxA6*W_)^exbe8-!J8M1 z;}?HR<6Ur$0lF3-_4KLJ33sYL{F`j>Ep}~y#>^+i!6zK0>yj2T9pF=GPhGHCk+Xkh zez>&u!$09$6Fe3vPk(#1`b=9dcSDWN;lE2KIhbbiuH9BR`?g8et8ZcP(M;K42VaS9 z$eJmARAy_rMyJe+YO6Vh9=!Wg+Gb9EF!Ocd7wL=A$`dBP4m*)^jZMm|aJ~3f>7Ccx zA1QsW+L$sg%>9hYw8LjO+zz%iGibjE`TFdDQ03dQ23!7O^j@bva~1rKXCl<)trUiH!K7v>K(u1488+ql?Va+wZADdn@ob;{Fy{5Yh?8O;)TTZD zvviV_Y5r@rBTwgZ-Y(<%UzNJlpiW}XUww)1bswJR-(#yQ7nZ4+iom z#<3<&@Xych79xGSS-#m@-EiA)#hrGrg+-gmNH9^0af_~-4pmg^aF{Uf6q zBfHFNF|Aj(v}e?u)Y2`~R^3;%d|O1{x_lqSYR6Q@go6|O1s|*|ov3A+@rLcNZ2ijJ z_2R6?Y6qI@Ti)AwzWw1>eK58E(7N9&v%OW$sr59m>$Tj}%sYCi(tgJi#}_gadt*+Y zesH(`@a6jEMXCEPpJ>z3KFcxd;4Y>FhFs0$#`__gCf-v&P=TvD0=jEwHZ+K3H*oCQHT@o_~KlyQ6lnTvWSMT^=^W)uRUHkRN6AR83zY|NmQu~qbPN^7Z6seOTelttQ- zr#m0qKBIFbQ~$^H=^r!x9OU)dQ>YR1$Kh&lr<7UY-M{AhS09UCvdyZbwc4)f^Sf2Q zuEi^{pWDa!PDZf*nEaiDBklXIJ@X7qjG4WE-FXkzhWz$_V(^OqJF^3*Z=?bMqU`!D)ZS@n+UDYv&p z*SuzU&Bd^o>A>n_y|&kJUgt~ZZunxs^G^EhGWB_eH~ucl-#qz!h|z!M3O#aG`?iSU_QuPhO|DIf9 z5|rQ8_%)f~Rwd7*xqQ~FdOw%H7g19BH>Yl|HjB#LTa8={_Pf5ft)6#rcdFn-?)R;= z>_7Y_=5Av$04-2j#Ii*^A<884<+8&WQFA&vHWzWm>@hzx|E0C(wiOGe{NkLoSv2d< z<--y?K52p`m!z|9ajmPCEc#rdeo~CHC}Xa|!(B`&ynQzEd{^4Km6T?`yv1F&SoY=R z6|qdt(f2hsIc>gHaP92A;EoQL>zVcKYs=_#r91rT25auY7FIfc;WYRwKRiTcw#sEfvHruc7;$4=UyE0iy>@E|B@Au|f zbpgT7{kD8x=PCr7`Y`a;EI;8PyZfh~f|5}E?RPw1ulTesNMt(kb*|FmEw|XDOh1Es z_|J!F+s&KhT|QH0A6Uh}ap&7ykTEA^WOx1Wo6uP#BvDke{Dehx>4&)vE-J-au4ZR( z6bUb2QYd)!Ph61GPkHSMsU{gwhQy2&SEstVFfCTSdU%0cLx4#Dqk@u<#8-wEMR#U~ z!xJui+dpx8iJgxOYw+>IZ!`^_yGLx4xVEOKUrl$RqjJX1ti0x490hHhmD6_-WHqC0X}NY|Yw#J6*l+CSR2KQTXTA`Rmya!qi?@Tgj9# zUAb-*6*pDn@C);VC&dPdQ?!{@ZC(6yu-~LRRmHN3>{e;U?&NW;O2Xy}&EpK|0rXBOR;9j1=v$r4CZ%-4o zF_$=Z|8A+Xw}n~_$Mf>`=lMSV`nP9nzkB||>Fw)yU+>+6!Q z^5u2jx$&nCLkY97hZ5EbjQu2A1=Gx*W7ga$@vGLoEKgE(frzO z-!_J>X+Qmh;$2V2%|G<9eBUFM1wTa-XBeF;Z+c_+HhV{2 z!|ZM1s`qQTuFEgZT)0vrlDFPoO0D~Tj@4M%3eaWv;KPgwG%j;dpbm32zL5=C5 z@}@T*Ui18T%@DEoQ2C@)wcj=GRBbxOqOy3=ozma#Yd^-Pf93jayj+s$<;Sxg_c@KO zW{EN+&io-bW$G`#i8sBxA1rIUC_l&aS#HHgvHO~j9=Fb3bop)kT-DDHp55Oy-;wkB zbu9*`#8}1!|4tf+tiQsacSCMB*RtBD)~;`D%A2*hQctVZaOmH8W9|CZ=dY05?px(u zXUtM_N{)R^ky;$h@KEaOruQr}vrk1-ypG=&d?n7+@=oTp6|&S7OB}&b+o+8XPZ`|+c#LNtC${EtZT4@i9q)?j zHlJ_*x**Pq&RfQ`yPxXG#U0%DK zqwQGR8gnD(ePZ+X3-7(E@i^nm?O8^%`9yWRCpg$|eEH$U)D1Ng--~?w_SHip`n9#I z!M>jk-6tAmi_O%G)|fu+z;*c_euDX`M?ZW>-4~p~e)toM7_X;ayJ+{_ZHp%D;*|g8 zCopHy)enE4?+gB6{&Vx0DCvjNq70><{_sX6S;q2w7FOx_VYN!~$=>4+H*c?5-m%_C zT4YM*|M?*)x6W1lnztadtN>BhdZ4)=~;b|~yf{=^xx&iuMp z&h+2tZMH%BDz-n%l8z?rc-&L5mPs=IsY!a_p`JxN5s$2WuSM+RV3S{;nQ(HJz47K< zU5sh|ALky}A8+&~J0OUMJME{RpnmGoyyT^=`+xXN@)et@I7MCS|E2@kQ*w;IdhdAt zJxW1Ca^?=D8;^MY*sEs0w7JjOwETT`k=(z@$NZMeF5LbvmNCJUPvNcGULO9qN7k-2 zYd|&ShigYFp6*_d^62|k3+Dg+iIrsnJheB=3x?^zC> zUj5#>v3}+c(-qebls9E;7Mr)5ao&&a_bN9}8*nRZ|Fm9DH=q#9h9&PuS&hQ)I!-sFGC|H)qTJqmam%aH27{?9*JOD!VFCy?fHjdOpu}c~HL{6biF;NQtfv zKd|@tq5J)X#rKb{yf3ir)dOqS4cp#7vTm%uUH)yZLd09EES>{x)6O(U`?W>;6?Omh zFbT=Iz?|T=N#eu?=|aQrN39F;!YlF$()yZ7<9C5 zE?Sp6O`L(@@3{|i7rgUf;yXRTO`qfMxst`tAL|L%nZ4G!eSgW3#+9$c8Uh&wDt|p~ z`eVE>(D=UQt7YFgFQi%D*IcyNxB5kC&wG*5%R4N;*xoJY`u{~rv{p1!l;MS#!;+`h zny=md;rB3p^0b0CNB0F^IeYT@FFyg>lZ+W%7;s;rDPpSHp|bt6zy7XgtHR z#j`eK>pip2Vuw7h{*RTrFMj);dfGXQHign#+WYo0_(mP#oc_~KkoRnq^Ix}p!5&|` zD*7#7|NFFG>?qGNrXL5rRaQnmezs58#r^ZP54&gAEcb}}>sD~kc3-f|^`rkA*m`T1 zd)&LZJ;UvIo7C|?VzFtna}KQgefn6wrtaj+a_(8n8&|#Rl_{PmT`H@d&ev9E{liZ% z_N~>SmGxieDp|&UYhQfs`&^}{M-fL-`+INw=;dZu%%rgC%GzxkUHJaIz4{|(#pdS^ z-(73Xx#@ZRNc!G(HQfihrW+-*IZt?H`em-es^k!cBa^ur&Tf6pG2LG7??%Dg?cvUW z(P|I39nHIZuY8iOG3Sm?nm4N4?|UwCw`I5(x^XGff*4kZl$X<2WwFm?nEvN-dDkn` zJU;WikHhv%zLmMv^p2U%t-aRbcbM7b-dnqxygchwqPi`FVM~(LEfI}bpl(6Ox@5gJ zW47OZ8+RT%`*7O#gypt1>YelZb|37`cP~EMZp?PDQ|fE{+C5eKggvfyX{G=Ed)|$; zLDI?cLvEC^=i4>0kF6cI%r)fr_u>4DJA4^iP49eFEU`XNKIzA;w+cJzOG@(CC77Sz z6+N3|?B!)L+h)?`k(cJ7b@c`9u%8zkA0Qa z{;uG?`oHUb8JC#Y_VO{W|1w*d@7k-aT9eK^%l-X~)j`yM62}Go(6=%rOg3Mp&Ym|l zPwnBZw)elU@CHmt$l1I2HK)bD{<<*kH+$s&Wbb+R^ZK-VYh13H&#p`Q%yi&WXw)K) zqA%W|)zR-c_-mfIO@b63YP-}s-g-T2`@ch~d>iQ6`c8l-86 z*fA&n=U%E>YJGp*?;VRa-j~d)t@n&`wUoKaaO!l8`o!MaWUiYz0YRm#2Np-1^W3d=?mv8IRoy;zsZ+Nr}?AxID8JhjoJR%?%nU5<@Oo-y?=d5Tjg5(ASAa`))6!=8Yq7Bev(NU!Sae@Yh4`9b2Zq7umFPv#+9m{;!jFvgZA9-4Usx+MNdK zBE;2h|Im}dH@SfMyGe!qUa^0VtQ$j)ey9wczwXzwr;R>r)7odc2W`mM_MSya>1wpe z@@LOIcIiHLSQhq`KU9oi>T1?n$t)GmdvCP%w<{@KG^p-+&$3DL@blU07=D{nkg*Ft9~EP;%4}4roA%hZNkSBVF%85 zg1Xr`ezsFCCjYp^lO9_7KjyMfnmoIV`>R*&8$lh{oOSE8bG$yhnRfewt~i6KjEN%taP>0#k;$Bk@2q7IoVY?Gj?=#H`&=ipFh9-txi2}R zbr{YVOk>!!QCL-b`hk*KZx;y86W0O4IvN+ziHDrU8GwoE|w=O7>LVF6R;y{P80cWWY53v(;0seA#lVhjGQ# zy)2%a{o_8*b#P&^lY3;{n4=ZA+iZ6kE5n}fb`7Dn_3pbQ8I_dwotd|aw_%Zn<65?^ ztE(6c4*%r)vC^+mB_eg{hm-X+%LOLpe0%78wTdA42O>0;1* zO&68ff2^4IU3p|L_+_8upj!a(k8|FOC`~#Uw_m<%+m54qegwKj7JdJ(*vfREC&kXd z>gHn=-nbo-*VcWU>*Qi|@ssO^V}JJrdt9|MSsXFtjpNEYObp#+^&N|T+8=)X{imOz z(##h-4_1Ab4{e)L)^5B~CWZaNG^1xsJ6Fo+D`wqbnKz4T?Y56|U0jTg>f0Y+t=?hl zp^|ki=-{?AmF~&52214_KFe`$)d+AA6+dWv{Wr+GC-=iweC*dcT)oodU%>S857WdO zm4oc!4jI4EwD`uq;q$zGlaIJ(a`yby_xZyy?Ogk^wF^G)D|k?NVad|>C6l@yO*Kf< zsIfnGcYoeRv9i~^d;8jc_0}x!?qAP!_kEj7=&5Lo?s^rOPx)07sKltH&dA@g$`Jc>+H(3$;3O?6oZ{>|Ck(a3o zx|Mv?&vWDJU-PZcmJ6-CrN#61OhMSvqJmRv5A$Bul-(m-o$zAbD@zrZ?&l7VpIvy% zaNl!L?v2R>uUB5>&x_j4c5E;I=eoAP{XveRrP~gbhTDG>*NBY$THMwB;PQWmX15Ku z9bcXmH}{WakKgmyx;5mzXz+>EE9K_g(rNf=^3mqhynohb=DxFbE%|F9C%0omS@tY( zJLcxTt6Uj@VNp!m=LPMaX>je(D{}nPF+tUX=kF?Z|SgE;hehSu6L?0iZ{I%`4`E(JWo7Ed6VDw=7(Qi zC%&&cIM4ic?RU*R1;Rb@a&Ic7&04REImP`=I4|w~f9G}Ig;Q2UyD{IIz3|FTY2yiU z48~nbI_KF7^*C?VK0nBF_wx}6i;tTgod11S{loN{AGPV%ZP(t`Td5t*EzjS0ny=+F zKi|HO>=y5YO#eNo%QID0(woP*=wo|B6$3*~{H}=)b}O9MRLtDYY-1rdYx~!^N7irJ zT5$EDf##9BYiDn5JM7okv^in=yoa&slmC2K8-4EAVez}0&LrMkXl-L~EO)`wv(Nm> zw{HkrvSrSmCDBdq(j+D~S%fa=-Fo)9@PhIQ&!FPTRHtFBGhxJrlbuzb*HjqAfSW zI;KwF>07l=hAsJ?-}^bA-?1O=&Ci^?ysJo}B&MqV-O@ZB_1O>i2;F(fl)@e`->8mp z)1G`ww{v@$>#DcD7x_1J`oX{AJN7bOTI-m6<8i=^zXC5d*}u7Unb+^MN_N=J@^AJ% zrQc`o|99Z+wdr~6|K(#o+g!<>@Gsl_taE+Q;}8|SXP2A!(m1l4tr8!6f1PV*&Q#C; zYmbmna*V#i|NFoF9?jqS;ndp?XTGje_@FxRaTUj|H_yvT-)!2yyLQ6lU+?Z_h6eK4 z`xk8wUDi|D`|pfr-Tq~2jOwwx>GvP~oAWkq&&{`@N*@y?bKDA?CoJdL$M6EQ$;Eg= zYV9n;X(tLVGxJNnu=X_Zk!0UKS8A0Vl+cHVV3+1n#wp;vUp#E>apv`-JU8st_e!(=K`|Rx<_SPl$5&nni9}T(PveJuc|YHU~ZZ($g~f(J#cTqm0hi#%w&HyL)#= zUv=D-xAi75KbNHKS@Zd$!1m-tVZXX(FZWSwEWg|q%p3a7Ta?-9XT7fY(jpMU1r99$yq`Y!Mw8% z^g1tem%s11XmN_^o~r4$6<2zua|UrE@#JKWGzvd@);j_OAmXm&|*F z+s&np6~$*fpY}mS{NUH)yC&XxbS0l9ou{eZzu|A+m$`1ME0fVr zB}(>4{oi49NB-VQ=Op&Y2`2ZM?jG9J*0Q2bdB;QDwVCl#U*139z5S5-`b_Zy8ZYii z`^hhve|1&Or&-b$qQYKTcP@EW&9`W`q>rnFI{(*aGjt!w=tO0zt$StO^!oqeIaf|| zGcbtxfX)TDc=_tj!acVaq@4-au<%cvS7}dSx5nIW<=ZDr%E^23Id8ew{LA`F^K|wb zD_5AUx!fMj@I|FR%!hqb&EB&Tr%$%p+p)OMdv5Jna&|7?dt0{m_AlK|c~?l87VnOf zEANb!c3k!Cnf-%@_Nxtgx8K~7@LWOs-_4L0hm&*d*BvPczj1w{fF$$&-GY99XTMju z7#8(tS>)c|D{d!v=I_$vV=?7>3|1#I*)jOl$6U>Qm z3V)a$M9d00l~4XzV&!?89_%6a9Vb!gR{E zbL{)gqSjv+%(Hk)U)_XP<}Gup9Ii7bd~d${#a8!Xw7K&9_TT5ry)C?#Nu+S?G?W$h zif6pgF81W3@GI66GHQ(JcUjcqS3eDF4hrNeu4~DR-+lc2{X_b}3IERg;L$Q++xCj# zc4?XN*YbP9R&Sq{S$x@;8K|bE-+X*s+x4yYqS`!f@2>YqW@JeCypwf@-rVa`Oe|io z?0CePv8Zs~LQT!G-EYqR6cjsKc6V=g+U|pG+-W<{R$U5Jn9*`OX9Z||)b3OLQ|t5g zcW3{QHu3YHmb0Zo?@pbS&yNe`LYz??(f{&1tGFBXZBaYOvmmn9e_F-ORm?HJ7m1~Q zT)Ky6+8-|N^xboL!u8AL!}Ut#!*z?j4~m8JX>Ge!%eSDg`1*?G>>aPoOXg?aIdu0` z=AD|q_oMS)%k8YJ`#1k)-kbV|>o#w>=aLrXrD?tN^%9GjcH5g&EZbh6DaiAFuX_2+w}qUQKFqh4{=PLy&6ck@ zdtS5I+gf$O&iy+tKe!g0@M`+L$sQGA7bf^U%MDxhdC|kSY!SSmN_YG6-@7Kc3EQ=N zE^Gd8_sY7{qbBloUAx{b@80@nyAAL5+E=__1aE~fs$(eoQdsM`xVFDwHT$-!WdcfD zzrE(!@jZt3%V|FQ$=>3h7-CFr;y08{T>I_|~Mf$M@PlmzTd!*;IK@dg85! z@9X|I*Vlo~B!wTyi|=Gz zs1jY{%>B*KS>t^)%Pq^>Xdsq9@>~_Dofy{gB_9cE^5M@(1$bhY!Wqs89S-eO07r z(s_nJ4TiEq29hh@TX$DncrCHxfn2VR%FWbcEEO;O^PDwKx@DhOxVm!w{Mxn;MjQps zX)MMK>^08?{O#mlE#tXY-xz%^Ol7j}thaU7^xv&KW*ZDw&`B_sc%#y=SV(;S22#mPRK&6}q3PB6ZH7)#kl5i_1y|37@8mHGFSg zTX(y-?C$$eKmG9k?d8uM!=|Ro9p8U`X(p?oiN>S8-8+u5c5a#>@FGuPfsva}sLb^p zYR6LNNX}h3)5SN7C-kI>RaQ&kqKP3UP2MhEzwh1d=;F}Il8g*dQqofhluF6o^?Tpv z(_1p6F`{2^m(tn;ui+ub2VP5e)<99_b)Z^b`ww3 zaPb&+99^ifQIdhd$<&$ijAB~sqn!2358u3gXg%HW=em2QO1H!J3(ni4e(T-!L;CR| z95t7_&V1y%sa3|EyW)}m0n2-`LeAB)9QSuKuF`Lbt*W}-QImH#YKEm{)wKQZlHW^p zE?*h;pyqwYv`6L)Jt+)#BY3`i$eTFh*4lCt7N0-IU)V4T>T6GT<^F%>?Us(+2fj?rGG+#c6_401NHeFhpE{NJV&+AKf+yyCvQ@nA#R=TKX#cw3YOjBp zPQhH!pS|VBrXMY4kZ_c|JCmK~Q{KcDi$s;4{Yp|Cr%vt7R_T3Ze#n^rV9)(Sv-PX4 zdlc<)VRhbf?)QJKjWP@jhcfz>AMtLO$2|Gk>&y1LYz)2y-S{5xUdpps)LWrGzNGm6 zF8@8*Dv?s0HHW+Qypr9dyHS>b!AW#-L;bgR6IeFM%(37)7Aq!ezUDZ`QeCtBhUap9 z>)z_mdL66mDf(ll=+E-^J#Ra|GZ?hUeR)*s@V5_rc6`=ckng zJMs(#6Miab+>@8Rv-o?U$8ipcsnhzuzkVmYaYdWgKlaB-@A*7uG9Ev$fzzPG$%H3D z>0kMTxuRYh3iQs@^31Cjv8y`1+vC#nJne#|E&OtEd4i){oaO;-*Bd*|?e^I8P)Ga2igK51l{5whj;S_Xh6i^$8>P*6 zesILgVxbuftAj zy`ON+>k0#dfR?53W5f4+f(lmo2?s6~+{wJ?P;lhrhWigMzV|+!nbq=L>~OSx{k{kL z|1T?_dLoSRvCnDa!`4f5h0GZkTD;VfW1ihr+~jLpb#S4&i@%tRoraVEpPiq($o8;c zQ_EeVRf`!c=FZDWV>Gz1+jU83*-K7#o)%_yw|;p! z0|7o8u5Y$FyZvQMt{m<%U%a^Nel*W}-K>9go!dUCdWvn?7WWG?Xh=GA)v1O-_ zKEto{sVd=-kTj52$z!!OjYTk~zySwKXKk)nYMLA-tOxRxO+P@W+Yih*p zVx;aajqkeqJJ99UA~}nzTzj6$UXtqsC0d}Z!>KR-r%Zs-2Y+Ao3=*K+YB_uO?kX}x5xkSzm4%fvVC=5g%5 zsu;;f{|B-c1BL4O9lR4o+$TJCU9i0zbVh)e ztR-7CulHVOE3c|D28Pv7bsy9yRDdkbVtMe$MEBo>O^gf&xNP|T9NO*d;u7>Y-Lvv& ze)N(&28INQ|7{?lqV=S@yN}dk9W7|{hRQJkzv74R^I8=Y$hH!R~3#dEZZ1H)SQ**UT|3ecWP#f*dS*8=1D=Q;gLuW@cy*tzh^u zv%I~dW5@56qC4+fA97C1WBRj+!Oir$pv8TV$p?zT^b;F;fJd0->RMVC~?0?!r9p80n9AtQSRh89B^7vawh|G-k6 zJyT7JR-H}SS>Cn%OdM0j$$5`rdkPOkfn5C3q?F}Y*2n$~hK#gIv4A9<4fjCSTitOh znsf2bBnwC81L6_7`V7JiXTfGAnoUgJbM?cVa*sI~3_pal1H2QMtaJlhRCuKf&TW*M z_l>VpnwQ~rf@>B>jeN!zP%M>YAF{ar$>yP~kh>Jaw!>XEO@*8R;P^A+J|H^nx#Rl@ zJ1#MN5Y;*VzHv1uVJ`7yIu;uCK<@nni^~ifn8P0UFA7Gs!)=9-Lgo+L@A(7;Z!Yr;d*JrIW61?R2Wb=kspb{s z9UUkBe-_sLm%CWDp^PImhVf1(n}(oZKI4vaTz8(yPBM{VxTP6Vtk?Lv!RWo1;KZIR zrGk~BJFi=-beb@{X$d%KDN)a`t9pA!N6Hzoo?_keCGRIJxx}zxvXkm#gJq1id!k)j zj26v1@O{mR`Fouw`7k@MbA;A(AK>Y^yEod!B`A-%kuz+`3)x8}QVdoe5=Rq?Y*@d1 z%@Y*#Txr!)I4yl$xrfvXy>EO;TlcgC@~!~|lBaC4YU9MHKh}L^3|0{a)|UP8eT5W*RpZ$d-dc&~!u{pl z9Ue`0-C2`!%}VbnW-jq&n5)qcc-Eiu56j=k?v5>C2X?gDm`9gfnqR`C>lLdIQq0(> z>97-|?MyDC-bSY2z0Q~Xm>tA9ulA^XFbH^fS4l}IIGbZms%u(xdrBi)gPw?6p^0T0 z6MNOP{{4Nk=7$0Iq!aDd&*QchV+G9!VT^X z&0v`m)l)2PXH}lGRyke5d0?h!MXykEqZBCi^ggIq`dxYS-k;flCqj3AK!Q$za=@o| zf`Ttu7-t+YI2O%2zv_609^(AD-H3x2+7_=3Vi^%SJn@o0vXHKf=i9U?qKI91s zW-hhr(bhfRd{1#xtrUZhLQg{esh!ggNPwIsyJBf4caE5RMf-{H%c>158*Z*qIbYt8 z-B|TrPH~~MzdtT3*NZB+ttM=-Sim0&Uv2q0-5q}8B}J7 zt~_YLS#bv>bEIO1(DBTV{$_6(mbk1)QYsMNu)MsxW66|_LO(V>o%wFAlC{4zgJvS@ zvP2nXpRaj>6R&W(T>sU7@zH#_TZ|qH1J2$&Q2+8CgW%1~M)Q+}GoMyovv#$bR>lx? z;*-zvBMytyS!eC3c2Suq)#zzb)Fk%5Y)Z1#qy~laU z=L~j*r)%26e@sz4(0jn`y_{g@R$i8Mi<=Hc{kiOK%^)e{wn_f(4pEEqyIoz9R2eR3 zKAN8+c6x8}lI#qI6AKJx~3@w%9Cv5mN<4Y>d({DTbLDYi>d{ki_&AsySDL=Qs*c02h&Xo zW}i*lT;6kJG8@Cqg#l;tSk@_7`K&m!+udcG@(0Odu~R5%S}9U>5#s)BXh%+XbDHw)R^W% z(+k&kyQ?_cG3F^3>=rGZF?~xv8^g`aM3&t_$1D%*Pm+0gS9wy@F|Pz8)w;E<@nzH4 z7+xAYR4Y1@D$^+4xbU8>lIA9n9nD7XwyCfD-Z!!20)xU_r4S>T9deI(O769I{AQV_ zc6|NG&n>R`Z-ieONHHu4ZuR0jxPxs6%L|av4ABkt$$w6LI`h46;+IPd3b9H(OXYiG zIR9|mIKA6lr7bM+j<4|ktx;i>$31>!GEC4DRSWWyO;3t(OAz^Gt+9zAnQOk`)wqPo zH_tt|go?j*YYdnBuTa|xrv9aDKzn~4XGN*um; z-`jIJgN^rt@-GRy&L%7_pO7-U?t<5vmMlJn548`99wfY%o5=Tp@dJC&on%v;uYH0y zy_g%01nuaQDPF;J?(htTH_z^>T*|sAxVJyiCsku>nXu}6m5KV785A~(yKT^Z8X{=H zZy~JlJx}Onn_ACuO~a{favN{1`OYWwQ%j0LCGe2OH-+Oj4_F@9-jH|Bb5d@v#AeNp z0(UEJWc|2e?XhI534=#c>%}iiJ3k(9>B-t+tPs1Ha|XN5;v>yfY4MZ1m>ZTjNjS3J zwUmAA;}w$p=G|SDm7DlhFv_VLIO{)}XmWu;VWqIzrCw8>gA)967-gQ`Rq6b~*QjJU zZ-Kd~S+DLrMw+rqQoc|sV~7fEw&Ud36#s_ehOfmQ!S{Le|MQVc4W4{=ll9$WcDf{#b}gKvp( zVw5T4ddBn2#U}z+@BXHG+JvEFi-6mO?sq(_ZOqF}>Z^W-d0Jm({A~CvFp1AHZi9{V zp2aSYoR}L2)n=%D|6%bE!OJ)AmBVgeBd*u-9h?o!HK%;>PBxkc-nYJtfy zL*0FKvFSovqHRBfCnz3MV&Xozu|rYk+Ra}|*8a>59_m~PA)D=wB~GyrvEX@Zz|<~e z5ptOEbwhc>QVDO)KT{6`?b$=9=`5cpYBt;IyvIbH%M1!a#feIHBYf>oy>RcV zbBJdWWzjleTF)Dj@a4#jxd+xc@GoNSKh-%eDZ|ns=kI&nTqd*aKT0_xxN{iI1a5fr zRWZ#MywMTZ9_Zd_RkSAaW2tHHnlu*q?AdWD_x_28Z9DYp(=&^?3=9kmp00i_>zopr E031{A9RL6T diff --git a/assets/images/revanced.svg b/assets/images/revanced.svg deleted file mode 100644 index 7318abbd..00000000 --- a/assets/images/revanced.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lib/app/app.dart b/lib/app/app.dart index 6d7ab8ec..8765ae4f 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -5,6 +5,7 @@ import 'package:revanced_manager/services/root_api.dart'; import 'package:revanced_manager/ui/views/app_selector/app_selector_view.dart'; import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart'; import 'package:revanced_manager/ui/views/contributors/contributors_view.dart'; +import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/views/installer/installer_view.dart'; import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; @@ -31,6 +32,7 @@ import 'package:stacked_themes/stacked_themes.dart'; LazySingleton(classType: PatcherAPI), LazySingleton(classType: ManagerAPI), LazySingleton(classType: RootAPI), + LazySingleton(classType: HomeViewModel), LazySingleton(classType: PatcherViewModel), LazySingleton(classType: AppSelectorViewModel), LazySingleton(classType: PatchesSelectorViewModel), diff --git a/lib/app/app.locator.dart b/lib/app/app.locator.dart deleted file mode 100644 index 6476d9a5..00000000 --- a/lib/app/app.locator.dart +++ /dev/null @@ -1,39 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ************************************************************************** -// StackedLocatorGenerator -// ************************************************************************** - -// ignore_for_file: public_member_api_docs, depend_on_referenced_packages - -import 'package:stacked_core/stacked_core.dart'; -import 'package:stacked_services/stacked_services.dart'; -import 'package:stacked_themes/stacked_themes.dart'; - -import '../services/manager_api.dart'; -import '../services/patcher_api.dart'; -import '../services/root_api.dart'; -import '../ui/views/app_selector/app_selector_viewmodel.dart'; -import '../ui/views/installer/installer_viewmodel.dart'; -import '../ui/views/patcher/patcher_viewmodel.dart'; -import '../ui/views/patches_selector/patches_selector_viewmodel.dart'; - -final locator = StackedLocator.instance; - -Future setupLocator( - {String? environment, EnvironmentFilter? environmentFilter}) async { -// Register environments - locator.registerEnvironment( - environment: environment, environmentFilter: environmentFilter); - -// Register dependencies - locator.registerLazySingleton(() => NavigationService()); - locator.registerLazySingleton(() => PatcherAPI()); - locator.registerLazySingleton(() => ManagerAPI()); - locator.registerLazySingleton(() => RootAPI()); - locator.registerLazySingleton(() => PatcherViewModel()); - locator.registerLazySingleton(() => AppSelectorViewModel()); - locator.registerLazySingleton(() => PatchesSelectorViewModel()); - locator.registerLazySingleton(() => InstallerViewModel()); - locator.registerLazySingleton(() => ThemeService.getInstance()); -} diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart deleted file mode 100644 index 62e381df..00000000 --- a/lib/app/app.router.dart +++ /dev/null @@ -1,231 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ************************************************************************** -// StackedRouterGenerator -// ************************************************************************** - -// ignore_for_file: public_member_api_docs, unused_import, non_constant_identifier_names - -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; -import 'package:stacked_services/stacked_services.dart'; - -import '../main.dart'; -import '../ui/views/app_selector/app_selector_view.dart'; -import '../ui/views/contributors/contributors_view.dart'; -import '../ui/views/installer/installer_view.dart'; -import '../ui/views/patches_selector/patches_selector_view.dart'; -import '../ui/views/root_checker/root_checker_view.dart'; -import '../ui/views/settings/settings_view.dart'; - -class Routes { - static const String navigation = '/Navigation'; - static const String appSelectorView = '/app-selector-view'; - static const String patchesSelectorView = '/patches-selector-view'; - static const String installerView = '/installer-view'; - static const String settingsView = '/settings-view'; - static const String contributorsView = '/contributors-view'; - static const String rootCheckerView = '/root-checker-view'; - static const all = { - navigation, - appSelectorView, - patchesSelectorView, - installerView, - settingsView, - contributorsView, - rootCheckerView, - }; -} - -class StackedRouter extends RouterBase { - @override - List get routes => _routes; - final _routes = [ - RouteDef(Routes.navigation, page: Navigation), - RouteDef(Routes.appSelectorView, page: AppSelectorView), - RouteDef(Routes.patchesSelectorView, page: PatchesSelectorView), - RouteDef(Routes.installerView, page: InstallerView), - RouteDef(Routes.settingsView, page: SettingsView), - RouteDef(Routes.contributorsView, page: ContributorsView), - RouteDef(Routes.rootCheckerView, page: RootCheckerView), - ]; - @override - Map get pagesMap => _pagesMap; - final _pagesMap = { - Navigation: (data) { - return MaterialPageRoute( - builder: (context) => const Navigation(), - settings: data, - ); - }, - AppSelectorView: (data) { - return MaterialPageRoute( - builder: (context) => const AppSelectorView(), - settings: data, - ); - }, - PatchesSelectorView: (data) { - return MaterialPageRoute( - builder: (context) => const PatchesSelectorView(), - settings: data, - ); - }, - InstallerView: (data) { - var args = data.getArgs( - orElse: () => InstallerViewArguments(), - ); - return MaterialPageRoute( - builder: (context) => InstallerView(key: args.key), - settings: data, - ); - }, - SettingsView: (data) { - return MaterialPageRoute( - builder: (context) => const SettingsView(), - settings: data, - ); - }, - ContributorsView: (data) { - return MaterialPageRoute( - builder: (context) => const ContributorsView(), - settings: data, - ); - }, - RootCheckerView: (data) { - return MaterialPageRoute( - builder: (context) => const RootCheckerView(), - settings: data, - ); - }, - }; -} - -/// ************************************************************************ -/// Arguments holder classes -/// ************************************************************************* - -/// InstallerView arguments holder class -class InstallerViewArguments { - final Key? key; - InstallerViewArguments({this.key}); -} - -/// ************************************************************************ -/// Extension for strongly typed navigation -/// ************************************************************************* - -extension NavigatorStateExtension on NavigationService { - Future navigateToNavigation({ - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo( - Routes.navigation, - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition, - ); - } - - Future navigateToAppSelectorView({ - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo( - Routes.appSelectorView, - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition, - ); - } - - Future navigateToPatchesSelectorView({ - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo( - Routes.patchesSelectorView, - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition, - ); - } - - Future navigateToInstallerView({ - Key? key, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo( - Routes.installerView, - arguments: InstallerViewArguments(key: key), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition, - ); - } - - Future navigateToSettingsView({ - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo( - Routes.settingsView, - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition, - ); - } - - Future navigateToContributorsView({ - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo( - Routes.contributorsView, - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition, - ); - } - - Future navigateToRootCheckerView({ - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo( - Routes.rootCheckerView, - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition, - ); - } -} diff --git a/lib/models/patched_application.dart b/lib/models/patched_application.dart index ecb7a62c..ea530fa6 100644 --- a/lib/models/patched_application.dart +++ b/lib/models/patched_application.dart @@ -1,21 +1,43 @@ -import 'package:revanced_manager/models/patch.dart'; +import 'dart:typed_data'; +import 'package:json_annotation/json_annotation.dart'; +part 'patched_application.g.dart'; + +@JsonSerializable() class PatchedApplication { final String name; final String packageName; final String version; final String apkFilePath; + @JsonKey( + fromJson: bytesFromString, + toJson: bytesToString, + ) + final Uint8List icon; + final DateTime patchDate; final bool isRooted; final bool isFromStorage; - final List appliedPatches; + final List appliedPatches; PatchedApplication({ required this.name, required this.packageName, required this.version, required this.apkFilePath, + required this.icon, + required this.patchDate, required this.isRooted, required this.isFromStorage, - this.appliedPatches = const [], + this.appliedPatches = const [], }); + + factory PatchedApplication.fromJson(Map json) => + _$PatchedApplicationFromJson(json); + + Map toJson() => _$PatchedApplicationToJson(this); + + static Uint8List bytesFromString(String pictureUrl) => + Uint8List.fromList(pictureUrl.codeUnits); + + static String bytesToString(Uint8List bytes) => String.fromCharCodes(bytes); } diff --git a/lib/ui/views/app_selector/app_selector_viewmodel.dart b/lib/ui/views/app_selector/app_selector_viewmodel.dart index a3541144..27cf1c4a 100644 --- a/lib/ui/views/app_selector/app_selector_viewmodel.dart +++ b/lib/ui/views/app_selector/app_selector_viewmodel.dart @@ -4,7 +4,6 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:fluttertoast/fluttertoast.dart'; -import 'package:package_archive_info/package_archive_info.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/services/patcher_api.dart'; @@ -39,6 +38,8 @@ class AppSelectorViewModel extends BaseViewModel { packageName: application.packageName, version: application.versionName!, apkFilePath: application.apkFilePath, + icon: application.icon, + patchDate: DateTime.now(), isRooted: isRooted, isFromStorage: isFromStorage, ); @@ -57,20 +58,25 @@ class AppSelectorViewModel extends BaseViewModel { ); if (result != null && result.files.single.path != null) { File apkFile = File(result.files.single.path!); - PackageArchiveInfo? packageArchiveInfo = - await PackageArchiveInfo.fromPath(apkFile.path); - PatchedApplication app = PatchedApplication( - name: packageArchiveInfo.appName, - packageName: packageArchiveInfo.packageName, - version: packageArchiveInfo.version, - apkFilePath: result.files.single.path!, - isRooted: isRooted, - isFromStorage: isFromStorage, - ); - locator().selectedApp = app; - locator().selectedPatches.clear(); - locator().dimPatchCard = false; - locator().notifyListeners(); + ApplicationWithIcon? application = + await DeviceApps.getAppFromStorage(apkFile.path, true) + as ApplicationWithIcon?; + if (application != null) { + PatchedApplication app = PatchedApplication( + name: application.appName, + packageName: application.packageName, + version: application.versionName!, + apkFilePath: result.files.single.path!, + icon: application.icon, + patchDate: DateTime.now(), + isRooted: isRooted, + isFromStorage: isFromStorage, + ); + locator().selectedApp = app; + locator().selectedPatches.clear(); + locator().dimPatchCard = false; + locator().notifyListeners(); + } } } on Exception { Fluttertoast.showToast( diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart index 551f0c48..295380ba 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/theme.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/available_updates_card.dart'; @@ -14,7 +15,8 @@ class HomeView extends StatelessWidget { @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( - viewModelBuilder: () => HomeViewModel(), + disposeViewModel: false, + viewModelBuilder: () => locator(), builder: (context, model, child) => Scaffold( body: SafeArea( child: SingleChildScrollView( diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index 79882510..c2e958e2 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -1,8 +1,22 @@ +import 'dart:convert'; + +import 'package:injectable/injectable.dart'; import 'package:revanced_manager/app/app.locator.dart'; +import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/services/manager_api.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:stacked/stacked.dart'; +@lazySingleton class HomeViewModel extends BaseViewModel { Future downloadPatches() => locator().downloadPatches(); Future downloadIntegrations() => locator().downloadIntegrations(); + + Future> getPatchedApps() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + List patchedApps = prefs.getStringList('patchedApps') ?? []; + return patchedApps + .map((app) => PatchedApplication.fromJson(json.decode(app))) + .toList(); + } } diff --git a/lib/ui/views/installer/installer_view.dart b/lib/ui/views/installer/installer_view.dart index f915a568..ef359925 100644 --- a/lib/ui/views/installer/installer_view.dart +++ b/lib/ui/views/installer/installer_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart'; import 'package:stacked/stacked.dart'; @@ -81,9 +82,8 @@ class InstallerView extends StatelessWidget { ), child: SelectableText( model.logs, - style: const TextStyle( - fontFamily: 'monospace', - fontSize: 15, + style: GoogleFonts.jetBrainsMono( + fontSize: 12, height: 1.5, ), ), diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 708c3ff4..7c30088a 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -7,6 +7,7 @@ import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:stacked/stacked.dart'; class InstallerViewModel extends BaseViewModel { @@ -61,29 +62,25 @@ class InstallerViewModel extends BaseViewModel { List selectedPatches = locator().selectedPatches; if (selectedPatches.isNotEmpty) { - addLog('Initializing installer...'); + addLog('Initializing installer'); if (selectedApp.isRooted && !selectedApp.isFromStorage) { - addLog('Checking if an old patched version exists...'); + addLog('Checking if an old patched version exists'); bool oldExists = await locator().checkOldPatch(selectedApp); - addLog('Done'); if (oldExists) { - addLog('Deleting old patched version...'); + addLog('Deleting old patched version'); await locator().deleteOldPatch(selectedApp); - addLog('Done'); } } - addLog('Creating working directory...'); + addLog('Creating working directory'); bool? isSuccess = await locator().initPatcher(); if (isSuccess != null && isSuccess) { - addLog('Done'); updateProgress(0.1); - addLog('Copying original apk...'); + addLog('Copying original apk'); isSuccess = await locator().copyInputFile(apkFilePath); if (isSuccess != null && isSuccess) { - addLog('Done'); updateProgress(0.2); - addLog('Creating patcher...'); + addLog('Creating patcher'); bool resourcePatching = false; if (selectedApp.packageName == 'com.google.android.youtube' || selectedApp.packageName == @@ -95,29 +92,23 @@ class InstallerViewModel extends BaseViewModel { ); if (isSuccess != null && isSuccess) { if (selectedApp.packageName == 'com.google.android.youtube') { - addLog('Done'); updateProgress(0.3); - addLog('Merging integrations...'); + addLog('Merging integrations'); isSuccess = await locator().mergeIntegrations(); } if (isSuccess != null && isSuccess) { - addLog('Done'); updateProgress(0.5); - addLog('Applying patches...'); isSuccess = await locator().applyPatches(selectedPatches); if (isSuccess != null && isSuccess) { - addLog('Done'); updateProgress(0.7); - addLog('Repacking patched apk...'); + addLog('Repacking patched apk'); isSuccess = await locator().repackPatchedFile(); if (isSuccess != null && isSuccess) { - addLog('Done'); updateProgress(0.9); - addLog('Signing patched apk...'); + addLog('Signing patched apk'); isSuccess = await locator().signPatchedFile(); if (isSuccess != null && isSuccess) { - addLog('Done'); showButtons = true; updateProgress(1.0); } @@ -128,13 +119,13 @@ class InstallerViewModel extends BaseViewModel { } } if (isSuccess == null || !isSuccess) { - addLog('An error occurred! Aborting...'); + addLog('An error occurred! Aborting'); } } else { - addLog('No patches selected! Aborting...'); + addLog('No patches selected! Aborting'); } } else { - addLog('No app selected! Aborting...'); + addLog('No app selected! Aborting'); } await FlutterBackground.disableBackgroundExecution(); isPatching = false; @@ -145,13 +136,14 @@ class InstallerViewModel extends BaseViewModel { locator().selectedApp; if (selectedApp != null) { addLog(selectedApp.isRooted - ? 'Installing patched file using root method...' - : 'Installing patched file using nonroot method...'); + ? 'Installing patched file using root method' + : 'Installing patched file using nonroot method'); isInstalled = await locator().installPatchedFile(selectedApp); if (isInstalled) { addLog('Done'); + await saveApp(selectedApp); } else { - addLog('An error occurred! Aborting...'); + addLog('An error occurred! Aborting'); } } } @@ -181,4 +173,14 @@ class InstallerViewModel extends BaseViewModel { DeviceApps.openApp(selectedApp.packageName); } } + + Future saveApp(PatchedApplication selectedApp) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + List patchedApps = prefs.getStringList('patchedApps') ?? []; + String app = selectedApp.toJson().toString(); + if (!patchedApps.contains(app)) { + patchedApps.add(app); + prefs.setStringList('patchedApps', patchedApps); + } + } } diff --git a/lib/ui/widgets/application_item.dart b/lib/ui/widgets/application_item.dart index 805e8a24..4c5c49db 100644 --- a/lib/ui/widgets/application_item.dart +++ b/lib/ui/widgets/application_item.dart @@ -1,40 +1,30 @@ +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:revanced_manager/constants.dart'; import 'package:revanced_manager/ui/widgets/patch_text_button.dart'; +import 'package:timeago/timeago.dart'; class ApplicationItem extends StatelessWidget { - final String asset; + final Uint8List icon; final String name; - final String releaseDate; + final DateTime patchDate; final Function()? onPressed; const ApplicationItem({ Key? key, - required this.asset, + required this.icon, required this.name, - required this.releaseDate, + required this.patchDate, required this.onPressed, }) : super(key: key); @override Widget build(BuildContext context) { - final isSVG = asset.endsWith('.svg'); return ListTile( horizontalTitleGap: 12.0, - leading: isSVG - ? SvgPicture.asset( - asset, - height: 26, - width: 26, - ) - : Image.asset( - asset, - height: 39, - width: 39, - ), + leading: Image.memory(icon), title: Text( name, style: GoogleFonts.roboto( @@ -43,7 +33,7 @@ class ApplicationItem extends StatelessWidget { ), ), subtitle: Text( - releaseDate, + format(patchDate), style: robotoTextStyle, ), trailing: PatchTextButton( diff --git a/lib/ui/widgets/available_updates_card.dart b/lib/ui/widgets/available_updates_card.dart index d2e05b7f..075e578d 100644 --- a/lib/ui/widgets/available_updates_card.dart +++ b/lib/ui/widgets/available_updates_card.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:revanced_manager/app/app.locator.dart'; +import 'package:revanced_manager/models/patched_application.dart'; +import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/application_item.dart'; import 'package:revanced_manager/ui/widgets/patch_text_button.dart'; @@ -50,17 +53,19 @@ class AvailableUpdatesCard extends StatelessWidget { ], ), ), - ApplicationItem( - asset: 'assets/images/revanced.svg', - name: 'ReVanced', - releaseDate: '2 days ago', - onPressed: () => {}, - ), - ApplicationItem( - asset: 'assets/images/reddit.png', - name: 'ReReddit', - releaseDate: 'Released 1 month ago', - onPressed: () => {}, + FutureBuilder>( + future: locator().getPatchedApps(), + builder: (context, snapshot) => + snapshot.hasData && snapshot.data!.length > 1 + ? ListView.builder( + itemBuilder: (context, index) => ApplicationItem( + icon: snapshot.data![index].icon, + name: snapshot.data![index].name, + patchDate: snapshot.data![index].patchDate, + onPressed: () => {}, + ), + ) + : Container(), ), const SizedBox(height: 4), I18nText( diff --git a/lib/ui/widgets/installed_apps_card.dart b/lib/ui/widgets/installed_apps_card.dart index b77deba3..a69971b8 100644 --- a/lib/ui/widgets/installed_apps_card.dart +++ b/lib/ui/widgets/installed_apps_card.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:revanced_manager/app/app.locator.dart'; +import 'package:revanced_manager/models/patched_application.dart'; +import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/application_item.dart'; class InstalledAppsCard extends StatelessWidget { @@ -33,11 +36,19 @@ class InstalledAppsCard extends StatelessWidget { ), ), ), - ApplicationItem( - asset: 'assets/images/revanced.svg', - name: 'ReVanced', - releaseDate: '2 days ago', - onPressed: () => {}, + FutureBuilder>( + future: locator().getPatchedApps(), + builder: (context, snapshot) => + snapshot.hasData && snapshot.data!.length > 1 + ? ListView.builder( + itemBuilder: (context, index) => ApplicationItem( + icon: snapshot.data![index].icon, + name: snapshot.data![index].name, + patchDate: snapshot.data![index].patchDate, + onPressed: () => {}, + ), + ) + : Container(), ), I18nText( 'installedAppsCard.changelogLabel', diff --git a/pubspec.yaml b/pubspec.yaml index bb504833..09ee05e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,10 @@ environment: dependencies: app_installer: ^1.1.0 cupertino_icons: ^1.0.2 - device_apps: ^2.2.0 + device_apps: + git: + url: https://github.com/ponces/flutter_plugin_device_apps + ref: appinfo-from-storage dio: ^4.0.6 file_picker: ^5.0.1 flutter: @@ -28,7 +31,6 @@ dependencies: http: ^0.13.4 injectable: ^1.5.3 json_annotation: ^4.6.0 - package_archive_info: ^0.1.0 path_provider: ^2.0.11 root: ^2.0.2 share_extend: ^2.0.0