+ * author: Blankj + * blog : http://blankj.com + * time : 2016/09/25 + * desc : utils about clipboard +* + */ +object ClipboardUtils { + + /** + * Copy the text to clipboard. + * + * The label equals name of package. + * + * @param text The text. + */ + fun copyText(text: CharSequence?) { + val cm = app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + cm.setPrimaryClip(ClipData.newPlainText(app.getPackageName(), text)) + } + + /** + * Copy the text to clipboard. + * + * @param label The label. + * @param text The text. + */ + fun copyText(label: CharSequence?, text: CharSequence?) { + val cm = app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + cm.setPrimaryClip(ClipData.newPlainText(label, text)) + } + + /** + * Clear the clipboard. + */ + fun clear() { + val cm = app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + cm.setPrimaryClip(ClipData.newPlainText(null, "")) + } + + /** + * Return the label for clipboard. + * + * @return the label for clipboard + */ + fun getLabel(): CharSequence { + val cm = app + .getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val des = cm.primaryClipDescription ?: return "" + return des.label ?: return "" + } + + /** + * Return the text for clipboard. + * + * @return the text for clipboard + */ + val text: CharSequence + get() { + val cm = + app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = cm.primaryClip + if (clip != null && clip.itemCount > 0) { + val text = clip.getItemAt(0).coerceToText(app) + if (text != null) { + return text + } + } + return "" + } + + /** + * Add the clipboard changed listener. + */ + fun addChangedListener(listener: OnPrimaryClipChangedListener?) { + val cm = app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + cm.addPrimaryClipChangedListener(listener) + } + + /** + * Remove the clipboard changed listener. + */ + fun removeChangedListener(listener: OnPrimaryClipChangedListener?) { + val cm = app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + cm.removePrimaryClipChangedListener(listener) + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/FileUtils.kt b/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/FileUtils.kt new file mode 100644 index 0000000..a219acc --- /dev/null +++ b/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/FileUtils.kt @@ -0,0 +1,42 @@ +package com.github.jing332.alistflutter.utils + +import java.io.File +import java.io.InputStream +import java.net.URLConnection + +object FileUtils { + val File.mimeType: String? + get() { + val fileNameMap = URLConnection.getFileNameMap() + return fileNameMap.getContentTypeFor(name) + } + + /** + * 按行读取txt + */ + fun InputStream.readAllText(): String { + val bufferedReader = this.bufferedReader() + val buffer = StringBuffer("") + var str: String? + while (bufferedReader.readLine().also { str = it } != null) { + buffer.append(str) + buffer.append("\n") + } + return buffer.toString() + } + + fun copyFolder(src: File, target: File, overwrite: Boolean = true) { + val folder = File(target.absolutePath + File.separator + src.name) + folder.mkdirs() + + src.listFiles()?.forEach { + if (it.isFile) { + val newFile = File(folder.absolutePath + File.separator + it.name) + it.copyTo(newFile, overwrite) + } else if (it.isDirectory) { + copyFolder(it, folder) + } + } + + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/StringUtils.kt b/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/StringUtils.kt new file mode 100644 index 0000000..6e4836a --- /dev/null +++ b/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/StringUtils.kt @@ -0,0 +1,40 @@ +package com.github.jing332.alistflutter.utils + +object StringUtils { + private fun paramsParseInternal(params: String): HashMap
d7_Q23crZ%^ptl_k+WEW@=zJLhA3s(Dl>}|81?}HhQ)-mm-~{GsVxqq+ zm)J4hTSgs(O`UF|bs4IN!)s;U^R~) z2AJ3T0cs9FBs+~k-C)g?k(wu>RwL?soj*s _08;>tm }(yBB-7NA3j{zkq#w5Exd*>imLmWuRiY3^7!6R@b+vhZL`WGx z1%9pk?~ujGmMKA53kck5`g}nxkh0Y9s}H~N{^!zSRKZgqL#V>5MhSi?ee+!lbwYH~ z|3lw8i=4Hzr2`-g5+H4X!6&V{ Y$dM!|lDr31S+!TM{T@7-?>b=BwzaI9-^ci$@d}=S z8bt9P7J&%VAOUagcKv< #lK+^5AqhgNWJn1q6G@l?LP|;qGnEpCWJpGs3L;FKm}ygjln9eBB_)I)WNIQ+ zGF38!WU4SEkYuV5X4*6r!elrw0aJwu1cD??LXbo<6^0}vK}fxerN=&9EWKgraNV!= z+2Xudtpm2&_Sp9(i#72ckyz6 z&a!Tn;BPWR<}x}X{6O>R`Z)P-f8)>3#~Kz{?!WzotasG^fA%=)|L}5ut{wG%|M~sS zPiCCVbbq>g-yLu5-@hN xvxzIes!Nk^%|H&Ec?*hqXVP z7K`&@vGfN>yKURpn;ZMSPx9{Awrbn9Z5y$3Y8x?Yw=rtP$uD{^-g}GB5&fS4_OEnv z74Bw?b0#6@1~_BRj!uFdof+dgoVnDvdkA;0c41tvN`Z{ER$6ZCxgUJZK7!4@%Cis5 zP36j7mcj?ZeUzt-%vI}si6qSiN+oIiukxQaRxZyal$R{kh h!BZKd3a0I;e!cd`^S z3TXonREfkBv_DLOm}8Wdf*bv3BQU3^c%cN#i)jHO26}$oLFgXJN)|ROoAyTHa(B=9 zN?<}RtrxbH@+RSSZ_l^800P!g-Z(+i3TR_+<++~Umw==Yq^8q8Paz>kDRVfkfA8rJ z#H{11{XG{E&Gi)EIiF~WSHxh?k86lMMY{qJd5F^D1SDkBktkgG*?k6BM>G?_6rvU0 z7V}?A#Zz&wujlJcj4B}7zGlsuO+>lz7E36kodLMo)pZeAN(N6yTP$?twglkT(1mbd z{O*IgTgJm;@D2cgpb7ve577;P0HJ?ai6qT>?@A?U(Q;!=4;5dtk6^Q}=}{YNP%&=v z;( GBoaysUJA2`FotznN KcqP1n{VM??=)0Z( literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..bc0455a37ba75bcb94772dbbf7f487d4512c5309 GIT binary patch literal 1708 zcmV;d22=S`Nk&Gb1^@t8MM6+kP&iDN1^@srFTe{B^@c!z;D4Kkh?oFY0nbZH_W!Ao zoBt(DXJ9;lZ@_F{!_P~5<>wXcnVG}P%*@O%%#v!+(<=21eqm0eML&h{ALdl3SCy55 znvhHiY-=Wr!!s`eu5H`Kp8tM|ZQGTN&bDo 5~QFLWKDu3M2-a^C}aw4VIdsMzb_+G zHRqyP0T8aDR7^X^w!*%3o_F7scJI9B69B5&vjBKm6goT3y8|}3{kHKTp#=oqgb6}X zW6LzNjV;b)#LQ2Lnh+}VAOS?6!`Ezc*1uub#y89s|85|WN&wN~ur?TrWe@)_?67tV zkVqteJ*jQ*Bla}%nQNzk#8oq3Z)g*<=dt0MO+rXqgn%`rW5YQ#F_+E!j4+#m-K>q_ zoEnF#gQ7O5EP_UDfWz7lbB<$O!(J1Dpq^%+j?X^JHq2XLA$46X4YvNdSV#n#GZbp8 zwTWa}*3G6T(;xX<6n{e+(e1TxO#>8wW(dtX8UMkOi5G-|B3aa-qE%8~FvfrAV)?$h zXa*F@3$&l=Bq*KZSR(m>fKQ~X_>lMj(W`mgBD0#x8!7cQW3dI7E&rV0=ZZ6_ z>RuU=O&41C2Gos 6ar@`e{ z+{sN2QaCII?mugtXcZ7Tvw@>1OTQEj$z{$jVZZB{iTRb2-35*F=J!caIi T8^paalB>?O t+*v^vuNm zs_3oZX H0bqpDh$LfL0 1f!z zaidy|tg} ~iIaZR3I@007x051(ysCtE>n+qUg4wj@ZBZQ9m)wr$%V=gogsyOz5D6ToX&BSeHm z;IGwTw=;=$yE25#j8G6YVnhs@-#c)K`Gz7pBYdQe^pI=h33 WoAj9&P?evx>5u zY^~mi{Q=D`&k7b{Q_aMVRWrI$jA{*p)apqPimHNQy@?>aC+1rtYH?Ck$S6?-kNIIm zQ%+Q?*D2lNVoxMZ)H4QR>P!R>Kn(PcqbNzK&Imw_fef$yRK!4l8r?{cBB(bJ00GE! zx0L4eJ3v=vkWgFQ(n+k|tZV4~Ro!&K{`Q+Ux|NO3Ml=Nd;LgZU^WSfp{QhvXT4D zKsIC-XI-TMV5FSU@?5Qc0MwiKAY^NAJj~$~tthGVXA&TQnCes!u+F{)W?qugtl|g& z_eR4SuEVCy>z_y}C^NOz%PlLv?iby^X81Wt8mp$MLReXL6zp&JJ3s)jigpn&Nz=Xc z fP6V!sy?4b7n0mP^J zr8#S~J3x(|3@=IQ%^n=HHkl^xSy8o+t!+LB*uLx@+6@&l1_C@j*+@xR#TL&z|8jRf z=lUbX&MSQ7zkdO^quUCq8tnjRa`=mq3hGR?`O4jW%j)lFeOsmb(_x%j_1fAQbbvTA z7N;#qQ{9b|_Wqiyz8~_+%sNtx>P!F?1G8{ftGPkN;bD5>AxU{@=CbD}clYBq{x74; zTJLzh0uZQpIeiv7 CGG*yEeJ|fAx+F+4~;ZYMy9z0I>JIG8!po48)99 zQ<4(o;;F|!?(S!ve-%|TzdcE_g9IT-TAue80E5}6l+8T>3_z2eoMa|$ym5CwZu9?0 zbz73w2>lLpN5fS5Qegs!HLS~JYj@NEzF3&HB+U-iPT2luuKIe|khHZk &{Lt5SbiS-o#>rV$j05L!AB4CnMu%#1s{+i3*9<$=Cc~_yfT1SFt>r0TTF<`pF zexIqeU%I=Wu=Q_dur5hYR4A@DbC30{pbB7sKa-?nuh-m~TI*%StfH-|CmyoCoTxUR z1JmtP=wNBr-OsywPqFi|Mb-L7pr*y;8CGSWqa&5 kslhRVA!-R+NQc241y zV|y^N8uER~*xavjxPcX|_sVYRif*VDH}katX7Zw>BYsj&)FYvy8BT*cN~UmML`V)H z+M s4y}JVCh%0-oNhGPN1;gmvBDC77J>2+;PobzPA;2r7A4L1 zIK4^#e{|(oi3$dY06hqq2L`|bH~^>iXD^@l_M@29mH-rh<%1{?c>oGOl$!s$?K xd2cmr)Y$cgZFk>jBSIEr+ooOB z#lf`^CnvUT8_%|F+qP}n##e0Hwso)|NRk`3nI(7uf{=rBDo>CbZJQ!F`9~pUX69vP zW;~wV!$oKN>)z=w#7qgOidl-`z&!a^XA6C1bQJ*0KI;NdjmT$NEK?>EYykj>M*Hxc zZJX~Ivu)dUm~%l8z{rqPd$w)c{Qs{yNmZr)4kbyd8qTXcuWTWJ`2~F7A;Kc;!w4rr zhaKi(HPc~)n+c*-5@a%mVKtE(5G2A!_(V;Nh$XRo-o~lj{;02m7E}#h6J)ANCn&Kd z6UlCJpZp;Oq=9skK{EWrg}C*FX7Fr=9Y{e f+RMy67+m_}2@bS3CQe}X@L46V NMar(d5SX?-F%sY$mV4kL~Re<&2q-denE)Jy})9UB7=eU zP>K-AZBPvt6^|z2GH5H|W5PdVF2`+AE+;B}E+oN~ELsMMG@L3^S#FcEDN&xbgd8 zZ3R&{Mb>Z|RU=ALCy`oe0S4jk_8O-t3V&W3vGAV55VZ$-uZ_xv$q>|ktne`NW94p{ zv)MS$hUp4eX20oSC9I5+BFAl4PL#mH2_} 6WyDCcZd#k4mX*aY-&_+zfb}Hd3X$j5)v&O|Lj^Q+W>odN zs0BAJFX~p^RT(`&pvn(Q7S^ R3vWU) z5Tse$%<$&+`Lb@$WY&0!F&R~Hp2=7@E(s%2N`7Fh3>LhujNKVr#yH%`V$2A>Ni{Wr z#+A8+zF4vAu2wiBV(c&7BUzm2Xsr%Y?XWkgf?{qWp?^QuQA77(IEhdzVv(hBX*D(^ zM$`_A+nDD|mtI1ShZ*?`l31L%yb_)m53`!t&5RJd$Pgn7V7i$GZrAlnAG5fTgh|Ok zK&=vm90sYo6RRe-GWGrWUjfH;BiD0jwMGWf&rNn gNxe(Y(i0@Nda84B;EJI{2lZnL6;pzP z^9(uJ5bi%mpEU65?GQPuk&$Xa&0)HyPUtO! b>Y7XP$LK@ERekW%%qSrnU(0QvKn zORG`Ny#IlZhf!1=VOcJ>$Z;hCxC(bvc{NxRfojr|r4p_!G|v|;97e9_gMqrPrlQ&+ zc{Q+;)HoU!&9rRxr34lY39d8b rqlO#5Z+ba?l*4)s{$xrP{U1!gxhoIz34qJ^KyD-%3HqEQQp<{HO60A;z}tGReO z?&+ZQTO(9Rtf?6`AR1S*YV-N@|NFVnO|-ragMDr}aI9XLcT<)hxSFR9;M~>G8Uke{ z2Jkz>I!6#$X=>WveINaQDJr@v07^5y1|^ARi3d}z;sqQXx3jgh=&*PMsWG5o-UQL* zi0=2_|0PXMUZUtbh;$~jY%N+$7gu!Ca~+GyRx4C?3x-z;7ZVWwhhKe?>{J=gk(3n- zYoQd(rC2a!>GtzG>N%f&S&fB6ExffRfZb#Vbj~>ic?sEf_g|nEC{(Oguv)3{Ay6%r zoR^E#)iFyrY;sYo5)4Ttdn~RQC%M@z&aqvoDf;^?x$_Dz8o`)d`c6pt5*) zPNX@;7bxsm>(O&fSRRNQ{1T=~(N+@xx!p%pO#OL)$Kh8W0=>Xs?UsRnlFCkrG{-_P z`+tyomta#a!3E)R_e`@HYnBU?38Kpx+v~Ug2ajJ_uGOxnz@@i9TuBI$@1FKKh<@oJ z;*1FJ7uYMZCO;Rdx&;F(`LhXg>%geHcX*uo`_R0ZVZs^~{sJ(6CPhuYG+~P1o+meZ z7ghoI6f=a$&$SF^qYa=oSkb~66e{yMWM@ThwG1to6LxHB)?&~(Ss!k=s=%Ur6iU_ObtG;^U>V)gaGXW!&mpPhdhdP$k6-%>LdMmxb~^>B>{wJ(?KIj8^I zbe#(bs0uR7k!B6syJU6|JAKN}BRn5@_l`A>0~wu ;$CN0K7TTSkEB)@Bb#+tAMB;_7`Y9 z2t0}1ThDYkwlC*z8q4K7ztn@{V4z;~8IS&V^(~mQ-46hfY_ASI|AICjz6L~A4BXPJ zK2cj*FBv2W7j{O3VEacr;5}&GA7OY26r^{W>qK4lJ_GF=7~Fz3X@xvgH(ex%&EZnQ z4g8&&K~ci4ST!YZr08a|({cs4(#qdbPsDPw9!%>NMZ5aWxg!Lu3J2B%OItYQY~cey zEZYqvn_oG}v@tJ-!3uy_T*&ffx;2W=R@RO6X1|OCC+2T5{V^wJJP4F=#qehYo!zjQ zoACAwIr06ec{>AF{8xhTB(yQZn~HDVr0}6;SS)s$&<)_W7dM%hk7%$2@tHgdT8$HD z1zSAC#}+<7VQ`=PB01pbH(1^RM9sKW=)k5##zC`_RG1vZtx57F#d7;{{`Lt2Ha#n5 z1h94j-I=bpHoThN0kk{WEI_2_h2d8p@Hhv!K~m}Ojp(qr$zX@zVFhrf2hZP08G#RJ zaR#3TZH5EFP?DcT(Qg|WCg533Kiyt>nI${4JJzN81cB}$FX z-YxRYNTsNWmF%nzIsGc- +85v$0PV#wo{q&dBYoi*f3HYEL z*nQDzC&lfTlX7OR9l-lXBMVh@5ZJ@Z!0__(S0IJYZI>?>CeOcY)tUsF5g-Gq!R!tv z>w~oYAD6yd7H)9 TU$EW5bpIC9FF{thh~)+K(=ZdPBSFi+S17axPMw3uB#3pOPDrPwS%a@xLwz2 zQwm^1+aB;Y{Q3RU+wu7NFKU%}Q0@L-FahUDf4x@v4!1#$FYChH|AN~a{=2&sLsW0; zUjqa<-s9zRAX7FpCncAG+r9y(XT0ET)GW5%Rc~1TMyD4Y|9(FrN8IXTkAnonQYA zkF{(_o!u!qG6CBQEuOjh=ZrV$?$4*IIoGmQTkop(T5g?4dNu~@KK<_Yeku6=!}1QZ zS^i 01H>y%~V{ahKURW9f<@9_BMSB5D6&^2gJaU1IZbO l*G(?Z+4@>>f<{^W6?XjC39 78_bd+F4L6K{EcxajqpGzL*b7_*i*sB2YECSZBoP!Tt{ zFkR77kP-4>IX{ 66%d{Wd%< zvhGgrW~ bZAP _`T zYQwRw;EQg&?c=GlPFj|J?U&2J5mG1Ne7^)fu{^y%`!rdHzJ85XVB4hm{_)*Cv`OCX zo9)`2en-UKVAqCY8}4JqUufO+&fNLMB>!f}nX_` )c|~`I`Rzrb++Z<@xo` zTzKu6@$0nuFj#KVeAmtB%tomMU?AM4aSx`S0jm!m*M?IYuHWk+VDRu!<0nXb-!6aY z!T)=Ee)u@B+vYQeHtt&W&$SW?ZI<$C*NS%}>si~fXX8!4w8`FJwN0Z&jkejl$zL{U z-g2wm<7?G=3af>v>uukOe{1z_mHTf+wSQvuZ^gg!HU>V{Qp)pylc=RiBs@ ZKRq%?R|F$B4PsatDXGI z3NFl?nW!_=Zz9p{9mK(hRJ8wBXE#3+W@gR~qT;dwX6E=P=169~I^Yw<%*@QpOfz#- z;7rbmq9dc2QRca82rYw?QPIq>fJkNr#|4Gz%29RXxYZ%WumoH~nO4A{MpchI>grQa zI4<>h%IK=NTj=xrU9T#0y9lO{WJ~G%bNTwK@9)o@0TLvVDg|xZFy_y=yKrnjf?fwRym z4lT)zq9dR;41P7apo`_`l|dWHiI6P;0NF_S@U?AQ_Y@m7=4#uv-G&e(Nj7cGvu(fk zZDaqtIz8(CPXKFAErO0 t-`TN)%Ry~M0GbAqntj2!g8yaIl=IIUfl_ps8jFhMfUV%1Us^`d2bWdkDiD08 z!l4G}jhWzQmQfW!)CRej61U+n0+=%P_zt?9X$lIl53rKL#Gn#-h~?bzcj)rY;o>~` zK`m6R55bt61-F8)!{_O8o|of0I?>6Wx4zV{{?0(~jA~?bc?F1=WdDu_8w0>jM);C? z%0omTuf^~xz)ebwM&T%q3Ihj6BH}a(L#eF_;L%BHKapQ%Z6QkI(b~y?DxpNjn8`f$ z0wI#;F|Q<{gL}b57Sg#W>>)!yWu8j~T9MM+j8LJWo>sUZNj0ihl+eJlVyZ!fv_Pm# zduULe@Vy{Vl0X&bgrPvkFyqN0TJzi%0t7V4b(v_kqG+mz`WhQ)Ly9&@(Q81V>Y^zI z6`gxQTA2;im8T=3M fw{9F2BNS1ltKXCS+#UoaZAcPkOJadaKed*aD&~X56_d8w=1R z$2e6r6i ##zp3Xz!3!Ge$=bUJ8-|uns^&a9R)}1;`Bl*o&2|`)G?PZv;#cHu!LuBrWWDY= zm!bu8)l!`g3k;$x?g2n_aY@u8K;kKQR-%%52@C`$a{3PI_lX=rQWUVDCJK2_aO5(k zdnv9hg#`wZ_94~LHsAsx%ce{^XAL`hvQp<{)7~|&_oB(^CJ=XzSH
(p@J zUBqMF6_H=5?2cTUaIKx6`f86@l0X+E#Sx#cg2}~U6FgX3491VIvHgqI+WV=m_Xw^` z2Vuw2K1dae*l-Y;R*fOoqQ1@BlZm%^ee#M`$}7qXNM^^3Z+y1H1V7!}Nya&zaed55`sb;faKeIiIAB%TrDa{)KEG zPa@JS`VO(x^RtOJIQ?)h>a4RnSpd=%$pxbI<~U98VEd4aGac(OL}6scx*KXqbRS5v z#(IQ_;;>IZj!9#kgKraW^Y-LdEBd0DRz W|RuGW9ignwAcGn2h-y<8O|_!gx5zTx*Em21Y^1*aMC z3-Za;ZQmYEyus;5`?yJrWZCs~3ye=zO-Ak|4??=arE!8#{|dGsXz7w+Z=Qc);vX}e zYsMq-G(<#hA3?m*0s-Cd1DTqeA!Hw8$GEZ4
WCT1n6yOuXjKZ+T|SxH=aRl 1Ru)&A7mCf^9gQQ)*ap5R&4fAp@kD zT;MmsR-|+>E{x2_5b5>};}3rz4xONZ%TqygulpWI)6z*RQixoqO-9FWcK2%Ht)8Ba zL9sNW3Xa3e5Nlo5gLIpoCQEmQRRxc3A0*?i{y?j5{Z(d1jH`2QfkEW^Y`!M0DFl%* z1l8^XS!xThfH;_OL%p}|;g^Z8^~)8sEP;WL%UinHK4kld8r%B$1flzkD6TnY3QjfP zI&{TOzqck{_vrgR#usmBk%Z%ja=FGgZfMbP)JTpHY=O~Oiy$DlEHz$p`-e#XzwJeH ze$!!3WU qf1+EPZ@Q23e4UTtp1VXWCJRV(wOB;!6m zICspNta;U-=P#Dr2Ce;6-RE#Q@6BxxvD^Qbn8X m-Q9w2ZX*d8>_wB>r17YXP zEH?*oNUF%d;&MQbway+8VGvH?0&=^!%X9zT4vOe2AZ|~~i!J;Y9KsVA0Jbw!fH?+} zWr6+ D4b zzyRSrGvJn92Q-Y4JOqSJ{`l^weGIDrn9{p|!2~WFgv<~DBF2bO@c%{Y`%HRQnk?`z zF 00?4VoF|!u;j( z6kQ#L>%3JMwlLedpHeKC%pWq64gpEx|Id*G+O|!%8jbDLZeu64zir#LZQHhO|Jt@~ zo6*ZV5+zBl+hzwS;0}u;WGA5W_yQ{0wk?~R{jVZrW@a8{X6E^sx$w__-`_iL7SbbA z3yK*%yEu++YNN|Cn%**FKy?KTDUM7VQUw)~B^Ovg!A%xWl_iwhuq?=qD$Wk7b9@Cg zy3x_JjG|)Jk#!Xi0{|e~>~Pz*?WMMD+jbk<5F|-9ZOyas{l0B`|GPTfgYLfsw~Zu8 zN>3nrW;eGPRlRl15BSTzfmlH>J_weE&u8I{8H>*i!P(pvorj^$VvOZ1KAW3<1PC2s z195?fAu5RWG1p(|;ZLkaujjTI)sn>>bPd9X_&^pyjzXS5A|N@C8b}vJ4>3b55FkCw zwD3*8AKc4N|261+{}bd`Znse+Xc^y1kj0QYkYq?Jq0j=lppNo9ND}1s5-a(5-WU=- z8}e0yjDcK+WGozHuNF%n=Ey)0Qy>;K5R`_hnoN !AbB%FacWut>!l}O15kCpN^zC5mwVgfNq zoFotvsYppKJXo08_=?CNO SXMP`zW0A~dt9~TuoscZ8AOe4f4838U*8py;vEXCb? zaa `qwp#U;zi6u_SNN`pH@{$C=8t$ 6zFr{u#~OBZ41j|^@~*#SS~58>p7-9t&qkLoBucjE{YpZ;>o0X$?W z=NBx^Nj)MtVGqfsLkUPXdgdXt{~7d6ZIW@ zasWSwNn(K^^P1R*)50<<2|uP9=?S;`^c;)e!_pP(Wi81Jo#svnKQ59VXog|>4Q{y- z_t_bPb5@)UUV0ROF|nUplC&UYft0+`tB6m@hyzP^o(-(<8lE3_iT&V`>c~mY1@Khr z1FKs$xePnl`SE5*mXEq-=EQz-Nq`{5fwnm+cK}{zw w6gmKzqb#nq^Yb z>5`b3fjj_7vFMUpE<_MyJQM(1j*INXrNtoKC+J#= zorF-^PU(1Q vT24JxuG3V>lyE>f93M?;bcE?m<2s*st|gTv7k zy5;-~BMylL*qU`pS!N=~ ;T4l7dP z<3x5;vXz%Bxd{MzC FDFHOk89RfnVlK%o>`u$H zUdKu_V6~R$q)Z=C6r*e|yp+s{Q?|}?GlNb74nQu;H{2T8s$*UrqG@W5(*jA0{Gus| zF;xWGBOE}PgRY%`iz!d419MPa3v%^0U!d37MQKOV2-CQf#7rWjAO}DnL-}y z7?mi-to19f)fg2_!xUnXDcJ%qD Xp?Ll}(RPG=$BPcXo&-IT$!0IwsYJLW>@&UM6dLEJRCC z?pS8ys`|3JwKNQ8eNb^@Vbbw~)O4wm6r@}jPKLjort^X~*N0k*5vPKp#i|=tQ(Y$s zCM&$z`tYOO{QrPW*T0kOCvLir3^!Ttp)JM6g-TA4a$`Ojf##DypsQ{)IJGc0?Br+v z;vuh3vg>5SrN1L8oCE%Dh;A$%rzds|$x>2+lnWDs<}hCrrIsO#GEa(Z@;FUvMvHMY z-E`xp;Z^u$ZF`QARpdY&r-7u?CsI!I$Hm`#DDZTh2WcRE__obXcT&eKUXpcu3m@Cv z_v3azWHwy<%M5LZ&(Ke@_X-l;NlD2hBpdxl$D2tfM-V_eHVqM5lN;av6UKs^_yJPr z$LX6-l$59>E1d^JMORhxL2;@`9X5?j&;#2Wu@o(rY=81L0J6pQmmL`~p#!P$D17lo zB_T-ZXxa@T^B{BqxD_bE^`08S9x`Qkh;yH0$MFUWe>e_SUU6giijZtE8JWb9k_v#G zxoS|!8-WDilqDILZX;KJE1a0nQs19#y!MN=_5Me9ZgkTcjK;S0Ne zz{<~UOsZ=qc`}e-q@NR73NpJ3uy($B_VZmW<&9*k9c!JHYIFp#G|&{U;%8N8Ix1uU zGb8&6mcUC3ugU2d!9(}H#@b} h^G{KsAPqL$dtD zrd}s!1i#ST_w$adN%I}`5ur}2ss!1Sy$#(mi6aFKTS64Jb?`Lt)^$S#*c2z+VYt!I zFLFkBgly!}^42D+KUmuyz0Vf2c3Oqj@ncDbVXDyziO*Ue6e~|d-X}BwFFRL=!SXUn zwSmm_-9(Fz?{2>Hg|*50&jX@b;~- nRgmyi;WI%LgpDemK-)B(%<+%O|`IYE8}kWNQN z{Q<>AnP^N`YK@jj2RL`f;6%plLkHNV O >FvgykK8+S1*WKyr z1#6>~-=f?OT0E5S(=?IY5*vHjgyt+CgQA4niY8e=j?V(`PN2z5QZ@lG9^hJ>T!V&+ zN|1+4S9m!CN}r72Az8EE*X=W42}&wTIesW&)v$p@%qO0Y=GUJUZcF`O>y!Wl@n2FH z<=+A9zog-^A#&nkSAXVlqQyL&tiW1OKbWDVne2jddoi^Q((9;MBYMGu;!E)8FWZIV zM`Bz6Sf813l`-lc&d!UtjyWGKL!#7|WX*%P#>c9a_zb<|ZI #h@!%z3!Tmch$RnjF3$dCTW~!Jvv}*~q4ly8)Fo)v_bPsx?B*Hd*ZO(My><)# zWKBn)%q~nh={DJnPFnKysdl0$3V Lre2>=+hHTJZEGJj|;C~2&pg?0(}WukitM+y!+Lb zur|xOr1&=h17Lp~#+JrN^fAvKLpJv!f;mKUIRF!DIL9?7&C*#$BpPuPO_B-G%Em;Q zUJ5i8O;Tby1k Fh7dsBC^i81^nBB6Xu@L$T~pVaZ@l`8wZrMFo9d&bwwWVz zIZ1V}MYAtKd}PCfcm4Y~oiUHhn}-8%JULTKVb-JY5cb%jtQ14Q^7cW>Ype|x|JD*< zvW=2TPqlNTW@9dr8svy9_|RtHdK5imjoC1E0MCy3ToKSc*ij`IfE~ 4>2NFyn^nhij+1bqoONrf_&o>pU(8=p3$ufAT~pkrAc2WWIa_ zvg!pJBwZg%5DNeo<=tyGfnykhTVpbR@`l^QI~R?@BsvcI54f d z>GI`7W~uxTqk^1js_o b{;=+A*UkP%_Mq#eHn7?n{-Jm+ zEYqS545?`^wm$d>zxr>xleg;DlKZM_5vPNMR}&wu@22JfTIeAQa0X7*)-h^K_W0G} z1Q4er?)#Ct&bqoZ=V=aJR-6e3DlLm?JUM-bv)8WimNWBYqGhpyD#=BLi*4f@l}GZT zUReq42R?!pb8( kc(jNltD?WCE$hIp_7?F^PeTpkXo6TCXql$X5>k5| z*wgojMz#Z(51ap_EX#!jd5i#+q`-1i*3Z9T+(PZLK^>(4WB_*qcm`jR3;6&os7vBJ z(aU83&c==4Aw%M j97b@C)d+b2-TXD;&9Y*+ zC{M(?ai4e-__ssZOSs$wXEC5DDmO;Z#J&iFP4woE@eVoA_8Ha=RmB1B{s(o`o1u1_ zj2vhK2)r?r2~YEc0r*1(Z=YdpP|Dd2f>;P>ii-OL4>@X>N8xcHdjPavEWsNmumv)I zC35ni(c +hQCQhJbzStp9hac?@T=x%I1KBi8)r4R zkD6eYdHl$pFz*f5gAAZ}d_zQL0*2 gPRo(jsDDSI=wfjq zxI{;8phX%_%JFc3;+}6yj!LpBJbNfsdqT*E)`)2JD5l#W!5860d-Q!oa R&%WPL#sx(E|g1Y(CnZ2b@RIe27fX1a+ph_+C3!Z1=?ru68zaO z3&WRs#dsg`#+p% = zgWj{*?l? 6H$#83| Hks;I QQM>g+Fb7EIKQ<8YZzVTEF`II*EkzAc~?o$s{NW;!{nc*aIQL5CwqDz&MSN@VNc6 zM1yzubXBUY8X>@Cs)&Rn4$UxVl9W)Zmk$Bl*MRgDt3o+^xCZSO7=1K0pPTy)5Zy8r z9npL9UyN0!YNAAFI1(!Wb^g*%Rv-nv!(R0fM`g?A8S|O&;k$pucwK*}ms*^2V9*3W zh>7w(>xQ+*dqtJ$(2@qfoip_)Y&s3+F37XG_|+K6EmGJT)os(zA#Ew>OhtEUx>M5G z5pC_#&^lEuQP?=i_3-P0JUcCC|JBVZ@n0%@`01*=VmyGy4WH!&Fk|c-oM9B%>Pbon z^**a6z-`rt){6WUYeflAQr4wDbGs$i=uic3Vu?0h1p8Df7FR)Zp{9r6?~zIsPjy z*YChJQ6?eDV?{F#qfmB~2uZGf!+}?gU=2tv_lotSzIT=B5W^#=2UNLt7q7V7T&qSf z@C~achoB7QS9p#?OT@&+M~pfZz@g@^2JX7{T>Y*A$z0=&H`iSKt{Oq@t_S{A&A&`F zYD9c&j1goQpb}QU2SAJqONfe(2=R{EOY&5R AMpnPa mZcd7eiwQzpT$0)D@gpy_W}GMrRZf;=tJP`^asWkP-3tJ0$`EA$ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..73c5cde352733b480f83a4153670c5f6800bc1f1 GIT binary patch literal 3540 zcmV;_4J-0eNk&G@4FCXFMM6+kP&iD#4FCWyzrZgL6^DYhjhr)o+P8iHh=>V@2Qeax zc@`H Eik(cBZSxr?6{}*~IO@G)9odtLZ993cjiXpc_S^P*Ne}?AjHEpLY}+=Qc5K^b zF|5roBnV)eMv|AAnVEU_Zsz~Lifl_l)JSUFU;sRU|1Y#N0hF$7J8U^`+twfGnWjB; z_b=$VGq*jmZQHhO+qR9WFUEV;{)WBYV6V*2iB;3A98_E}MmF{=PIhd6jlE*q8L<)P z;EUg|?H>^HpxqT`V5V&w^XJmPe}N3j3?^;k{~x#A0M2b|$DE99Td{51tkb1UD%<%3 z>g_7&Y}=~rdOUL{VD7oLDIf?)0 ?VA#7Pu=NFO~iOxi(mwer{r8@$*`wYF+7g$?UIJ05g)uek3*lM3W7D?D5iRVp{{u z$38$|8;74)Crj(E HaeVml6 z31;E~V5%-4V));g0-eE|4wjMQ)&sNg***SFKTt3X3pH{!B2BDv5*(Nx>mZ~>_6iNd zn9u;~G{gz)dVO3n@}iy}M?Yj=GZ1v{b$l2=pOSbJdq7g=#nA`3u|2{-0Wc;aQC@l> zzcPx;0Ot54$xBZZT-XIn1nM$?H4e%0(gTIofoe|Bfilw_MOHho5b%N4n50Myz`mBd z!Z+m>xBf$58~mFPKA6l!&%wX3l3GdeK_WKfW{ZQBZh5mWDA5H8|ACnm2vtLpHYX ULvO;ORa`Hj->dZuvFd$64AlUlO)|= zSWFCBZb7JK)GRg5O{LJz @`zP{A*KC zK-Q1x@buQekFBOjNqiuq9$84wvPJ 2)UJop@dACQg?+xB8`|CnCn=IL=D***9ak&Fw< zTZO?N=Of)OSQ6m;e2L42SC*@h;##3z*zQ W6kqknnLv6TL1m zc `}pEbj;M~3=nSN^G`wjt&k0Pi2#n;bMwDFI1H)qn;76}?@f zUi}Xk@4+{pz67C-Q~UsMk0}F(r22uu^l%Dpjnq4wd6T?&XModwdlEU%fapOBNcO>z z643nsqfUkh%t)Ra`N9M++V%hHsZFFYHw6Hf)Z1<1YcpgKBtM_xfJ<00)~#O#I4gE% z7cD+T5?uh;(7_{#@a``t6bAp@W*MoQGyZvCfaBi(=24cB=CWo1c>Nnh=U5V>72QT) z4Y}<=B)4#awEG=N^$h^m;!SX$WJUo=3qs^*K=?Fc!l$1MaI)8LI#%;^pazPr0fx0( zBDR_lF?h6nNu+M( x1OyQXFV8mW*-Zj{(m7 z?_T&YLl<@;>mRiz9)>|HojG2Yi~5`n$7Xw+wL18-Kj&GSYYVfdFSGASNycO*RaG z1Oy%+uyMX&l*<#yZoBA2ExILp20(#OPm-`U&WZtp8Da3R4G3)JO!4-W0gm_R+mfVr z^c*Vt4dCBiDyo`cOF#fnAT-V>3WB ZL zp6y>5sRwp*rhNCp04IL_!M~jc49-p_?)-6|B7nI(k`{nVg=5y@jnM|OnLt7H$S9X4 zfKjjhS25T{>SoQjv3ear03DL-1%NBsrCT)qmE4PDCsK^mTa85%JpN*UUxf~{fK!v) z15h(+2<>12KYA4r%pIkT!r(QF5SSdkGV+B9V5~d8?93Wck8Q$WJxNe3D+U1P=hI-Z zgxb&nDXkY1uybq1xcQF(rp7LrfPFy<1R%*Z><^KH6&nVCwXjfX&bEQUBoFx4M(Vpv z`}K(dPW jhZc#|x%uJp!z>zOaIQdls{)55yysBvntSvo~04}K&VZ8VCW+|=~0^Rb8SCQ1{ zrCorg8CghfCPmLd8}%UXJuQe653jx%f7=^4Y=$2X3~;;$-`*tk&_1MxfYDE4+RW<_ zo>vf6rbw_E5)i1xTNDJaa 8 zyY`wwc+dhWdX3Oy4E2#OPZiY;5&A$EJ4y%+niL3X>5;UZPE&t=5kAa-s)ns=C3~UW zG9j2Zs#v`V-gu(tpH#-a^XoSRhjwOlr9?NlI3XcW??l(o$!UX3jEY+a{@=+M*Z&mO zkC+$@vbxTiUd2p7m}mXN;%i=VGGvm*c*StnBriS~hB4L07q!&3IBD#5u(GAqHD0PE zD6lo^#d9D;p<`$@?CCZ`LOY9spkw=L!SS`^7N@Z4P0}&6w>DGGvM (Lf_9Sptl}!X4kbC>7V^k{F0eXGh@?qc(%nZ7v`i}hYF|eBh79a-n3vseG zFtBeg8{3=Mmj$D83z({BgY7Z`fCMn|A_X-4P_+d3?nTMi-n%ZC5_Cn)<++hnc2LA- zK#&;?;57y#3xRcUzqq|jZ0~(|2GBpVr$ +ix!i z+i(AH!y6ABn57E4W sbUT>q@h2N-=*Be+GOa}U4 z9tf$|q`DQU2q8PM01uF7#T)QXMo1Ubj>i3=3xSovjmN(r{6Mu;!J=R~FdPpZQvdnZ zS@HfbB-UXJ1P~)FKm@4M;hTn_&yx|=2~C5sG#bwn@;?K%ED)CoVg;WG;%2};3IC(= zJP6Aj(UOel&+j|tt?ev;q6j5B01e>Wad`^7vl9}XjId;cCnc^J6>U&8B8V3K2q5Nw zy%PRBB>L2QF2Cc2P=|34yRp7IE(bi56OfJ2Y=k8v^pHO#@Q?~mJw;DneuppCWxxZA za$|kf0p&}l1HQ=#%1CHd!iNWu!-FWnf4^HsLe%}IzqDU#e|oY15hi!@JRI|0mXjJJ^p?-A6fm87b;)lC??db z+-qX3Z+syDzj5P7Jb2^AjYE9jSh3f*iU~Wja@@#kLPKLcd}!!3k*=edDF`wvr5p)V ON- H0mNk&E*ApihZMM6+kP&iBuApig`zrZgLHHU(>jhuu(?3+ITM8pK}ZP%_= zcRb>T`3@_VBbz&*_Eb!n<)|HN8iG&>WT~V<0nC=&FyO+IxR~u< W2qZ;NRJvNT9auc$>4>sckE$?asDs+qP}n zHf!5 ziEi6ik`y7R-Mb4~sd;pDb>9y#4@-fNArKOToDZSkGC#lEz-UJzdMOZUez|%}D$jrR z6zwL5 FGk(fO$NY&$JmUX7d)O6~YpK@&@^oVt0AWKM zAaRiSkbRK5kgt#oNCo5{q!*%qs3AHC0fAHzyZ-t9{7lEs)xFPWeajDQ_Vw7+Auj+% zo3K13A60@dAnuS!kOPnxkP=9HtQ3YyfX6T^iT#Xp@yd{i5V!Y2SDBxpEga>Tr?C8g zAafvBb0Iz0N2fxGPKlNeMT;;L9S+e|^yXP51a@67kel{_|ASeqHYm?W>me*i=x0IV zA)T0qgjb^BJ)sbeP@py)4o`18k9iSXvGTKeT^ze^I@6G1V3=oz1Vau(vLQ DfHNRWYM2-$rm>;G?Xq+VVH}5AS40(kU%^n(ql(IO57iayaVa=DTwMo zc#xHlA~c_O2qe?d8% 7y3`V|V4qJuA#r0|Ux>N}AU#3Bu%lB7X4 zfqJw{(p#i`FM4VeChAo%7t$z6hz=#`uuIaDi42vU`3wWGg}juceyl)QdU#1Pz9Yj> zRTA+km;sScu}TFI=j)w=p=OpLpZQYAb&6IIK#L$ZzLd$QURFNCgb)lVrmmNOU6R&b zlMse-S;z}1lx2`XX;6hg3wrF5lEGk>GZf28Vag$AF8Z@J0kQ7Yg zQUp+A3pM03A;dyDr1fnA{isAO1=Fz%!>KT@p`1}L4kAU3j=X@>9qBSmVo1NmX2=H& zQ84j*=#=!NT?$6A6w_p~hbgjxu^6K(?b4bk7GttZu2_m3LiF#VMqk>c)sZg5a8g@} zyaqmyHq_`$yRZuFoe3hbA^Q!a4ppN!FQ5P`(a;_@()OQxW@Yr1 TXk5wcHq{wUF59yQo30i_mwA}Law C!VNx>7;r(HOx&%C@g5;s+f+@0x?R48Z}I)vpwTNV-~m@hAbsgi06`LrBE zs_CDN@Mh^na-+bWwlH R_<2W*H>m#sI%E z=)w*0w+XQnc})VpjWUWlLF(8{2BG#pl2GCZsigrBW55#vmO$2sXt^a+0?Z(b3O-WC zcfj*f3gn1Jbc!1T_C8(s382UZ&5 df(**^YKE3_NPya)d8dLK1NMPk$$KEUa=E5BiMSSvDAVf&y7;{|2o#b6L)?Vi z2yobz20^SBt3uOkg!Facgi=76%iZW96|hQ>zAj#$z>NTYU^mKkgTAbpAkweFBFgj{ zR_u)?5f~HJNNxl;Y+E#9gQ%HExf}>XK_+G5zUfFj76CiGU19*l2pBZ569u;gSF>(M zaWy7Wrq^&{L8wGT2H^n%AVz>gSPax FuFY^wM1I>C%oVDrOmH-rM8y|I zarW=G0%*oVqxyjz#SU19(k$@;CFqq*i4t2i4&dzJU$ZO!_m7LXKd5TMxEjkO$stgI zYDQGJ(uNF%PbQ6E0qjtMB|$y58r4lM7Xj1|zEdPyFNsSaBx(-PFy!nRG_);=0H#>3 zZode=O8P01{ZlG^NHY%Q%wa>o{0vql(pab-e%rS?J1FvfW5W7)rc!M+P@sSo89#CM z#4ZEsbKIw1av&t}4q=9nDD@U7K$BlCGjaH&LN7{avrdE>b~-bN xg6WANa_sCU>Bs&7>`NflJ6)=T0M z37jfSBU+WH5<8}q(x8bYsR?s();KD*n2SR`buJ!{B3iohi5i R;EEb*zn_A&%Q0nk?G1SM22u~Gn!jur<# zco{7Fhj%iV|55B0)#bY7tOiy1Z6kmY%pWSK>H+ZxJI+HQ+69DSh6VQ!BnuhV oexzMr#TPP4Q1r-xtYiIT$e7r(x zMJO?Irf{*JISWTEg)%;lax+l8t8 -X_A_WwavjhpO2 @fTO-Q z4kc=(d&L85xrLnV5%?h;kUS8su6u%tk3vfuLcW{)U6W9w#Bd3Pml-d=c8ly3XNkww z73sbiX~9pF4*~-~UzF(*>6J@y$|3blC)Mj3R7zapWHqLnAP(txGqF5+&oE_tc$a1) z>G#Xx=OolvtLy+OwF!;jEU);dduT zQlJyaWN4#qh7%%#&_{q@U@ mg)!Ny7@^vg!uT3{PGw>h2JyXgtV)(4zN?a}S!yg?BRXY>W}MH`5{u8ro=#U5STl z$N&359F0i~18-FX3ff6U+?05^-q$r4_X|=qyN4Z?-0T^3&I*svnXZQ<*c%7~4||6W zU{=F7lwLAnb_+#c7#Z<13X~j<=lGGhadDD(GFun=cR`d>1!gBuiPT0S!dnGrLh8G- zU`yX@V#`fm6-Xh1d6DmdIDGsnfHgZIN)#kQ!^!7(CPwtcP7%GnqvNi`#q_EQ&p$qy zlbA^tt7uqpvKX|KL10yKGHE`PyJv*?eqrL_lgx7wu0%~D0&+11#~N5^!@@_4Yzv1( zvM=sS-%k7r$5@8&Hrf3>1Tu(VUNn1Qv^fwK4#ZC~&qlZs6?s?>uLh;Xhe9R);&IO9 zU6 -j7GX-{q(LvxE$Fz$g3vXgQp~tp z=I*~sN~-^V>ZAtrAzVQ{!sKouf_YKwno;_<$HUv}XC^olDQSr86yDXAJY#{CEHzIc zyA(NDgm(v6XcSg$;0q?VB2H%OhI|(c)87*;d`yR5C_QAM-8?)8^YEr{NMn?DdxIj^ z+Ri(Bie-m?30*To>czzeL1+3Nf*5?<4GjH$5h-YuNP>SZ@pv PL z9Z) AY{k^q-24UEtcCMv|aqDMu8~fV0*Ag-Y(R^Xv 4>WZ$*JDO#SVWPI;4ZJvrFs8Uc#!!~PxAK4ZUFs@3V z+ceI8*&G9vZM7zH34)bC9X=QRu}{Xsjl%^PF3uS;?i(CnB?rzCSjjhcl4E%f^B&j8 zC6#nzTMeLd<`gb297I<(Blp6ufeQfn&fx(JI `! sjc?c(P%m|bqx z3Qaogqwr@oL*x>!Vo`7~jwak8d?POI5Dq}U5wj&5;rGp2q~UN62mWTWx7_0~_r^B7 zGoaM|w}Hp8_F{2&!X4855PN6MfZKwLk6VHb&^s`Z3i=5Tn4ZUd+o*t-Pw`Yc_u3Zb zc^0Z?z1W}?kDD?BdhaIgGd&(#dj=ku@x?a-2N&Xv!UXW|4%c8B2rmpQC9aV{H~|`U zejoo?v9tMB)Z*#hslX++YW&MZ#FH1qzgt7ByM9 C^5fjJTPn=V`+ z8&A Gk% lfwUUBf(2seBcvAJr$mQI{6cEdwB8;gLF`o{ITJ zHw@1w<71PAdqo&bJ)wyq)x-_UA;o7so!F=Wec(SUb~4*opnl`a@br)1BEhj~P565U zgd|);1&kBIECyJv$PXeD6DW?x{n{IC0#x!$pW&LnJQC*w-MA@3Yw44UH&Z7N8n9LY z{{%ka;=u9mU59jlZSnZ9jQ9`%^lQh!Ixi=P40y%^cPmFg*y<>I_m9U)Z$E0fg_Q!! zne;t3W^vq%*u^iK_-8O$q>^{3xXjetvK~2Fl<|NR-URxUHJN26E pi#`2KO@!Hap5lN zxg2-lY%?*={v5j;sLr3V?Jwz*Rlw-IevAMXP1vdgHev#5iiE4B>4!ok-`q*wtqsic zTtb)7$Xw6u&^4k<+(yiZ_>q{tVmwskkmd*b=0wc(V(c}lf@rxO^Z;h3TZ;~Y4j;f;>^K5JaN=MKrelFdiYhS`ITul=@NpEnWC;-0#s6__AG2@L@ zMHnOiU`~l~Y9n@WM2MOjcpbk?N=)%Bs*zX0%#xFD`13dl%R1YfcvHF|J_#R^_^l`m zN5y<#Uz|yOY^P6F31jq;V;+R4HE1W|Fls>Dr0gGFafe1G8~r2xv!bW-h;ARtW Aow*#9Rx8(MFYEM9b*#;sEvw@k!9U*!3&!ah{eXR0^(* z3hb|N)c0LMqgS0RhGn}`G(%W~_6@BU7X-&H1FDf&5;q!ryj=(H(0m=30&HrtHcxZm zjqoUk^%iJks{HYdv&`N9l~8GE7L=7ESW3~1UXru$yxu14;dqa&6W9ZLqplq@vrkq7 zM(>0e#M~6#D^O+A^swh}++kg(tqYaB(q}mHY;JwC$Ixg!8x64RiPieGc{ctol0gV8 zr2!BDaO`Se9vZKoz?caz#8bsT1aNnTMRF@H=V|!owSQaT8Bk)1m+P_PPO0k&4jJ}_ zZQa9xWl#S+>Yb2pItXX~ft(&Lb^&lu9w&cLjylH-dN>6p0P6=~#47Y@co;q87qGoC z-U-J_wx$Q1g^rft?Ey53of1DSB7MTe)jOe;&V_1OF9^7VWz&KfxCf@BPKK#k#|&CL zDtg0X0dVTf`@B#zntc6QmvD#BR%3V!u8;EXtZ~%yUPGfp3GWr@t5$hF)O%2FkR!lu z$9~Ic;p~fps{GD=s~3qjss&{0ES(fHfZcwWyDD0Zx{kPJ%o@;0Cp4=5ec>!|Im0_O z;hoAP#OWU;CgK&uu|MQasir=3Vp%Z#0nx^b3kiSEH!l{*RMr7lq|L$}V6F#uQr zT;?SyPlMYB-x6wi;LpEz_7rck_V!MnLZd_HNGymQ6>Z}jzci>u4#2Hod#6uU6QU(& zr&t0U4$g|9>UWLbfG6+7_a9VBpjzc0fAJ_MU$Jd)wG!Wv;zJ)S*B0!8t%sZvD gtb6#^+YuL<8HydC5+NHlLN8QD2WM;DF1H@$1ee@ zlDT%&26{EJ;ovQv0ITfXGjADGf|}t@O9A)H> guDGmFv3w2{?G{xg3(zL3%;1k>0lQE zQ}ZM9$QvNa8URT~x{`Zl#KN@aolLED|1Qq1Qya85h)Qz#8_1$3^SD`uWTc@vMiQSJ zDX(2Qw$I#j-7%9&kY0^!9HIrlDVi7ufNO2eFAtOu??Pk#B=c{DuL*Nzt+bpFm5wTW zyhxGxcGjnwh)Q0GI8hRrzCVXGigNG3QP>gegInjC2*;vQi>MLMux<=2OGZW1k%HBM zT30W~ugsWX=Hf#sl>7?Eg%20Wa+yc`oWf|JcPdlG%|}@rGa{#hvzJw)d=z|3M#oBr z7graENccyeYxgWz6m=e5$qT*Ohn#%VR% 4G3p0o^|?Me98u8_n7zk63Xbu!^Wck4#;JJ%dOz7soFMrXvR+iX0U?w1yC%B}o9&v2e<~b>iC5a>gA5?LdxS7S!hdEix+ee=CzTt-+&+ zxMIe~b?x1;q8suHYlj?;?OPwkgRpt+xERDI5@c9c!5Fv!yr$;l;53gwe{yVIbFe2a zrGH@o#zz!T*TV=Nxe9IoK~0`60zx3PfPfOh(GKjNccya=i+x-U0Y3cZ^Xl9q;}n1D zIf!2jgrHME2sEdI?FyzQ4IoB$OfRBHBt(}PpdT|Pu4_+8P<`^ZpuGUU0GNT&$q8F~ z$Cv?Tmq;iu7r@~K l`NyhB``s|t^i;WSozo{0-!rIyv)B!= z9|tefJuv-K-zWqvEhlyG$E$(lGfYQKb{9Kcq!@!0w F=560@bY2LgC1U)1Wjrl-z{@xE8(l`CGT hhm>b!ixD|j8K=_S`QI$Fsv$6!;Ky?|Z*`p!wo9=~0wJF 7=vEIcyo#XmWLb>&xrKl2si)_n;`%8JXWA6Ie}Gz%e>@1 zc{&_H=@D;y$jcVxczu*H25r%$3D(@mqi L#I z4cYg@CV%?YUml5gb!Mui;ZYV{h_f=aqzdj4TL<7BfbRpaVLDP8q38yN7P2HeP;Ce? z@PiwoI0UKReQMJ-5noY~T8VgFR2?x4)|nJ;Ez7Qg$5@EBG5PO_B?sZtTy%8+%Yr 1CQ4NZ8fU*!tGmRA~L#6&&1rzW)$nuPivW>G-c6<7R6mQ zIk9KNsCb+FqY?VRY&$65=Rop@-`>{-(Xm8^FuJkQh2Q82&Up~NKLFeHepCO5o{7nq zjYF$YFXbtVJ^;;`GoZ2{H!(h9Ond?moBTJ!m|bw>5X7JJ4;KO}fjo&tr_nT(!#v-t z#PwMWJi(ku4o>`39J?vT`AzAEj)+gpEr=VC Z2j5S#q| zn`K-e2M)~Fn$!j%JN#pvaR?gnc+gS~{}IG4<6S%+Oswxxpc`n8tQNJu@lYJPS;n8* zZyEt{*2d?`F1u{lk<8G9VFwx3s6$4+=?IAWqmD_5y6PUeatQvsTtf!l{DYr%Xqru` zh>c}gmi2@ox`x=;3P~E$_oxejEt@b Qq7ku! zQeC*aAUFG5J$oHG3wZ&bdP1V^k_8{RAD&H1axJRs(bWw3mh*w*J?)BM=)ho*hjDU4 z(k|TRy}#(OKKSr0k=LiIHq~$W@*jB@EIi`z{igJ+L-%^qb7kiixWjd&VMa6~I&gW- zNl1Ds;)vfk3waMB(;R<1!e9BGSRKgWJ#lS+yt(?!WY?tZljo$lKK~lh)|k$wbOq6M zjz+XK wP^zLED@*2eLOCl$$0GJ9Q^5e+FL zpMjY4gg9$KZb@Qh6}&bc6> V zKd}yr*Q`&{B>;@#+}6vcMwR6zAF?DnJ3A2}qEvFo d3FFZg zlw}bUiguby0bE`2fbgP>xJtL%YPH71AbD ~wlDxtN&DXeVM)(Ndqw<+4~T(HJ5NK$hXd)M9eA9A#O? Q%88hV5c6P(SUqaS0IJT+4*&oF literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + ++ \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..3b65b4d --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,74 @@ + +#FFFFFF ++ diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + +AList + +开源许可 +返回 +未选择文件 +❌ 错误 +确定 +描述 +日志 +AList服务器 +添加桌面快捷方式 +关闭 +复制地址 +AList运行中 +取消 +admin 密码已设为:\n %1$s +admin 密码 +关闭失败:%1$s +已复制地址 +⚠️启动服务器才可设置admin密码 +AList配置 +设置 +密码 +启动中 +关闭中 +开关 +更多选项 +关于 +监听地址 +编辑 config.json +请至少启用一个服务器! +AList提供者 +account +路径已复制 +检查更新 +启动 +所有文件访问权限 +挂载本地存储时必须打开,否则无权限读写文件。 +读取存储权限 +写入存储权限 +请求电池优化白名单 +如果程序在后台运行时被系统杀死,可以尝试设置。 +关闭 +自动检查更新 +打开程序主界面时从Github检查更新 +唤醒锁 +打开可防止锁屏后CPU休眠,但在部分系统可能会导致杀后台 +重要设置 +打开data文件夹 +点按上方路径选择“MT管理器”打开data文件夹 +网页 +跳转失败: %1$s +清空网页数据 +清空网页缓存 +清空网页数据库、Cookie、DomStorage。 +仅清空资源缓存,不影响用户数据。 +已清除 +确定 +自动打开网页界面 +打开主界面时,自动跳转到网页界面。 +下载文件 +系统下载器 +打开链接 +已复制链接 +启动中 +已关闭: %1$s +浏览器 +选择下载器 +上次使用 +开机自启动服务 +在开机时自动开启AList服务。 ++ + + + + diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..26adcb6 --- /dev/null +++ b/android/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + ++ + + + \ No newline at end of file diff --git a/android/app/src/main/res/xml/backup_rules.xml b/android/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..9147603 --- /dev/null +++ b/android/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,2 @@ + +diff --git a/android/app/src/main/res/xml/data_extraction_rules.xml b/android/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..1a8ff7d --- /dev/null +++ b/android/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/app/src/main/res/xml/file_path_data.xml b/android/app/src/main/res/xml/file_path_data.xml new file mode 100644 index 0000000..dc047bb --- /dev/null +++ b/android/app/src/main/res/xml/file_path_data.xml @@ -0,0 +1,4 @@ + ++ + diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..38a4c6c --- /dev/null +++ b/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,8 @@ + ++ + diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..5fb7661 --- /dev/null +++ b/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,5 @@ ++ + + + + + \ No newline at end of file diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ ++ + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..671773a --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,47 @@ +buildscript { + ext{ + kotlin_version = '1.9.21' + agp_version = '8.2.0' + room_version = '2.6.1' + ksp_version = '1.9.21-1.0.16' + } + + + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "com.android.tools.build:gradle:3.5.3" +// classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + + } +} + +plugins { + id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false + id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version" + id 'com.android.application' version "$agp_version" apply false +// id 'com.android.library' version "$agp_version" apply false + id("com.google.devtools.ksp") version "$ksp_version" apply false +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..98d08c6 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jan 13 16:28:25 CST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..bd1b49c --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.2.0" apply false +} + +include ":app" diff --git a/android/src/main/java/com/github/jing332/pigeon/Api.java b/android/src/main/java/com/github/jing332/pigeon/Api.java new file mode 100644 index 0000000..440d7a7 --- /dev/null +++ b/android/src/main/java/com/github/jing332/pigeon/Api.java @@ -0,0 +1,42 @@ +// Autogenerated from Pigeon (v16.0.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +package com.example.pigeon; + +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MessageCodec; +import io.flutter.plugin.common.StandardMessageCodec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Generated class from Pigeon. */ +@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) +public class Api { + + /** Error class for passing custom error details to Flutter via a thrown PlatformException. */ + public static class FlutterError extends RuntimeException { + + /** The error code. */ + public final String code; + + /** The error details. Must be a datatype supported by the api codec. */ + public final Object details; + + public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) + { + super(message); + this.code = code; + this.details = details; + } + } +} diff --git a/android/utils/.gitignore b/android/utils/.gitignore new file mode 100644 index 0000000..7607bd2 --- /dev/null +++ b/android/utils/.gitignore @@ -0,0 +1,2 @@ +/build +/.cxx \ No newline at end of file diff --git a/android/utils/build.gradle.kts b/android/utils/build.gradle.kts new file mode 100644 index 0000000..a54ed45 --- /dev/null +++ b/android/utils/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "com.github.jing332.utils" + compileSdk = 34 + + defaultConfig { + minSdk = 21 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + externalNativeBuild { + cmake { + cppFlags("") + } + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + externalNativeBuild { + cmake { + path("src/main/cpp/CMakeLists.txt") + version = "3.22.1" + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.appcompat:appcompat:1.6.1") + + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} \ No newline at end of file diff --git a/android/utils/consumer-rules.pro b/android/utils/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/android/utils/proguard-rules.pro b/android/utils/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/android/utils/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/android/utils/src/androidTest/java/com/github/jing332/utils/ExampleInstrumentedTest.kt b/android/utils/src/androidTest/java/com/github/jing332/utils/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..acfa0f3 --- /dev/null +++ b/android/utils/src/androidTest/java/com/github/jing332/utils/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.github.jing332.utils + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.github.jing332.utils.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/android/utils/src/main/AndroidManifest.xml b/android/utils/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/android/utils/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + ++ + + \ No newline at end of file diff --git a/android/utils/src/main/cpp/CMakeLists.txt b/android/utils/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..d407feb --- /dev/null +++ b/android/utils/src/main/cpp/CMakeLists.txt @@ -0,0 +1,37 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html. +# For more examples on how to use CMake, see https://github.com/android/ndk-samples. + +# Sets the minimum CMake version required for this project. +cmake_minimum_required(VERSION 3.22.1) + +# Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, +# Since this is the top level CMakeLists.txt, the project name is also accessible +# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level +# build script scope). +project("utils") + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. +# +# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define +# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} +# is preferred for the same purpose. +# +# In order to load a library into your app from Java/Kotlin, you must call +# System.loadLibrary() and pass the name of the library defined here; +# for GameActivity/NativeActivity derived applications, the same library name must be +# used in the AndroidManifest.xml file. +add_library(${CMAKE_PROJECT_NAME} SHARED + # List C/C++ source files with relative paths to this CMakeLists.txt. + utils.cpp) + +# Specifies libraries CMake should link to your target library. You +# can link libraries from various origins, such as libraries defined in this +# build script, prebuilt third-party libraries, or Android system libraries. +target_link_libraries(${CMAKE_PROJECT_NAME} + # List libraries link to the target library + android + log) \ No newline at end of file diff --git a/android/utils/src/main/cpp/utils.cpp b/android/utils/src/main/cpp/utils.cpp new file mode 100644 index 0000000..d939831 --- /dev/null +++ b/android/utils/src/main/cpp/utils.cpp @@ -0,0 +1,65 @@ +#include+#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define RUN_SUCCESS 0 +#define RUN_FAIL 1 + + +int get_local_ip_using_ifconf(char *str_ip) +{ + int sock_fd, intrface; + struct ifreq buf[INET_ADDRSTRLEN]; + struct ifconf ifc; + char *local_ip = NULL; + int status = RUN_FAIL; + + if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0) + { + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = (caddr_t)buf; + if (!ioctl(sock_fd, SIOCGIFCONF, (char *)&ifc)) + { + intrface = ifc.ifc_len/sizeof(struct ifreq); + while (intrface-- > 0) + { + if (!(ioctl(sock_fd, SIOCGIFADDR, (char *)&buf[intrface]))) + { + local_ip = NULL; + local_ip = inet_ntoa(((struct sockaddr_in*)(&buf[intrface].ifr_addr))->sin_addr); + if(local_ip) + { + strcpy(str_ip, local_ip); + status = RUN_SUCCESS; + if(strcmp("127.0.0.1", str_ip)) + { + break; + } + } + + } + } + } + close(sock_fd); + } + return status; +} + +extern "C" JNIEXPORT jstring JNICALL +Java_com_github_jing332_utils_NativeLib_getLocalIp( + JNIEnv* env, + jobject /* this */) { + std::string hello = "Hello from C++"; + char str_ip[INET_ADDRSTRLEN]; + int status = get_local_ip_using_ifconf(str_ip); + + return env->NewStringUTF(str_ip); +} diff --git a/android/utils/src/main/java/com/github/jing332/utils/NativeLib.kt b/android/utils/src/main/java/com/github/jing332/utils/NativeLib.kt new file mode 100644 index 0000000..438711a --- /dev/null +++ b/android/utils/src/main/java/com/github/jing332/utils/NativeLib.kt @@ -0,0 +1,10 @@ +package com.github.jing332.utils + +object NativeLib { + external fun getLocalIp(): String + + init { + System.loadLibrary("utils") + } + +} \ No newline at end of file diff --git a/android/utils/src/test/java/com/github/jing332/utils/ExampleUnitTest.kt b/android/utils/src/test/java/com/github/jing332/utils/ExampleUnitTest.kt new file mode 100644 index 0000000..ff67943 --- /dev/null +++ b/android/utils/src/test/java/com/github/jing332/utils/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.github.jing332.utils + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/lib/bridges/app_config.dart b/lib/bridges/app_config.dart new file mode 100644 index 0000000..602cc2c --- /dev/null +++ b/lib/bridges/app_config.dart @@ -0,0 +1,29 @@ +import 'package:flutter/services.dart'; + +abstract class AppConfig { + static const MethodChannel _channel = MethodChannel('alistflutter/config'); + + static Future isWakLockEnabled() async { + return await _channel.invokeMethod("isWakeLockEnabled"); + } + + static void setWakeLockEnabled(bool enabled) { + _channel.invokeMethod("setWakeLockEnabled", enabled); + } + + static bool isStartAtBootEnabled() { + return _channel.invokeMethod("isStartAtBootEnabled") as bool; + } + + static void setStartAtBootEnabled(bool enabled) { + _channel.invokeMethod("setStartAtBootEnabled", enabled); + } + + static bool isAutoCheckUpdateEnabled() { + return _channel.invokeMethod("isAutoCheckUpdateEnabled") as bool; + } + + static void setAutoCheckUpdateEnabled(bool enabled) { + _channel.invokeMethod("setAutoCheckUpdateEnabled", enabled); + } +} diff --git a/lib/bridges/bridge.dart b/lib/bridges/bridge.dart new file mode 100644 index 0000000..0af5d55 --- /dev/null +++ b/lib/bridges/bridge.dart @@ -0,0 +1,13 @@ +import 'package:flutter/services.dart'; + +abstract class Bridge { + static const MethodChannel _channel = MethodChannel('alistflutter/bridge'); + + static void startService() { + _channel.invokeMethod("startService"); + } + + static void isRunning() { + _channel.invokeMethod("isRunning"); + } +} diff --git a/lib/bridges/event.dart b/lib/bridges/event.dart new file mode 100644 index 0000000..b006464 --- /dev/null +++ b/lib/bridges/event.dart @@ -0,0 +1,8 @@ +import 'package:flutter/services.dart'; + +abstract class Event { + static const EventChannel _eventChannel = EventChannel('alistflutter/event'); + static void onListenStreamData(onEvent, onError) { + _eventChannel.receiveBroadcastStream().listen(onEvent, onError: onError); + } +} diff --git a/lib/generated_api.dart b/lib/generated_api.dart new file mode 100644 index 0000000..b069a17 --- /dev/null +++ b/lib/generated_api.dart @@ -0,0 +1,9 @@ +// Autogenerated from Pigeon (v16.0.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..804b3b8 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,126 @@ +import 'dart:developer'; + +import 'package:alist_flutter/bridges/app_config.dart'; +import 'package:alist_flutter/bridges/bridge.dart'; +import 'package:alist_flutter/pages/alist.dart'; +import 'package:alist_flutter/pages/settings.dart'; +import 'package:alist_flutter/pages/web.dart'; +import 'package:alist_flutter/router.dart'; +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // TRY THIS: Try running your application with "flutter run". You'll see + // the application has a purple toolbar. Then, without quitting the app, + // try changing the seedColor in the colorScheme below to Colors.green + // and then invoke "hot reload" (save your changes or press the "hot + // reload" button in a Flutter-supported IDE, or press "r" if you used + // the command line to start the app). + // + // Notice that the counter didn't reset back to zero; the application + // state is not lost during the reload. To reset the state, use hot + // restart instead. + // + // This works for code too, not just values: Most code changes can be + // tested with just a hot reload. + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: const MyHomePage(title: 'AList'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + String _text = ""; + int _selectedIndex = 0; + final PageController _pageController = PageController(); + + void _switch() { + Bridge.startService(); + // _text = ; + AppConfig.isWakLockEnabled().then((value) { + log("isWakLockEnabled: $value"); + setState(() { + _text = value.toString(); + }); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + // appBar: AppBar( + // // TRY THIS: Try changing the color here to a specific color (to + // // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar + // // change color while the other colors stay the same. + // backgroundColor: Theme + // .of(context) + // .colorScheme + // .inversePrimary, + // // Here we take the value from the MyHomePage object that was created by + // // the App.build method, and use it to set our appbar title. + // title: Text(widget.title), + // ), + body: PageView.builder( + itemBuilder: (context, index) { + return [ + const AListScreen(), + const WebScreen(), + const SettingsScreen() + ][index]; + }, + scrollDirection: Axis.horizontal, + controller: _pageController, + onPageChanged: (int index) { + setState(() { + _selectedIndex = index; + }); + }, + ), + bottomNavigationBar: NavigationBar( + destinations: AppRouter.destinations, + selectedIndex: _selectedIndex, + onDestinationSelected: (int index) { + setState(() { + _selectedIndex = index; + }); + _pageController.animateToPage(index, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOutCubic); + }, + ), + ); + } +} diff --git a/lib/pages/alist.dart b/lib/pages/alist.dart new file mode 100644 index 0000000..7363ff8 --- /dev/null +++ b/lib/pages/alist.dart @@ -0,0 +1,24 @@ +import 'package:alist_flutter/widgets/switch_floating_action_button.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class AListScreen extends StatelessWidget { + const AListScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final ui = Get.put(AListController()); + return Obx(() => Scaffold( + floatingActionButton: SwitchFloatingButton( + isSwitch: ui.isSwitch.value, + onSwitchChange: (s ) { + ui.isSwitch.value = s; + }, + ), + )); + } +} + +class AListController extends GetxController { + var isSwitch = false.obs; +} diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart new file mode 100644 index 0000000..f12be5e --- /dev/null +++ b/lib/pages/settings.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class SettingsScreen extends StatelessWidget { + const SettingsScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + + + ); + } +} diff --git a/lib/pages/web.dart b/lib/pages/web.dart new file mode 100644 index 0000000..362ba0b --- /dev/null +++ b/lib/pages/web.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class WebScreen extends StatelessWidget { + const WebScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + + ); + } +} diff --git a/lib/router.dart b/lib/router.dart new file mode 100644 index 0000000..9072d7a --- /dev/null +++ b/lib/router.dart @@ -0,0 +1,27 @@ +import 'package:alist_flutter/pages/alist.dart'; +import 'package:alist_flutter/pages/settings.dart'; +import 'package:alist_flutter/pages/web.dart'; +import 'package:flutter/material.dart'; + +abstract class AppRouter { + static const List pages = [ + AListScreen(), + WebScreen(), + SettingsScreen() + ]; + + static const List destinations = [ + NavigationDestination( + icon: Icon(Icons.home), + label: 'AList', + ), + NavigationDestination( + icon: Icon(Icons.preview), + label: 'Web', + ), + NavigationDestination( + icon: Icon(Icons.settings), + label: 'Settings', + ), + ]; +} diff --git a/lib/widgets/switch_floating_action_button.dart b/lib/widgets/switch_floating_action_button.dart new file mode 100644 index 0000000..1bf7f01 --- /dev/null +++ b/lib/widgets/switch_floating_action_button.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; + +class SwitchFloatingButton extends StatefulWidget { + final bool isSwitch; + final ValueChanged onSwitchChange; + + const SwitchFloatingButton( + {super.key, required this.isSwitch, required this.onSwitchChange}); + + @override + State createState() => _SwitchFloatingButtonState(); +} + +class _SwitchFloatingButtonState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late final Animation _animation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200), + ); + // _controller.addListener((){ + // setState(() {}); + // }); + _animation = Tween(begin: 0.0, end: 1.0).animate(_controller); + } + + @override + Widget build(BuildContext context) { + final icon = widget.isSwitch + ? const Icon(Icons.stop, size: 48) + : const Icon(Icons.send, size: 32); + + return FloatingActionButton( + onPressed: () { + if (widget.isSwitch && _controller.isCompleted) { + _controller.reverse(from: 0.5); + } else { + _controller.forward(from: 0.5); + } + widget.onSwitchChange(!widget.isSwitch); + }, + backgroundColor: widget.isSwitch + ? Theme.of(context).colorScheme.inversePrimary + : Theme.of(context).colorScheme.primaryContainer, + elevation: 8.0, + shape: const CircleBorder(), + child: RotationTransition( + turns: _animation, + child: icon, + ), + ); + } + + @override + void dispose() { + super.dispose(); + _controller.dispose(); + } +} diff --git a/pigeons/pigeon.dart b/pigeons/pigeon.dart new file mode 100644 index 0000000..e69de29 diff --git a/pigeons/run.cmd b/pigeons/run.cmd new file mode 100644 index 0000000..0f5371d --- /dev/null +++ b/pigeons/run.cmd @@ -0,0 +1,2 @@ +flutter pub run pigeon --input pigeons/pigeon.dart --dart_out lib/generated_api.dart --java_out android/src/main/java/com/github/jing332/pigeon/Api.java --java_package "com.example.pigeon" +` \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..41c1d98 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,301 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + url: "https://pub.dev" + source: hosted + version: "64.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" + source: hosted + version: "1.0.6" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + get: + dependency: "direct main" + description: + name: get + sha256: e4e7335ede17452b391ed3b2ede016545706c01a02292a6c97619705e7d2a85e + url: "https://pub.dev" + source: hosted + version: "4.6.6" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + url: "https://pub.dev" + source: hosted + version: "1.10.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + pigeon: + dependency: "direct main" + description: + name: pigeon + sha256: "316ca90adb6b47eec171f092a50b71c35f66019e215dd943bb25dbc72730dd5c" + url: "https://pub.dev" + source: hosted + version: "16.0.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.2.4 <4.0.0" + flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..bbedf13 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,94 @@ +name: alist_flutter +description: "AList for Android" +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=3.2.4 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + get: ^4.6.6 + pigeon: ^16.0.0 + + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..f56b290 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:alist_flutter/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}