From 868577557914d26dfd0247df43482358adff0ef7 Mon Sep 17 00:00:00 2001 From: Riguz Lee Date: Fri, 21 Feb 2025 20:54:20 +0800 Subject: [PATCH] updated password list --- assets/fonts/AgileSans-Regular.otf | Bin 0 -> 59288 bytes lib/main.dart | 3 - lib/src/database/database.dart | 10 -- lib/src/database/database.g.dart | 57 +++++++++- lib/src/database/tables.drift | 1 + .../screens/components/category_group.dart | 43 +++++--- lib/src/screens/components/category_item.dart | 22 ++-- .../components/password_categories.dart | 50 ++++++--- .../components/password_categories.g.dart | 16 +++ .../screens/{ => components}/passwords.dart | 19 +++- .../screens/{ => components}/passwords.g.dart | 2 +- lib/src/screens/home.dart | 98 +++++++++++------- lib/src/service/password_repository.dart | 7 ++ lib/src/theme.dart | 2 + macos/Runner/Base.lproj/MainMenu.xib | 12 ++- pubspec.yaml | 10 +- test/unit_test.dart | 27 ++++- 17 files changed, 276 insertions(+), 103 deletions(-) create mode 100644 assets/fonts/AgileSans-Regular.otf rename lib/src/screens/{ => components}/passwords.dart (65%) rename lib/src/screens/{ => components}/passwords.g.dart (92%) diff --git a/assets/fonts/AgileSans-Regular.otf b/assets/fonts/AgileSans-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..e0648bf5e29f850ccea410e0d28ee53db153923d GIT binary patch literal 59288 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≧~_mkcCU zjg+vTRfBy=Ky_gk(u1CIL5)!pknlF3!PZX1DXvxYA!OOIdE$E2~J|a!0B#2a)O=O87)A~ z&~jL*D^OFk2(-{@&iSa2@E44X_ULK)-B(b+H+C;C$G#cYzMyiGt8x6pRjn zPCSfyphIACDFz*P4E07wK~tVVebH&qny1hZbP06R6*L%j>7nQ{XoMS}sqTV?N=6T0 zcYg$0<{@aj<6v_+%Q~<|*1$TkE^H&VG24(;v(?xdtQMrP7R$3XtSwuetqD5%Cg||% zpkGQr^W9^vG1r+(%oXM;a~UL;V~#Q(na9j4xIq(V;NaqhX>a!PL%yAzuo^ybgx65QgvsOy(8n&OP)5y+ZHNC-j+7Fx8lP zj6EnjS6G@YnLs9(>BkIWqM1Y{l^M@WVP-J1n1#$TW(~6mgm6Do42$VJ2<08-33R)R z`NSd^i0UwK^;mltPIuOqZ39cW8{3QR&kkXu*?4v|o5@aNXR^Pr3)yAtMs_>9hds)k zX3w)F?4Rs&)&vUGRwl?A%Y0<*WZh(aWD&ABS%z$yY>sS+Y=f*&RwO$r`$Kj|Rw{cb zdo3%Ii83PlENA6vd2P9)+)eH)Z!hmI50gjAhstB+@$wXTmi!0#Jo!TTGWmM>R{0Kj zk^F@Giu|7ZmHdOeLSdt*uBfH3Q#4R`D0~#H6#-xN?*-SvgiY zMLAPBN4ZG3O1VY3Te)9ZtURl{s{B*=Q2A16;uM^QGjJ}PJLku>eL_lkSZ5tUq3U8PgiS2?O&RPHKYRa;d@Rj?{t z6`_h&C8*L=lTmS2FI0c4%r;7!8a8!p>}_0ZJZ=1K zI@$!=^tBmk6K|7flWsG`CdcMin`JiZZ1Qb(+8newZgaury3IqI=QeL`Ogzim@O5~B zZ@@R@J^5CA2fi!ci|@w|=41H;K9wKO|G>}ZSMmk?e*PqXng5f2!GBcC)iu@i)h=pp z^#t|o_QO(QGhzdyqSMo2+b2dvrz9t}Pad9}6gw&~ItpGoO22_I$!Ss1(XmNs9RVXM zt<#tHPEvU%OO;L*0$qkhrKBgtyL)(g27N&ck{ar2!RTti@A{=wLig%md9(_U+WlKhO|{0u#xr^w}qD_ze9Ren(5Y_SlOdPohcE^{5qhS zm4loqjqJFuCF7)w@lu8HU-}>AmYVAB5hdkXl3#0B-lYMvBr9oK-XwaKq=h3(+QOtI zO`)fPRYzO?9Rl?_X!udRDVeQC!N=GD@; zr^JG%W#*+CU#DMsm1fn`OG?KL85TDr4*naGK!4)nhKz_CG71=tt{kK2N`65izsNQ% z|H^B0C8yC}_>8uSF-5d67G23}^e~zUgA*N1!8$zA7Ez};R#BnJFY^X3F#xe?BWOyJ z94SGS4$Y@fNy)M>XwEkb!y>n=M_N*qaIAHlqpeGamRF8(Gz?~R96;r8Mt>Q-=&HjS zP5GjW3m)+lzG*tXlC}6MJW#YRE3uLV>yo4tzOXP7-bMjPEqnPwDuF^JP0LdKQXWXl zQWK(5DMyI_NfauPEsGd(Bx2qzAO%aAVpt_9IvHM)E76m`=|*xD?vpDy{zeVZx|Ite zI=PaIfXXrgjzCH{P9XHzLuGDO8GnbxFpNR>7uDXOPO zD@ajMpsIyFDiI|@EkN3>cpYepLM6%cZ~B;CrH|>Aef&l((Yjyym~IvB^e?lO0p*z# zD(P3zRy4Z`eT&Y5%y9r?>9ev<6+K3?tJJZmHOL%Kp%Q<5WpBS~I$9i?lI)h88V9>J zeWYbdZ)qb^Vx{-E_zcT4IX#7zr^b(!9>LQx?iJB~hk{PU%21IW2ZfdQ`&jl-Q^=&`~s<5SyARC89=; zPDvgsC1Vpur;SUEO^Z*8i%*JA8yBCHMs-&LAjG6cr=_MRCW4YpPD%mY91Yp9$Hqq` z#KgzNB_xlBWgZ)qI2sImQAt#l(skkG-qP~$l_VY3?l4KBXu?v^K@u$b?iQ7hX33Mz zMPBZKmbVs`M|;b|TUzR0>U&slJuOI{z7mqBrBN>*>CMYh=4C1KvNF`tEjlXIqL`(| z>6@hmU#a8^o6?)5q(haoXO(ndm2`_L>GoC9-ctGt^IuCms!;N*QrpwQhqXfz{#W~L zq)T*Uz7Zs)4GZsu zE~@UTo~T}_-l{&>a5ie2?`)dbw6O_T(P-h z^Tg(*%{v>+%Xu|#%h!|cI4*oMxaG9vyYYSbVf;uwh0ow8@H6;1{9=9`znS03@6&Bw zzi!Kx(d$Ny95ZI*$aQ1381c9|o7X|!7|0t9dFvrhojPgZMk8*=;lOssiQv^KGdeb+g?<|)xB9xF*L9I7C4C8j2g@-$#K(k1!3V? zK3#!T$_qG9fy%rizFQpBdDr;XIBiFV82ZRhQd?%DP6-XH0M zuNbhL%j@UA##K)OTlfDm#x0*}3~Ts%{E;_`Wt-=%S#MX6vNqnJ!EJLDq%QY;^q}d3^$i*w z!VcK!%>k^ub2%;fxDg|6)ez5>zIxJy2+haez%Qyko>^v_?Vcq_=t$K{KCT3`3H<@(wFd!NKex8 zK?|&XcxlJw-A47uMH|x(>ep?UGjF}|4mV6tlQH;3Y5u`0xliliTDifGNqaq6TRt7j z@eL(b=P1@F@gQLHN}d+CWlY}`pFwT`qh+mgnjt%BGnah&-FFET*! zQHd{^x`XVQPjhB3@w9>Hd&!&rg);(oXvQQ$V@oblIkM;)=nGjPZQSS6WC%)mf@|I>Al z9itsFwj)i*m>dH%?#z*sqO+obx%iFPP>GM58!F1gVx`(gIM82FxPIlTe7$DGwgB-h!augkU0dCY%D5BHDJu# zS3!l;U9*$y$;)g}=WnVRX#k zv|jy&EL;(1Y{zA7F3LWv$ISKJ_tp2$uUuVd{KzFm_D%QK6It`)*bdu0E5e zaO8rev4*x>_J$pkPwFxM_$+RO>jqsXoN)$;(?vzCJh-=Deeu!!;ll?H88g7B(G3h6 z7nNjuT)gkk{hPBh@(nk+-^Ro(4Am30M|)x*w!gQ=nh(DpdipSQr;jn24AJ!(xpmut z{TmPM+c`0;?}*X8K|#5R)$w=c>Iw`))mF?^Xz&0f9$$*XHD6Ez?{4L0Yk^Z}t691i} zP>)}-=^#`_P4jnRLRLCGBv2CpA!{KK+ic6g47u}#03hheYQC)j<-@t2!O5$PEuKZrm z4qeDAAAqd+|8&I9)vj5%3u$i9L{E$s)T0E6D_lAgS0~l6#yLYQ&V)&ly||9`*v|DT zkrB3O2VzI`F9R@Z#DB@poQra6)_q8DaQD5XrRR2?F={@XI)Up?H(-|Yn%g02L>m=! zmQjD7kv=t1uP(}dU4iCOozX8F;U;g?x<+e-5+z=XLqWp_fQG*bYl_FLT6{PY8;DJY zY5FdHS$%oBb}UG?X*%pU8f=hZ!8BhNlb9HjygYr?G-Cj1cp>&8wpXm({p*@db~}^u zq7CW+!dhN^fg2#Gf0!`g$85d&DRw=N?H;R1Qt|kV_XpI!@lW<`o-uW!q1?61l*ug( z3R&o>*H{#yKPbfBAt4zjt{ME5YA17UnYzo&rxi?kHp&pxx~B6o7p}HjzGT_ipABlW zPnqbWUMUUMm1*K;D=FQs+N9D!FB!E#RZ&aaCJ=3B` z*_qiWQ~hMPfR}M<+&oyor?5<>z7Ip8#y?K@h^IWqQ!oO#i3GX9(K(9p`Y;gnIXoJ~ zMhBX}NvXNECLwWHbQ*{&u~(!HosyPd7rkNB&h6@5WY`m|dV(LmQ0v4PCv&l=E9KO$ zxUnOmCJ)l9%cm#t>caQ<+IzKWdZwBjIH$v{=)NAxX_mzdTo9%w-i|J?pS60{8rOPo zY0KF?Mi|nef_n70MQhYgS7zYr&+yc@>M|`-6U2?*eQNvzJ+A-w2q-&6n;p)pjli<# ztou+>^5H|<5(oP>Z5?{=B4O5R;9Vt7_b^=`m6>Pn{w9znqW>a>%GVrKZQI z$@TuioiZ8j0m{^=e0rHuy#+MLR^cJmSxzD96US0WO#@k9hEH+WtNoFN2F=e|e~UNY z&_!1Odv6|A%GBQ84@U~>xa>D#jkg0c)t529KQ$A(U!0I>ix=PObX&bGZ_~DI37g{L zUPy+)9!9VfoPf0a^>3O~ryGqJwgIUIUT{XBo;xt$xQWT~@Y zQ#p$n2IPggTc%cyp_6|UJ0G=$!Je9y^8F}%LgsJl)nb~Ce5X_&oBU23o~edI&$JCj zwe4yUu`s;UL#fVyaZ_M5tRQ><{7ohT+jX;E;3tpN{e|Z+OVJ>8jhu)*ae3Af*Hs6{59!x0o`+4C7-2s) zJm=IT>Teuz7w><~sGsi=upBmwnI=~SXz`O&$5DSQIOV{*#>_xy3(Ne(psftt0wCs# z`4KTQ5xD-CSqPk<;16VS5d*PA&Ip`G5VnJMFw7hT9y#V0#LPty$_eqC%zOqsj}Vjv z-bb{XWgsqT9|Cs~1PwAv5IFppWr$gd7>Li#MG(map@Pgx#H>IN{{k*W@Ux-=GG;Xc zu1N6hf%_2LS>TR@cpwOdV%8yMEd+fdW&;8bDzgbebQ(Ae8Q?z;FfSZfijlkc^>_Om7WcDKP3^Mx=ct*iZ$UviKSn&U%a}4-vQ6TjdgM*WS z);l5aK{7`e@ScL>lR1jeMTR+sz>&xR&Ep6KF%Xi<0OivN{He?t1ioQ#`JyW4OAx6wAA$E0d~*{7SmW5m2-nYV~} zgTNyVfoe<{Vm>g8h+x4mCIyQSQ-N59ff!*H0?!o;1QxLzVr>|d$gpY~whm%-i2be> z+XS(V5!+P3dLq^Xv0gq9mdM5wPq-Vf+Ok*;JBJj6^GaCHoG)O=V zQC$eU$>739z!(Iw4MyMu1`j+q=fQsq?qn2zP&xztckpk5%M;wu;OqpKG#bT#V;ns5 z;QR%DEx5?pd<4#V@Y{l$9Q@1RiU+SVxTo0y1kP#jo3kMHOA)&rfzKEm_TV#T3zgtk z2gf)IqT+zS#R|@H@cM#F8XTzLAP1j5xYOanj7Br;ejb7;*%OF8#ew%9T-@d&s@y0rGJ92>C?$9{EN2WBF%AJ4LEu zI`}D1!cDL~cpeuh^Wd(1S6R;03)ii!V^_zy zPOCb>b%xj3TjzY82X#!}MSnNZ#UU)s@)>H19ruBrTUKg0s3_PFZvzzQT;~sJ?potKc{{{{R8#S)PEpE2pI;I zA>MGpSj*Vd*wZ-1_^WY?@sRNXgzvPoKV<*ALoEk4hhH2vIUIEO!{Lo%O-Bbu569k) ziye%3-sGnZz)nvHGt+Re_*&#kpvlv}i0vRk&> zT(>oDSKVH@l{c^5yhro#&1W?)Xnv;oGxu8VS?)i%FL5t$KkNR?UG%8w;p-9aG1_C2 z$83)^9)~=xdvcx~J;OXlcuw@3=lRr2<@KFc1Ftx*AH3#yt@1kP?d3hdJJ0*2PaB^V zKKVY!eQvjC(4s|)&=!d;4z;+|;+e0`*TXl#cY<$@??T_-eP8;DEtM@DT4uLA((-L9 zMJvZv0j-9#n$T)Tt0S#0w|e3y_%-+Q_Y3hG<~P>wgx^zt5C7%@# z+T3VU(YABj`E4(>tKDvLyR`vL0%ix4v}fCgwl8jvJ9O=^wZqlGK7oaS=8nxergl8h zN#3bjr`?^db?)2wK+rYXe}}&h{}?WYe+oB;f9`|& zuzlox0{SfJv#f8wezJaj`hDu3JD~o6=mF~n>>O}mz@-6K2F@6`XyC?yy9XW{cxm8^ zfuAB25p^S6BHBf?k4TKjiufsFVZ@?{-y#-AEQ#0@Q5111;#$Phh>wxVNNuD;q<3Us zWLRW;NK9-k!YUd8?1C z*t{yQAaC8~{LO3Od6s!}DKBqxUfxxi_#M4EAO74CA69nwq|hbw7;z?nxaXZI=$Xfr z5{<6wnNh_3FYe4JT|#`G@_BE-=-Ld5d*pH0Jxu3H+&AM|XSqDW>FkO7$GmpW_atPD zI>J3CEp-mW{R4+{WElQ)-Aw6T~y~Ov7X%uG3Hm zlCEmf>-bNToCO?LendyJ&tGEju-mxBZHNmo?bO8y{dxRj`A(^f-lL={%%}D`CjmdO z*1@(Ij0An#2{Js_^u}Jmy7D*QR%l{6;>F`_R{RRII2%Rr5~;#ZgL?=UKdS*ByO6Qx zakmT{_)wgj$%xBt$V~N#kB*)ackkVNf3Gno7tCtY{*77f0tr;CRCbP!itH1|oBc%p zDn_;-zW6kDInChA3%J1r+N}+``$v!7z8!wFMT_uofB&Q5_l-DKeqharnCMh6>!jxI zC|px;z=(hRJXY!kM){Ok?JHdLsjg1@-bK*9@)z)h&vl_#9Amxnpsv~INd*d~cc}_= z(`rJptzZo_nFK!-^Wl0Z(~6(gn~b`5<&KSYeNIBd5okH^<=MDbxP6c{OjWo6yJl^rKE#|cE z#UxR$(~5s?+cd+`8Sy(Oh@eb_rz6)(UqvdXN*|QZ6D~dop5@RLV`#gvWTp}L0~gV!QA8g z{DTxBsz--d!sx+RcO8 zKjEfCiwBG~YCpFw=GyZ`2?bt8?V*hvMzim2KCnggpIiKezk_Sj&f@O4woLRfJ!!~q zDSr>$#<4SBSFla#&=2QDKQMyeYo_Vl`Bp-el9Ne-R3x^ggD|%jE#5ZMT^P>G#MdTc zbLj<;Wa1EM5soE3Rr2c7O88pUJp9zM^2#bRKVUlZ(d`5+(tgD0(&}>|F}io0w$y6u zRQ{{6lk)Hq*B;JagkIrN+AF-646M?24Qat1t!$}E=H0*HBX4C!1rtP<$58t7i2lT8BRV|q z=@Kz7knE#(IJ`AS@rrE4V>wN_Aeo(#F)I^&MgJxOzC)R9qV~v^0T^NndkYsECJCE_+hmhQs>3 zkO}i$hYrMJs?jf)>);DSF7f$@6YIbg@*y?pa2lLU>oubiQi0ae>I31YVjWEtWh2|a zupH>fVB%hxa!pRUzDn(#zle;}zG`f)!_l;yru%suHlKk@=}`-DFgA*(k8q?VsC{!C zsDfKb1sbx0UQ=by>kJmij$naK6<$T9$^*1+l{}ENp9L~W%3R7*)oq)G`5Y|IS;B9n z@}n&!?LdDI7NAsxYui4+wZY;q2R`58rgT`erJ?0ds*qg!X_7ym@OhF{#j4w-=5Kyd zNAOtvxtfG5Dfhm-?aUj?Bx)koT9vy|TPo>y1GR*ePhZMuMXM^;xMyKxYMja7b6`Kh zy|Q#z|K>K1Fy@Mrz)xeItR!`N#^nZ;ie7U-7{bHPUsyeaFEV#^ z6=sg1YYq>4o5pJ(eG8TxUP%XMb$JBMvZ`=qIZW`BnVD3X#$ML$l`4dpjN5sMz*V}+ zDGicNnz;(=#|RA8&~cvhR)@!Nc;a89UXVdP3Ve~s$w!VDL-JfPDpHVr;>dR(5D#I7 z7;zzHWTt2;JHL|aWDy3Fzr_L%7Q;EDSg}NjbMaw?`8sAKF`6jV@`PGqxoLV!5Xgyi zdKpp5S|=%v_Pzd@KTJ{{%Psko0&WbF@BD=dIbXilRlpNTTp8B(njK&=-Te|Te+xT70anQHUsQbNNJ?=JcnX0~*uB?aZLb&jiUj8vCzxeA zQWG!72Iae)UHt>>S`P>z$e_6fbR1q+Fuavh>h)&bX3;)t9- z-(Z6st{+%R!VFoIXnnjwiJL%sOwMkg+opd{!qEYf`b=FFrsraHU8My5Q@)NB|GNC3 z;k26q9Jub-pm_erS@3-|v@<)yeEeHcYcQ;RuR%sf?4=-_65Ej|#gv4RKaA9Crqf+< z3+7}dzdGK+bOCmG=~WyoE!Y#Ff4|Ar)AcGkTC<1q*RlEm)D{Qf7dngAQ1yR#4RLLy zIpNnd2R$6rjx`M8O0D&JVp{33|WDL-Uy`Ij90in8*^tkE?Vrt+Gfr8CzC zYJIi^({m}DxN@*kdr;ht2kUYKLt}6@a);9XNPjv*rTyIQ>jCTFCJx&#e0CYvv$I;S zY5SM4`Vknf@inotM1PohoU1T-Wjb6S(&?sJ43cBs>ZHsRG?PIZ_B^j(lvEnxL2fHC z^tum#!cn&YSwg&;VK1@=cF5h>>jm+`OE{>R&L0#cbU5A6Q`DVaU#Lp`IhW4c(((;g zwIY5}$Le|bLbkmA`sSS0T&!%*!x@<#7!7+YWTjg6SgpOk3^;Y-GKCME_99t9wdp{5 z6l?$sOKhCw%!lxzwEvDMzbL)9SdMPHfD>1a1UCtCa&oayD}2U#rA_*8qAIVTba6h_VQW6mmKrfiI!r~I90)p0 zx@W;&Y~jkXA9wg%-Q=b(874}Z#o12mcv|ZBy!whhCzEM z3Sy>)*#+-OykO8y+Pr=4uWR8%y9l+4yLe9;VsVEh<4aBHkRy7*A$fwBL`M&oI_R`T5(yPO=!#P< zH7Gm3nFh;D3D5?qnL{R#vt%s(T|u%#sC%eg>z~L;p^Cl7r1| zVmtT>tXF)p&$C$GkLs8yk z{9Ps#PTb9hfVT{34`$=-;sG~63BW&?lOB=Ve;H{=EI&Bu1 z6YiO_B>V$_o(0$kDDo3HpBBCz(@qGK=pR71o%j(BsS~oX)|_WyHk)#rP1()<2fw&P zJmtj$zXcY4D^Nx@>Mnfg&QMcq58&;*`DA1Mdy+y|MBkGB8FPPAGi++d+QR*OA-qA!svUd_UypHTTTM~6Z`V6+$c`&Y0z%%8I}-W(CnM? z48MGiCuK5V>HL-pfQDnDy^Es>cvxF2191Dj%zySiFtLKFx0fp9lC{>|Hl^ z1_UmaD#r?}72nmSTJi8cRp$UA6GT6}pHE z#8(aZ1?Erx%J;sk7riq??-%$7Sf20k@iJ|Z7*3Vi8g42qmIeNb`w-}(Eh4MSTj=~k zi{2ITMhLhfH3CB14Vrb}D?NJ;N4{WoeZWU>b(#3V)LJ*@BGL3z9O>6-y^p?C@VGGp zjk6jmwj4jc?23Nh_DN}*AXX!IT_@b+#-_fDJI(g5cQ!6~OB+40cWF)<5zp7nA+E&^ zH+qOy<7(-zqAwZA2=hK&>!8E${yKl+;q}V{U7B?b@-}KJS^rtOiAl^~zpO0~!^I38 z@Sbep-ARdq>Sz~|DVMobc#6W@&Dsy6wxEy)V-^Qxmd+fNhQj?Rmf!Ki2HV0id>&NY zZc}wrur5%kEjaGPX?KT`Oio+ihciii?ziZOUm-@ohbWqp_M~m8FRu0ITK?I+Ms3nU zKgE5KT!C8Tt$B;CWv|1q&&jTz2G{%cX_K}ZNgWu%b|Fm-nlOPWd@sX$#U+kHm>_w{ zL3TZWSE>RD@2=B+`nPe8 zU<&#g>8SnmCDu{<(HiX-CRscR-#?^MR79m{hF?{tK<2&pZBonDix6~197(k$=}gp) zLNtjwk84KLPU0pdQa>T&a{02{+@;321R7e-SH^Bl6L#(2ezDLvGdDNakefSmrXlB* z@_!G^Bm*YW2sDV7xMFId+q)%u>~`ZzZs~|Y3;OFx-QW>C_I^mb3%NIic^3?tY5Wcz zw|yD~GkgM{xDzjE&$bb?Cr9(zOZ|B5iPx<+lG?u-w0YeiBY+2Nx`FNBy>|26pGHY~ zxa0_4EbU-7Ywk9W1Om70fx=CDAaSQA(70K%lE{90pmL`sC4=Vxk)N@8A0=`v8_ylZ zYcB^JHfKToYuQl$ayHbz836S!2Xui7k8$lC{Cr${i?zZMItSV-0lUmgDyv-iQsoL% z*#fZ+{x2nMScZS0Uu}XK^zA;pTf7lJqERGx6L;_Enfr5vMS$u1;;kg?cPOs*4j?b6 zL>`Pm(qG-7@_vVk{;SY#kn%fcCS`ankT`q)#IZ3n`eaoZ*qp4x<{DiQOu%Vsa(H%c3SX|Mw&BAKRc zf@zAg@JrTF&|^DA8raAVG0qSSucD;TtYTlV)0oc7aDLHqYYp4@_XBooSn2(Oy9S8+|E zl33DVU-5xxbAz zz@6jmZ&@!(KoR6Z z^+k9JR6cRJN~94O@TTkBKxry9vxWYH9eFYq&aJi}PSwR2UG!c1UhvyHUZ}Fa*kqpzO{!WgOL=xcaw+z~T+8K3}H2YNfdo>RoLMwECM* zJMsrfkEfWMDFE?TVr}SxPp7z`cwif_Uj23I)Wg%oqlwNy=5u~=sgZH%bAKCUaO1MK zZkV%EDJr~<-#jL?o{pP1B6m~7caLZvnHHyrfk zv~3(j*TmZLdD6=XFemcF*20NZ&jX)gsJ8%|LCk})T3v>o&Xj9f4n#u({>=d1znORTpfPy=^@K-*=P5aZar3`cVl3l2;^q{j_#jR&~55qA1Ep7)8? z5Nz(I8yLN4*+?37|8N(aD{O8b3vnVgoq|EocF_j9Jc9b);O?a#-@q?WYe81mMUFPic&EW^~|x$U#EH_wm0O?a){FE*Z{!!{68&b{t_&~>*l za@Dv+OYD}+|8-BEK|7(Spm;}#-CuquNe$xX>q-I)=AX*M=^WM_I`HiFxVZ~w#_dd*JoU&!gf>Xsk$ zod!sD0()mkWu~szkad3{OaU9%~h+$OwsYzq7@7sO6fARhIC_X#P00}=|_m?DA<7vOVL zAMXas#Bhp{K{2EQ$&Ip@c0zwZ*^MJ5{krcL2S6A{N#YOm=!{!fX+p*Ys%2+ro?T_lEu)*esNnJIDe4!oc5MiuO2?V`fb3~4S0hS z0lU5D<*>I8&u=@I57X?knA85*T9^jroBa!c)#K}g8)dl%`Rq!=$$62Q7mBj6hD@cc zWvB2{XJW>S9yDx!6_091ivz@5Shb{s*+|?(qh!+_Va`=xxe`yJUkUug+b%WzsqJr? zLJ=4F@Y)eU!Yp1qO2-jz1S{H7$V&(kezJ}w#YWn`Vi!7;i=70qK`$L(jHDRSSmI@H zrx^@(8o1Hm{$Q3{4#i0l(J|6UmVG4}C!LAF?6p!R<^u!p6$fcZ`ja}izk={e><=D{ zAo3fHr~qG34A@g+@&(cj&sS(Bf{rtF&t!fRcgw`yVy>^ShzC0)<+^4sU%^^znSa0m zo=fm{%5y19|IDu#lxGGjX{bwk%JOapN(tYRSRDv>c|4OR2FY-{2c7vmFw4;wk(Klg zYaa784$eRRthg<-6vt)?d_ImtoKKzZ<5Vu9T#vBTmK8j)nP}Bg5YdLdRh$FBZ<||4ZvMEFO3hjRWHw;O~2@rEj zGl#?o>F(H-sK0VOT9Wn4@fmpXMO^bKMQvWP3|iCP6Xm43PJ1u`{L=YK+_mj-qW764 zz&Xrfle%_#EU_~ta|KBus{-{z-mX7EM(te(FiJ7k&a5rmXwV*vSjcJb(ewJj?$dCRyessO;37Ae&f$4NQfP16? zgoBBtTYGS96|37hQWx+tDVhu4htCsWtD~E#r=T4>8IHD%gg*R!918hGVyx*$XCWz) zmQ8@NB3f2pF{XYj+|7rAtu-6J77yuQQ~5U^vq5Qp{^t?2`lM{Ae<<5Zyys~kk3e_5 zGIf`9*K?_j48Yk9qTR)Hq7Xvcqfl{omgre(i}) zJnO~p{kA6+XgoEuN8UaRwwh-a=wXgf6nWq-2HqU__Ji@B!4%HFAw;nO9pVS$( zx2k+q2U^Z`(N`^(z(eWUTmQ!=lYRKLU}XJx;UJ$VX~Dw?>@7QCRtvJ{kPOOE5*@w* zY4~oyNgh}(z#B}CFm`a_Ebtd@5WR#(u^I_k0<--iE`~kxgSey_pCwq<-S&@bcJqU! z7Ag?_ldikfqjXwxHW^G5j-)GT_Q)Ssdvc-h{BC0mM_hdpoICYfvMR#Rkej^w%)~2t z-08y&Jj5vaaoP$*XP~PdY6qCx>)M7Le2bMAFJc`WvvjcCwhaaYU6z1nWKvdau=*Tu zb8QsAc_8J78V4b}I<9>fvhPuCvdXU+FZxb-){}nw(fT>|d}<5k6!`iBpP+Yy6{c*E zQU5NZdJHw<^W3tY0Sj8|NsA5zV9H)1V7G_Ft&n@SXY-@I`9DmBGsxC*12^&LHrRky zSZuC_1H1~mbGbh$aE)819^NnR-L#0uO)XX%xGBm^?$mIzIeToOWVng67;Xyqq2$N? z+$v$S;U%{uK6>6DJ*gD}=A0i#3fSi%@sUhA7Y!QGKuIxWcvcynR3`gWpKAW6JCtws zZn}TwXm;ve!wYU@ROdxtnr_so6KP@`LS|9ATPWQTR=Uudm-i!}JPTkFeA@(f+Qt)k zcgt9S9sGwf?VqOZ@ck9>hOQ4Kv-fk_*OWQ!pWm`KJ5~^zz((Zr(C=lHvHXRuC(~FM z%9h12dL4C9t}YyB&H%-p&Q3`nzwq?t4YPj=-0I3-`I-GRGz1G|&iu)ht&R}`8f z*5QT$68C={pnGR42WV&z9Uzy^(g4k?9D_p(1^nF7=|IxhNSTZkdhoWZ#p@s%+r)`= zJb3Ns=6nwxzIu^z$BKcb{mxXiIhoBmC>={n_g}WfO?JbkfPWX?g7>EgcQ(JJr4zoX zi_e%QLf|M=UQwkojbXzEI0j6>158c9w^MYsyy?HG{I_TZU(Q~fRHd#Im6(Y*qs2zwvZyDM{dem_^2mhZUr{l4O{RlYsJ0 ztW@B&N^Qw5qExI`wgysbd9od!f#5=jLXeT83QZ4w+d2UU9i&dBbK+A_C5LdQ@~yPJ zNwlwz@_EWj0-k0H*J;y0i6nqOW(%HG9!_x*n)7(`%+iey^B-cnQrik<8+C4jcSF0^ zbQ?@CrRF@295ffx2m~#JIcgL*N2%SLOEe16rIZ{|{)C$-@Z^VNGNkMjS`kcG5JzH% z=P7=N=+Sv_(#e7EcBMd`-Vn^>a^LpuJDmEaHAWB^>W+6oVCdSgDJQ9o5c280>rDFW4QOI-dXdP=2Gu^I5;-%Q(mas; z?U$z^*rqvl%dYgm&|5?ny|^6!?@OWb@|yD2r%UV}iGWWgAw<*>KAvjuPD2V zwqM#|%>B0EHV`jq`SiX&NUjI1D-$P3i=v6SxD3A1$0KqSkI4w^0g({`1M~0@Hn}tS znl8tynE+W^F2m%k*v)?r%J?(BNmWr6*$Sl!tw}-YXL$PSiy~0Otj2gbyOSKyWO{82>_ZGx}XR-UR_J zQDvg*E)M?h3<@3DMdLk{Qoe>AL2d7 z(X3@)>IGSd#{8VJSxflklCmu&e&tQ2G8*g&0l`*_x*Z(>ZE3%9hcC3|nfCkhW6Jka zY^y^oV9759I~Zs5-?Q#g_5%7z2R^c*sL{)WK@doFYP{wc@wnk?vS3@<&3u{wtT zncQ{c8_p~hW9Xd7aL>{Sg2wWGYq@}bp~JA$yuy-+GbL9djC>;+)JbuNi80&1*56Dc z18FwtO$;Cm7P{CD2v055;TGTqDH%~hYW7s%SSRueM;trnx5wxZ{Qm>!r^g!H9u5S~ zxH&|Q-hD6D`2f4j1u7w7B#ngOG=;tS|0(Y~z@j?3zwhj_yRZw3vaV%ucL|ChHdHLJ zcTurpi50t1RAO&YG$uy$O*Qr?8a0Xy5gQtN!5X{R(HMIa>s)fz?Dw0qmx#u+|NH-* z=X;)S_nC9%PP=nw&dix|=FIMAU@LM718=VM5)z)xc`u!-#B)J1Sqtos_@Redd7`|# zn%$g*iu649rFO1q^_pWbYU|y}5fLHcdOJ6&@|o*lu<^Q5@7CjyOQ;I2WV=~KL3j|V z#-!sX@#TQ;(pH3hOukYCT$Mny37oY z9oejbb|KSA*+TFYg0tmHbPiNPk-tGkv>iE2$ihCHSlk|C!~_R5@cv70@qo#QVEZ?_ zKp{n51uCyAS8=+Ad%aSk@=CD^mbGcx^0|FQZTkum9sZ4vC)z!Qb?qwjh7Sa3E`51` z9v*qipH9_~y@Gs*BVO`sJevO(Nlx4e|LVu9meA&J@hWFF&@QNpRu{)wu zT8u&TCh7=d3|Qd^E(ygaQ9L+|H+IS~cQ8V!$cLR!hVf#0CBo%xRrJLV^L*IWImmU+ zoryY^x53;2UD$g!c$DZ>d4JlO27&e!y;mpb8qx7`Co^9>ZGJ{6ndnSVmRU>lsORAN zs|U8X#q{Z2!(kpeowqLkGjH;9dEQcVVk4e|ovh|R$gx&MtG6t8AbLWBrOU$y23~F2 zkLNkFSysPjc}j_NSQboDzO$C#O(q#HPg^~py{%oJ-jy8Y=Snd?K2)4uN3Si2$1*x6 zjIXl3p=_tXi3$(8JJ4=%Izq?oddp~~123%O5i@z<6W+*%Qiqy_kX-FMc?suZ@XjZ^ z7naI*6|8Lh7|Sv0V<~D{6EKj>>&)*^IKRj_(`Q;xV1u*I`P3T|$+s>~uMx}BT>s6PU5{wopI*f; z(rYYoAD1E6m|~Z^p}TNJ%`)oEIB3t`yUrWntbGi)Umv-<^%qy;a9$ZZRQ5{TuDth@ zcjV?9ikUYsh|oBe5BW=+%ZC7j7@Mqw(4!}%-u9EiHvpbXPGH2JR#5KFYyIMiLTawq zrQw(ry;@ZtkFnW~21Y#=z76IS`(k8hmhl&Z5+^#k6?S&|Dc=lIJ~J`Y_?}U2E=scB zOKYm}MKG z+aTcd)18^T@-xU-_QRLFHDAr1R^irTYuD9w8tw~RZ*_$BBv#1+RKum#`V*=F`QV%Lirlj ze-q?gV*NMD)l_LHHzPsJ1Th)W==sO;Zk@TEJz(*#220hPmG}Tl$5^9fPK2?YP_RPo zHRLPQupr}V=>pFMuY2~UI*}vt`g#_Dw_pBOiRT8z`4ehqoe4wTVm@4Tr~2+b&uRv|TU zo=rKwg_Qc+S&H0Q%NyKk0Q(Pi@t}49M%Ut3Zk~oe)Z;{p!yUw6e2y5BRdF3?VCS`$ zVC&c)$H@H|1Q@`;BZQckHC9*s)=NB#BPfd#pUJzR06{K=*YWqCT6tlf zU-$U_rEKnND8yRLope)?!UI_ap1+ny~{_oX;P-VQyB=M_^JjuJyG|} z0738<{EMr6(z5`$BKHgCzLq=kJ8}W5CHqlw>ZQ|Arxbb|M+|%__3ww&42HD*=QcbD zynK9MjTZfzDk07aRkQUdn^qv$1W5?_MFdvWD?kJ-?f%d+uuC3gJx*y!fi9=yz*%3XQD4bVEz0 z;%ykPZbg@G?CEpn*m$$s zkGS)~u3_)*b>Jl8(r(Qcmk)%Rq!K9AIm)L`lwjVDH-7R0>QAaE+T)6^vy01U4Qp}w z?CtBjuI;-sR53+Fw+(liKSnhcea05a-)I-14vQJ&BGZ&rF9UVlrss=u!iDmZBlC>w zxv3Ns+3e+BrLar(1ig7d4CijFO1XH_#;u{J#nH0R`iiw-N{t%>A?^}7q)VMDHpRhp zwRk=Jb(!38t;@O4?;Vy0psuyn8%KNAY|*bNq!0}Kj%Hjqns(i3dE(0SD%o&r*5R~c z99r=NXGj`p&cc6Z~h}sOO1l1be zqHLM)3~uEG9~`)g7BL+xtP%H*9Tk8BfnHeFs3!Wm6s2ga&T^vqFRn%qsmIWL;`D6p z77fEo5XjI+)}1SK+3PQ8Z1Q zN}&4iez5^)JRR8KAUmh%U;&WB!xgxT&pL zovtOSG+nyBwP+o2+s=>rIgr zcHku!gus73kbLFvp5gtRiSwr~_}0c--OT3B^E*cL-zoGJF74iCam7H)y_~N|z6q~( z{~4r6s)n3^CbU==^cA)N#b4fearcd3if=@#nDS0@Po?2Vw`RI%saQf|3*>Ry1*oXv z2HB9P%z5cVWo)+}8TXjaJI50r2K>PFemK=@Jq&btwn^-YGK%49XsP7{H~cLmkYu#1j4^^sAr89?>AL6ist>J85a+y zT^59Y$*XMR^y`OHk8v|Mr#6YM6xF4S)4V3#o{VE1XG9OXUOs_k<}xLeVoHg7mAU`< z2Pr>;@IRbBevD0tu3lORez()2RjnL206KiraZ$tVv1(BW=cPWva@;#glyjQPZf#a; zhPTfr6biRml{@xKcx6C0n+XDh{dVN7b?Rf;QxmD$Q{ zUL41~-W7b|&V%aMr07=a-7;lLUBL;EZVxWq5huD}Pl zmMD|Zxg4hsok9|G_&~IQ@19zp-og}Y}^@_GvpH9Rdc0JzoDDYxt zpK_(@)Kau1osczYAQ;3^HN96HVmkNraEKAaA+lGdT^1Z7n9dc3`My6f3EurV;{n-x10wAxQv8#KAy?@ZbjfSONnk<2O&-av)Xf6wt$9uH5aJQV&{Tf@5j+C*c5-puma+$=zUAONZ z{GCbodzi#N^x)p2F^YClfd_e*M9>qZ0{%fBCJ_|OD+nfW`)*6csuU>Gyj;05hrkf5 zcMskc*-XslH!fA@{sH_HPEMuBbst&fuJy!mute~Ql*XyvuFrUQiEC`!uf!guU~pvoi0%#yBUbFX@cp&GGY8@u)u)f!x&nO-^Ap`!!SA!!>C@jTgi%zz7s`cFbv&s zan#*FUYBbH!)SI_sXmWBl@Qh%+a@WH%pp_Ur12Xqmesql@ zrtvj@u2Y^H%zH$BzP!cTf@z3ty2+xt-sjkrXp5@Tx*ECc2A(o^fBm|)cWdl{;4kE!e`Z^OAOrI~<#w|!5XM%0)A-2)2 zwP&Oh>|qe_(qQ78M`chro9Y? zf!?gZG-cLHJKDi^yJ!GsxuG0JDmyqU#|_{Wlc)h)TeXfFK*`e>Nko<;UQy#(92!9Q zkgm0fS7>X2W7gtY;uYt@sR2090J4st0bmHLSM>eS0FI<(c^W`?BQ$^w$1uSN5QC&f zT``d3{^?kb)?jpmLCi0LSyT(pWfny)-A(y<{qM}88kj}p4vSVr6SLTIY7_>+?Nnwl z4a@>6a-E-oS=6l!W|6Vy-d({gs&}a2G>1)IWSrdIuE?0%pS{4Yoz>z@QBGoZB*FLo?8l zQacF(Sl^ySYjE~|#6YM*FLi-)^)rgN3N)N8;l8ne$mB~Ny{2RIVv4FLU zA*9WB^B!R3EnppEJk|60i<4<*XF$HUD;$({UTu0V-nepU@|iJqrRL;0cDZyzdl-Mb z$jBdy@6V~Cemy$G*T3G)#IYl(VS=>TDAyImz@Lh8AT5h(Nu=@F@b;(D&6>lxpt^qd{r#`si#~5s9Z3; zm-**8OcYH{9kpPP5lUQ-?${-ixHef+ zsc!3BC9b=m#5G+gaeaSa3n+2TpGS!+?~nvhu?qo6Yw%gib|m(`NbI5OH;~JCK`z?` zxr`U&vVD@B?@Y3H{>CnQowdx0g*;6!tEY4=^O8{J^J1IW;Vvl9X;~JF!%CZwrxdW2_OMg4gr33(d~*hUc1Iv}~7sqDeD*h;6%vWI{Pm9>=--$ZLG1GM`vdM>Fnga&>l8tD$Us#ij z$_YVv{QL9* z$tmmOlKbKU);@ic!cOOLWvufaaDEclaGT3_@56m&60h`g6`aDI8{S-x8 z%ysOk-jW4f#@3XT$?J@9Dd@*~%g|1u2E-2EF5X67VC@?h*SBx-pp+EUV9KE6zR*^@ z`{g%Q`DiiMQA{9_y1X9Bj4-_jRE*Df5lqQ5Ju_l$nST8{JF%j&anIEa*@0&c52=O| z7NrI{s}0jB4WEG#ubCy(GCeqg6ZX7BvBL_4-R?AbX}9)^gTB|~xOJk0g#K5jd7|L*>SaL++nj4{qCb@L=a{En9Z(+_L4i&Ig_5&iuSwgYhwLsX0`T z$m8Vu)_xb1@W^jCE|$}Ot&~-YU+Y(smu;eyZL+Q=FMj1CB}{i+Kbe=}B`dGHpp-p^ zbJ+dCHllv!({4rZ$pK4qKC;}&Ss>50-YR2QXjGQ#0rEvW(|1&+>57-nO11OlFhzcb z@`fFJnhrC-X;y?1cD@`+)80|4{5pcImh)-({v1d?Ed6bSj!V$&{ObsvS+S7s0e)A@ zYY1Pa=O20kn3WzK?Rzns-wSOMO7km<<#baA4vW`2Tn@}RTCDkS>zN}^)~&DK^j#%D zX%1;|M1g`6Z!NzhR8%yt$^#l4b>QUrnP#^!{A`A)VL7E>y=Vuw=uaKJie(iy4ws#|B$u%Ei>kf?uCUML+xcT z4;&Bgb<(#_oUnMijRzREj$S!vu4Cowh0C^5nqf%um|WM|HF4s?4K^NVI3K^e)C@=1 z3|*=1hMjXqfG+(FUBCTc4*CJ%lDjja))8V?1fts z9cvz=Afu;0l7DyvEi;lB%D-cMq|AhNkeQE^F!=}YbJd%7h5s(JuTUQ5`auZ`#mRWA zKlYhsmqWF3Jf8?{C841Pb2c1OwX#AEsc_&YrrC3*X>(2qmzUGva$X6!$u7$O(aM*V z64swx|EXv&yL7L5n`nb{OCOX5Frf2$P^LD!VD`tP6Y}+^x zTDQ$g`J9=fphLv5fkX1Q4CW#Y$J&!urmh&hc=%V2bG*3jiBjAO+<1MXvG$el7?U*o zYs4!j;{C`Ssom|oVZlCeq}zK5>GnR}=OlD{f9KKdJtVm&LSI+kqU9m-RgqlO`n>N^ z1N_+CZr-{Coq5c-i~$MfxP2tqtX?tMsgcI(5e*FD?Q=ZQvtog-UlA$bP;C_ zmdyFP-$P3o7H0dbHu9eEO7%qHUEtQm69w7{1c`T|_wXxPo`d&vLXw^Ep6;*wRtdi< zNUs~>c0+n%<;tvh%Psj~jC&s3x_8SG&s(8RBQUd24&O5H7W}X^0ydDv?gTq~a0`l^ zgg-fjPv^swZXnOHomy<0HdFQe4o66PFAmVT1GuK^!v8YtZtusb89%L zJa}03YKj9l3|y@Fe1td#UqY1z>Cc^(fzMCJsd zhh?pJbNg?be&uahc(xP{4c9j)K9ifqRJ29zXv#weI&O{HHY&yz({5zLYE{2q-OXX? z7G&5m^5B>=(2ugP^8KF-kLUfkW(UzhH3K?zx~#hG-Kgs-#Ykt;da2lb(>$LbA6zB-Sj7J>8?89 z=g-C4GzPZv&I=z!)OsN=U}E2H!Sj&%KDukGN@W#?|6 zdw)>6gBuLXdPIsFLqaM?E8041+N^3TmO90f%)5!YZ-##P!C+ft?JkN>yrYzS8giJt z9J=g5n2y(gKnUpvsmvc)`DQs>F()d+lw(RSeq5(O>&9|dm~Iqw_IUAr${cD`nX z(L7T=jP@ya*UI0?Q>@V^m1^8! zlnJ}0@7g4*(x)7T#}obWiZDgEfNja`~K;NPOhdrcg0n`%rvZ5?7X=e(0(TyvhsowsbuMgkqA zKPF(3UL}CL+$n*i?5Wt=`~UlNY}ha4L%jJ>m)3yiu0rT7sOMGt8=di$1NKvQAmfTPQ{Ns3V!P=WcUm8OF43 zFsWsr(nImb%2W&ptu#hGBSCJ}-of=JzFk_j>^W#{rjw7XZJ5|ix4nPoafUZ9J0l$n9@rUJsl3O7ly4mfI^1={8r6Br z^&7i3ANp}u=g3O!dp5=bK=ioVwYgWs$eh~O1J*v7^Mrr$VEaSM{G2he%^Ie!HlRWK zb`ETIHrMg|JI`MJ#dfFVdE9qlS)@cpRg0{>xxuy;PRo3ySWMH#p*BTxvNo^(%g@WU zoO4*JZ;wG7y`^(W?xnNLKX*F)fe_SKr2NLWS}(5e6Vs}5%VOP}EsqaM-(%aFw(*)H z!u8aOv{=tghcY&9<=&f}xB;k_&ax=3ZIdw#1I<|P8F)?BWwE6D{M&mx^qw|Jo@iA< z?qQ@p(y>7~buZ@uF0fHX&M2)Il@Oc!h|jgYynip}zQI-4^&VJNl}z+~KG!mMLFW#a zPH$eQggwfMeH5@%tic#~5;s)G8q7`k0tEfCD{of9u>gNYDY~r7vR$^+jmgh%9v#)= ztOJslE8ACI0?~{*)f$&-ysqVajJ5BS8riB_U0aD;U3u^a4qkBBrlFl}-D3wRK2@R@ ztgR&$O-Ag=8k2=(;d?(mKRRG{CGeJ;_mlQ-J!?~T6t|XH+-d1P+x`p7xMr&3=TX~+ zwzIXYG${NF+)$MK*`XWPd0JNHA+{c8xu47Rj%wwG;2a-ddCd-yz)84irU*2v4TTbk zBFZFX3a_i!`B3K}FUW;CF$nJf%5p;Bur2=BA%bg1EM%FKw$8;jtb5aEEnM&X$uMF- z&q-YaYd7Bf=;4oB&Tri~ari3d4MS3&ZsLNae8Dx9(n`4}b$Ni(e1dn_#oO-sWzUga zEM*V3W^lvN$9uH=a?WQ~r3!ATlAyYw5id}jSK(DT7EKyK?W*O4Qq}32<)s9cxQ^YH z;@LN$r7bYK0yYYQOH@(V0XpUIQyCXCDeub8b9~r2w)i5Czo_N? zWhBEcUcl;{RP%z^d#PR%pT;iN}Bv)hMB|5pE#$~p+UWtFNd^J-3_*}r!?5`g}_GSIg zVt!vPA*V3^-dbKiwbQUJz5AiZ$XEE6l}9oz+0M7#FZL0ewy52DMb|Vi{W1wkkq^4I zin3Y$lThVY6CQc>{KD0T9Ts~+YCj!D@RsHY<*w_H;{F}$j(v98TRFyuS`Vefckk4r zM~x4howp2GxzV;^UGgCZ5{Pz1S%dp-+?={(^`j-uFQ$I}$rn?3NyT1xRF6&x?E}p+ zwhfr82u`IbSH&SMv-|+ktV%fu4U`v0K+FS-I0jc$tKcwwb$V)qrw_$3GU280$695) zIHqV8N&_(DQY@z|vF~G0R#tCG5;|7Q@{r##dDohr)iSwPP2I3QQ%1zw`X>!tpYq-ERf&$o#S<2d{My?LC4@5=dOedi1eyza z+XIv~LhyUE641&>Vm1Lv|4d8lxYl;))i=)>V4RDOSRJwDhc$cAcZ&B&0ubNAROUXXgavXm} zdCqZbCJ6DvMf{0Y){ArX<~g3cCL^nSRBPz%@|4vw6ctBN%@JkwohYi)c}nVkqlQu6 zol!M^RgouoVNsE!|65cg>Hjv%Ye|8&vXc~OD;4FnJwYjdR;BV4o{(Fo&8$1`wM_0< zN;h`UN5elroqm8i{XTD;8wS|zBvH9e~I@A?GI$_G%-6`%|~ zk1GDka}iYdfatbOR4sI|susGW(M7`9yec}F+XKKkD9Mndd)XDlB&3UeWv@G zo|>Ne`pMU4{91G(H$I-Z);*#``1+oUL2m641$qumRnkY>-%`nYe%__|=8m{%z zme%&xCTbUI*J#gcuY2XgO%kQN8hiD`EfUkbzVTY_b;Ro@uZLc4odb6{4$_U%P14QM zE!G{?-PAqQ;RZszzuuw`(U;c0tFNbTf?FL&=;z^9`(yf(daidHisLrNMuw(_mWFnQ z&V~twj}0>nU*Hz|b+~mR-LTJa!f@G;Wyto{cpGtleUNvEcUkX9?`qyL-W_po{aEiQ z-k*6d@ZR8^;e8f&INtQW@6CPue1d(d_%!p0@p;dur_XSoi9VBkzQRovD}7RYHsb!q zBR&^!KRw;v=%X0DjaJ;=7-S4JmNix~)-cvJHZr!s{q+5f!*Ql~5^istVVrGTVq9a~ zV%%#yYCLJ>0k~VKf<8hiP!Y@kCqu;DiMU_uHC?T5YT6WA&tQIew6%iu<u~#2vtRWzN!>OT-O<%Wy-W73V>J*HQMi^PsN|gF2C!*A!^Ht{cL6 z_fNEGSl52PV%^#y-}mn~cu4<#D+aF<38~r{opMP<`*tmfsvbvVQ57$A^6kNI754K8 zAz~~+2V5N^apBq@52lh9w;j;pHUV1Pc9b<@eai3`=fF)7iw7*-V#A`|`ICFb^xx-r zY*^f*$^1G{h*7^)>1tb>b4#4_>ZtT_t!y#vM>eU_blLLW4wDk&Qj|Wjq6-p-p?+2B z$-mN_f#ZdKdK~^9Cg0$(t{af2`&{T`wEp{Zjh;h|e}1A-S%QN@e^8RAp2$)X&*nLT zrBrZ5DF!);a#%x%CqiFjgj|vbxk}R6@8&{O5!qJ8!Seh0F4N!Wm*;<{pB^;X45S{a zAC9}y-g0yXV-;Uluo5T->)z2T6CfJYF zvrLXLD<|^RYsVirYamEF5fnU1LSws6RPua9}dDa)uQ!;r=LKj=N4)I|Q zbIRf=@dF1=>J~fT>)8XH%?%URrcGTNc=gEgQ>Kq5X4cK()r7J}Q~bD7F?)EFI7x0w zNM8HN>cI26mmc2s{_1+pX%L4oa;x=?MZU*a)TbV}T+J3lwxGg(+9{*T#gVLY-F~BliB@xHHxgOWLfGgf=U4JMOX`+1#^5 zi|)OfHS3z1k+C)H2zn7^6c#_}_DsEcd#3)??V0)IU7l+*C;#EvO#UMzBgCa@rghWR zLye}L3l^uP*iJTIUm^thm-;U5xpS<;)X4}H=cM%2)X6$+XY){xOtsUpMQ9ux4YBGi zugb`287+#*VWvg6JiYi}p`xLU&OAJRV(&gfW_~loR2{@X=ekUT#8LL#^AjeI&c2#= zO(PTRrb^@b#J0CJKGYr)p{fsdJVf{6x2M{3hiL`f#OM){Ht`Yo7U-TeS+C;0NQ3Ef z<6Fd(&9n=aie0ebd3M1}>KRuYmz^Sx%l07sXY~zN&+%7BVxj7}CO$~9L54wjq~niN z&CxCczooc@Or+wL3+hbJf7BSO(2aG)^`>UHYVMDBgJgK+UI~(WMF?(u-3NTX@3alYMT zYq9Br!zM9CV_VZFtf&3hi^mVczhziIiL9 z>YOmiwQ;IKT+)a|B5_G$&^s5gNMyneXWJegiGZ_9am8d$am6IxIQ@C$k&vaaOb1C; zZY*?yneMluO5QRgwdFjAiFBHf{*x}I@?v*Z(B+6hQB8+qZ!dM>cBe^R|3~5b+y)xG zzC?*v4Yagj{QAA)F9&kVg`JN`xe)G@3>!~3o0xt`nvUy&$0|v>7CaFpL0W`FbMt64 zGSN|qr{dg%DbARy+5|HAq}{}X1McdH@hz)N%5~bO%gggS+9`rWZ|6)UfXG+U z(tws3)T&z|*-KpBf!mFh8o$Ov91c4;ruU8LO&_`DhT(GPapK5|snqoAJnOn?BE$;Q zkO}Nc;L1nnTL>_TbLS>(jY5wgZCjcyO+F&Tt^eLc`r+SSB5C~?4y0bk;3$jH`;{|txu5Dz|t%k!8-m)!ww!R>lQVN7r{+H$`M&MpUh8qj}0ltrm_n0&Rm(?*`TlYEOm(nc2Ik+c};ikLq0aqd=CT*AY0Ui+d zDdPIX(|~6sTv&)nhXUwgxKI$+*7-?{!?T~H(fEr?2sH&Yg%BcKQwjFUnksmQ=i7|9UJ|zwHfR(bBbnoR z3?Gc^{Jh|YQ>xd40SVQ%oK}w=N{^!^W7dVRe z{|(lomI~tP(z3WBv=-9x#y3_atkX_0-tcRaZ%_U`L_aM5VGj=2Bk55PEYuUl0+;{#q2nBFI{)gcnx*z>@NO#s>`{(+)ALQEp z9{;nj?qB~tB@x1X%YEOS;lAO%{13xtyPvwRx*xjly&m5E;`Qe@pWF}Mbb0%;`}y15 z|GBIC!mF_E2Y**u?i;T{qcm>(8NIzd|0st)(4G5*`&8aEbM5YIceb#*&*i%OHFn?n zYyZ5yZ_l+z0W_}(IV##zSjVso^paKJcENqjeH`YxdzpJV>hp;^$Nk)$=Kc!iI-YLl zP5ZyKyOsYog?|(OO*%%YUv}^Eq~Jd1z6Xk!@1E%1@80U(f&UiN);fgQ;XdO&4ZmCN z#V8%_KJU(S$GTmxyYRpJZ*uT@*uQHRuZRBYC-;fG{>VA{Gxtr8oBJld;JC00T5#v6 zuKPSV7YZr{jlWLwM{|8&$L_uEx$Ym_3*5=>sqPtwhZgVt#og9D#oZV^#&Pv+py_|} zjqWQb?Q}OTA%64c{t+o%M~gcD_BM<9LB8F0{>|HR-{Jl#*XF^V-V2oeBDXBuKfO`9 zYuw+X_BVRkEZ&P&>rTl{#XZf_vx8n%eq{}b)Q5(laBV5;@cK?qyH)!`w3-Jf$*CyIjqbzfuh18ubhF*tfr%HR zgs+Ow>J#+~Xib@(6x4K-+;4Q_8uPqA>NPID=5ig}Ak%#UZ=)WAY8oxT)59VMo|1mx z&eO;IafF3j3))Bf^R!zJH=u9J?YSPJHtE~l&&2yZF+4f(d=a^dUhU%Bdj?O~R}ekO zYoi~~%PsdtHN`XF0*BqJ+^OJp%iOEo`|xyGv_p3y(%%Do)IHjr3Va+PzXCq2#yq8B z@H3RVtiDgk)?dLfvfT6(E4|{AHCj}AwRA> zK}0BAc=Wo1(`e!AdH?|m^Bj!yhT&jrqw%L76^bPJ)=xzPbBbAiG zK?%k4#(1mU_*U0995L2-t?b|MRv-U3{zeX8{BZ`CJ5GWxXag`i?}#4bj{DRn{lzTE z)7K;B)!clc$exbD5V_S~|;d!KmRL|y!WigSbiDSlOIe;P!sN#XzdyTtew z{Q%$_b>dzvT;KkCmFKtkYl_gSS5T**1gu;6BIVWSFKI;gme6o|^Ub-T5N97^Qtooy z1pbTghY?=oKCfb+=SCffIlITro%YHvug%k+=XLq(@VOz?^7{(o|9u#nX$nV*{w7Eh zq{oU5Y#0;V0iCkK!G*>8jxW|Ui^AtKn3*v1V3J^#!>oo$g%PWoJ7C1R=3y9&U8M0a zlVN`|-Z{AaUA#~6-T_j)G!m&W_Z+0e+&u`kBUqBja9K|9Ai;423lWSYSdCyaf;|X^ z5UfdX1;JneS(r$ZRn|NP)R0{x?1kzSYK8Lfl@hVyt1-$jzeyUC7mN`mAJ(@U0T+b9 z+OR1YCJd%5Oe9P-Nit@^+>)5_$y?&rL72ab`#{8H(Z=J(GscUSGla8Ld`ra-RQyE6 zvWkCGu^P|h<#{&Ye4;5IOhFYFRdFzISRVJXut&mFgQ)}4NPUjZ^W4VN(bNsk0{2$& zfH!cw`aDv_<5fIa#UlQvFrTS-Ch)vG&q=T^hgl7i3bO@fhiRYbu!>Kp_?(I_tN4bB z@2L2pibcFVCum{sknuTV}Lv5 zanJRSgQM`23U8J)MX&!(P_^66c z0cXMp_p2~BReVpy*}yMUcb5nIX)I^_ykYWUGoA~m?luoL)$t4RE8$m~a0L}tRdFp9 zH&Ag?6}M7xZoKw>rTw}fy`C`rVFs&sxQfTBI6=jq0Oy7mc!v7?m5S%6cp-4!{4Z6X z#diqzWSA7cO@7-|yhp_uDn72_Gb+BQ;w%;C#=E6HKLAEs^Fuo${F?`xy)0+U##ioU zv@vr*n4;$3-*Fh8%bFw2)y#FwjbIlzTE%VNz#Y}+ZYu7r;sGia@zJ)-BULhGjuBPHTD$b49NPUjRJKDf>RB<;I_XZx2$2}hQkuc+7Cc}KH zK7W?yd1n53`IGQ0@NyNeegmhf&s$WyL&f`4EaD%AIiccnz?btp-+=uN%tM%GFuz*P z1V{n;0ACdcsJO6-9V#xa;&2s5s940S3{wN9o(GG5E+8tP1>qPK<2j&{$2}kxxNksQ zz);}PFcV;=s5nu@UjWbYxC=fTut0TR0=yz%Z9rN;I^kU^KA_^GDn8}G`MaslnJUhW zcQw!RO{94bM&N7}zW{c@z}=#O@m8@}#RY(Es(X-%O8}RKsQ^BBQ^oyNJlKQtuU4Ojt9Y!6bK@mgx(GgzzbMlD1ZIYczf$oW6)#lrQs88m z6b}wKpgwO>@pcvO0nUIC-z%{AUV+8;3Vhsh=B;DwN0I^V|EnWy_Xrwe4-sQ*%{dxr z4Nk0IDrE8ZSM>6BL8MIJU$I++yy0=Xyu_7mJQDdgwm z`#Jf3P4=&a9a128mn7!Zu#?L+5m%a}LcB|wEmDvMt1jYQ11N<)lv+1>OKVEu8&CR_ zPCDhGA^FCUZwmRYB`8+P7&}Jxd1RkQ_MH@K9>LEj?mWuBnBy~TY0QEd+bcq9hf?@E z6mqW$Maj%3`$g47UuAw+?L+nmQou{vDy-BOm8npzQxJ63Aq<1 z_zu;kk-`K~m>>!hM82iSw}9}~6r}f3*=h?@7%E{#B@8I?Ol8Z~P~2Hsk>^=NQNt+Z zVIr2Mxv*=RlYOWNscB9yTBM)}p}2Dh4kI{}Qf@}}h6ID?dkaxILKcTHJIyicQLY>W z?J7Kr8h@_ZuZob`G6HHY0T!TK*(h#p3S%P}L|<*AFjXnca}Cnb^d;ZAmq zi||sK2rp4902JT&Ii=rEbruGC+MJ=1E@BUYu_BaCElQz?3dP%PA|2_m>LT($Jq&9|;Tw^A9ioAT^v33_kO)J*(PWPn zb}3Q3MM@|53AwBiaixz{h<8b!(7UJ{;4a?fDfv)J%|TQ;mF#sXh1K+SZ+d$ZN_iXk zjwJXYeb(Gv&XN$g^gw7&)K@k#o%k@|{TGJE>5VOa;|0 z^4W>vVy6T4APV1*e1i!3Q_dfeuNV22BzG?XB^R}?pD2vj*J4t(2qVd4m&u+@A#YM{ z>rpx@D2MfguVyr**^kn!t3r{(UaDQ>upUv)DT;fF!gKOHr9u%tne3G)OeG3a$>X9G zzJ6rK>MHDBR8z2mV~*8+g53J5l9vv+6^9Wx95Q!P#TY%0lo;61tVIwSW|y9yjbP`^YFzmx>+DC zkyc1+r8H3JE=aW=l}<^S(pBlEbWh5bUO=K%!@QZ96=1eM4gJ48(kvUBPq-I~ zp2JO}Mfba8k0eMvtDE{2cMs8TxkD&?P0{zbSBRcQiWMOtCoAAVf;66zGKsTw7U88{ zSW@Fe`HjaJBCa{StflXqQ*%bCMo5~ijg)E6JW=q(2 zY#G+Q4KNTshJ36a_?HDzk|rq#e}AbQBro$p_OcP`HA)%;8DtIQD}|)6#H_5aWMsiC zSTeKntg7VC>Ofw&5NpbYAg%_oyhA0?rl+C?d?Agz1Z(;$unzA)y`@XVKpBTYWoM*c zq}q~8dMUlfK&Vn@#MH9A4E&uPV<+Hrnq3B63Qw2tBROLUa>j)qa~uxYVl(_S%ng}j z{NW1cWT!!Ykey~BLUx+fM=rgDT(l|= zts{F7J>~r>D}7ZZlvu7zwfI)yYS%a=U(Gw35|Y0r43gDWl*n+Yf{+hD9pR&4d{OEW zO3Mma*#Oj^X#Hj?0RwFR>?g*7`C+ukv7iS@($kyDlU=0Y4T`~t0`3yMAwS+Fa-b!d zUlHP;{ruHne#p0(YD$BY1Qjx(LOoHT22@xMX-7!q@wN(}NF7n69uygcoHW7Tg!*iX z{4|qVAa^aLw#Z+M)Dby;PwIixd*bgW^@7yAS?Z6!KWcsu@~n|M!T73gEd$>GdUudi z2Gmj<_7Koa8OnhLIj9VKRjE2)P5jFsAB_qjAbj0a^eCVeyN@1YzYoLcL=_jca`qy8O+lM$cV4_n4KA?QFWM%%$Un&Cn zFCaP5FIZtO1ioB^6=A`E#aIcrhq5rK3@gbZ;8Gsr@uFxWwPCNrn!vXyYX*BXi-x^9 zYXy62)=nzMdb8eAC>y{ANF~vZhDhOT3rm;YVcXeuz};*&py(|OY(Hj%<#BNOfK(Pe z$B&YYok0Il0{zE1$&a09=g~K1vP`KGyTC3;h1o@RQL4-?u}hLKyNo`@%&xF2l8L#{ z&y=P!4&^`_`Kh1NQ9p-S1oEvxzAK`SibS91MSY$Qxd-*JX7~mpeS?m^!9d^O&Dyh0 zk`L?5dLec3J-)0DdmnicUuCAR^2eJTRF)V&qAbl+mRc%HGb_k2+oTdTQ;C|XM730w zqVM(OsWxJXveZ&pioPcrc2SmQ%7+)_Lr3}0Q$7ro4{yqcFXh9BO4mfCYogLMQR(_o zSq4y9=A$z6r!vcreA__7tG99A!sA z*@BvVsRnFR1Nn)XjYP*LqFO(qS21ophIXr?zu*KC%Ea>rk3=8pg%bCM4QF)F>xq*^ z0kDalPD6c}hI%m#^;Y^ky_JD_D{m@EEtR1c^-x|^dR|mkUQ|k6R2n{10w(%)Eq%L( z`fw={+8Fk;ku#AW(Jx~(gML{jMs$=bRAQ8r;PRAHiwFIiW!0x(s^c z+URXNqL&{a4Fd&!jNSj-6GSm|f@nOQAX-W%h>p?;qG#d+5i2TA5U~bonw}FxMXA59 zBGr*vq2KN)4U~q1Zaxuf4tTSmc5$}Ijx#Cczz6E0=Wh)T(o2eyMxa!tg5EvhJm-!a zpuGs`U8x@ULmP~2dV}YTlqRBQ$IPD6^PEX?qL(c%Rg>zY^xH~Zq(0JMX_S;8O_RP- z!+Fjt1wk6B0=Q2DaG4mXtJGH-0@V?dPzPB1qx3vyncg8;sv1&5DOzeLbwlaKOJg7z zF&!Ml6V7waDHuI^B=}e(skzi%>JGm2fixDU$!4G(c*1$kKoyfpOO=Q|T1XwFSk#ng z@l&M#fEUf~(IalSb_T(j1Q!roN^mv7G=kd+?jv}#SA}7{v}XujB$!3;7QqJupAeJ@ z{wAQ8SFaw!`gj=$<|9~;U{Qj>1j7iHB^XJtTCZM%;=Sq+j3U^IU`K+n1p5<=Cpem5 zLhr#t26=r-@H2ul3C<&!L~uF5)dW)sZs|L;M=!4(1TzSpB6ykLErQtue-%*YMbI4g ze!m{N!UP=zixUhd7(uWy!5ReX5sZq%0eoExf-wX;5sW3+mtY*hp#(=0oDespSDbDN z!9;>z5S&GD0l_5%S0MSJy0rw;2&NOT%p z=uOZ}ut2C0L7K1A{J_5qtnxLH~rH zOz<}W4PFF|1oIIrNU$iu;Nd6@Lm0ub1S1JnBUp!EBZAQc+Yl7%MWSC7{pWv+nK(g- zl>RNGb#;tVMcjW1#k@`Q`~NK#YZ~Ic{|;&~FEC@oD`qeMyEZ0Hm?A&_77G5xh|B#O zDCTJld{IC#FB4-Q0R`7&7y}6CO;F5-8Ti=0gJPB`W@Y~lig_dh-~4w_tRXR4N%}X? zv)27@VG(-QzlAo6^>1MS#rij}C`JYKFe>PXvB&^$-Lc@}i5L~k!)i)0fF< z7V%lQigUw>^-tk0X2t>st2ju-4iy(w-(X|8DMhM26;%8uZ`6oUDN@!v@KCN+ocYDG zwuOqLRov8r4Rt(UXc#In!)S5~R9#{vjbWsr@9%ER5Qi|HJxd9rN33rc2EcR@>BHp4 zi-MaNmF9+Nh3Cq@hhYZz!=;{pdKs;Z>7NjMK=2m9ECCJS7_Biwag6SmAq?{L%upCE z;=L$0L)hhDk0|Mm_dfKQmiy9 z!^*O9Shp%K=C+vGR$`S|6;_qKi`}8>tOl#eYO&gwUDjpwSbZodYseb0##rHMBIcTy zX*OpqSWDIl^UOA^EsJ68FxTwBI&{}K+^i?-g}G-R)|d5T{joYW z0Q1f`Hi!*oLs&dipABWhG?=02+yDRZonGg#nisAqr75i`qbaK?r-{&%*Hq9{)I@43 ziM2s!S716p!`}D@V6GhuYATCSdo|2aegiFift4VkJK+oIJIsJJn5|$2EPVm;)kxn! zwpb(0#ynUfEfzW+5Qga-=tr2YFrY?P6uj@D^ayL_kEJKlQ}iIuv3d^ulv0lLD^__l zI-_*w|9N`LrMCY8t^JF3BihUVjn?wtkNj zJ_X&(!TR9}?AL6;`bGxi=-=E&@kL(}gfEXo9}tE1-34to4()U__7$eEMD_)ng&l<@ zYz14((pWm%g>{Uh>=f48uCg2K7Q4qDvM1~X`<4AhYxL+JK#AxZusY=hzV#4TC;bd6 z)G|HxgtCDR(rr+rmU&BeK$lwPBi#jcYMBxHJI{bk(tS{>midC~y#V&Zx+KTSPccxg z2HYtJFa#8=VI>3=OAA;@zk`>?5{{Er#tgeD`X$1h&typR$I12iq6y6MX+t*uKOL%Q}XA73{)% z1vr9z4LFj`1RTY_0UXU{0ghp_0mrgAfaBO)!0~Jz;D>BJ;Qu@>V-vucC&DDaOoEvV z^AXGxn2%vTftiYTq@zEXgSz_z4gDScpg%(1X}1MCL7x2yJTvTW2$_PsK83*v;REvl z`3S8rRf+R?VNIk1a=3?Ab0&93bNo`a=yc2Cfo!EV+=07L- { type: DriftSqlType.string, requiredDuringInsert: false, $customConstraints: ''); + static const VerificationMeta _isFavoriteMeta = + const VerificationMeta('isFavorite'); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0')); static const VerificationMeta _categoryIdMeta = const VerificationMeta('categoryId'); late final GeneratedColumn categoryId = GeneratedColumn( @@ -397,6 +405,7 @@ class Passwords extends Table with TableInfo { value, expireTime, remark, + isFavorite, categoryId, createTime, lastUpdateTime, @@ -443,6 +452,12 @@ class Passwords extends Table with TableInfo { context.handle(_remarkMeta, remark.isAcceptableOrUnknown(data['remark']!, _remarkMeta)); } + if (data.containsKey('is_favorite')) { + context.handle( + _isFavoriteMeta, + isFavorite.isAcceptableOrUnknown( + data['is_favorite']!, _isFavoriteMeta)); + } if (data.containsKey('category_id')) { context.handle( _categoryIdMeta, @@ -490,6 +505,8 @@ class Passwords extends Table with TableInfo { .read(DriftSqlType.string, data['${effectivePrefix}expire_time']), remark: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}remark']), + isFavorite: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}is_favorite'])!, categoryId: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}category_id'])!, createTime: attachedDatabase.typeMapping @@ -520,6 +537,7 @@ class PasswordEntity extends DataClass implements Insertable { final String value; final String? expireTime; final String? remark; + final int isFavorite; final int categoryId; final String createTime; final String lastUpdateTime; @@ -531,6 +549,7 @@ class PasswordEntity extends DataClass implements Insertable { required this.value, this.expireTime, this.remark, + required this.isFavorite, required this.categoryId, required this.createTime, required this.lastUpdateTime, @@ -548,6 +567,7 @@ class PasswordEntity extends DataClass implements Insertable { if (!nullToAbsent || remark != null) { map['remark'] = Variable(remark); } + map['is_favorite'] = Variable(isFavorite); map['category_id'] = Variable(categoryId); map['create_time'] = Variable(createTime); map['last_update_time'] = Variable(lastUpdateTime); @@ -566,6 +586,7 @@ class PasswordEntity extends DataClass implements Insertable { : Value(expireTime), remark: remark == null && nullToAbsent ? const Value.absent() : Value(remark), + isFavorite: Value(isFavorite), categoryId: Value(categoryId), createTime: Value(createTime), lastUpdateTime: Value(lastUpdateTime), @@ -583,6 +604,7 @@ class PasswordEntity extends DataClass implements Insertable { value: serializer.fromJson(json['value']), expireTime: serializer.fromJson(json['expire_time']), remark: serializer.fromJson(json['remark']), + isFavorite: serializer.fromJson(json['is_favorite']), categoryId: serializer.fromJson(json['category_id']), createTime: serializer.fromJson(json['create_time']), lastUpdateTime: serializer.fromJson(json['last_update_time']), @@ -599,6 +621,7 @@ class PasswordEntity extends DataClass implements Insertable { 'value': serializer.toJson(value), 'expire_time': serializer.toJson(expireTime), 'remark': serializer.toJson(remark), + 'is_favorite': serializer.toJson(isFavorite), 'category_id': serializer.toJson(categoryId), 'create_time': serializer.toJson(createTime), 'last_update_time': serializer.toJson(lastUpdateTime), @@ -613,6 +636,7 @@ class PasswordEntity extends DataClass implements Insertable { String? value, Value expireTime = const Value.absent(), Value remark = const Value.absent(), + int? isFavorite, int? categoryId, String? createTime, String? lastUpdateTime, @@ -624,6 +648,7 @@ class PasswordEntity extends DataClass implements Insertable { value: value ?? this.value, expireTime: expireTime.present ? expireTime.value : this.expireTime, remark: remark.present ? remark.value : this.remark, + isFavorite: isFavorite ?? this.isFavorite, categoryId: categoryId ?? this.categoryId, createTime: createTime ?? this.createTime, lastUpdateTime: lastUpdateTime ?? this.lastUpdateTime, @@ -638,6 +663,8 @@ class PasswordEntity extends DataClass implements Insertable { expireTime: data.expireTime.present ? data.expireTime.value : this.expireTime, remark: data.remark.present ? data.remark.value : this.remark, + isFavorite: + data.isFavorite.present ? data.isFavorite.value : this.isFavorite, categoryId: data.categoryId.present ? data.categoryId.value : this.categoryId, createTime: @@ -658,6 +685,7 @@ class PasswordEntity extends DataClass implements Insertable { ..write('value: $value, ') ..write('expireTime: $expireTime, ') ..write('remark: $remark, ') + ..write('isFavorite: $isFavorite, ') ..write('categoryId: $categoryId, ') ..write('createTime: $createTime, ') ..write('lastUpdateTime: $lastUpdateTime, ') @@ -668,7 +696,7 @@ class PasswordEntity extends DataClass implements Insertable { @override int get hashCode => Object.hash(id, type, title, value, expireTime, remark, - categoryId, createTime, lastUpdateTime, isDeleted); + isFavorite, categoryId, createTime, lastUpdateTime, isDeleted); @override bool operator ==(Object other) => identical(this, other) || @@ -679,6 +707,7 @@ class PasswordEntity extends DataClass implements Insertable { other.value == this.value && other.expireTime == this.expireTime && other.remark == this.remark && + other.isFavorite == this.isFavorite && other.categoryId == this.categoryId && other.createTime == this.createTime && other.lastUpdateTime == this.lastUpdateTime && @@ -692,6 +721,7 @@ class PasswordsCompanion extends UpdateCompanion { final Value value; final Value expireTime; final Value remark; + final Value isFavorite; final Value categoryId; final Value createTime; final Value lastUpdateTime; @@ -704,6 +734,7 @@ class PasswordsCompanion extends UpdateCompanion { this.value = const Value.absent(), this.expireTime = const Value.absent(), this.remark = const Value.absent(), + this.isFavorite = const Value.absent(), this.categoryId = const Value.absent(), this.createTime = const Value.absent(), this.lastUpdateTime = const Value.absent(), @@ -717,6 +748,7 @@ class PasswordsCompanion extends UpdateCompanion { required String value, this.expireTime = const Value.absent(), this.remark = const Value.absent(), + this.isFavorite = const Value.absent(), this.categoryId = const Value.absent(), required String createTime, required String lastUpdateTime, @@ -734,6 +766,7 @@ class PasswordsCompanion extends UpdateCompanion { Expression? value, Expression? expireTime, Expression? remark, + Expression? isFavorite, Expression? categoryId, Expression? createTime, Expression? lastUpdateTime, @@ -747,6 +780,7 @@ class PasswordsCompanion extends UpdateCompanion { if (value != null) 'value': value, if (expireTime != null) 'expire_time': expireTime, if (remark != null) 'remark': remark, + if (isFavorite != null) 'is_favorite': isFavorite, if (categoryId != null) 'category_id': categoryId, if (createTime != null) 'create_time': createTime, if (lastUpdateTime != null) 'last_update_time': lastUpdateTime, @@ -762,6 +796,7 @@ class PasswordsCompanion extends UpdateCompanion { Value? value, Value? expireTime, Value? remark, + Value? isFavorite, Value? categoryId, Value? createTime, Value? lastUpdateTime, @@ -774,6 +809,7 @@ class PasswordsCompanion extends UpdateCompanion { value: value ?? this.value, expireTime: expireTime ?? this.expireTime, remark: remark ?? this.remark, + isFavorite: isFavorite ?? this.isFavorite, categoryId: categoryId ?? this.categoryId, createTime: createTime ?? this.createTime, lastUpdateTime: lastUpdateTime ?? this.lastUpdateTime, @@ -803,6 +839,9 @@ class PasswordsCompanion extends UpdateCompanion { if (remark.present) { map['remark'] = Variable(remark.value); } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } if (categoryId.present) { map['category_id'] = Variable(categoryId.value); } @@ -830,6 +869,7 @@ class PasswordsCompanion extends UpdateCompanion { ..write('value: $value, ') ..write('expireTime: $expireTime, ') ..write('remark: $remark, ') + ..write('isFavorite: $isFavorite, ') ..write('categoryId: $categoryId, ') ..write('createTime: $createTime, ') ..write('lastUpdateTime: $lastUpdateTime, ') @@ -1405,6 +1445,7 @@ typedef $PasswordsCreateCompanionBuilder = PasswordsCompanion Function({ required String value, Value expireTime, Value remark, + Value isFavorite, Value categoryId, required String createTime, required String lastUpdateTime, @@ -1418,6 +1459,7 @@ typedef $PasswordsUpdateCompanionBuilder = PasswordsCompanion Function({ Value value, Value expireTime, Value remark, + Value isFavorite, Value categoryId, Value createTime, Value lastUpdateTime, @@ -1451,6 +1493,9 @@ class $PasswordsFilterComposer extends Composer<_$AppDb, Passwords> { ColumnFilters get remark => $composableBuilder( column: $table.remark, builder: (column) => ColumnFilters(column)); + ColumnFilters get isFavorite => $composableBuilder( + column: $table.isFavorite, builder: (column) => ColumnFilters(column)); + ColumnFilters get categoryId => $composableBuilder( column: $table.categoryId, builder: (column) => ColumnFilters(column)); @@ -1491,6 +1536,9 @@ class $PasswordsOrderingComposer extends Composer<_$AppDb, Passwords> { ColumnOrderings get remark => $composableBuilder( column: $table.remark, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get isFavorite => $composableBuilder( + column: $table.isFavorite, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get categoryId => $composableBuilder( column: $table.categoryId, builder: (column) => ColumnOrderings(column)); @@ -1531,6 +1579,9 @@ class $PasswordsAnnotationComposer extends Composer<_$AppDb, Passwords> { GeneratedColumn get remark => $composableBuilder(column: $table.remark, builder: (column) => column); + GeneratedColumn get isFavorite => $composableBuilder( + column: $table.isFavorite, builder: (column) => column); + GeneratedColumn get categoryId => $composableBuilder( column: $table.categoryId, builder: (column) => column); @@ -1573,6 +1624,7 @@ class $PasswordsTableManager extends RootTableManager< Value value = const Value.absent(), Value expireTime = const Value.absent(), Value remark = const Value.absent(), + Value isFavorite = const Value.absent(), Value categoryId = const Value.absent(), Value createTime = const Value.absent(), Value lastUpdateTime = const Value.absent(), @@ -1586,6 +1638,7 @@ class $PasswordsTableManager extends RootTableManager< value: value, expireTime: expireTime, remark: remark, + isFavorite: isFavorite, categoryId: categoryId, createTime: createTime, lastUpdateTime: lastUpdateTime, @@ -1599,6 +1652,7 @@ class $PasswordsTableManager extends RootTableManager< required String value, Value expireTime = const Value.absent(), Value remark = const Value.absent(), + Value isFavorite = const Value.absent(), Value categoryId = const Value.absent(), required String createTime, required String lastUpdateTime, @@ -1612,6 +1666,7 @@ class $PasswordsTableManager extends RootTableManager< value: value, expireTime: expireTime, remark: remark, + isFavorite: isFavorite, categoryId: categoryId, createTime: createTime, lastUpdateTime: lastUpdateTime, diff --git a/lib/src/database/tables.drift b/lib/src/database/tables.drift index 8f6027e..202a937 100644 --- a/lib/src/database/tables.drift +++ b/lib/src/database/tables.drift @@ -13,6 +13,7 @@ CREATE TABLE passwords ( value TEXT NOT NULL, expire_time TEXT, remark TEXT, + is_favorite INTEGER NOT NULL DEFAULT 0, category_id INTEGER NOT NULL DEFAULT 1, create_time TEXT NOT NULL, last_update_time TEXT NOT NULL, diff --git a/lib/src/screens/components/category_group.dart b/lib/src/screens/components/category_group.dart index 5213e37..a391be7 100644 --- a/lib/src/screens/components/category_group.dart +++ b/lib/src/screens/components/category_group.dart @@ -9,25 +9,36 @@ class CategoryGroup extends StatelessWidget { @override Widget build(BuildContext context) { - return InkWell( - child: Row( - children: [ - Icon( - MyIcons.chevron_right, - size: 12, + return Row( + children: [ + InkWell( + child: Icon( + Icons.keyboard_arrow_right, + color: Colors.grey, ), - Padding( - padding: EdgeInsets.only(left: 10), - child: Text( - name, - style: TextStyle( - color: Colors.grey, - ), + onTap: () {}, + ), + Padding( + padding: EdgeInsets.only(left: 10), + child: Text( + name, + style: TextStyle( + color: Colors.grey, ), ), - ], - ), - onTap: () {}, + ), + Expanded(child: Container()), + Padding( + padding: EdgeInsets.only(right: 30), + child: InkWell( + onTap: () {}, + child: Icon( + Icons.add, + color: Colors.grey, + ), + ), + ) + ], ); } } diff --git a/lib/src/screens/components/category_item.dart b/lib/src/screens/components/category_item.dart index 9c6368b..bc3b467 100644 --- a/lib/src/screens/components/category_item.dart +++ b/lib/src/screens/components/category_item.dart @@ -1,23 +1,31 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class CategoryItem extends StatelessWidget { +import 'password_categories.dart'; + +class CategoryItem extends ConsumerWidget { final String name; final IconData icon; - final int count; - + final int category; const CategoryItem( - {super.key, required this.name, required this.icon, required this.count}); + {super.key, required this.name, required this.icon, this.category = 0}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final selectedCategory = ref.watch(selectedCategoryProvider); + final selectedCategoryNotifier = + ref.read(selectedCategoryProvider.notifier); + final isActive = selectedCategory == category; return ListTile( dense: true, contentPadding: EdgeInsets.only(right: 10), visualDensity: VisualDensity(horizontal: 0, vertical: -4), leading: Icon(icon), title: Text(name), - trailing: Text("$count"), - onTap: () {}, + tileColor: isActive ? const Color.fromARGB(255, 238, 244, 255) : null, + onTap: () { + selectedCategoryNotifier.setSelectedCategory(category); + }, ); } } diff --git a/lib/src/screens/components/password_categories.dart b/lib/src/screens/components/password_categories.dart index 88d372c..75eb319 100644 --- a/lib/src/screens/components/password_categories.dart +++ b/lib/src/screens/components/password_categories.dart @@ -4,17 +4,35 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../domain/category.dart'; import '../../providers.dart'; -import '../passwords.dart'; import 'category_group.dart'; import 'category_item.dart'; part 'password_categories.g.dart'; +const CATEGORY_ALL_ITEMS = 0; +const CATEGORY_FAVORITE = -1; +const CATEGORY_DELETED = -2; +const CATEGORY_LOGIN = -11; +const CATEGORY_CARD = -12; +const CATEGORY_SSH_KEY = -13; + @riverpod Future> categories(Ref ref) async { return ref.watch(categoryRepositoryProvider).list(); } +@riverpod +class SelectedCategory extends _$SelectedCategory { + @override + int build() { + return 0; + } + + void setSelectedCategory(int selected) { + state = selected; + } +} + class PasswordCategories extends ConsumerWidget { const PasswordCategories({super.key}); @@ -28,37 +46,41 @@ class PasswordCategories extends ConsumerWidget { CategoryItem( name: "All items", icon: Icons.dashboard, - count: 10, + category: CATEGORY_ALL_ITEMS, ), CategoryItem( - name: "Favourite", + name: "Favorite", icon: Icons.favorite, - count: 10, + category: CATEGORY_FAVORITE, ), CategoryItem( name: "Trash", icon: Icons.recycling, - count: 10, + category: CATEGORY_DELETED, ), SizedBox(height: 15), CategoryGroup(name: "TYPES"), + SizedBox(height: 15), CategoryItem( name: "Login", - icon: Icons.recycling, - count: 10, + icon: Icons.password, + category: CATEGORY_LOGIN, ), CategoryItem( name: "Card", - icon: Icons.recycling, - count: 10, + icon: Icons.credit_card, + category: CATEGORY_CARD, ), CategoryItem( name: "SSH Key", - icon: Icons.recycling, - count: 10, + icon: Icons.vpn_key, + category: CATEGORY_SSH_KEY, + ), + SizedBox(height: 15), + CategoryGroup( + name: "CATEGORIES", ), SizedBox(height: 15), - CategoryGroup(name: "CATEGORIES"), categories.when( data: (list) { return Column( @@ -66,8 +88,8 @@ class PasswordCategories extends ConsumerWidget { ...list.map((c) { return CategoryItem( name: c.name, - icon: Icons.folder_outlined, - count: 10, + icon: Icons.tab, + category: c.id!, ); }) ], diff --git a/lib/src/screens/components/password_categories.g.dart b/lib/src/screens/components/password_categories.g.dart index 15e4f64..b27aab0 100644 --- a/lib/src/screens/components/password_categories.g.dart +++ b/lib/src/screens/components/password_categories.g.dart @@ -22,5 +22,21 @@ final categoriesProvider = AutoDisposeFutureProvider>.internal( @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef CategoriesRef = AutoDisposeFutureProviderRef>; +String _$selectedCategoryHash() => r'f36f2f15f678bf45945040c2c0612e492c370f68'; + +/// See also [SelectedCategory]. +@ProviderFor(SelectedCategory) +final selectedCategoryProvider = + AutoDisposeNotifierProvider.internal( + SelectedCategory.new, + name: r'selectedCategoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$selectedCategoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$SelectedCategory = AutoDisposeNotifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/screens/passwords.dart b/lib/src/screens/components/passwords.dart similarity index 65% rename from lib/src/screens/passwords.dart rename to lib/src/screens/components/passwords.dart index 1b273c1..0200bdb 100644 --- a/lib/src/screens/passwords.dart +++ b/lib/src/screens/components/passwords.dart @@ -1,20 +1,29 @@ +import 'package:cryptowl/main.dart'; import 'package:cryptowl/src/providers.dart'; +import 'package:cryptowl/src/screens/components/password_categories.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../domain/password.dart'; +import '../../domain/password.dart'; part 'passwords.g.dart'; @riverpod Future> passwords(Ref ref) async { - final db = ref.watch(userDatabaseProvider); - return ref.read(passwordServiceProvider).fetchAll(db); + final selectedCategory = ref.watch(selectedCategoryProvider); + final repository = ref.read(passwordRepositoryProvider); + logger.fine("Fetching passwords with category=$selectedCategory"); + switch (selectedCategory) { + case CATEGORY_ALL_ITEMS: + return repository.list(); + default: + return repository.listByCategory(selectedCategory); + } } -class PasswordListScreen extends ConsumerWidget { - const PasswordListScreen({super.key}); +class PasswordList extends ConsumerWidget { + const PasswordList({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/src/screens/passwords.g.dart b/lib/src/screens/components/passwords.g.dart similarity index 92% rename from lib/src/screens/passwords.g.dart rename to lib/src/screens/components/passwords.g.dart index b5d58cf..610b66b 100644 --- a/lib/src/screens/passwords.g.dart +++ b/lib/src/screens/components/passwords.g.dart @@ -6,7 +6,7 @@ part of 'passwords.dart'; // RiverpodGenerator // ************************************************************************** -String _$passwordsHash() => r'aa7ff3580c7d39ea3d5182b9446eabc1ddff78e3'; +String _$passwordsHash() => r'5301cc8a4783c1d44cba5783a68769c1a1eae1a7'; /// See also [passwords]. @ProviderFor(passwords) diff --git a/lib/src/screens/home.dart b/lib/src/screens/home.dart index 2f0b96a..6d24105 100644 --- a/lib/src/screens/home.dart +++ b/lib/src/screens/home.dart @@ -2,36 +2,20 @@ import 'dart:io'; import 'package:cryptowl/main.dart'; import 'package:flutter/material.dart'; +import 'package:responsive_framework/responsive_framework.dart'; import 'components/app_drawer.dart'; import 'components/password_categories.dart'; -import 'passwords.dart'; +import 'components/passwords.dart'; -class HomeScreen extends StatelessWidget { +class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override - Widget build(BuildContext context) { - final isDesktop = - Platform.isMacOS || Platform.isLinux || Platform.isWindows; - logger.fine("is the device desktop? $isDesktop ${Platform.isMacOS}"); - - if (isDesktop) { - return DesktopHomeScreen(); - } else { - return MobileHomeScreen(); - } - } + State createState() => _HomeScreenState(); } -class MobileHomeScreen extends StatefulWidget { - const MobileHomeScreen({super.key}); - - @override - State createState() => _MobileHomeScreenState(); -} - -class _MobileHomeScreenState extends State { +class _HomeScreenState extends State { final GlobalKey _scaffoldKey = GlobalKey(); int currentPageIndex = 0; @@ -68,11 +52,62 @@ class _MobileHomeScreenState extends State { ); } + Widget _renderColumnLayout() { + return Padding( + padding: EdgeInsets.all(8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 240, + child: PasswordCategories(), + ), + Container( + decoration: BoxDecoration( + border: Border( + left: BorderSide( + color: const Color.fromARGB(255, 222, 222, 222), + width: 1, + ), + right: BorderSide( + color: const Color.fromARGB(255, 222, 222, 222), + width: 1, + ), + ), + ), + padding: EdgeInsets.only(left: 10, right: 10), + child: SizedBox( + width: 350, + child: PasswordList(), + ), + ), + Container( + constraints: const BoxConstraints(maxWidth: 100), + child: Center( + child: Text("Details"), + ), + ), + ], + ), + ); + } + + Widget _renderTabContent() { + return Padding( + padding: EdgeInsets.all(8), + child: [ + PasswordList(), + Text("2"), + Text("3"), + Text("4") + ][currentPageIndex], + ); + } + @override Widget build(BuildContext context) { - final isDesktop = - Platform.isMacOS || Platform.isLinux || Platform.isWindows; - logger.fine("is the device desktop? $isDesktop ${Platform.isMacOS}"); + final isLargeScreen = ResponsiveBreakpoints.of(context).largerThan(MOBILE); + logger.fine("is the device desktop? $isLargeScreen"); return Scaffold( appBar: AppBar( @@ -96,16 +131,9 @@ class _MobileHomeScreenState extends State { ), ], ), - body: Padding( - padding: EdgeInsets.all(8), - child: [ - PasswordListScreen(), - Text("2"), - Text("3"), - Text("4") - ][currentPageIndex], - ), - bottomNavigationBar: _renderNavigationBar(), + body: isLargeScreen ? _renderColumnLayout() : _renderTabContent(), + bottomNavigationBar: isLargeScreen ? null : _renderNavigationBar(), + drawer: AppDrawer(), ); } } @@ -173,7 +201,7 @@ class _DesktopHomeScreenState extends State { padding: EdgeInsets.only(left: 10, right: 10), child: SizedBox( width: 350, - child: PasswordListScreen(), + child: PasswordList(), ), ), Container( diff --git a/lib/src/service/password_repository.dart b/lib/src/service/password_repository.dart index 81ad7cb..d08e34d 100644 --- a/lib/src/service/password_repository.dart +++ b/lib/src/service/password_repository.dart @@ -13,6 +13,13 @@ class PasswordRepository { return items.map((item) => Password.fromEntity(item)).toList(); } + Future> listByCategory(int category) async { + final items = await (db.passwords.select() + ..where((i) => i.categoryId.equals(category))) + .get(); + return items.map((item) => Password.fromEntity(item)).toList(); + } + Future findById(String id) async { final item = await (db.passwords.select() ..where((tbl) => tbl.id.equals(id))) diff --git a/lib/src/theme.dart b/lib/src/theme.dart index dba3969..1700c81 100644 --- a/lib/src/theme.dart +++ b/lib/src/theme.dart @@ -20,6 +20,7 @@ import 'package:flutter/material.dart'; abstract final class AppTheme { // The defined light theme. static ThemeData light = FlexThemeData.light( + fontFamily: "AgileSans", scheme: FlexScheme.hippieBlue, subThemesData: const FlexSubThemesData( alignedDropdown: true, @@ -37,6 +38,7 @@ abstract final class AppTheme { ); // The defined dark theme. static ThemeData dark = FlexThemeData.dark( + fontFamily: "AgileSans", scheme: FlexScheme.hippieBlue, subThemesData: const FlexSubThemesData( blendOnColors: true, diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib index 80e867a..4ecb408 100644 --- a/macos/Runner/Base.lproj/MainMenu.xib +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -13,7 +13,7 @@ - + @@ -330,14 +330,16 @@ - + - + + + diff --git a/pubspec.yaml b/pubspec.yaml index 7f98515..cce65d5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -59,6 +59,10 @@ flutter: assets: - assets/images/ fonts: - - family: MyIcons - fonts: - - asset: assets/fonts/MyIcons.ttf \ No newline at end of file + - family: MyIcons + fonts: + - asset: assets/fonts/MyIcons.ttf + - family: AgileSans + fonts: + - asset: assets/fonts/AgileSans-Regular.otf + \ No newline at end of file diff --git a/test/unit_test.dart b/test/unit_test.dart index 5dce174..9a8cbe0 100644 --- a/test/unit_test.dart +++ b/test/unit_test.dart @@ -4,23 +4,44 @@ // writing unit tests, visit // https://flutter.dev/to/unit-testing +import 'dart:math'; + import 'package:cryptowl/src/common/uuid_util.dart'; import 'package:faker/faker.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('test data', () { - test('should generate sql for test data', () { + test('should generate insert category sql', () { + var faker = Faker(); + var sql = + 'insert into categories(id, name, access_level, create_time, last_update_time) \nvalues \n'; + final count = 20; + for (var i = 2; i < count; i++) { + final name = faker.company.name(); + + final time = DateTime.now().toIso8601String(); + sql += "('$i', '$name', 1, '$time', '$time')"; + if (i != count - 1) { + sql += ",\n"; + } else { + sql += ";\n"; + } + } + print(sql); + }); + test('should generate insert password sql', () { var faker = Faker(); var sql = - 'insert into passwords(id, title, value, create_time, last_update_time) \nvalues \n'; + 'insert into passwords(id, title, value, category_id, create_time, last_update_time) \nvalues \n'; final count = 20; for (var i = 0; i < count; i++) { final id = UuidUtil.generateUUID(); final title = faker.company.name(); final value = faker.lorem.sentence(); final time = DateTime.now().toIso8601String(); - sql += "('$id', '$title', '$value', '$time', '$time')"; + final category = Random().nextInt(10) + 1; + sql += "('$id', '$title', '$value', $category, '$time', '$time')"; if (i != count - 1) { sql += ",\n"; } else {