kv8QheKkMenh}H1h8Ls1>vE2TpeI`^@dv
z8-2dh%47=^q=#RA`@U&02HS+6mW_11d*d*oYE}F=Ez>(;v<%+NsL%iwg|;Wn#AlE5
z1>Z6F5lW4y$n2k|MGkE7e;WL#G!P$F_u5o%b#183<@zL+rhTH(S9XI7`3O7Am^!Q;
zl}C2?a*OS#qooK@><}Frqm$7HJ>>rV$n1(USR}78f(ktU>tVu7d5HkU1mDtVD1i?IU#8Z!)r1;!57nin0P=)DdXe6VFKXBoKh`a4{fWG=E7aEsSsWES
za_Onz)2u2B;h#8njzfExZV`PkB3gziVe#>ERY6mSYYLz0hHlLO!ldnMZTZm$!eWrH
ziYWC&=aJ6=Puy1}QA+e>YA
zpAoo3VPJU+RwmND7X#tDQV84h%5MXwctt3P^e
z)bMSiJ~5OMB^qomw3p?BbvN^@2J@;oP`v0RrE^+lYt4*{*PA?
zc|RP#_9d44W2nakv+nSV3tW|W8-ty#KtN=q@(zEsyZ>)eToVRUhaTy9OE0-ZDt)=P37~b#
zTj~Y>xxZ%8c$qJV0*IU)Htn@s@m14QS4%ehcK
zU;(vz>-lVygWK;KQ~Fo`d{(X>FT=~EoHQb&5l?L+h`5L?%&v+46Ltk_99$AoKn#7?
z_!hV{p|z51zY27FvvBkp?~Oe9fcxQ9*2UU#g8fh}PMmS4+b~8oo
z3y3m3NO!Tp8K2hzx8*^E?w~f%&|V)YgbnIen=%-Tx**}$RD)ROLETfssfYZ=+ag_B
zu?L;mSTr>HDUW%0&c0%$n}SG-M!mw$GNDP|`nb}Kv4=uJ$F78fj8Dv2saE);AQu%$
zf2gqyT4(KbJ-6WRoy=|eX0H$)elfcJk4Bsz-pKk0m*CpYXVnE`>QAh=t-slu=hDlh
z|5UNWC+1$d7CC*J3{ciD_&boR>9L^Fm^GFt1`()Vr>5roa_pUdb82IT;F9@?lOw19
zIDUAWFi-B{XPT_IjwX@VrzU%lF~x`9KGTg60uUDgZ^I0#ICG})+t3l^?>fGF-6O5b
zITFtJz_pzR%0sk?!Yf8|=N|*XRg>XjIN#o<2DG8Y#CgV!AUpdZo7`pvyiEr8aQ^u=
zrkdc2)66BVwj5GEu=G?s@}=V=KYG!%bgB5@kE_-Ccj?>c?Z`4)lRL3dOWS~CwA#!c
zTjZC!9u*I!#&UfY&sZzJJ!sd%fG-~)pZJad)oBUINvg27)uWHVIPSbRF$Dni{z1zd
zS*d6xo3NqvcjmC#pGp-gujF$GKcp%D60pq3hCKjIEYV`+j%-n6nC)j9*Q!K_Axg=P
zTNH1XLZa2%UK*NpcwT&yu))n%B;tParmY#fsN6#)o^
zY`>BlCpiFNU%#Mu20mRDnuSgYP13`HY^>DZ5YKQ!H(9WCTtoM!TZ;?y19UQZ#*@1_`huL|?}S&6y`
zLfk4H7wBk{vNc92rvG$}fX2#5+^l|`Gg3EWC;419L0frT!*l3wJ{9*Iy;jRxv(OAh
z*jA>Erxn9)u8HPX1fE;Vd1I%o?63vy8Rfj=GeDFvwb(;22a5By54c1rJS7Q+JHgGy
zHzID|jNpn*_rsct0bnjGW?{$j*AoF&4C+UoY{*VhX3bjZW`Gqs8L+*4!e0mYCAg_p
z@$xAx=Z?R%*xCsX7U+vWR*d$eccDd5J^_j0x>8RoCeVP_F-M)tu-fr2s#JN1V1($%
zq9fV5gL(Qr{TMVb@kau8W6m)Y!uSZ$K@+hrHY|Q6j+dV8#c&_f<;L7;jyrHS=Ou`c
z!1x#FDiwbGUuDQ&J7a31^X*)c!KBkgN*uwVx}r?Tt(_I98Yf|G+D
z6fz*=#gSIjy%Df3D_gMXDVE@mmTt#@HCOOBx4&h1R!T*N+tmX;4dvh1T5hp{R8UeM
zvfl>O&Rf95HU`XvnEJN8Mc0M|WZd@mh^ftM`Y{&gIb-{?xI#-T#JoK1(AF>KmL@H5
zRELr`Y3*I#4hv&i$V`R@PGP|2=5IxR+Z#T25rig$vs3!s4M1kpygBHkntoX#!YgFn
zJ}Q8ulz4v?D8S)O6Pp2VY?^hG^(iA#
zG@d16Z1%sAe-~OsP@e%4Qv;*vmkw-G6L}5bo?|pqDk~&Aa8E;w{8dad4B|tSGIuXg
zON(G4u?3$3H@*zIUDHCm(*@=iV(hNbg8qmn3F18{yHk%Rv;DUqrs06)EUy!)$34C3
zytn|1^sv)EPSjV<64r-H*UBNlq$X73Ss7lNqOsnmdFyIR
z1k$DMuJ#`alC<`S1bt-w3Yw;SC~+2lJ-3>FL)s94AHK^djOTJCDkbbcA5eqTj+YuB
znt3<)_P$#boP{RFW}D2=gkP9C@MyR}M%?>Ngo)#saQeW_ZKwYjTZ{;nNS(KnIW6FZ
z4#ukhwJ??{2l{gmcnr_-X)DQ7{udeV6K?mNm(M)1(+p{h{!iNIBu_pXaX038GK_VC
zZyXaTXmV(>U!)Y#4M*&6%tym2I(u5{
z>%#IsSVA9Pe2DXj;SW~R(yPSBwnB(Q#1>QewGp6DyDGIX&O$%%ZAU2FqP5t1Hwo=M
z$Gb1!4^jY(;ZyN)XC=YQ9@zc~5M(s!Fj9gdL`Bt%7J@t(D^kFWyg}dF9k809FpJt>
zLlSq@wd0Z*b(Yzz8y3B$*b<=gyvF`to^nP0N*bos^Nmq@)Ic?R@gQ5w#n5V*{_y3$
zMCDICQ|%$0dJGI}H^SuTw}Br}wVas>WC
zi!6WE6KYGM0sqp-P}{?L&i~Zj{FHuS7QS?EKu3j9^?6o$;oiGCFwA!J;dkSUspgX*
zwZSQQuzF@|&=(m3(hfx{iorXegKqRbNo8biq$trvfTT!s9)?!r5qAF5bL$C?9=_0y
zN#k3;knHba%-HI|6wsqAxaB949_qzb^3gd%D)Ami`=?wz%Gc_A6ae*RKxuHP3^Bw<
z|ew
zTftNMP)3CqoMuk2XYihy<-D+zHho(B=E6zUI8pePvH}WEyh2k0p1eAi~Y>5HqG_S#$(B@G`+1TPbq4u=
zc_C#*%Irxxss&f9=)dx63VlPL3$Y1EGYFww7tONheORoA@bV-q*{Ay++Y+o}}{z(47?cy`sK;0luLAHFuu7x-4txuNYhRe^eD5
ziGO~$SutGWA;^5|qjSThdh2Qh+V+*o;A9W+>Q;|$lb%99J+lQ~IU81Ez)akum-pF5
z@DtfbR=;jE>xPcBuLKj3VS~)kiP_HI7&VcIzr0GBh5lYWr)>N2h#ES*sKiN2qz5KP
zw2qxx>y8t#YGX!=!CiZLYO4vQpYYkV0d(Kkg8F`fhfaXsC|x%x%YglWH+{
zCY(pqkWhvFn84kZtUqgcl6|B?0VjSnD4P!Jj%FK%%E^#Kq&PEVXak{#{LP#wTPX-p
zDIL^3gJ;L~J>7^{g8FSan&$tyxLrF76C}FzaV4)EjvQeHVx3B6SAu|UC${r45IZdv
z?TM{ILDdH%{b-BM-5Dn(!Ey~bHI+77YVe8XKAHBbSIEW;oi9(_v)D5<
zewiFOfGKcCIumuV1b#wjQ!-t?ubv=;y1A4fe&3?iW65ZRJRjfjC*)J_?RS870o|l}
zcMb6%m09r+Lzbm@xIxB5@TZ3F|}0{%igDDZzAA^
z2>h}oO~KZ4Jn394Q+$Eqh~HD+i;YKHsn+IFk2ovVTC<@1y3#%y_LE%r=;w|u
zxo37v9EMlOu%bW${pgH}&2F2~^tj(?{QR|)A?Dj-J2!tOhWtNM*DK`=`=){qxuUP~dhwrTpv#d0P0%x|P>B%-nGct~2V`mT%g>5uBTWsp9{
z{uaze!d&-OT;3Hn@vpI^4e6$;;@@LH%N)iw`A{zPGxW3rTTwfk_-fLoKCzw$SoxaR
zpn16Cg6hARf@RXn8=)O}13#LSM3c0gjB7LQ5>^!~bXBLB5PpPXiW2U%Z#)IswtrRY
z*3wqTYL&xxuxOEm3ayJ?C0EYxWDEP*H?6gvbs&}Ym&K~k?8kBrS=taI=LhmFt-EFg
z7A~(<0&o_)v(&R18eFvBL4Susc0qgo`S2k%F_a^=6fpraG{)tLyuLgFmq|7w)g<)~
zyD)7#t;{v_sWQOors+z-ZzyG`3d_0sUO%QNlrEB)o$PvA8O5=_(VrUn@xe=a+^rDH
zh1AqbGAacrj^SJ*bd=ac`C9)_n7Om%eOCM+>;G1MVX3?4_;AMe>s22B`}pH9O8L-H
z?_C4I)Z>yek^jncack`Iw+tQ!kb3f34@0zVl5t>{0Kr|OF!771-5i$CGb97w)=p0B
zUnx~229@tM>_3inz7VE;t{5doaJk)w20-ro4S{)|BtI;?mc$4}D28Xn47*jzcb3+se}tN^Xt~l}AX=
zj$HGI-d-y^4A}VXs$u*1%~!;;%*98aroyVbnl04ivZc!5qiVe!U`ypYX%{4b;*&^5z4`J-vAQFV^Er1~X`jSNqFJ1eP7Nf2$pgbziwgrYs-Nc?HH{BeECdaLR@+
zI1z|2S7rD+AObt~>tX5b9GZgYULNn`Jg8)fqv4H=uUenf*Z}CYZ;|}HSa#lvF9m~>
zr|D$q1}^Q>D5-Ah!@*OZg8oq
zM`@wn)Cbd-axbjdMsJZLfDv_5HH8{qsjZt6Tb`6BMQZ1?qiaOJD>dk_DQ=zP0eapu
z24s;Xwd|GbnH^cPhDy>BZ<0q-W4D$oR7RjmYbhc{8f8F#G{t^M`HZw@_#PbpxPzK#
zevC(l2ixfNQ$(|~#UPxr0JgNrHxUtdZwa`D>4NzL&oD+87n%s*Cl?-{m~maAmfRvC
zvMOxd%l)awb;K|3rW5a=`FgCO%g0>!YWIgG*Kn6J4t-6x3o;OCLE(5gQ+mKh8T8sI6xyLgz8zfH%ZEl
z{aK_@Wz!cag!H1tYzNhslu!zWM~qGURa3|%#8e-iB<;jLvQI)w=jgmNt`oDlLh$I!
zSAljaP327tI{hR^ix)F#NX<}Bw)~uRzM+W#2ABlS`yn#d3st4nJVK|;i_SI_$fGlu
z(kkPN+5q<>U~Q-w-7T%j-5^JV5!({|B)SR&C@EFjFf!_&`8m4g<2i(Wf{sAX02?sQUQJ&-|R=XNQghlzH
z5(-tkcSxP?MvowSzjKpJQC@2UXV9CU&k-j}(;s-{YIq1#F&c2^uW%Qda#_r{^*@;8d%w(33n2uFdR{gxEmB_h
zQ!(?G>?qlgok7%!Fw+|z6b6)^-x3z;fn--Q4wO>Z7=WW(z+(@e7E1U1J0BE&O+0Ye
zwqxbZ+xUu8`U-EB&K`$KOxQYoD{RP7^}36vm12Atu-8qTh4wi!!&jF!WJ76M72%^I
z|4>3BDPL5%J7xC^0LsJm%!llYN0Rt9aoQ>m$2q635KF^_<%9bNYh@pG^!K%z4Qr@P
zc<1)|Tg}xSB|8eesdK{fQKLo?*g9R;&g4PRZF~oUifjj`*4Vpxh#4oUX
z1(Cx6GPRE0lD<&`Jr9yWRryy1H7aABWoDO%a^KQLY!6?MlMEtd_}1LpVX`&fUzt_t
z6=pjW^DDDo-r!4kx(TK(NIa;lilmq@JU0)4z3=KU!_R$it{YlJlIQOn{NXz%vNeEL
zGXFwGY{2uc6KOFUdC|rT;M`LF5Q{Tc?Yn{%dbk==giuvxMSLwCY?0`c5cJfacXbbj
zIg9qa7A6=pWysu0T|IC;;6lzT&p#-|3Voq&n)vF`+nZ|hI31gnF@1LS`>TtRfr-3V
z7wTh5LRUHF2U6B$laP_F$_j7aye!vjk&ZBe`L`T_Its(uop86ZX*+EtwfOAs04t`%
z0B`h`un)1dh<2qsej#pMq$cOwscVg;)6>5-+x9|8jJ^MKG@1$%dS)_8FAZ58uPvnK
z&A}9MbvdJdGk)9Yp{F_f_9145{6rEeF_9?|-23VG(1xTx-1Dl_QT!!jYg|;CMesTH
zyD!}yDbF-9d*q+bm*vD3I+@rK_g(bM&y!_kIw4@UoRN4SA#|+$y?P}+5xe@m8d=G
zdNte2YxxH*mWxxi;!Br|YSY0odh}=3<`;U8Td9Wwt&kae5>qcU+6`VYYx_W-!Pk9{;(GB-F6d`lV
zUq{F)r85%fL^sDI-}*u9lt0CjD<*`^Fx?JkPMu)Abiuf#O}91u)VPm_DPV$*GB(}BML-ez`h#y(Nfd>
z>d}fTy1j7n`cI@iR1xJ}E-@mXwd{B-TCi1RtUylt(eRaQ=<@=#N2M?}2@1=8vSC&Y
z3N2Ah`A=Vjd_nuPFONz5mYP%2nld?eOO^lLO5413F;ZFBZAWtH8xw+(0^W|_(7xBI
zn}GKUpaXL5RFKp+beB-@w<6)@X76QCh_#nq8TK4HX5E#JtG;oL(LU3XOw~#~NA0sSZZ7LbN{jrYblY)A%&s6fJ
z7XSl4*zr_;s6#CME9g-zaR>FR)v(`ctJv0*@4>Tj6J3}R)4iHB>hl`}1M}}4RdipT
z-DM^-k5Rp8Zsb5^IHB^_?~avT0d0PpsDvP&iIOiXU`oCER4CSJ(6s`a}<2*EG{%D
zjlTGKfUC5I<5yFSpCc~zApQ>O(cNY?Ur+T$M)oLHP67T07UT`2)enN;1_FX*gIAr$
zIbn&$|8g{FKg9Z6gaDIg>TR4O)`@_3<-rF9UIz$0e59Ui;mT)(wBcswUt;TH++#G4
zP&HWkySW=mWsQge%XtWt(nmh1HfMnPTcuvW+moP=|2!RWntVEL&Yg&YH55&V%nqNB
z$*RIfj$T%CX`*oc^>f~IL)h?+b?@5hr?EvU4Np_qNC{4i(ZtYW8TKPv>Y$o8}_@mJI
zwnH~&oZg&Hvc6b5lhV!-=NF_OAN+cpxw7tpM~-@rBY)K0*RFUWrMFV$u9zA&fmsIx
z%P_h0N8x7&{2wSi{zQhSYYHngjkti~rMYVr^@|E97u`G72yim}Sgj({-i!%wfkr2n
zsaGYvnotqTgYufe@zdiT|J5O#Oi=5>2yQfS0;kK0yaf7I@VKH*MPfb2s
z%y8H5$~p!Wao#5SKtLdMwC_C4(wS6G?)G4rJkmXS^7~5WD;S5_zDG#~vmO*?HTt
z0F%_&?zmo~=;)6u5>u*;6+An*jQWv5{^>B9n169VZTOs7v}jNbsVB<|AE7=IL`wm4
z<2Pp=2N}G0?9Dqa5&X7`^78AIibp}Kfu6y9S1ciwJ+10t$+0aTHUp6ZYv?!6R@Efe
zlG4fudcqh*_`MQ}wL5KgW^sBm0f*G@?m}qw@jWjJgQ-fd!ItPk3Vy^YB&GOT>&?>i
z($GIz*#k=OswOF+M(R`91k95<`ekx9{C@1=B;A;%a2-1?Z)jL58&W!s{`(M>}4
zNp^1V43%aGuXlfg<-9DY8*cw@h02eR{qXsE@hc5WgqCpQ=ACfx`L&w+IpNQ9ha29C
z?4g|HIgteB6x!mxWTZ7_=d7Ptp&l5&p@?M5su#uK#EC?3e`KO_#|XrJ#_g!=S=DMA
zr5>Ca=LhTaGNpdY^Nb~
zQSw7p!3Si7#JkSoR9?VzRSsO}tV{yZovx=h?g`%6sS2*~m&tS1d4%CuvZwVSw|K^)wh6zq
zq|+W)cE%?`@O>Q6y;!JJ=CAIJX)<&AzR@o|*C?NPFhno3KFg}HOGNv&ntq=@aE1KU
zop8$-B4?}B;a1)qSCf4b=>7h26pWHz9mbq;Edx<@_dl2#AOD89v-IO-6HsCu&wvvT
zhNx?Qw!+^={Zx?s#6yn(U#C{wOCQDr9h`{2UP^huB2gX#DYJ8-ny9F?&(bM~1Qy5NLu=8$X6Yok;@1TmV8|qxQ@oC0l&q=0dTx~_Z
z#jPvolV;y^=p%t~H3DSO7*s*zY1ZQ9%UN3dL~n^xAHb^(GI+2Y2QO3#sIWX~23&tf
z{``bdr~LK8Yc%n_8c+>MOX|uNb2Ioj0~zewYN%uzJ)XFOP=6zCXd{@!@4mf>3k%v+
ziTf1{%d$kFwze=ko~wh?lW~$TjmMSn
zxh_vLyTN3uhvW@0nB{Tfrhqbwf_!&w7YO@96uuGXI;AL
zDG$$B(x&m_kUMcg6lg@pcXD6Owg>9aj4qC3lc%m|v2)sHxK^$eR-scY`bABrKE9Xw
zZj?zy^j76Da<_NO9d-MYv48>^!>}5BdeIA_t97v1pVU8hPq**lQz|hxoD6yKwcvDm
z^~gOM;NJg=nn*H0l9EuEmUe4|^lY~Ec(ElEQC7OpAgxktC)`Ra=be*;_@Jm^}eA`ykIgrbO^7sDOssm%)ev=Wf&2MY=@+
z0qM?x5kZNen_&Q9=mDuAhPemd_xJtX`0w7!S+mwzhgr{gV)y>+J;8?51ihG#IzEr?
zsf^=9U
z4~~x4^Gtu?Sf`bt`Qzr3T8*a958;t~??7gFY|1TgkPOFA}YPO<9X`t*uz6%~tj)L+;Gp$zu`DZ{p>Ti|JziDV|CTxx+(TnEOmh
z%}u8`i@k37HBRl$K6B+88|eeLw+}jdQ8%9UhHSpLL(;F#f3W^B
zU$Ik)-{HAI#PytlzBZ;QIqn*Zh|7(92sB>h!!_xH6Xg!fT3xz?sfke{xZSsj)Fyq}
zeh=7X>_YBr@k<}UB;95L@kWEV$3d(;x12x-_}(S?VOEX^UWG7IMq6O8T^*stW>b+R8@y
zi-jBZR7zJZf?oUm?@OITn;U+g>$#!vBIu4Z2kzbmfvrf7`VB4Fv!clzm5To4>DCp?
zvA}EPcEt@Rt1BypB+<34rj%n&VUzYuqKF%B(Y*IYcL`f$a^_7KyVJ5!5L7#7E*zHk
zcg&m~gS?kNY8NwNK-iII#@6<9DdVYNY|izcZy|hH>oF8%NCEW3Bbhxf&;CA_NADU~
zGWa{}uk3}s*%P%WRIIqOeJpMAg&l)v;U;Vf7F(Wg@QDuu&u
zkDf*i9a!N4%SaoJm>(>6z5snoZp
zo;`mrDJcn-ZduMHG++ljh+1n5SNKeeNVbOb9HdZMsHWKSm_xF*96m
zAj}qTzg8%IK2f?MGoHoI`H@_k`0Dfzpl!6*FJ0>%PW)qo(SZdRJ~Ga8`btw3@c=8&
z>nhU)7OxSuKh-}1Eq~8HA(~4QpD7LARNYZcsEFb=Y*xPLYD^}Uy>j|li%lizNczR}
zLI@FC>yZ3&5Qkx8NBo@+ESHqJP0^;;RVBW-bclCzAb2P23|e1#p_3~2v@2xGK2?rH
z>d*f8;4SpwNQ*7|jLiiwu&i&y?-a20!Tq4*_$4bhq-5YpKzv{1^Jqu!$=7g4{-5Na
zfM87qWUy|wx+cHczlct<;BeZxSIhP<>9U3hg)xW7eypE~$O|{sI_{V4vGnxxyx{Zj
zQ^RT;bT+#-&W47JpQ40#W_l>MM?KyA*K*O)vCLS0bFOjqdKU>DtXp3Hu@~PfPejth
zL$g};q`{k|&?3q&-)!#7{?_9Qxlx068*#7VOdPUM<(VpCH~2jylMwY%)?hl{^Am2%
zT`8GCU0BQ-bC{H;2#i!8J0ImsR6qu?Vugijft@k5r06}dn^@O4%jIA(vTOs>Yv1iNZOO#tg5Z7G
zla(n^KZ!}A+BPq=*_ho`0cEBF*UUF62}V1M!VLPY@T!RJ3Qp1$!hYRGZn@(_!N+$v
zaxvn{5ex>lKGTY-JhyC56mS<7cU|F4j`#Ql)YZ_RJ{mFkc*YZJU+Mn-<#RD4(4`;s
zMkp03tE(0iLI=AN`bC6qy&)~ZBL9O!FsUHZr`{`yzIB
zF`s*|Wi}39IL<0aNaBiusdiwNJCT>q@L*i`#!Yf-`>kb3YN{WSy;F90+^1k(A)M>P
z^By_zBp*W6M1wDW%94QOb#(~kvlNx9zrX{AQzy7){5%xJ~*Ne>dW
zLg|!^;uEOk9F|UHw|ow4>IUD(YT8R}Bzq)S4)4HybZsP<w2*GeYy*W+*e4w00c
z448N}Q;i%uJNuH1uytUe;2lY^{3Q}^4sqfiO5u522FU8x3tOM^p<{m-@AE_B(Ls=z
zA5Q~5I-|j8$RDZhyNX-11y7gbrcwuH%LWf1W8)(W%T&>kcvxFZkBFU5Dvvv~7@kX1_p!!PaZOEhF?ACKvTs92|p1{_sHK4TXxAO&!Y`
zZkF8YW#Vm-3T$W0nIula)4i2hS^63itT#U-U3fi+@?6sBQ=nIN*1zH*dixfPp$Qyb
zBypY)$-eS;i$5RSBso1aZxZT1_`s_ITJ`U|fwqrjy>8~sx&xJR;c^&QPn*@3W8NR9
zIANEsu4Ap%=N_TwLckTvf{5#9u4T+$x$&l%d~MP(RkJJCXTpVl{&
zg7tRkbzpslg$aICi|)rr4yZ_bLPS$=28
z{T&H7+?BnZt-D>dt4qr?@--VU5_tHg`g;?-vdE6yFb4+km@2U)sLsnB%Hy^L$~ZwO
zNp<6dCM_U=ZH<&AIuYDR`EAR~Fo6KmVzv3%w;rYk6W9Wlwx7Pj49A9vcB02`ft8gv
zfKq4=;`Z61l_65QzE<6We
z%Q?sx!%W-C-`M7+In^SiVJs75V2}>-#&|}ZaCTnC#3V0#?f6(1Chl@$z=uQIs(dx6
z^vOBkqTd_!TwhCs;7o;3Z_7lKNls3gvHWq%mbYSWQ35nN-)3iL2NCgL#TfVuoaO)i@Zr?t%WkV<$w*CMHe+@771=0OdiDdh!J1p$
z^U}E3a5!3-db40sWK(+B3QHFW_MO3q3@o`1GNLdCC({}=NZ)A%u$>neg}eq#ssNf8
zK#FEMJo+ZLnlRV`XNs!e)08X8!!!5~h|fINI-$oq0ll7H8#Kzy|W5q&b63d!2oazx
zM#?Nma|A|*YMP*I5>K|_V8jc#UXn!gz0uvXEYld@2KMUoEQQr%8exYL`wfg<&RXq7wV*+A8nR=R9
zd@hOpA2NbP&;bX5U1ilvYMg;wyN-v0eAF~G7Y7_`t40gI6hh0r>n>LLnm8$A>P{zG
zh9tILJAhrEs+`uy-aYUYcY{!vp&uCsf3rJsouFP}4lYeUkYJO$T!z#?r&f?q?@32$
z0x=!8vjUh6N&EMv?w;Hc69PjlUwHpyKY3=+JeoUI4rCXwpykP^i8z5a_pIKgtjR(f<^BD+9;xoL0ZVVp6^3Z2c{X__Qi
zC>*g7H1iIK^Pviylx~~;ZAjwFOId4jDBR=yN*^VxhUuOQIUxbz1ZQm*P5bu~YN=b6
z#Y-laPD=yc0wm{rmP1Rk6r}2`!_sg#oL-t_#c6;wpLTYkc;Xrg^cLc
zaS#%N>NLJM*t
zTjwJ{6kBEFRch6b^lECUP^_W|WlB#RL%=lf-_hI0v*UblucsHhX_DA!Esu@YYnb-D
z%HIjUOhm|?q`bi{VE(8ofmy2=c}xuz2rgbko)@4(Xe+}2A$dw;^fo@(LtsKRq$Ela
z!|Z8W73ueq9(K#I#9DnnB)D`sZ{9JB*@jT!^6{W5ut{-$9@09NV|j?avk$FKS&>fV
zdnmF5zia96@G9Rc>ko+;r)R+%1`E7
zpP7otpnewzXm^jjY(yBW*$O;coCBLD387=$%-t9Z5?s2XX@r-o)L>judF_rKtECt>
z$MCSZNSazI?SM%%$;r-g2PWLius4GiOVA=6++dVd|3Oj{|P
z>bsY>@*!a1@%F(@>&x{bFKs!dDU{DEzRpoRj8M2`tBaT_;1NQ#!R7H{eHq^=Oj3SG
z4kB|juKw;qWXVD@m(;$TIJUO2f!zJiK(Y55;QTtT$)aEY`t<~7o-N^LFmQHB
zO`7sW?Lh|M_)N<@{5y&Kc`fok)WiaX-)*?t!*+(^_kp^pGGJtuBn+_c1}NfxKihLJ
zt)Tkz;sop#@OM-&HY7^{U-?2E(i0BMjE)?^Jy+^ecT47;n=G#kB;A74m(?JKzJVIb
zJNOS;eIO>@pLpvnR(g0|sxK^XA2gvs9sF1Qb|c9xaUNv-J~+j?8494!B25eM@4Ks=
zb7+Twr&n30eH|t#J&J%gb$kh-9t;owGdZVWOc&^jUw>axqd
zBHNI?xETWmSQvdX!~dHycp+dXP2b7@80+Ku4fAEAWbsi#1I}AGQjBrpQpErKy_^bU
zoUw%IwNAeZT3GuyAGTdhM(>_Kn{NKs!t0NOk126C}-
zFR3Lp#~1Swi2@6ZG*e<4t43yIY^9_p1WxCQO>uLDjYjWpa(p>(L_!zcza1o*-0_i=B-l{%U&-@^3X*dn?YU7KHjRPw@S<(ooHJui`<2MMJ)u7xQA9l}gmHd&ful7e
z{H;{FcK8K$52U5sgKn@*
zZ2FNTnzQbuVG}?6Jp&_fl=UOR8tqyJ+B!B{H+*BUHQ6k5cKiKJ)G5n7*8>y;A4B6^l$)z)R>TqWfBs-;7OwNm1xy~%$t9g3
zWkjQpe?qp}(&aF@2h{xD=;&35<@_?d_(a$bpMk)vSouek!->&v{#%9s5H!MdQk35w
zv)@E^hrHZx4t$3LH2&uG5pF$B4xA1lm9U)T4>6}C1Ri-~st6uhC-&5Hb_?!z2*b-M
z%MvfhBrRTwdq6GsDgiA|WHjIgg>l1HYbuUz0CIMZHp3A+P*To+2?KP?N33YmYsYds
zSQSe9oj0P|@Ue*8#jV?eW+TKfg4gM#avOt`FLf8)@XS9XFO!nV+GU-%$kTrn|{x;5TE41_+
zt(XFH)wgJ-va1Gt-LK$JhPy13!LqeaGgnzGBueIjYGalD5gt?!@$Zv|UwU5)u;FC&
zxZnosWWKi8qoCou>%b~3Sp0NXKY=ZH=qCKBATg>j@mjmbQ-Xr!c-}50hKboMGg3@O
zc$sV}El#%TY;UrfhF9t;{2Nu6^fZ~1*6nHYN@7?xtMi-jO~}}m0CTe_Q?QpuFm@iq
z{0q6f_}Sv}EXa4(o8w;PYQ{g}T*eiwkol;TR6h{&+
z%CqbuMx3bfBYK#V_!KGF%@7pScW(k4;bP2P4Y-SBt;kn|X}JKhiGXSxxqKS2n*`0B
zcT6tcXF=H~bz%|iZQ~nn~F_KXige8`vGT-K53O4J>+1oZCo0gb*#!qHb2+n
z$H}}ZDZG6r%EJrnsC9mw*YYG1!xZp|%-eZhp17tBX~=2q>zOnr-va4N+>q{dw~L&r
zN5SJas^C3syLtK$|6bXEKd>)*0>WYOE5Gr=vSu=H&L)xxcl5^4%P5La9$EtfUTFy0
z@l(rA#&!s2i*&%CHQT+L(zZt8b?z(D!U1zk(+sYzVT9RZ>K@bzAH5)cBs-DfiyWwO>3Er+ct;+@BPndEH*Ssd1Q^U8Hw{nq)%WBKU~
zwZ1wXKzyaLJGbvrq+CUI@ar2w${u?ya)}9M#5w!c!X&>uFU4|)>Giv!2q?x1c)Bc-YbD_&J~zx0CyB80mmI$=0UC!kOUHXFoT{UIG!^h5n;Xxk
zA-;WxLyP`ie4HG{wX9)RQ}K5s;Px@zgyxaWc@u}f24}7w1MqG~+(avC_9#6R1g6|M
zxpPJ5bGJD@A`z0c#(@5HA=N@K_N&Z(xdOGc<31T$e_Rm5=(p-Q375xZs~XGdb`
z%bVQbg3qsWLnsZH8Sb!JZiJU@&w9rzWK#!0S_dai3HEpsS9P>1e&R5@LUbGNBYE^<
zs95W8N}_h$G8&QJVv7~BJPMh3HZ?@rP%1CW5a&fP;267W{d)#+w?lA*qFDpPZT`Et
z*RuejLial^F`(0F$m0QpC$_YOj6Pn|YT4RW^6}tq=1ZLQu&1e7Yru+QXU~+xc6qw*
z;}|%12DI&%UW`Nx3w+c5c8SGAGTg(JjRl|u6WJe`3sO3DR~>^u(T)4PJD~hNo;g}
zJ?0PNMkaH$V@&r>7P$l+E#PotE&2kOTv0Sd9uQIMDqb7)ldCdICanS2-q)iaBJC~3m`ixRf#sFx7;N;
zCP*Wxeplu;E5|?L0&3sff{R?<^hoj!?EyvIMM&O}~u7=nm`Y&l+j17vvk_g>iUF@Oq!
zy(9vbBj;;=M<7ZO|LPa*PsFfi!bO=!LUHRNRynP*lp{-|lImt2!Nn+L6E;3-YB#l(
ztIsuvgb7V)q;~gjsuH6!hTT6HaattN?C^*%&7j5I?P=+?{c1G>+Yow;~aMH_VoQoz8G=Oi{3it%;}CM12Hp6IOz0t&Li>5YSd=7Xy{o(ZN{tv#qCf1mrX?-`FlHS@R+btLm*7TNsy1B%dc;)Uj@_Bj%fleaQ
z-|@v)s1@Yth$Fe(FG9m}#3kWw*}_6>*CM;5kv<{WbHY`^c{1ugNY6vc=Io+zx!07#
zZ7#=0(&L8`c|@?H1!sX>QBQjpBDjH6q(c~fIiSa6<6BS3u}@0KmxL;Pq!epwugPk~
z_jwi7L&?^)#Rtlmd~FrOOuzM0Z56Pnmwi9A*^bkcl)rUkg8;-p#v~+X&8gYD-*(rlaAAtd59PwLylKGQJS*1T_4K5c
zZBL)kYuVA~wdPaq&c@&$TDuuCyRYM1>C`=CLsT2$#Kg}6-)J=`Rg~@rdFRGyQD>B{
zKD(;1IX1pXGLrlaD`5#eOT?4YV_^rxs5f
zY#aN!zss4iw#JTU0La08palk;h~!6a!+$w5&gu9x#+WgkPIeCOZ(7`4mo7G*wwDa&
zl_Q)p9taeTg^mfymQzLg||a
z;cI7{l1~U#!zIe|)J%fH)mOnckpowO93v8dNx-3@$W?j7>UE#b8ga;;x^vOx__kkN
z7KTl6dI15+4??2^8y@nKg>EplpiN#gCFzmM(9nU@@)K~GJ@({7fE47k741Aeof5=TZNKYS*r~+
zwg(>HzPxYSymZ!T+KMXvboFrVxmn=LOXqAw3#7RLhD|*sc*-hQZ!N4}(etKqwJZ3t
zndL=|ps&@LksZ8dnOznl&wzp3-`nFnp&d>-RxawM46o8}paizIN?t_v_A3Q8=uWWa
z!t>h149fg|O=-tJ-oJ59JuqNEi3lDRV?(1}Q$2QCyt0C7lfgLK++UD#zqdb^>)STT
zo@W;5e>L?X-M!0|PTX<~3a-
zW2cRV{~1310jAnNax(KdA}yqGczA6@kGBv($vMZ0);WE;Cwm*^k(F@2>ZRuEWMFT%7!{{#z9TBZ@|G!?{_oF`0f{0I
zPc}ElhDn*l7PC{sHIXw%O}yizyd2N$K1$;SO6B&&^|41-u{T-BmchA(7U>H>wrD1h
zvz=*IUMA_MKhp;x43*rv(7wXBCrcoAdhJ
zO_SGN7ISsq@lOrH!lH2`6pn>wC)y7^JmP^c3$I=bG;3Uju4Haq(D|%uX^5?8_u*7!Hxa_S;fyJXmpTXUuS*1QnT!w~Yac@P^
z6V$AuXi!TXA@g$pX@QbCHUGegi
zW1}r)X>|?z^1Jp4sx;=T?GCF@4ZiEEY^ze?HMJTWy@^N3*E35#_v^BrZe{=8T%Mml
z4Tw@@IcONEdWtE_3oK*u?`G?sY?qBXmZAE?RzsKTy=n+$S(Tw{b#Q)dyH|~ttc+5tbl=WfDFu4&|eox89
zfjY}Aq%V(YuC!ImWOlwP_bSI~WQx*A`#Q%fF16-O@k>Gm8Hd#8Uq3U!>`>Xt@ZK+y)uFm0OI||IQ
z6&LX0SYq9H-Q-hz!BR{bGB^M5;8g(Izihb(Xz%jPc?$-dm=pGX9gm4@mi31$IJkD6
zAiA~lzN`6Lp9KbJ@ELx-5s_-oAvIE6JBICvO-;O6?v^0>N8)50(~L)db&wR8yhgMX
zD3X^;mHI8qN7VCcUCbAh?4$BBg#h;Z4BHDVu7slpYvcC|bf=7Z=m-M^2Ofp1b9ujtCIQ=;lB~1W18IOEGX7@G(ZqgOn_b60NV*S>48jb2EEk2*n?r~K6Uf(TKZhsVTtGZP#
zFtv&*K&wsI;zZ&xwQoSMeIc%33Fh>=OsR_T+b8Xm6^Vn}LaZFG!aLJt{yC0&?7-ts
zg6-?1qFdF?V>qvwvd#Oh{GT)p9EYB2coAG#7=O*!IN~jsNM)YTlDr=p_PNcX>^N(#
zJNinhxMe%PuIWdEN-DEG?)hv=F4rZ+&ve*D&O6#{52En`P%l+;ZulQ`rLOk
z6=Z+4N#!Nr{vWe8u#V@NtAR9Kex>f68^!i7UPYQ%xQTAv;mr1uOR~IUfZ49$(&pk?
z9A0`K;&*e#v#z50EOu#|q6%_R?=WYT80fQH!4G`+neoZ(%v?EQNXDT6SuVO@FN*F$
z7P?!y*WI#_p7GorvvHr`ef$rv-{v41v4O{Yu`#oeXu2Ph=P>o+3;~%JPaEs?x+(o+
zxTdX2)VJw{((?GPr&<=5DQg~KJq>{InfnFDWOZ>aEZGt<{beics47uimNMA;ETxxU
zV<{w?Y@4H!AG+g>Ypg%EJx&&(`zxrg@^}H&L?_7zzKu^VKgWRqavfH3FE(ejJreUd
z1Kf{$RdbrvA8na+X5W`y&07l%h`l-Y)sE?y)BM+FAui*WT2S@PV1g3UIDNO$Fll=S
zJBlNVBbF~~TtJMK__Ajya<_IB=mHeLm0h)k_%8llyUYymzO;a=&kq(^rKDDq7Nz;Eniu~}7{Hsg5
z&&i9!eLi2iRGZ$%oOALI|Jk~8o9Fg;+EL+Ult93YQPja((G!Ui{gKc^zU^8*7G(};
zjwHGd_ES#dA+1%8$6~=eC*SNw2d9{5b`~Gm_iC0PJ1E?`1Eq+6zy13DZcv&z<99+TYvvDPkBK)MgJF
zF@KXyL-8U*SY$evh04tKs2aupuasDkwDclRqc_NerLpJ!YKe`Od^7t*zVP8k4-ai_fK0|DNH}-
z*%l;lNp|KQLyn5?3A28Zy5>1KQYdDoY5uQxUbA0Y3AmhMHa6U)Y`jp}$}`mVw2aS?
zO+24Ee}3;|RlgBwy66QviA{?!-P&NCWx)!6PT$irEL_Io?O%zk9vHn~{#`WZha$78
zvVBg>op~~@qx~qA=dV7N*QEql5{njDYHvrAYXuL9>yDrdw0y)Ov|dtNOMUL{Usl5;
zpUac!5Y)!8v``#!(pyCkJ83E3ZC%FBvZd9n%^9g(a>f2R*kc_nQ@ioOt4*;eFJv@&
zfFkmF!N0;@XZVLyj1IxEdhB-#es_330DWVY3Hyo8*TJIEn00I=A4srj=W3SDVfsL)x
z@5l#TiVS9|fqsHa*~Ei)m6G#pADokJpD1AW%jD^N^|Tplo#?n?hz)XZ;B8Y?4uh-R
zu!n>2*N)N+v!@9X}IBv_L`?%x=K%&l_$
zSD#Hq&dI>876MB;G6H66>)aALyB3RX^NN}@4UZdb#_IqHnZjkj&Y1Vyqv^zJWfEX1iqNy?WDpd=S6L;0tIeGAlFcm4ssRKnDGxI8(&dt_E
z&^36#2Nm{nE~He=8|=>j2Apd*#l63@v=!o1q!`l9m|Z38#_aPa
zNivUO!T)>p0ZOEIM5pypd#lT+|W@mNS00Q0YAaS(mxS05Uuk|%=ApKRt*>nlYrHi4`>vN*(nn$i^JpW<9Y
z4BJ&NPkLtVe6ZUiG(AEItpD_3B?k&sdk#Q%uy^|NgD9+T!Ju7VnLR_93l|$p%vs23
zN!>2z()mc&74k^FF|YaSZjzvtz&Qk_iJq7hsWY*zs=^LvbH*&<+N`TjG`p^n6N&GBk0
zpK+@yW^7tbKlA5?6Lkgcin)
z31>8Y@6`JK7^KgNPECt7sS;eKcV}
z3FuXBrJ^^@0i$8bxoP%6JHn54ROuFGll2K^F6>qaEdc&Oz|Vhfw!yor=TD*F=l{cv
zJU9$!3IOL^@&9@QJ0AjMk#kA*|9tbmtolF8|G&F|0yN-o|DSdY-~b2uKX mBroadcastData;
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
+ switch (action) {
+ case WifiManager.NETWORK_STATE_CHANGED_ACTION:
+ case LocationManager.PROVIDERS_CHANGED_ACTION:
+ mBroadcastData.setValue(action);
+ break;
+ }
+ }
+ };
+
+ @Override
+ protected void attachBaseContext(Context base) {
+ super.attachBaseContext(base);
+ //解决4.x运行崩溃的问题
+ MultiDex.install(this);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ initLibs();
+
+ app = this;
+ mBroadcastData = new MutableLiveData<>();
+ IntentFilter filter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ filter.addAction(LocationManager.PROVIDERS_CHANGED_ACTION);
+ }
+ registerReceiver(mReceiver, filter);
+ }
+
+ @Override
+ public void onTerminate() {
+ super.onTerminate();
+ unregisterReceiver(mReceiver);
+ }
+
+ public static MyApp getInstance() {
+ return app;
+ }
+
+ public void observeBroadcast(LifecycleOwner owner, Observer observer) {
+ mBroadcastData.observe(owner, observer);
+ }
+
+
+ /**
+ * 初始化基础库
+ */
+ private void initLibs() {
+ XBasicLibInit.init(this);
+
+ XUpdateInit.init(this);
+
+ //运营统计数据运行时不初始化
+ if (!MyApp.isDebug()) {
+ UMengInit.init(this);
+ }
+
+ //ANR监控
+ ANRWatchDogInit.init();
+ }
+
+
+ /**
+ * @return 当前app是否是调试开发模式
+ */
+ public static boolean isDebug() {
+ return BuildConfig.DEBUG;
+ }
+
+
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/activity/AddDeviceActivity.java b/android/app/src/main/java/com/kerwin/wumei/activity/AddDeviceActivity.java
new file mode 100644
index 00000000..9b89c562
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/activity/AddDeviceActivity.java
@@ -0,0 +1,410 @@
+
+package com.kerwin.wumei.activity;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.style.ForegroundColorSpan;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.location.LocationManagerCompat;
+
+import com.espressif.iot.esptouch.EsptouchTask;
+import com.espressif.iot.esptouch.IEsptouchResult;
+import com.espressif.iot.esptouch.IEsptouchTask;
+import com.espressif.iot.esptouch.util.ByteUtil;
+import com.espressif.iot.esptouch.util.TouchNetUtil;
+import com.kerwin.wumei.R;
+import com.kerwin.wumei.adapter.entity.EspTouchViewModel;
+import com.kerwin.wumei.core.BaseActivity;
+import com.kerwin.wumei.fragment.LoginFragment;
+import com.kerwin.wumei.utils.NetUtils;
+import com.xuexiang.xui.utils.KeyboardUtils;
+import com.xuexiang.xui.utils.StatusBarUtils;
+import com.xuexiang.xutil.display.Colors;
+
+import java.lang.ref.WeakReference;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+
+public class AddDeviceActivity extends BaseActivity {
+
+ // begin esptouch -------------------------------------
+ private static final String TAG = AddDeviceActivity.class.getSimpleName();
+ private static final int REQUEST_PERMISSION = 0x01;
+ private EspTouchViewModel mViewModel;
+ private EsptouchAsyncTask4 mTask;
+ private WifiManager mWifiManager;
+ private List ssids;
+ private String selectedSSID;
+
+ public String GetSelectedSSID(){
+ return selectedSSID;
+ }
+ public List GetSsids(){
+ return ssids;
+ }
+ public EspTouchViewModel GetMViewModel(){
+ return mViewModel;
+ }
+
+ public void executeEsptouch() {
+ EspTouchViewModel viewModel = mViewModel;
+ // byte[] ssid = viewModel.ssidBytes == null ? ByteUtil.getBytesByString(viewModel.ssid): viewModel.ssidBytes;
+ CharSequence ssidStr=mViewModel.ssidSpinner.getText();
+ byte[] ssid= ByteUtil.getBytesByString(ssidStr.toString());
+ CharSequence pwdStr = mViewModel.apPasswordEdit.getText();
+ byte[] password = pwdStr == null ? null : ByteUtil.getBytesByString(pwdStr.toString());
+ byte[] bssid = TouchNetUtil.parseBssid2bytes(viewModel.bssid);
+ byte[] broadcast = {(byte) (mViewModel.packageModeGroup.getCheckedRadioButtonId() == R.id.packageBroadcast? 1 : 0)};
+ byte[] deviceCount = "1".getBytes();
+ if (mTask != null) {
+ mTask.cancelEsptouch();
+ }
+ mTask = new EsptouchAsyncTask4(this);
+ mTask.execute(ssid, bssid, password, deviceCount, broadcast);
+ }
+
+ public void onWifiChanged() {
+ StateResult stateResult = check();
+ mViewModel.message = stateResult.message;
+ mViewModel.ssid = stateResult.ssid;
+ mViewModel.ssidBytes = stateResult.ssidBytes;
+ mViewModel.bssid = stateResult.bssid;
+ mViewModel.confirmEnable = false;
+ if (stateResult.wifiConnected) {
+ mViewModel.confirmEnable = true;
+ if (stateResult.is5G) {
+ mViewModel.message = getString(R.string.esptouch1_wifi_5g_message);
+ }
+ } else {
+ if (mTask != null) {
+ mTask.cancelEsptouch();
+ mTask = null;
+ new AlertDialog.Builder(AddDeviceActivity.this)
+ .setMessage(R.string.esptouch1_configure_wifi_change_message)
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+ }
+ mViewModel.invalidateAll();
+ }
+
+ protected static class StateResult {
+ public CharSequence message = null;
+ public boolean permissionGranted = false;
+ public boolean locationRequirement = false;
+ public boolean wifiConnected = false;
+ public boolean is5G = false;
+ public InetAddress address = null;
+ public String ssid = null;
+ public byte[] ssidBytes = null;
+ public String bssid = null;
+ }
+
+ private StateResult check() {
+ StateResult result = checkPermission();
+ if (!result.permissionGranted) {
+ return result;
+ }
+ result = checkLocation();
+ result.permissionGranted = true;
+ if (result.locationRequirement) {
+ return result;
+ }
+ result = checkWifi();
+ result.permissionGranted = true;
+ result.locationRequirement = false;
+ return result;
+ }
+
+ protected StateResult checkWifi() {
+ StateResult result = new StateResult();
+ result.wifiConnected = false;
+ WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+
+ // 获取wifi列表
+ mWifiManager.startScan();
+ List scanWifiList = mWifiManager.getScanResults();
+ List wifiList = new ArrayList<>();
+ ssids=new ArrayList<>();
+ if (scanWifiList != null && scanWifiList.size() > 0) {
+ HashMap signalStrength = new HashMap();
+ for (int i = 0; i < scanWifiList.size(); i++) {
+ ScanResult scanResult = scanWifiList.get(i);
+ Log.e(TAG, "搜索的wifi-ssid:" + scanResult.SSID);
+ if (!scanResult.SSID.isEmpty()) {
+ String key = scanResult.SSID + " " + scanResult.capabilities;
+ if (!signalStrength.containsKey(key)) {
+ signalStrength.put(key, i);
+ wifiList.add(scanResult);
+ ssids.add(scanResult.SSID);
+ }
+ }
+ }
+ }
+
+ boolean connected = NetUtils.isWifiConnected(mWifiManager);
+ if (!connected) {
+ result.message = getString(R.string.esptouch_message_wifi_connection);
+ return result;
+ }
+
+ String ssid = NetUtils.getSsidString(wifiInfo);
+ selectedSSID=ssid;
+ int ipValue = wifiInfo.getIpAddress();
+ if (ipValue != 0) {
+ result.address = NetUtils.getAddress(wifiInfo.getIpAddress());
+ } else {
+ result.address = NetUtils.getIPv4Address();
+ if (result.address == null) {
+ result.address = NetUtils.getIPv6Address();
+ }
+ }
+
+ result.wifiConnected = true;
+ result.message = "";
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ result.is5G = NetUtils.is5G(wifiInfo.getFrequency());
+ }
+ if (result.is5G) {
+ result.message = getString(R.string.esptouch_message_wifi_frequency);
+ }
+ result.ssid = ssid;
+ result.ssidBytes = NetUtils.getRawSsidBytesOrElse(wifiInfo, ssid.getBytes());
+ result.bssid = wifiInfo.getBSSID();
+ return result;
+ }
+
+ protected StateResult checkLocation() {
+ StateResult result = new StateResult();
+ result.locationRequirement = true;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ LocationManager manager = getSystemService(LocationManager.class);
+ boolean enable = manager != null && LocationManagerCompat.isLocationEnabled(manager);
+ if (!enable) {
+ result.message = getString(R.string.esptouch_message_location);
+ return result;
+ }
+ }
+
+ result.locationRequirement = false;
+ return result;
+ }
+
+ protected StateResult checkPermission() {
+ StateResult result = new StateResult();
+ result.permissionGranted = false;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ boolean locationGranted = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
+ == PackageManager.PERMISSION_GRANTED;
+ if (!locationGranted) {
+ String[] splits = getString(R.string.esptouch_message_permission).split("\n");
+ if (splits.length != 2) {
+ throw new IllegalArgumentException("Invalid String @RES esptouch_message_permission");
+ }
+ SpannableStringBuilder ssb = new SpannableStringBuilder(splits[0]);
+ ssb.append('\n');
+ SpannableString clickMsg = new SpannableString(splits[1]);
+ ForegroundColorSpan clickSpan = new ForegroundColorSpan(0xFF0022FF);
+ clickMsg.setSpan(clickSpan, 0, clickMsg.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ ssb.append(clickMsg);
+ result.message = ssb;
+ return result;
+ }
+ }
+
+ result.permissionGranted = true;
+ return result;
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (requestCode == REQUEST_PERMISSION) {
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ onWifiChanged();
+ } else {
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.esptouch1_location_permission_title)
+ .setMessage(R.string.esptouch1_location_permission_message)
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> finish())
+ .show();
+ }
+
+ return;
+ }
+
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ public static class EsptouchAsyncTask4 extends AsyncTask> {
+ private WeakReference mActivity;
+ private final Object mLock = new Object();
+ private ProgressDialog mProgressDialog;
+ private AlertDialog mResultDialog;
+ private IEsptouchTask mEsptouchTask;
+
+ EsptouchAsyncTask4(AddDeviceActivity activity) {
+ mActivity = new WeakReference<>(activity);
+ }
+
+ public void cancelEsptouch() {
+ cancel(true);
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ }
+ if (mResultDialog != null) {
+ mResultDialog.dismiss();
+ }
+ if (mEsptouchTask != null) {
+ mEsptouchTask.interrupt();
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ Activity activity = mActivity.get();
+ mProgressDialog = new ProgressDialog(activity);
+ mProgressDialog.setMessage(activity.getString(R.string.esptouch1_configuring_message));
+ mProgressDialog.setCanceledOnTouchOutside(false);
+ mProgressDialog.setOnCancelListener(dialog -> {
+ synchronized (mLock) {
+ if (mEsptouchTask != null) {
+ mEsptouchTask.interrupt();
+ }
+ }
+ });
+ mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, activity.getText(android.R.string.cancel),
+ (dialog, which) -> {
+ synchronized (mLock) {
+ if (mEsptouchTask != null) {
+ mEsptouchTask.interrupt();
+ }
+ }
+ });
+ mProgressDialog.show();
+ }
+
+ @Override
+ protected void onProgressUpdate(IEsptouchResult... values) {
+ Context context = mActivity.get();
+ if (context != null) {
+ IEsptouchResult result = values[0];
+ Log.i(TAG, "EspTouchResult: " + result);
+ String text = result.getBssid() + " is connected to the wifi";
+ Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ protected List doInBackground(byte[]... params) {
+ AddDeviceActivity activity = mActivity.get();
+ int taskResultCount;
+ synchronized (mLock) {
+ byte[] apSsid = params[0];
+ byte[] apBssid = params[1];
+ byte[] apPassword = params[2];
+ byte[] deviceCountData = params[3];
+ byte[] broadcastData = params[4];
+ taskResultCount = deviceCountData.length == 0 ? -1 : Integer.parseInt(new String(deviceCountData));
+ Context context = activity.getApplicationContext();
+ mEsptouchTask = new EsptouchTask(apSsid, apBssid, apPassword, context);
+ mEsptouchTask.setPackageBroadcast(broadcastData[0] == 1);
+ mEsptouchTask.setEsptouchListener(this::publishProgress);
+ }
+ return mEsptouchTask.executeForResults(taskResultCount);
+ }
+
+ @Override
+ protected void onPostExecute(List result) {
+ AddDeviceActivity activity = mActivity.get();
+ activity.mTask = null;
+ mProgressDialog.dismiss();
+ if (result == null) {
+ mResultDialog = new AlertDialog.Builder(activity)
+ .setMessage(R.string.esptouch1_configure_result_failed_port)
+ .setPositiveButton(android.R.string.ok, null)
+ .show();
+ mResultDialog.setCanceledOnTouchOutside(false);
+ return;
+ }
+
+ // check whether the task is cancelled and no results received
+ IEsptouchResult firstResult = result.get(0);
+ if (firstResult.isCancelled()) {
+ return;
+ }
+ // the task received some results including cancelled while
+ // executing before receiving enough results
+
+ if (!firstResult.isSuc()) {
+ mResultDialog = new AlertDialog.Builder(activity)
+ .setMessage(R.string.esptouch1_configure_result_failed)
+ .setPositiveButton(android.R.string.ok, null)
+ .show();
+ mResultDialog.setCanceledOnTouchOutside(false);
+ return;
+ }
+
+ ArrayList resultMsgList = new ArrayList<>(result.size());
+ for (IEsptouchResult touchResult : result) {
+ String message = activity.getString(R.string.esptouch1_configure_result_success_item,
+ touchResult.getBssid(), touchResult.getInetAddress().getHostAddress());
+ resultMsgList.add(message);
+ }
+ CharSequence[] items = new CharSequence[resultMsgList.size()];
+ mResultDialog = new AlertDialog.Builder(activity)
+ .setTitle(R.string.esptouch1_configure_result_success)
+ .setItems(resultMsgList.toArray(items), null)
+ .setPositiveButton(android.R.string.ok, null)
+ .show();
+ mResultDialog.setCanceledOnTouchOutside(false);
+ }
+ }
+
+ // end esptouch ----------------------------------------
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mWifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
+ mViewModel = new EspTouchViewModel();
+ }
+
+ @Override
+ protected boolean isSupportSlideBack() {
+ return true;
+ }
+
+ @Override
+ protected void initStatusBarStyle() {
+ StatusBarUtils.initStatusBarStyle(this, false, Colors.WHITE);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return KeyboardUtils.onDisableBackKeyDown(keyCode) && super.onKeyDown(keyCode, event);
+ }
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/activity/LoginActivity.java b/android/app/src/main/java/com/kerwin/wumei/activity/LoginActivity.java
new file mode 100644
index 00000000..bab79c1c
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/activity/LoginActivity.java
@@ -0,0 +1,36 @@
+
+package com.kerwin.wumei.activity;
+
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+import com.kerwin.wumei.core.BaseActivity;
+import com.kerwin.wumei.fragment.LoginFragment;
+import com.xuexiang.xui.utils.KeyboardUtils;
+import com.xuexiang.xui.utils.StatusBarUtils;
+import com.xuexiang.xutil.display.Colors;
+
+
+public class LoginActivity extends BaseActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ openPage(LoginFragment.class, getIntent().getExtras());
+ }
+
+ @Override
+ protected boolean isSupportSlideBack() {
+ return false;
+ }
+
+ @Override
+ protected void initStatusBarStyle() {
+ StatusBarUtils.initStatusBarStyle(this, false, Colors.WHITE);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return KeyboardUtils.onDisableBackKeyDown(keyCode) && super.onKeyDown(keyCode, event);
+ }
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/activity/MainActivity.java b/android/app/src/main/java/com/kerwin/wumei/activity/MainActivity.java
new file mode 100644
index 00000000..9c05b48f
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/activity/MainActivity.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.kerwin.wumei.activity;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.style.ForegroundColorSpan;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.location.LocationManagerCompat;
+import androidx.drawerlayout.widget.DrawerLayout;
+import androidx.viewpager.widget.ViewPager;
+
+import com.espressif.iot.esptouch.EsptouchTask;
+import com.espressif.iot.esptouch.IEsptouchResult;
+import com.espressif.iot.esptouch.IEsptouchTask;
+import com.espressif.iot.esptouch.util.ByteUtil;
+import com.espressif.iot.esptouch.util.TouchNetUtil;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+import com.google.android.material.navigation.NavigationView;
+import com.kerwin.wumei.R;
+import com.kerwin.wumei.adapter.entity.EspTouchViewModel;
+import com.kerwin.wumei.core.BaseActivity;
+import com.kerwin.wumei.core.BaseFragment;
+import com.kerwin.wumei.fragment.AboutFragment;
+import com.kerwin.wumei.fragment.FeedbackFragment;
+import com.kerwin.wumei.fragment.MessageFragment;
+import com.kerwin.wumei.fragment.SettingsFragment;
+import com.kerwin.wumei.fragment.device.AddDeviceFragment;
+import com.kerwin.wumei.fragment.device.GroupFragment;
+import com.kerwin.wumei.fragment.device.SceneFragment;
+import com.kerwin.wumei.fragment.device.ShareDeviceFragment;
+import com.kerwin.wumei.fragment.news.NewsFragment;
+import com.kerwin.wumei.fragment.profile.ProfileFragment;
+import com.kerwin.wumei.fragment.device.DeviceFragment;
+import com.kerwin.wumei.utils.NetUtils;
+import com.kerwin.wumei.utils.Utils;
+import com.kerwin.wumei.utils.XToastUtils;
+import com.kerwin.wumei.widget.GuideTipsDialog;
+import com.xuexiang.xaop.annotation.SingleClick;
+import com.xuexiang.xpage.core.PageOption;
+import com.xuexiang.xpage.enums.CoreAnim;
+import com.xuexiang.xui.adapter.FragmentAdapter;
+import com.xuexiang.xui.adapter.simple.AdapterItem;
+import com.xuexiang.xui.utils.ResUtils;
+import com.xuexiang.xui.utils.ThemeUtils;
+import com.xuexiang.xui.widget.imageview.RadiusImageView;
+import com.xuexiang.xui.widget.popupwindow.popup.XUISimplePopup;
+import com.xuexiang.xutil.XUtil;
+import com.xuexiang.xutil.common.ClickUtils;
+import com.xuexiang.xutil.common.CollectionUtils;
+import com.xuexiang.xutil.display.Colors;
+
+import java.lang.ref.WeakReference;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import butterknife.BindView;
+
+public class MainActivity extends BaseActivity implements View.OnClickListener, ViewPager.OnPageChangeListener, BottomNavigationView.OnNavigationItemSelectedListener, ClickUtils.OnClick2ExitListener, Toolbar.OnMenuItemClickListener {
+
+ @BindView(R.id.toolbar)
+ Toolbar toolbar;
+ @BindView(R.id.view_pager)
+ ViewPager viewPager;
+ /**
+ * 底部导航栏
+ */
+ @BindView(R.id.bottom_navigation)
+ BottomNavigationView bottomNavigation;
+ /**
+ * 侧边栏
+ */
+ @BindView(R.id.nav_view)
+ NavigationView navView;
+ @BindView(R.id.drawer_layout)
+ DrawerLayout drawerLayout;
+
+ private String[] mTitles;
+
+ @Override
+ protected int getLayoutId() {
+ return R.layout.activity_main;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ initViews();
+ initListeners();
+ }
+
+ @Override
+ protected boolean isSupportSlideBack() {
+ return true;
+ }
+
+ private void initViews() {
+ mTitles = ResUtils.getStringArray(R.array.home_titles);
+ toolbar.setTitle(mTitles[0]);
+ toolbar.inflateMenu(R.menu.menu_main);
+ toolbar.setOnMenuItemClickListener(this);
+ initHeader();
+
+ //主页内容填充
+ BaseFragment[] fragments = new BaseFragment[]{
+ new DeviceFragment(),
+ new SceneFragment(),
+ new NewsFragment(),
+ new ProfileFragment(),
+ };
+ FragmentAdapter adapter = new FragmentAdapter<>(getSupportFragmentManager(), fragments);
+ viewPager.setOffscreenPageLimit(mTitles.length - 1);
+ viewPager.setAdapter(adapter);
+
+ GuideTipsDialog.showTips(this);
+ }
+
+ /**
+ * 侧边栏头部
+ */
+ private void initHeader() {
+ navView.setItemIconTintList(null);
+ View headerView = navView.getHeaderView(0);
+ LinearLayout navHeader = headerView.findViewById(R.id.nav_header);
+ RadiusImageView ivAvatar = headerView.findViewById(R.id.iv_avatar);
+ TextView tvAvatar = headerView.findViewById(R.id.tv_avatar);
+ TextView tvSign = headerView.findViewById(R.id.tv_sign);
+
+ if (Utils.isColorDark(ThemeUtils.resolveColor(this, R.attr.colorAccent))) {
+ tvAvatar.setTextColor(Colors.WHITE);
+ tvSign.setTextColor(Colors.WHITE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ ivAvatar.setImageTintList(ResUtils.getColors(R.color.xui_config_color_white));
+ }
+ } else {
+ tvAvatar.setTextColor(ThemeUtils.resolveColor(this, R.attr.xui_config_color_title_text));
+ tvSign.setTextColor(ThemeUtils.resolveColor(this, R.attr.xui_config_color_explain_text));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ ivAvatar.setImageTintList(ResUtils.getColors(R.color.xui_config_color_gray_3));
+ }
+ }
+
+ // TODO: 2019-10-09 初始化数据
+ ivAvatar.setImageResource(R.drawable.ic_default_head);
+ tvAvatar.setText("15208747707");
+ tvSign.setText("物美点亮智慧生活...");
+ navHeader.setOnClickListener(this);
+ }
+
+ protected void initListeners() {
+ ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+ drawerLayout.addDrawerListener(toggle);
+ toggle.syncState();
+
+ //侧边栏点击事件
+ navView.setNavigationItemSelectedListener(menuItem -> {
+ switch (menuItem.getItemId()) {
+ case R.id.nav_add_device:
+ PageOption.to(AddDeviceFragment.class) //跳转的fragment
+ .setAnim(CoreAnim.slide) //页面转场动画
+ .setRequestCode(100) //请求码,用于返回结果
+ .setAddToBackStack(true) //是否加入堆栈
+ .setNewActivity(true,AddDeviceActivity.class) //是否使用新的Activity打开
+ .open(this); //打开页面进行跳转
+ break;
+ case R.id.nav_settings:
+ openNewPage(SettingsFragment.class);
+ break;
+ case R.id.nav_about:
+ openNewPage(AboutFragment.class);
+ break;
+ case R.id.nav_message:
+ openNewPage(MessageFragment.class);
+ break;
+ case R.id.nav_share_device:
+ openNewPage(ShareDeviceFragment.class);
+ break;
+ case R.id.nav_group:
+ openNewPage(GroupFragment.class);
+ break;
+ default:
+ XToastUtils.toast("点击了:" + menuItem.getTitle());
+ break;
+ }
+ return true;
+ });
+
+ //主页事件监听
+ viewPager.addOnPageChangeListener(this);
+ bottomNavigation.setOnNavigationItemSelectedListener(this);
+ }
+
+
+
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.add_device:
+ PageOption.to(AddDeviceFragment.class) //跳转的fragment
+ .setAnim(CoreAnim.slide) //页面转场动画
+ .setRequestCode(100) //请求码,用于返回结果
+ .setAddToBackStack(true) //是否加入堆栈
+ .setNewActivity(true, AddDeviceActivity.class) //是否使用新的Activity打开
+ .open(this); //打开页面进行跳转
+ break;
+ default:
+ break;
+ }
+ return false;
+ }
+
+ @SingleClick
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.nav_header:
+ XToastUtils.toast("功能完善中...");
+ break;
+ default:
+ break;
+ }
+ }
+
+ //=============ViewPager===================//
+
+ @Override
+ public void onPageScrolled(int i, float v, int i1) {
+
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ MenuItem item = bottomNavigation.getMenu().getItem(position);
+ toolbar.setTitle(item.getTitle());
+ item.setChecked(true);
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int i) {
+
+ }
+
+ //================Navigation================//
+
+ /**
+ * 底部导航栏点击事件
+ *
+ * @param menuItem
+ * @return
+ */
+ @Override
+ public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
+ int index = CollectionUtils.arrayIndexOf(mTitles, menuItem.getTitle());
+ if (index != -1) {
+ toolbar.setTitle(menuItem.getTitle());
+ viewPager.setCurrentItem(index, false);
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * 菜单、返回键响应
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ ClickUtils.exitBy2Click(2000, this);
+ }
+ return true;
+ }
+
+ @Override
+ public void onRetry() {
+ XToastUtils.toast("再按一次退出程序");
+ }
+
+ @Override
+ public void onExit() {
+ XUtil.exitApp();
+ }
+
+
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/activity/SplashActivity.java b/android/app/src/main/java/com/kerwin/wumei/activity/SplashActivity.java
new file mode 100644
index 00000000..9f9328e5
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/activity/SplashActivity.java
@@ -0,0 +1,68 @@
+package com.kerwin.wumei.activity;
+
+import android.view.KeyEvent;
+
+import com.kerwin.wumei.R;
+import com.kerwin.wumei.utils.SettingUtils;
+import com.kerwin.wumei.utils.TokenUtils;
+import com.kerwin.wumei.utils.Utils;
+import com.xuexiang.xui.utils.KeyboardUtils;
+import com.xuexiang.xui.widget.activity.BaseSplashActivity;
+import com.xuexiang.xutil.app.ActivityUtils;
+
+import me.jessyan.autosize.internal.CancelAdapt;
+
+/**
+ * 启动页【无需适配屏幕大小】
+ *
+ */
+public class SplashActivity extends BaseSplashActivity implements CancelAdapt {
+
+ @Override
+ protected long getSplashDurationMillis() {
+ return 500;
+ }
+
+ /**
+ * activity启动后的初始化
+ */
+ @Override
+ protected void onCreateActivity() {
+ initSplashView(R.drawable.xui_config_bg_splash);
+ startSplash(false);
+ }
+
+
+ /**
+ * 启动页结束后的动作
+ */
+ @Override
+ protected void onSplashFinished() {
+ if (SettingUtils.isAgreePrivacy()) {
+ loginOrGoMainPage();
+ } else {
+ Utils.showPrivacyDialog(this, (dialog, which) -> {
+ dialog.dismiss();
+ SettingUtils.setIsAgreePrivacy(true);
+ loginOrGoMainPage();
+ });
+ }
+ }
+
+ private void loginOrGoMainPage() {
+ if (TokenUtils.hasToken()) {
+ ActivityUtils.startActivity(MainActivity.class);
+ } else {
+ ActivityUtils.startActivity(LoginActivity.class);
+ }
+ finish();
+ }
+
+ /**
+ * 菜单、返回键响应
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return KeyboardUtils.onDisableBackKeyDown(keyCode) && super.onKeyDown(keyCode, event);
+ }
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/adapter/base/broccoli/BroccoliRecyclerAdapter.java b/android/app/src/main/java/com/kerwin/wumei/adapter/base/broccoli/BroccoliRecyclerAdapter.java
new file mode 100644
index 00000000..9a8df840
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/adapter/base/broccoli/BroccoliRecyclerAdapter.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.kerwin.wumei.adapter.base.broccoli;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.xuexiang.xui.adapter.recyclerview.BaseRecyclerAdapter;
+import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder;
+import com.xuexiang.xui.adapter.recyclerview.XRecyclerAdapter;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import me.samlss.broccoli.Broccoli;
+
+/**
+ * 使用Broccoli占位的基础适配器
+ *
+ * @author XUE
+ * @since 2019/4/8 16:33
+ */
+public abstract class BroccoliRecyclerAdapter extends BaseRecyclerAdapter {
+ /**
+ * 是否已经加载成功
+ */
+ private boolean mHasLoad = false;
+ private Map mBroccoliMap = new HashMap<>();
+
+ public BroccoliRecyclerAdapter(Collection collection) {
+ super(collection);
+ }
+
+ @Override
+ protected void bindData(@NonNull RecyclerViewHolder holder, int position, T item) {
+ Broccoli broccoli = mBroccoliMap.get(holder.itemView);
+ if (broccoli == null) {
+ broccoli = new Broccoli();
+ mBroccoliMap.put(holder.itemView, broccoli);
+ }
+ if (mHasLoad) {
+ broccoli.removeAllPlaceholders();
+
+ onBindData(holder, item, position);
+ } else {
+ onBindBroccoli(holder, broccoli);
+ broccoli.show();
+ }
+ }
+
+ /**
+ * 绑定控件
+ *
+ * @param holder
+ * @param model
+ * @param position
+ */
+ protected abstract void onBindData(RecyclerViewHolder holder, T model, int position);
+
+ /**
+ * 绑定占位控件
+ *
+ * @param broccoli
+ */
+ protected abstract void onBindBroccoli(RecyclerViewHolder holder, Broccoli broccoli);
+
+ @Override
+ public XRecyclerAdapter refresh(Collection collection) {
+ mHasLoad = true;
+ return super.refresh(collection);
+ }
+
+ /**
+ * 资源释放,防止内存泄漏
+ */
+ public void recycle() {
+ for (Broccoli broccoli : mBroccoliMap.values()) {
+ broccoli.removeAllPlaceholders();
+ }
+ mBroccoliMap.clear();
+ clear();
+ }
+
+
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/adapter/base/broccoli/BroccoliSimpleDelegateAdapter.java b/android/app/src/main/java/com/kerwin/wumei/adapter/base/broccoli/BroccoliSimpleDelegateAdapter.java
new file mode 100644
index 00000000..ed722913
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/adapter/base/broccoli/BroccoliSimpleDelegateAdapter.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.kerwin.wumei.adapter.base.broccoli;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.alibaba.android.vlayout.LayoutHelper;
+import com.kerwin.wumei.adapter.base.delegate.SimpleDelegateAdapter;
+import com.kerwin.wumei.adapter.base.delegate.XDelegateAdapter;
+import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import me.samlss.broccoli.Broccoli;
+
+/**
+ * 使用Broccoli占位的基础适配器
+ *
+ * @author xuexiang
+ * @since 2021/1/9 4:52 PM
+ */
+public abstract class BroccoliSimpleDelegateAdapter extends SimpleDelegateAdapter {
+
+ /**
+ * 是否已经加载成功
+ */
+ private boolean mHasLoad = false;
+ private Map mBroccoliMap = new HashMap<>();
+
+ public BroccoliSimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper) {
+ super(layoutId, layoutHelper);
+ }
+
+ public BroccoliSimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper, Collection list) {
+ super(layoutId, layoutHelper, list);
+ }
+
+ public BroccoliSimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper, T[] data) {
+ super(layoutId, layoutHelper, data);
+ }
+
+ @Override
+ protected void bindData(@NonNull RecyclerViewHolder holder, int position, T item) {
+ Broccoli broccoli = mBroccoliMap.get(holder.itemView);
+ if (broccoli == null) {
+ broccoli = new Broccoli();
+ mBroccoliMap.put(holder.itemView, broccoli);
+ }
+ if (mHasLoad) {
+ broccoli.removeAllPlaceholders();
+
+ onBindData(holder, item, position);
+ } else {
+ onBindBroccoli(holder, broccoli);
+ broccoli.show();
+ }
+ }
+
+
+ /**
+ * 绑定控件
+ *
+ * @param holder
+ * @param model
+ * @param position
+ */
+ protected abstract void onBindData(RecyclerViewHolder holder, T model, int position);
+
+ /**
+ * 绑定占位控件
+ *
+ * @param holder
+ * @param broccoli
+ */
+ protected abstract void onBindBroccoli(RecyclerViewHolder holder, Broccoli broccoli);
+
+ @Override
+ public XDelegateAdapter refresh(Collection collection) {
+ mHasLoad = true;
+ return super.refresh(collection);
+ }
+
+ /**
+ * 资源释放,防止内存泄漏
+ */
+ public void recycle() {
+ for (Broccoli broccoli : mBroccoliMap.values()) {
+ broccoli.removeAllPlaceholders();
+ }
+ mBroccoliMap.clear();
+ clear();
+ }
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/BaseDelegateAdapter.java b/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/BaseDelegateAdapter.java
new file mode 100644
index 00000000..52c8fcda
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/BaseDelegateAdapter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.kerwin.wumei.adapter.base.delegate;
+
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+
+import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder;
+
+import java.util.Collection;
+
+/**
+ * 通用的DelegateAdapter适配器
+ *
+ * @author xuexiang
+ * @since 2020/3/20 12:44 AM
+ */
+public abstract class BaseDelegateAdapter extends XDelegateAdapter {
+
+ public BaseDelegateAdapter() {
+ super();
+ }
+
+ public BaseDelegateAdapter(Collection list) {
+ super(list);
+ }
+
+ public BaseDelegateAdapter(T[] data) {
+ super(data);
+ }
+
+ /**
+ * 适配的布局
+ *
+ * @param viewType
+ * @return
+ */
+ protected abstract int getItemLayoutId(int viewType);
+
+ @NonNull
+ @Override
+ protected RecyclerViewHolder getViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new RecyclerViewHolder(inflateView(parent, getItemLayoutId(viewType)));
+ }
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/SimpleDelegateAdapter.java b/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/SimpleDelegateAdapter.java
new file mode 100644
index 00000000..f0f280b0
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/SimpleDelegateAdapter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.kerwin.wumei.adapter.base.delegate;
+
+import com.alibaba.android.vlayout.LayoutHelper;
+
+import java.util.Collection;
+
+/**
+ * 简易DelegateAdapter适配器
+ *
+ * @author xuexiang
+ * @since 2020/3/20 12:55 AM
+ */
+public abstract class SimpleDelegateAdapter extends BaseDelegateAdapter {
+
+ private int mLayoutId;
+
+ private LayoutHelper mLayoutHelper;
+
+ public SimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper) {
+ super();
+ mLayoutId = layoutId;
+ mLayoutHelper = layoutHelper;
+ }
+
+ public SimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper, Collection list) {
+ super(list);
+ mLayoutId = layoutId;
+ mLayoutHelper = layoutHelper;
+ }
+
+ public SimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper, T[] data) {
+ super(data);
+ mLayoutId = layoutId;
+ mLayoutHelper = layoutHelper;
+ }
+
+ @Override
+ protected int getItemLayoutId(int viewType) {
+ return mLayoutId;
+ }
+
+
+ @Override
+ public LayoutHelper onCreateLayoutHelper() {
+ return mLayoutHelper;
+ }
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/SingleDelegateAdapter.java b/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/SingleDelegateAdapter.java
new file mode 100644
index 00000000..46b7329b
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/SingleDelegateAdapter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.kerwin.wumei.adapter.base.delegate;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+
+import com.alibaba.android.vlayout.DelegateAdapter;
+import com.alibaba.android.vlayout.LayoutHelper;
+import com.alibaba.android.vlayout.layout.SingleLayoutHelper;
+import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder;
+
+/**
+ * 单独布局的DelegateAdapter
+ *
+ * @author xuexiang
+ * @since 2020/3/20 1:04 AM
+ */
+public abstract class SingleDelegateAdapter extends DelegateAdapter.Adapter {
+
+ private int mLayoutId;
+
+ public SingleDelegateAdapter(int layoutId) {
+ mLayoutId = layoutId;
+ }
+
+ @Override
+ public LayoutHelper onCreateLayoutHelper() {
+ return new SingleLayoutHelper();
+ }
+
+ /**
+ * 加载布局获取控件
+ *
+ * @param parent 父布局
+ * @param layoutId 布局ID
+ * @return
+ */
+ protected View inflateView(ViewGroup parent, @LayoutRes int layoutId) {
+ return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
+ }
+
+ @NonNull
+ @Override
+ public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new RecyclerViewHolder(inflateView(parent, mLayoutId));
+ }
+
+ @Override
+ public int getItemCount() {
+ return 1;
+ }
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/XDelegateAdapter.java b/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/XDelegateAdapter.java
new file mode 100644
index 00000000..86881382
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/adapter/base/delegate/XDelegateAdapter.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.kerwin.wumei.adapter.base.delegate;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.alibaba.android.vlayout.DelegateAdapter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 基础DelegateAdapter
+ *
+ * @author xuexiang
+ * @since 2020/3/20 12:17 AM
+ */
+public abstract class XDelegateAdapter extends DelegateAdapter.Adapter {
+ /**
+ * 数据源
+ */
+ protected final List mData = new ArrayList<>();
+ /**
+ * 当前点击的条目
+ */
+ protected int mSelectPosition = -1;
+
+ public XDelegateAdapter() {
+
+ }
+
+ public XDelegateAdapter(Collection list) {
+ if (list != null) {
+ mData.addAll(list);
+ }
+ }
+
+ public XDelegateAdapter(T[] data) {
+ if (data != null && data.length > 0) {
+ mData.addAll(Arrays.asList(data));
+ }
+ }
+
+ /**
+ * 构建自定义的ViewHolder
+ *
+ * @param parent
+ * @param viewType
+ * @return
+ */
+ @NonNull
+ protected abstract V getViewHolder(@NonNull ViewGroup parent, int viewType);
+
+ /**
+ * 绑定数据
+ *
+ * @param holder
+ * @param position 索引
+ * @param item 列表项
+ */
+ protected abstract void bindData(@NonNull V holder, int position, T item);
+
+ /**
+ * 加载布局获取控件
+ *
+ * @param parent 父布局
+ * @param layoutId 布局ID
+ * @return
+ */
+ protected View inflateView(ViewGroup parent, @LayoutRes int layoutId) {
+ return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
+ }
+
+ @NonNull
+ @Override
+ public V onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return getViewHolder(parent, viewType);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull V holder, int position) {
+ bindData(holder, position, mData.get(position));
+ }
+
+ /**
+ * 获取列表项
+ *
+ * @param position
+ * @return
+ */
+ public T getItem(int position) {
+ return checkPosition(position) ? mData.get(position) : null;
+ }
+
+ private boolean checkPosition(int position) {
+ return position >= 0 && position <= mData.size() - 1;
+ }
+
+ public boolean isEmpty() {
+ return getItemCount() == 0;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mData.size();
+ }
+
+ /**
+ * @return 数据源
+ */
+ public List getData() {
+ return mData;
+ }
+
+ /**
+ * 给指定位置添加一项
+ *
+ * @param pos
+ * @param item
+ * @return
+ */
+ public XDelegateAdapter add(int pos, T item) {
+ mData.add(pos, item);
+ notifyItemInserted(pos);
+ return this;
+ }
+
+ /**
+ * 在列表末端增加一项
+ *
+ * @param item
+ * @return
+ */
+ public XDelegateAdapter add(T item) {
+ mData.add(item);
+ notifyItemInserted(mData.size() - 1);
+ return this;
+ }
+
+ /**
+ * 删除列表中指定索引的数据
+ *
+ * @param pos
+ * @return
+ */
+ public XDelegateAdapter delete(int pos) {
+ mData.remove(pos);
+ notifyItemRemoved(pos);
+ return this;
+ }
+
+ /**
+ * 刷新列表中指定位置的数据
+ *
+ * @param pos
+ * @param item
+ * @return
+ */
+ public XDelegateAdapter refresh(int pos, T item) {
+ mData.set(pos, item);
+ notifyItemChanged(pos);
+ return this;
+ }
+
+ /**
+ * 刷新列表数据
+ *
+ * @param collection
+ * @return
+ */
+ public XDelegateAdapter refresh(Collection collection) {
+ if (collection != null) {
+ mData.clear();
+ mData.addAll(collection);
+ mSelectPosition = -1;
+ notifyDataSetChanged();
+ }
+ return this;
+ }
+
+ /**
+ * 刷新列表数据
+ *
+ * @param array
+ * @return
+ */
+ public XDelegateAdapter refresh(T[] array) {
+ if (array != null && array.length > 0) {
+ mData.clear();
+ mData.addAll(Arrays.asList(array));
+ mSelectPosition = -1;
+ notifyDataSetChanged();
+ }
+ return this;
+ }
+
+ /**
+ * 加载更多
+ *
+ * @param collection
+ * @return
+ */
+ public XDelegateAdapter loadMore(Collection collection) {
+ if (collection != null) {
+ mData.addAll(collection);
+ notifyDataSetChanged();
+ }
+ return this;
+ }
+
+ /**
+ * 加载更多
+ *
+ * @param array
+ * @return
+ */
+ public XDelegateAdapter loadMore(T[] array) {
+ if (array != null && array.length > 0) {
+ mData.addAll(Arrays.asList(array));
+ notifyDataSetChanged();
+ }
+ return this;
+ }
+
+ /**
+ * 添加一个
+ *
+ * @param item
+ * @return
+ */
+ public XDelegateAdapter load(T item) {
+ if (item != null) {
+ mData.add(item);
+ notifyDataSetChanged();
+ }
+ return this;
+ }
+
+ /**
+ * @return 当前列表的选中项
+ */
+ public int getSelectPosition() {
+ return mSelectPosition;
+ }
+
+ /**
+ * 设置当前列表的选中项
+ *
+ * @param selectPosition
+ * @return
+ */
+ public XDelegateAdapter setSelectPosition(int selectPosition) {
+ mSelectPosition = selectPosition;
+ notifyDataSetChanged();
+ return this;
+ }
+
+ /**
+ * 获取当前列表选中项
+ *
+ * @return 当前列表选中项
+ */
+ public T getSelectItem() {
+ return getItem(mSelectPosition);
+ }
+
+ /**
+ * 清除数据
+ */
+ public void clear() {
+ if (!isEmpty()) {
+ mData.clear();
+ mSelectPosition = -1;
+ notifyDataSetChanged();
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/adapter/entity/EspTouchViewModel.java b/android/app/src/main/java/com/kerwin/wumei/adapter/entity/EspTouchViewModel.java
new file mode 100644
index 00000000..fb4a2429
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/adapter/entity/EspTouchViewModel.java
@@ -0,0 +1,31 @@
+package com.kerwin.wumei.adapter.entity;
+
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+
+import com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner;
+
+public class EspTouchViewModel {
+ public MaterialSpinner ssidSpinner;
+ public EditText apPasswordEdit;
+ public EditText deviceCountEdit;
+ public RadioGroup packageModeGroup;
+ public TextView messageView;
+ public Button confirmBtn;
+
+ public String ssid;
+ public byte[] ssidBytes;
+ public String bssid;
+
+ public CharSequence message;
+
+ public boolean confirmEnable;
+
+ public void invalidateAll() {
+ ssidSpinner.setText(ssid);
+ messageView.setText(message);
+ confirmBtn.setEnabled(confirmEnable);
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/kerwin/wumei/adapter/entity/NewInfo.java b/android/app/src/main/java/com/kerwin/wumei/adapter/entity/NewInfo.java
new file mode 100644
index 00000000..165ed133
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/adapter/entity/NewInfo.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.kerwin.wumei.adapter.entity;
+
+/**
+ * 新闻信息
+ *
+ * @author xuexiang
+ * @since 2019/4/7 下午12:07
+ */
+public class NewInfo {
+
+ /**
+ * 用户名
+ */
+ private String UserName = "kerwin";
+ /**
+ * 标签
+ */
+ private String Tag;
+ /**
+ * 标题
+ */
+ private String Title;
+ /**
+ * 摘要
+ */
+ private String Summary;
+
+ /**
+ * 图片
+ */
+ private String ImageUrl;
+ /**
+ * 点赞数
+ */
+ private int Praise;
+ /**
+ * 评论数
+ */
+ private int Comment;
+ /**
+ * 阅读量
+ */
+ private int Read;
+ /**
+ * 新闻的详情地址
+ */
+ private String DetailUrl;
+
+
+ public NewInfo() {
+
+ }
+
+ public NewInfo(String userName, String tag, String title, String summary, String imageUrl, int praise, int comment, int read, String detailUrl) {
+ UserName = userName;
+ Tag = tag;
+ Title = title;
+ Summary = summary;
+ ImageUrl = imageUrl;
+ Praise = praise;
+ Comment = comment;
+ Read = read;
+ DetailUrl = detailUrl;
+ }
+
+
+ public NewInfo(String tag, String title, String summary, String imageUrl, String detailUrl) {
+ Tag = tag;
+ Title = title;
+ Summary = summary;
+ ImageUrl = imageUrl;
+ DetailUrl = detailUrl;
+ }
+
+
+ public NewInfo(String tag, String title) {
+ Tag = tag;
+ Title = title;
+
+ Praise = (int) (Math.random() * 100 + 5);
+ Comment = (int) (Math.random() * 50 + 5);
+ Read = (int) (Math.random() * 500 + 50);
+ }
+
+
+
+ public String getUserName() {
+ return UserName;
+ }
+
+ public NewInfo setUserName(String userName) {
+ UserName = userName;
+ return this;
+ }
+
+ public String getTag() {
+ return Tag;
+ }
+
+ public NewInfo setTag(String tag) {
+ Tag = tag;
+ return this;
+ }
+
+ public String getTitle() {
+ return Title;
+ }
+
+ public NewInfo setTitle(String title) {
+ Title = title;
+ return this;
+ }
+
+ public String getSummary() {
+ return Summary;
+ }
+
+ public NewInfo setSummary(String summary) {
+ Summary = summary;
+ return this;
+ }
+
+ public String getImageUrl() {
+ return ImageUrl;
+ }
+
+ public NewInfo setImageUrl(String imageUrl) {
+ ImageUrl = imageUrl;
+ return this;
+ }
+
+ public int getPraise() {
+ return Praise;
+ }
+
+ public NewInfo setPraise(int praise) {
+ Praise = praise;
+ return this;
+ }
+
+ public int getComment() {
+ return Comment;
+ }
+
+ public NewInfo setComment(int comment) {
+ Comment = comment;
+ return this;
+ }
+
+ public int getRead() {
+ return Read;
+ }
+
+ public NewInfo setRead(int read) {
+ Read = read;
+ return this;
+ }
+
+ public String getDetailUrl() {
+ return DetailUrl;
+ }
+
+ public NewInfo setDetailUrl(String detailUrl) {
+ DetailUrl = detailUrl;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "NewInfo{" +
+ "UserName='" + UserName + '\'' +
+ ", Tag='" + Tag + '\'' +
+ ", Title='" + Title + '\'' +
+ ", Summary='" + Summary + '\'' +
+ ", ImageUrl='" + ImageUrl + '\'' +
+ ", Praise=" + Praise +
+ ", Comment=" + Comment +
+ ", Read=" + Read +
+ ", DetailUrl='" + DetailUrl + '\'' +
+ '}';
+ }
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/core/BaseActivity.java b/android/app/src/main/java/com/kerwin/wumei/core/BaseActivity.java
new file mode 100644
index 00000000..b6529fdd
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/core/BaseActivity.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.kerwin.wumei.core;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.xuexiang.xpage.base.XPageActivity;
+import com.xuexiang.xpage.base.XPageFragment;
+import com.xuexiang.xpage.core.CoreSwitchBean;
+import com.xuexiang.xrouter.facade.service.SerializationService;
+import com.xuexiang.xrouter.launcher.XRouter;
+import com.xuexiang.xui.utils.ResUtils;
+import com.xuexiang.xui.widget.slideback.SlideBack;
+
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+import io.github.inflationx.viewpump.ViewPumpContextWrapper;
+
+/**
+ * 基础容器Activity
+ *
+ * @author XUE
+ * @since 2019/3/22 11:21
+ */
+public class BaseActivity extends XPageActivity {
+
+ Unbinder mUnbinder;
+
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ //注入字体
+ super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase));
+ }
+
+ /**
+ * 是否支持侧滑返回
+ */
+ public static final String KEY_SUPPORT_SLIDE_BACK = "key_support_slide_back";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ initStatusBarStyle();
+ super.onCreate(savedInstanceState);
+ mUnbinder = ButterKnife.bind(this);
+
+ registerSlideBack();
+ }
+
+ /**
+ * 初始化状态栏的样式
+ */
+ protected void initStatusBarStyle() {
+
+ }
+
+ /**
+ * 打开fragment
+ *
+ * @param clazz 页面类
+ * @param addToBackStack 是否添加到栈中
+ * @return 打开的fragment对象
+ */
+ public T openPage(Class clazz, boolean addToBackStack) {
+ CoreSwitchBean page = new CoreSwitchBean(clazz)
+ .setAddToBackStack(addToBackStack);
+ return (T) openPage(page);
+ }
+
+ /**
+ * 打开fragment
+ *
+ * @return 打开的fragment对象
+ */
+ public T openNewPage(Class clazz) {
+ CoreSwitchBean page = new CoreSwitchBean(clazz)
+ .setNewActivity(true);
+ return (T) openPage(page);
+ }
+
+ /**
+ * 切换fragment
+ *
+ * @param clazz 页面类
+ * @return 打开的fragment对象
+ */
+ public T switchPage(Class clazz) {
+ return openPage(clazz, false);
+ }
+
+ /**
+ * 序列化对象
+ *
+ * @param object
+ * @return
+ */
+ public String serializeObject(Object object) {
+ return XRouter.getInstance().navigation(SerializationService.class).object2Json(object);
+ }
+
+ @Override
+ protected void onRelease() {
+ mUnbinder.unbind();
+ unregisterSlideBack();
+ super.onRelease();
+ }
+
+ /**
+ * 注册侧滑回调
+ */
+ protected void registerSlideBack() {
+ if (isSupportSlideBack()) {
+ SlideBack.with(this)
+ .haveScroll(true)
+ .edgeMode(ResUtils.isRtl() ? SlideBack.EDGE_RIGHT : SlideBack.EDGE_LEFT)
+ .callBack(this::popPage)
+ .register();
+ }
+ }
+
+ /**
+ * 注销侧滑回调
+ */
+ protected void unregisterSlideBack() {
+ if (isSupportSlideBack()) {
+ SlideBack.unregister(this);
+ }
+ }
+
+ /**
+ * @return 是否支持侧滑返回
+ */
+ protected boolean isSupportSlideBack() {
+ CoreSwitchBean page = getIntent().getParcelableExtra(CoreSwitchBean.KEY_SWITCH_BEAN);
+ return page == null || page.getBundle() == null || page.getBundle().getBoolean(KEY_SUPPORT_SLIDE_BACK, true);
+ }
+
+}
diff --git a/android/app/src/main/java/com/kerwin/wumei/core/BaseContainerFragment.java b/android/app/src/main/java/com/kerwin/wumei/core/BaseContainerFragment.java
new file mode 100644
index 00000000..29987cc1
--- /dev/null
+++ b/android/app/src/main/java/com/kerwin/wumei/core/BaseContainerFragment.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.kerwin.wumei.core;
+
+import android.content.res.Configuration;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+
+import com.umeng.analytics.MobclickAgent;
+import com.xuexiang.xaop.annotation.SingleClick;
+import com.xuexiang.xpage.base.XPageContainerListFragment;
+import com.xuexiang.xui.widget.actionbar.TitleBar;
+import com.xuexiang.xui.widget.actionbar.TitleUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.kerwin.wumei.core.SimpleListAdapter.KEY_SUB_TITLE;
+import static com.kerwin.wumei.core.SimpleListAdapter.KEY_TITLE;
+
+/**
+ * 修改列表样式为主副标题显示
+ *
+ * @author xuexiang
+ * @since 2018/11/22 上午11:26
+ */
+public abstract class BaseContainerFragment extends XPageContainerListFragment {
+
+ @Override
+ protected void initPage() {
+ initTitle();
+ initViews();
+ initListeners();
+ }
+
+ protected TitleBar initTitle() {
+ return TitleUtils.addTitleBarDynamic((ViewGroup) getRootView(), getPageTitle(), new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ popToBack();
+ }
+ });
+ }
+
+ @Override
+ protected void initData() {
+ mSimpleData = initSimpleData(mSimpleData);
+
+ List