From 585cb91df56c413061e6d8410fdb04b6a42c1c77 Mon Sep 17 00:00:00 2001 From: mohamadmahdi jebeli Date: Thu, 31 Jul 2025 10:25:16 +0330 Subject: [PATCH] add scanner --- android/app/src/main/AndroidManifest.xml | 2 + ...-glockenspiel-treasure-video-game-6346.mp3 | Bin 0 -> 50880 bytes lib/core/config/api_config.dart | 9 +- lib/main.dart | 2 + .../discount/bloc/discount_bloc.dart | 9 +- .../discount/bloc/discount_event.dart | 5 +- .../discount/bloc/discount_state.dart | 6 +- lib/presentation/order/bloc/order_bloc.dart | 46 +++++++ lib/presentation/order/bloc/order_event.dart | 10 ++ lib/presentation/order/bloc/order_state.dart | 17 +++ lib/presentation/pages/add_discount_page.dart | 102 ++++++++++---- .../pages/barcode_scanner_page.dart | 124 ++++++++++++++++++ lib/presentation/pages/store_info.dart | 105 ++++++--------- .../store_info/bloc/store_info_bloc.dart | 4 + .../store_info/bloc/store_info_event.dart | 2 + .../store_info/bloc/store_info_state.dart | 18 ++- .../widgets/active_discount_card.dart | 54 ++++---- lib/presentation/widgets/discount_card.dart | 2 +- lib/presentation/widgets/success_popup.dart | 43 ++++++ pubspec.lock | 122 ++++++++++++++++- pubspec.yaml | 5 + 21 files changed, 560 insertions(+), 127 deletions(-) create mode 100644 assets/sounds/short-success-sound-glockenspiel-treasure-video-game-6346.mp3 create mode 100644 lib/presentation/order/bloc/order_bloc.dart create mode 100644 lib/presentation/order/bloc/order_event.dart create mode 100644 lib/presentation/order/bloc/order_state.dart create mode 100644 lib/presentation/pages/barcode_scanner_page.dart create mode 100644 lib/presentation/widgets/success_popup.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d59eecf..6a292df 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,6 +5,8 @@ + + 3L&OI!d#C*q$`T~l)9Zo=7%fvl;v_|*O8N(eqF8F8(gdn+}k!XcY1*qYy1A? zl3UKR#50N10d!!-{@jr_MSrFKvwpJStsT9R;x@VhDmr%QL8z@}Ed%GfOgjR=dnS^7dN+RpIse zuD?R@?o8B+U&|6ZPP*NFFw~F>lZy}ZT)M(U(V1KJm)fM>kDk<#YvlJwuWBN9?)ndp zr%jt@HOX37pLDPzZBy)~0N<8rc^8D|Y?t>C#}2M=?{rGp5f&1&(_*e}Db? zl*RZu#fO1eUABK{z6XN5vg<3O_b=)$QWE zyevxS-=B{B_f4C3Sg1XK7s;ZC5M??>4PZ@Is5w+34hpv=E54{Ct;S`Cb{-{KKV?kE zJSlicWal}~i9{}IoIH$u?0ot)Z3A143AplN8LB8RPDY&#^zl7mhfHdC<%b9)vFn+V znmhUQIy=)|W%UK=5|4mojKj|JxvNcu^q8k%b<0;_o^#h7ICS;A!ZVUsH8tv%#q#Le zp$B#qdVOm@-L=Q`E(Y*j>*T2UNZ*>ObY6620P24!UGo@?y4$AsVo!T;A7lkLE7icg(C+D5IKDobr$x6!#t}>{RuU)e3(BORh+huTQLUJ)4Xq zc$Ivyc)6%)|J~`BA$XWz;c{m%?Ppo+#G(&+?#UV-$AEiY+vg_1g(Z&)OkZ4{ zb(7!Y_|bUojo!b1ZVx!~Pr#?dM|bJpZO-lbFhv#>g8ygMcj*(^qQ7>aNf$_EUY8UL zu!%@-*ZH-ZUb`B8-1hL;x8|abvtFxe)~`4>@6YI+Prhxx*0bO$<0k#X-lJVct#7{5 zFPj(5zh}Nc+nf~5kJ?UH^l_&q+9&+j;cz;J{TuC(Sd>v-GNW273M^vHN;ELBI;<-A5Y7P~5<10i$~A|BfsZ23-C5T3 z<=1G+>^Y@J%N*yD4!kXy)r6I|Z?@&0`8yP^Uxh7bn7IdsU&QV|h)cIBPl5By9(fu( zFxyPZaZfC$?kK2!&bTa@QJgn5wWnlpH+TsnN^izYxk;Io5Ip3Zn7;5 zm!vXJd1}wlBHm&XK6cv1Coc$nfwvc&md{K|A1N_()VpifJfz2UP;PSg*}m+eQ)m)+a+pTkSddmqbb;m`k`n01-XtBBbd>>!okR^$8v8d=5EJqy1- z@tlQrN_F@~JH4QfLif~=Ybk?>J1&Acsg6*}1$U;)v=VAHCOT1zxKn7 zRoC37AN7ce_HrNj?sWfUR>S#V>qASi$CGn+{n~KBt}WM=c`_U&KBdd_7F%ygu_)ul#Drt)or{7k>HFaDC$jzd*OrutVFew(ReJ zbaJEp&BfNF!U}-kMLJ61hC&z-xtd5+=YmFuTDOpjf7{`|pj*IY*L zbln{~ICyEIENSpm$o!RKNk^QFk3MkqOUt1olImO&B`dY<%FVTi(n5tnJ#Y{~DUYg%w2sUilWxC##=UoGxnq?{dw> zo;TP{=hNGa&Lk}OP`P*3tXmT&Z~U0D==_=JFJs#u72T}1U+i@_pt53nlGkwy|GOKX z6$&S;ZL$B^mS=2wQUCo_VCvYZZDW`)5ZiXNM^6CpNVk_R-n{SUWMzR?GIXX0k_VN` zMG!=V&djEtQy%&?v@+!7lU&cLbBXVEFY`DO^X+Qf%#{VD*|wd*MC4Xh+^g$T_U_UK z&gnO89f->a@s!Au=t~2o*T(V$r$MF;YJ!)Ao*5YELxqP%!KF~IG{#^$$iiRr_?2UE!`Zy{L zywwA0$l=QtD(*=&B+iyT$j_T?qrtEi_rcO%ecG{K)7QGi7;O=B1{g|8&$--@#N`FP znS%YecXD=W*)Nsm@{ujz)9-WX@tn%y43zVEodk}Z#XL+%*;(KS)p5}Dhw^7D!Q zF^A1tj@{l8J!j&}T&L}y3#R|LJ({tS{*pxayR;?qe%aK4%VpoUj0rEFdoZESm>5|* z<-i<=19PU8t#yAtF`7|krU0i7_TM*uIf!ZRm%-caAA6LC4`X+dXc;rFhm!U1oQ^vY zXZ8}6`(9k98#BWl+-I+i%srDI+Tz02TrN4=doX*hL+iZj$6j_tL}dOu{G^^=IPaIl z^lW~|rZ>=l-JsScY*$dH=J1}2v^nxSbeJyXLUImDgYiMEGU?oL`vENyj%jI=Gij4C zO#WDJpN4k|j-0G?P$$e}FQ6P0OH~Q8;RTa%2lw>yP9c-E$Yc##Cf2}!+&SwlGjE<$PJrH(1J6)hZWqfPT zu8qVt|IBZhw0i^EfKLx4@=b3&SGzdBsxTY$e)tA^J@CdyJUp#$j(J5%Ftc~o)}^~2 zV*p>?Z1a0qh-mefi@pww0^;K@@%jJFsjvF+>UC4=zLE2j3@#bJI+h$Ukkjr(8_c#-6|W zhOo8CFT`Wdk43`fO>?G2)Na3T@%4LiwS(-x?B01E^Id3_#ZbFmo@p37q{obNG1c!N zxg1zimipX2akFWrV7D^Cq*MI~!eQF!!)|7?nis@XD)XI2N1zWm}_gH159_W==`jhpaQk1Y=&33Dq@*Z$fQo zr;_*aj>*D91^448qQ+fD?5H#6d-ZYT{`sA0{R{J_2lvgHgVj8(>HSo*H+JrZ51SUf zD#x&&>m$z|;TF$q!Ij_mvhZeXuRe@n*XJ9C=+ANsEPfqy+eKejDs@0De%t=7yi>Zvs^mz-Ytb(>d-uOBn>xhT!u zV&LJQla^qWnM0U1x9`nQbHAG{e(r^BM_aYtnqckU+MLL*=;zzm5H?6Rndje&A|>zp zZG(m{NvS2WuqgU??%pO*HIZl0@4vtLRb}qL*Ohx?#=fWAwz> zvU+WG_z?D4D8H6$IrY>0%@t*tvj_CtLKC}~{NiS>zYXx>%AzwJ9u5Xuhdyh++`018 zzum|BsM6v;6N9T87OnVu=kd?q_jt~m8+GK=N^R}ZPws`&YPTjw&(3`IV(Ibai!aYQ zcwf`9LepY=U~Wq&A!}{rMR`RN-zZGZaf*{3oJ)VXcFKvjpCyAZ_UoWJ>ClzZ*6-WK zT6FkEPF`T>S`m07%ON_4SAM}h+W2V8l!8I+*tDuGW3y-9pMPNN^3e`;*!$aG)*9zx zEylUOW{eIx7X*aey65{SWOS{Qt^1h*&GS`{gg?G7(B6)iBZbFPbYb0x7q&* zTi^HsJK8q=dzxnUlmi>hy^Z#tx4J)Za`x;KsVDmH&r)YB>zbai?AhgWij&i>DNeSK z?#v~3f$J%spM;eD8ql;v9&MRD2EMq(c#ptn%t288%GB9&(R=NI)gmvkl2chxl(mL? z(mV-HP>+bS1Qo;m2WH=&u>Vv2bJyZEi!v9vl@~cY9%il`shNLtFm1KZ`y)$#Bt`xC zaYhB@+V~N6a9VTv*S_?eQuEQphaE%Qrz;QBi?iA*Jm+bcR0DBN8z({5YklJkV#pQC z(B4#nK9y;v$!}juOV7`%BNov31-z(`+3XfXA2E;y;iUR7bqXfV)`#iNs1uDsqrjys zl*MDA&PB>~uvHtY@g-sswtzS3!pd)KB)R0%R8ed9Oh{{2yb{q%d0g$}l&TR;9(+`V6P z;!h-@_K&6OU-t;d{c-F6T`Z?cmFnxYK5H(3wqDaQpI)uZ z=s6B{J8F7bG3>>)pJ&!*iy-j6>E=`jCpTEKKL1!P10sbBL@f#Ftkj_!2^5xJ~9g&$_0%33xz^V8MAnO94qdbDYj#Ggg`Pac_< zL)m?JUr>YMj>iE1a$gRt=;hr&-e(|I#xMcvSmD}VXrzPH-&OB>6!qpIsfNL z#z^;u&;6@>R!=!yDlzzYO{kq88JTJHdR7h`XQ7r6jr*w~83PAG!|Y=S8*x)R606RN zU(WEHUmUd~(m{uBoa=J4Ii7ciaFZ9io{HaJj`v({dHZ>?{ye1NfVT3&iw$av=c&)5 zw_f{NJkuQSX5Bgal;PiB%YJx9%XiK_%8A6V)}naCpxDZKLMVK^&M>ND+M>zFhEIG- zGK#F|@TJcUrLTta(bIuLWOzT>p3IukwK}Ei?;U16JB~hSQF^U8=Jlt*^62>wpXKU} zZ_fN3v3r93DW&6AigsmCr&-@l2KO?9yJPpK`0VfTU5%}Q!%ZC}@fS|+xEk{6uL6hX z4j+$>%OVWV{2o;d)BZL8`(NXss~QaI8+=paiw?D}agL%?WEwc(J)dLaE!EWc3&ky! z;g*xHrp9+QHO#+$`Qp1f>n@rNZ@4~@LO6{U7b|`dg^$Bda?%}Zarh=E9Lq(&foT8yubmnWz(>i?pSU<_rsqddaz>(2$Ph`+GWVQ`tA9gtR zlT+n;yH$1+Pp;;OBN;t%;B=Ye96zhxcLyHcbF9DbILFr|aq&^dx0>tqnrt)g%zdK_ zr!&sW`PYNL6-(6D$$eXmyHgi#F@CGj1{RdY4Y=qB8kA((c`?*YQB4aO22vcW+(e&X zd_uG^ncbJBbjd5vr?kG^?dSi>Qj)$YADX(q*)J*6Z}Z$`1O1X}}nGTx$b34CGPC%Fd0#!qUud2$EAE7=i>iv51NrXUEFBwoDMd@}rositN_klZKL`&P$aL zwy`Z+F@?mRJuS=LpE<39R*IPSG9Q?$6lD@vs6p2f;2kvc|7V(En9}hEvo#R>N3Jnc zdp1#y4BkfY;;gYhzs3`C!#4kU_qKAxH@Gw9kX?VSopffaf5g$-mGAp%D`*1SeZB}o zEx4x6yGoOkJu79Imh*{qkV+zLY->ueD^*m)ueVW+6Ox6iD+RYXVLJF8;>t;5d%@);GDN}xu zj+jO48-2t$)YxgJT^{KXkdlcoG|Wam>>eOKxm1297t4Oum#dJi!e#eu7crMZH(5l3 zHbVqW$QmX+%dzsjfp#}{w-E$dgrTJ|2&6KNmk=}q*tkAgawvkV3}UzI$=74?xFJW! zq*rk!3qSE=W=V_Z1@pg;uf@dIQb)9bxeGkghREtBfeD2q5$C!y&qM^%^`egJRUdA@ z)c(YalephUbKSvCuXC=R9qDdR$OCLoxdDvCI0mi9E98Q05i9|u5T)~sRRV|r^--yL z##o=V{2qpB&^|f>bu)1yCPrzcN;nDlTg*&|LsGH#JM{*^&Yf>%)rYNY)LA>y)dRDnIL z!?)G;j-E5L{BV=~v@PD{7yn&_=ZcE+CQER$5n>Qqg@!pGCKM&B7#y%EAR1Jp@g|M4 zDFotS&Xd()=p?XQ*uI$j=DqHoc=5#sj&dg^u3C31G;~!;C@*Ai)tXQq-`>sMon~%sF39Wbyir%!nO8tu zy$W2_%}D}LkjJLnU{k=cA}a|~3re)KNr4h#C-c}OsADHE&d4NN41@LdsT5uqWlwS5 zF&$7a^x(TwXT$18KKNt2V>4sz&YBF!GD@aezn?#d*>>Fb4C76~Wq(_WA^!2ep?a-B z%oDZ|ocL_&9|y@!?pBRA@q<3?_@5^gdl%bc>J;n$mV&8lN#+iokIb9JQ@z+r_fv_zcfRTs9|ZKQ)=NW-)5c zCxRQ&ntOc>VMRr*mYLS391a?WP^u-FAh>TR$r>K_yw*qE#xw$fv{1^G3smXobua=- zyGT_+5Z~Ociae7jY7^EQo4KUb1V2W-oQxTH#V6*ED*BTFzPw*nYe>#etZAeFxc)-U z#oFYP(*okAq-#AlZwYc#O~sCaFKzB_mT&Mb{s+Rzl#b$R?tkG{C9*A#ne@TQj|091 zNUW*@F~B}vU6KeoeT^g!=guzwW+M6Xw$`=3&L^!-zhar;ayJyEKt6)_^!OB}ULRGK zph=Jz=2${ebF!a;zHL`Gcf)i-6cfJ}YCjO=TU>~aqkY?DDHev3U}JEYoBcHVDU^u% z8o`QEOET%1CO%ck8bYndnU)wuB2viJNd(YwX;7rQ#^Owjl?BST5K zJ_AAH(^4#$;As|%QF6rMazQfUYXQURd~c=&MWSS>1p%-TxsRHL+Q7!v1e^x7qLI_k z^qGi_5k=C9!BlAYn>t{G3~*(s^ZXGTV*;5VmdMbwnZMa!xj>b{G*Zd8F^wfSE$qP% zpCq-=7`pX4%FZWQK+CVTxW*Z(p{xHbPM`_QyJN@M?yv!;qd+yS;tAUwj2ot8BM6sA z^_GVz@;8jjtoSofHRiU$ZN-;VWqVhOs1y1abPNqmHQl(v*fRisk#auC@BoQbjY`PH|m6*;;L(`Ze za)k!XK+@GoGJ(qLZ3RuG(D1+|UIu#xjB8a1QVhWy@rCK~Zf2oV`Ks)e5}=(LrjZWc0rp1{16qFMrjxf}H`MAGj#CL{k~#7;V+oXwT1%iblq?YV zlhg8yRdVY-+FG4;yo)R=m;y!`BQRpTneGZ{ws6^UqM7>sM_4w;EZ`y+2_b>n%kzeTjUhzk zr7*#ol;7eE@vNm>1y$IW3m8b>c2%!Gs!p*1R#4SD)lh>`3oP7~@2TN?IJpMw8j-Cm`U%H3M6kZ zN-17@NgDJ*f`2K&zW@i6>}wb$yAc4+WOSptTN9{Pb>EBqe1rXz-^cZ(nJ@f-{RAg| zVTxQxsJcbgV%T>~T_91+ftJ9eeP1cbP51YX4!!hdRA>VYSV0NnVxvEAqRUR7s+^+n zMZ{ThKCEVip#cIpA0f3WS&az8O?_!_=W*7Rl}5PQpXc210%*nddFNl8yRu|nkHue0 zu#xL@dQ@;)lzpZ%uF3D_*G+)4_vJtwQM@3<3dZ3ikPS1_jE}?XoNiizthsMHHqInT zIVDW0KFxs`R-VjqMh(=0NpJtKJe?Mml;OYmhM%~JK*A-IGmLYyt=OIL)l2wS% zoVMP1a>1YS*Xq%6Ut#uZQukll$q?eHdo! zyo+$$M$o__8fQOKkE*{w0^Ur;D(*nreH$SLLkUzE1S!(s1j}9<2Rs6eGodlPeQ(v* zI~ST3OIdNOZSp(^inQX9L20HP=}yqC5i>isXHY?Lrfnb1fyo|c>m-RjS~t@l5I!K` zCw)w|dJ>9|UE$@}z3=(2^Im^Kdfym4TB{r&EI$&l+mY7%UmW)R!SI!xW1?#P;Tb?&0K8Ha(}z|$a{hy%heCKRdT$x(?HnP=v;(f6E*Aa;8# z&2ipy4oqE~ILb?w!SuvNwincsLSAt*S=CUMxH*ZQNb&S+XP9Ng7jkz}7so4_qtNqA zW)L9F1ma4yAkUCz`n#v{a`UtWWNQGsn9=&!K1|$ik$!X?3~!0VaDgjsaC zS^(&a&QR+p7U}co(B;tS1#1ZWI5=1;<4@{bu6}J!lZS-uN{hW&U4CDSKq9#>Lf~vs zkBYNdK1x=q&c||+zBb#~-we^8mTSDYBcV-H6Ee@v!3tOFvpVd&MeS1RzV)EgU~$|5 zYNj0}ni_!92(9X}?dE(Qp)32+nl_u#66oV0BW5%%gSMHW$xoc3&I?on3dJ@i7@gr} zKfvgef7*!p#MGkR@|)`N4kEA&Oo!B>&R3SFx1}16{6)9%3mrgg*1<+%5vFty2rg@B zWL!Z=tFEhT3xeTfT#=fI9VN3e%^wz8J9>3Z)#3Zu$^`JM*d-0VXY{9Y0KV+C>WY?HHRR%D}ooL6|WLY1uja~QVo-i7bT@6+pMW_@AQB4FHM}%F&3Xg zBrJe%Dff=;@Vt5Fd3&N|AogpuaCU^lqS0N`emNYd+Btp-sAeX0UJICY^V+-TwN8qQ z;8jNupG3@dy0BOdXy~AgIlt-OR`fH}` ztx>A(gYY;xFu{xQNbn4j<4Y!qT=;lX(c~&6$>~17S_SUH5(r zyBjGTmpx@|oioHt_J#`$aXI+{WihJr$I^LRYBySixQL6xa3(}17f?%YnxQl}o#zK! zZ3XoirpTKjjpCy>Xj8;T3oy!2aW;@MF|3bnWk}2-a9N7HKuri)LJ}#~&=_fXGNuP? z5X9~lG_to*PI9y4g3EohH9&|!V<~_#VDN9lg;CCzC!WwX?uW}A3mv{6t%67t;D2v; zKhdW`UoS|ZCkAe~RR$;yqN;Exs4g=AiiVS%X|QNjV|f%~^wE)%_=0)jb@d?NJc zC=M?L(-vW5b@ITKSTKCcbY!}LzzBMvYmL$;im6@y|BLVc;;h@lhL^OCiR+nlBoT@d zL^3ii&CsO0!)|~ghQ4GM=NN?1ObKzc@zViJ5BZ?gPu@dAQFMIJ)S zVWI6zxq@mh<LPHBME&RNNwo&8N$nIsnkyrve>?O25 zdRihY5%o@2I%he{5}hr)Iqib>l`xR8MPf085GZ1b7>2|r@i38MDT4$Y0z~8}S^iAO zj!H#Y{=82}=>phOA^og$_&m;zB*af@1)T>uX&Oe*`ZfiP&fA`D2FA2 zPo6Diu@b?MuW?_lu2)kCcpbJFOj70FBv{(*M#Du!-3d;9k{!N>bI5k6h7P%yE{s(| zW?-2@?m(HOKzJ+2_-D~iSCY6v06zc1n6daPYIw>SlDwU}@4M$61#lSE8L_A|tvXCOj7V1)=V z0AdOrtlw3b^J}nvg*%$;zv^}B_%pFw-~|a*n8o3sMpWKA(^KYa3_^4Pp!eQ5u>i#C zmJr9CW}r$)e~-&n-=G-)P2q=DcOqkouC$y9>5rs?~9E93F1ox5EZKgk!rLoOQ;YB zV%Wq=aFu3l45FbPO8pADPOAbJ1oE3W((sK)AYFzv1>u18go~ zKU*=_NJz*aHYlbc&~rWt~n-YX8#r&X~1J z1SDxn0X4uu4ars31c-$P+z(>%A(k9MAT$q%oZAox*F81Ait}SKo+N5iY`_Q2?QVdc z2Yn!{%RYkJXXGT#=XKBPPj+g(KOw z7^jlN3ZF^g5d_op!$Jz6I8fp82w+$D?|J>#6^3O)wG zZ(%=6ogbAlN@SCqM4gQScl==0=p$U#65mN{yWPg?NOF;9;#9x)Z-;EWljBKSn0u+$ zt_MEqB|;%l>mM$5^IGhR?M;b3U1rsH&urBttYfiH+WKxmS`3N5Ufq**$yC%+S>whA zLZE9$jH_g6dYusQ%YFvts=!W;zb&#jgy;*$HU*N={I?^5w1Sc}gJKU+e4F&@a`s!% z6{}UOtg{XDhLGEw6dTyklz{6-hX^95CchIU6dVEh7O6@7R!}AdLP)M?2BD_;7MO1thKVHg^!)_}I8wKB z7ggG|A}!_Ooiew&%~9D{Ja{UYx_Ogx2(cVFd$=f4mz1 zSkRb#7C6J7q_K5f5tvpySGHU@`iQkWMfj5h*1ZrLy5>;XPB|3M#5)y=r7_i971C z;pan1Oru3(w;h3@kl)Yh)+ID9W0Zm*w$8!nxZB=;psgyPsUfWcXAW6rWm%pbwQ<+H zqEpkdW}-R~$S?R??x&!zl@xgq1^7u^OMDh^ssfauWXX_#tot;OCTTS8cuU9=L+$b? zFMm3dD1pQ}u@AvZIU)fUEofgHQjsQ3%kt2=v8w^(ie;u^zG18(-=f8h882)@3?(di zBdW-i2-M9=51OD!(8Oi*rK#ln{C0kbbY)&+NFYX7scFU95VKfd-)9?2f`(x$&!+fP z3<#<`U&K%d)a(tqVUl;q1Z&p8{()VR+4GTy;ahCa-R^3BdWr#etiHc!7{~242o_uZ zx_1JbXt?;j6Gz@^cgBb&p0O$=`yh_^@zeLaVj{)!(NA56+9ik;RMNWoSIXHjPTAGf zum5_9{$*fOxUj%1Q9q*~M9{Fzospw@ZK=#+D;q)zNM+-hX3lXA*>+qJIuvr(GrRXM z1Z_8qo2u?G<99HdI?RGT-*kwlxG*b+sf)R3Uga{qDA*wpS^56vNXC#bFa2LLoRLKm zC%O2MjdvKOMu%@F%Yx)2Lo|!wH>i+hP!;6TjE&D4)DFP%a6ynT0 zu+B+yCO_}j!2Rbh2RudJ%sMp9jD7o(_qn;bx|`>6yF+tjdu=*GgqpkdtZ@74j~3!b z_CcyBQ$&#?S&=(_`w5Cyay-kI+XO|9;ZBu9%)_?i2IbvU}8q9IEwjN>sKjef)2h- z-!$^qNE0bbWHK55YGI4ph!C9pd2ap1MF$!gSr4}yZ>U;dv;-(&^LnH#(#1h;@gIi< zGGT&f8{1uU*Z3Euz!PBrnFwfr^=kj(Psk10u%LHZ zj_q&zTxz=l_}?u((&;JsOz3be>$mdEEl$Pk?A0s+1@_#m5l-UB*fA_BpE@DM0n=n~ zz%!Z=R8Trk^3@E*;IFu}Jf^=596%jJ2~L%CEzC+| zgOrW-?skaca`RcTtbYAGPfyN=klRLuG*&@z)_b#HYGNTb&ub==C8tOcONz*g)|-x6 z5{QwMqH@IzB*l*~{7wE|!ASrTPAE*`D8*5N#Z0~-XfoRYSCr#jOm{qi|2a@5?D_Hd}8x=aSnY6 zqUIEeaU9iqyHHhUpwT*_72eVS2M>k9S6wjkmW0@7yjKq+2)+t(6OtG@-s|hw0=Ud7Xi_znIF7>a& zMDiKB(J(1zkfm{~^{komiYMz%%)4`LoyFpLOBcMJUglWrdFy-R=dY`870gdua1Hyl zWPQ%h^W ze=JXLamI0zGT4+Nt)J$V*3FA=8W+P!=O8x+U~fsxX1;cr|4PHK5B=(UM-NT!kFpJR zx2?WpwCrJddTGGCgOd$H`bU&0YlD}H^MGVbg=$TO`Ip9F(Mr%u4anAWLM`Azs3Q?3 zd0~616U9Cz#Z=z9Fb;tsofY4yA|Q;_M-q(LtUCaeP(CKkP- zmNO@-^u;1)s2U|Eqqr!P1er=Xrb?E+l!H*_h~z*90rw}qNnP#h<9(YJ=S^*y3A73@{mjdL5fz8!L<~E{;TsABW@Lh#U!pc`Y+vySB;blVLrFHS1t^3v5&?6j$wAV=$h3R zONzHVkq9tsR57^rh>`rJ++hl)5|okE-l)0_)Fm{5sG%>XFDLt{a#-VQWN;BA z2|&=j+(TTjm2>wQ))7@F<>2dp;$~XhQ_mNvAs$47@^5vlZHq~ixmHX%2iguc4X*`W z@m@Or8|(;Dog>Q(`F^3A*9JLxNUn1aC0W_Q%^`*;) zXNw5>AQb^uP<>&0=qZiBrTQquVMyYUw{;}%ZCjltf0^ura-wBHvS|5~5*C}}bbDQt z2mat%@WlY%RLg!64ER-}cOk&{WUT|%<5vS{-Nd4r)b(ZmE~%|qa|uLSlVKd51%f6^ z7%-GTSRvjnF=A$TwgM}!lK1k?&_czh2|{pOQl6ByDFp9FaCP<uXBYBz`SoUWjM$WVr zzZ>i@^ZDY>vj(*^iWMkn=pNF-5zw6k8QPu9w$x=+WqIonL#16)92)1JpH~icXg=lI zP0eeB%5>hOPuyhK*X(S^g<;Fn8Jn=aw0>7JLjx&|)0AMU&Z9DkFb+hI6tPeNmYC83 z^1+MDS70NpxfBCJ2L#^OK>!r;<_}$mfEVw;6?LKukV!&{m@A3|@d$z!>W~a=L85A4 zmrIzzB`OjTa+e0#)#6#iB_b6>0JcX2)xj45H@vYV|87jMp+c5n34{Y)$<6EOX6d|z<(2I-gEHrrs5bkcD-pyO~_YB)M%@Ue=eXL1akm0=*3R$ya zyNK@26o85ZMJ`Z6K<`y;;NNFZ3?$kxBA8l%^@C8$0@uN>ZCk1@Uq1q3t(g3}C22j1 z+U!+e*X@;j?SHzWTc`vES5ds%?R6_SHDP)vISse;->7sPe>y6{p>&)Pna9!-v0Owf zNbOp(aiJhz{4?3#K9ykgOoa5UAuE%tK@dvVgdt zwIOXZ{Rdh|?tl}8q54Elb$lA&AaYko?v8`1dHPC#@2V!p`|vol<9o5~T{vgOX{W8j zq^Aa#K*gtwr_U8F%6?{W0r6(y0>^=FFUzwe5V$uIlx9P2a0Ct5!^FP~JAA5?$2FaQ0#s8cWJFZBF=bhiyh`1E4puGaUFW!uji zKRIxBIP5lgxnnMO9DL{JY8@mzi$lpSZ_%>X(!Iv<3)B)L=0*G-~ zv|PY|_yUG#cu>F?6OeCBHA!{KB~J|v`isd1`ckpNk|@sgQitScF9H8@z1_h7#qID3Kx*l9}8Jw|2wvGwL6!BL7AqaC$y8Dm$!} znaGU}VeX4Bw(u_^vu!~Z>zZ`lUj(9Nk{Y#^(_v^Y43*0MY-4 z=zujyNF8TGWR(CCO$V|UctHL<2r#Qn2!o4^MWCKGoyj*T6m#6BB znB-^w(_syd$q}Gu{}k$80{i^M`Xcc=6bpW=c1Z6l)HCcbf$s`Cp;xa8>ZtmvACdK# z6@8W}GJ?;CQ-}t5GY{94BcPCyE>1QB`qX1kP=hObL`&7x*AVnm>Y{wU@sQ=)U~5+5 z!(=p8m8cLfXWAfO&Z`A8e{&qxXI-dv7gaZd`Ud~_v}O$;F}mj)oq_FcR<}t6@)Rp( z3MgR88t%&h<^#w|z>HjErYE!^V6G~Mfw#H=Kd+IvmT6rpWdspRo>!yOw;J5+YWu5~ z=$8u!`K=uSz;Os7)GJZL{9mwPcLp(oleRe#4tOxKHM@T_6?A%41dveRSx_g@ufNHl9xm>N`rQ?sMNC z?HcR8Kd5uubujB$m%TI_$T?O7SPb>ZlP>cO6BDbKXzDgE1t$HX4qaXS2JfA9B?*F- zeY-j1hCT#+87}OdiHj8>iEcp623aPL+lAcDr-OlaDdhxK&jqsU^;IH{#fm(iPJueb zs*!QS66k0lRh(9L{8$&a$u#E{c-Ik}5bJ_&B#%dj&MCKwF@;Ix*6%1Pn;0B62*N~n zX>UZU2k+HokH85unRfd$*Y}vMZPCHGUXNWGAAo^Kn0h?IB6OekL8oIC_Z*ZPryjRy zGoPCLwf9E^eAfF|gvBhMd-jo4!f{-XDtT(BMU*Z0MJ7comu2MdHMwodKHG4c?!Hf1 z!}rK&@DGcA$e?MwA^0KzIw~>gh^igo_g?}e78Lvd9b1-;?-VgR2sNC`AP;)rk?^pn zA*DRpwYqyTURh4h@2tt!=2zF`w|-1JEd&`eT!YNg9k))LywWPWeixrH1|E`X21q{0 zWTYHYdF-Ib9d$Q&>qB=ZT%(J046rwu3F>cP4eCS5DHm{@QGF2n$eo!~y%Bm|bsQ*P zi1TIlJF{DXTB@t{!Gi_R8K0g|-iunoecL6*Fi8zGR1MDtF1E%nOHtGe@i{a*lO7?F8Z`dAK3)!QwpGt7Ia$q|8NL^Z;zD`@h249Gke+-J&lfEfe%8hVBva-`}*J6ilM?OQ|TNW6vmk#-3{!F>IizjPYH6t zZu?#d&HOXPIiRwgD=lvZ{Qv?au+XrPHBQpcS;N`lw&juzQ#S~UkUsbDXBrf{H+Gzh zkQzjYoDB|(uCFTyU%u@*uvtG-SSx`mG{}&V%HG>Wo9f;Lu$?s84vSOnxi~*J=?Vt~ zmta=X)I{fZWXn6s<=a42qXZN)1*+Rf&k$cTzGqWH1rU>RY=L!Z8~1GVYyycI{{*p2 zhujQMG7jt$hq=t)5|F<8s6zQ8ds$u}24W;yI{K&S{w4h~*H%!&AF>7z@1F9RESN@> zkVG3k^;%iQ{%(*?M?%QbM9w-Wjc0?L1AdK{Kn>%83J*M_U=J~M>sM`g3jPlC4cUwV zLltyk*qK#BBT8^M8kNUGXo{>U|A($OfotMw+s0?I41okl*b)UL2?P)V1Vu%q5-=zN zYEZx#yg7FV}S)-Ru#pX8tJ85stkJD-^ud)*dXL#WVz|iMeQ)kg}0S z$&t`rNoXeA)}h+E#(o?#HPFf>01I)QCDE*o{Yt9d_Nn{|u217@8K|5+1R0ORB8S zzInJ+?A#~=H0xoE+NyMqRj5~o!lCcTI8pr4{w%!&;hzV^hV}t^elQ~_B(0fu&mTg&N(wUf6CoS zXe3Xz8T;~E^a^R;KLzty7Uau^N2H0P-XTxVgwB!@vp!uOBS8fZmQnZy-k}Gy<2C86 zzb`A9e>vjcr{~#Io12NOOJ>R%cUwPx z9khA#(1$huys%syS2uLkPV{C^*!v#_a-aI%1`_%H796RbwE326{h0}c*WI=1YkgVv zEsZFDJ&=uUIw+I9a#~teLWD+?fwZ5ViY@TfW_(auG z{d?DsGx|;k=9D0Rede1{wB}Ls(^vP8nYW$VV0~Qs*$n`Aa-HYgd#N>rDX;}CN{@D8 zOvqRB+|V@Rm=G#*pI*>58=v8m-~OxY_~Vmt^{NXggRhWXwwf??4 z$7i9~oa`K5;;aus%sAOd8%aE}vE#3B1+>y8UP{L>EcF%~2}bR^0l>g5yt? z|J^mhqf)||ae|1!*F%HJLxT%~x@JG?IP%~^ba!-<>+b9pH*t)ifw_21G*i^jem*)~ zGn?a|qhKd@Flwf@fBkH~eW6Ed z^4{`$$&~}eBTD`K{$-K6Y8 zXUdT6NCdly5mnr5wO)Hg^1EgCNB?R=LAE6tCuBIVNc%FJ4ul~4RZ|XC+GalCPT(8% zTn%Ma+{wBfusG}6IbK`zzwAgE^8Jtz@n-PrO2n^CM7xQqN6mfk;cJXO{_9h378nRt ze8x2q-CmVd<G0G>*_ER~3Z@ilO zdL;M#ZSF2^N0l#M@-p8quDor3U-NLKRb-o6DY_0eY!zw)TAc220u+w0cVdvR`EWeJ zb#6G~MIZ{qEjxMd&V&mtY~+s*4--z*TD-GAKl(GI#Ru;L1cP2eZ;`dH03RXwd;u<| zQ_GEO=adHeu~$fs5J9Qxv~<6!FYnXV_QZrHRt4E_#Yanz^~~AZ1J8<)nt4_imOM@C z4(QisTdwv0;(qt?PIF|3jeA>;9EO;v-)5SA+1c~Ny$#TdR|C)r_W`N>usmJm-aLXF zF;2*-a>rB_Yr!k@2=cMCx38>$d)=H}yl;@5=UVnrYew4sDs%a=)htWl#1yIY&BM{O z6lh_&#;MWHF&1zxR_p8LkMk=?`woq{0shst?~Y7+2k{Zq2i0jO#~|n(w0uH&-?242 zF+|uyL7W~YTsUo+`e$+IDi!#^4uL|t22l{n<8IDORn*bDW0EL@d~5)-uV?U{2CbkJ zBSAQh`LGOjneW`U&o?~4SvJ=wrpeFM?*BOtOax$4OHw~)%C82eSuXeXUkZ+XyquXF zxIPE|CUd11x%8gP+yZl++wReKx{COravr`r+hq zwLj2y=O>=fjt#Dxw4>fT3lN~O(K>G?;}vw>6TJ~!x) z!BpMPINxzh+6)&Ic!$!n03a~Pmt}F6l2PO&djwZN99dHc_RVu{vt1y&>nAk$(PfHgf{-%@+^pc-hb8li)M_^l zh7+L}R<7rD*uiOp)HoSljc`x`f3>4U9nQ<$-Gj_`-z1f%;D_)g@lAD@dc4frMaeA}m_tL6)cINqi4cF;W)|l<`$*VRa?Rrp|Bc@TEkZC$; zKhFU&|6{3YqW^oU*WbykY+Ivbi9`M(5-~tQOl#f2W&YMxQP*4Vx_i-Ie-=7l+f^9< z{lD(VtQks;_nG5)-}cz;IVXPLybIYQGC0odZq7>#mue43PczPtR=Vzl;C6H(dt(_uT7aP3}jxz)31s^H)+dN|y<(0al=} zcgkq`+TqEV$$(kX(MY!_tG#(KiIu?@7VOE5rkF`e1l1;WuS5GQe#pNW5=6TifYQgH zw4KYcQJW;kf~60~a;f1_9xShXgCv6mfC>dFRZ^+9SXA?wf~0`3y6B9BAGg)qsN7ZP z{`oIoP1=68{)Dqn>1*A+RVVJR)c?clDu2k}wJ|*g^i!T4jw`=nL(Wwj3jAVcudVcW zQ$(EUH3kTrkZslZ?4qmw*3N_twl_H;T>eOu#VUyC8%Lt%mwQR=DR;K?->_q?1CYtJ zmjli2`P;xn+X+|02xmTJl)C22fcZk>Cv2V=@deb~SCsqdfS-BYpvgR~K)#!@K(~mE zP);NQ;r29iI)Ku`i{cPF1u}!8^p#{Xj_%LAExAQY1EeiQJx(K`@RBrB)P9sogl(if zR~L)X5Cw(jOqrrnI3IX`ZGCK>QlSY^8w6^#K*4;jW*gz9QATzv?mGXU#J|6N_|<}m z3u7LBJbl%1Pv0%4&jwb_9&I^S_(`B>{%O^kr++LvJuy0N1i;uyfG=3!4;3=9|F(N? zC*z~sI?9x$p%=|9pR}gsRUb$Xq2g9cZ|x6g<`n- zVEb}K@hv~=t>12Rj<*%FlOmT?KVP#oHN`UK^T{bAoKeJ@>Kb&9sf5Ka;fZ!SX)Ou4 z#^rV`>O>(OC;T}y61WT@JUxF_@btTh zXTIFI?BNfe|M}A|#!VaM*Dw3&z}FMMo3?1=+dubw@x`2g-@0Gkd;hV<-nIH{aA>IY zr$x^!9|zgV1D0U+L_+&kFrGV&c6zgOi-e9W|?U3?$KwRoY+|gCV zh2_?rP_&*s4;oa|=>YX9b($4m@`g)?b04*poix1s)PLP?&=GXgyKB?Lkl#34TO|^^ ztZ~HU+tBP(81#@Y$cn`+>Rpk6FD(3@9-O*a(!uae#p%R0Kf>ji6;hMc7jV z5XZNF`}{lY5x^QGR`FJ0TaOOA?Jvh(YZ~2o?r4+3y+U)bLQ}7Bw^q5M4QZ$PjnotV z={}QB-Ag|qthO#VVzgQCx}>UT%deZO4p()R{`ytOeO`!yM4hBK)y3)_gvcwl`#DF3 zI?Frc9jQgM+Sr;R-Dw|#B-lA&qEmgq_5i0E|Mnj_$Ou%8Xwonq5{4P4<=fx{;3M7S*=)035V7 z&Denlx#?ySU4?Mw;AUnca5Atl4pf9sFh(a6zL4xWyeOV))2 zwVVfOd^x!VeK+FMXMXzFc5HSuaHEuZFZd2JmWvAJUaw&uL(4DXB$`?d#Ya+~OFsuU z;3Rupss4Hoib}LI_ zwngK473$-k9zpKdrND;WN~DK#T+Ka`w6B7Tqrsrm)#4Da9btwV#^c8Ty z6x5XGxP65>RDs5`>}U)Pg&!lc)k({6Hjhpw-APp!C}c{2f(FVho3v(KGLKo%y55CZ z(4HLK>kq|VPBIA*R|K)60cvPUHOY$^7r1spjq)y_6Q`NkIkGYNsOfqPFC1W7)&W;$ zAxFxdiBUlDg0$~P(ciRwWa(dfeP)Pik?#Zb0jShitiLL(f~06Y7aY)3_%OXu=EiZP z%}8}B8&8gKx)~`jRbh@cv{^WjGa+i8^`Cx;53-+1$#AhqY}L4Pgo&6?!=BK}U!{}Y zCT`3fdmmuRRz{HobN)D9ji21nh_U^g1?&Ya>UiMQ!5eMWaD*xhm0+|^XEq$cs(JI6 z_4FU~%`f?rTc?@Fn11NrWi@SkuUz@b!n&B+(X(QR)^Py)kk)V}j6Agx?VbBEH&gXd zYRd6ut99Xj_zP5zTC^DoJ5M;sG_sDN?wN`qe56W-#K;i29;vqQ=xR5bR7$0&?bxB= znk!VQg~rAiVLS#RH{pCli%}2;pI}Gv`5LvF&fqclPf+cZ)#giSGc#hU!Zs>ws=_>y z9Ds6NH|Du|YyNd<>KeLXF(6$>maHSoG6z3S3P0avT(XfFC|qk;MCL#($$rI*^U=f6 zM*wWlp2CTAMUiiXtwF-xATEm%N%EFJlmyh5B*5$c#4g9Ect6xSloNz_P%}w+2bA4t zbbB46RGm3Om9ifa1PfCOfRTWmWsvs$+dB&U=ah=`zHddxkngj?)zOQJYN75OAn`!$ z6#+;%iuN*i=YM6@sy>}oD`OYYLYO2uVZh&d!)L*q_p|AmGNf8rJWaRQbS+nSn{gHN z_xg8S;bk!nw)&XTr?OT>mb?n7*6i9o)zPZa>xF*q(SL9?2Wz>nc)R%*(EtG|RVU4|f~lJehg@Ur6i9o(o_L+oi6^(^9@OX%{)uSP<6 zUu8y!u7_5XKd!J(hKZ%|#q9Z@wiVI2*e)oUjZyhG=k|UsSZ@U(nq##!JJ#QHG9+wu z6vgZa^6xXgaH=jk#SwKT+!CcVcpXU$C)^Wx5UE=oNmoOSo9y}0(UYyckYS9u#Esl{ zWD#$--oEr2IsaP6Os7D7;e72hG5sCo;^%VlGB&8+=p#pb)KDjdfnOG{a zUK({72Utf;biD?dtwAC+0;RHXTh#}pd{Szd6E5{WnW`@QaQNM|0E+t_X#M=1EhT46 zeXoI7{m%z$M8m8Pao&4WUB~X_{Nny{)+no?=7{B?=QS_K<2HSbFUlL1me-uwkuBfn z(d8doe(dCqvw{r2rffFG5o{aedRm$~Zp0R}vXAsJv?yCVHDP!v#U;9r-(>TqTltES zpbttN5guvf&~2)yv4-|lFvN^afiu8LLu=?*0?M^=Qh-^@s>{08uMXKzrqz3FL3(74V-PdhXc9m0bpxt~ow&>Z^M-G>{6 zG#JOtbuo|HuD&RmVn-xx`1Py)eEyHdlk*BagRS|7SEK3q{2P;hWaar8Zum`mmY|;*3ztP5^yGYbe8f@e1;ka>rAWvhDd ziZIY03?5!!R&tpzjdQ=7fhv!N6wTkmk}s8rQ4_%pKvqmYd7z#BT&Pjdn<7Zwy2m$> zJC`_EheG$aDl^Js{jz2|-0kw~d$K%;U08h2Y#x4hHTUw=T=PAri0i`z=6lzNb=DD) zBF#Vgc8`7_W6QMq<#PKT!XzsmOj`% zod)_FXU{N*1wTUBg~WlJDe7Eo^#4S+O{*{ZX5+TRHQy{mTG>eS0>rpv;-cBkH2GMF zqM{ejlw+f#!=e{ZkO>Sl9W~J<<0;Pc{>JgWOYzUXEMwI%;RLiu8~~soakj zWMs1ro5459bDygs?SDF8I|FHLDX2pTt0j(3OiTyr^S z?+XBbDM0zHOy?891|;^OIzB2ai%lIgq%|T7G3i;echY=$K(R zBCRjm@G>yuv(7!BwfK9*9tQQ?SqUNcx8@#l@ozqSaE76LR4HOIB)1qsA8`_I@nvCS zlV*adHegy;

-|>gL+heXQ zo1R?0rzz+ALx+65!-^^y7Zz6zb{a%S9yBbjoU&%ga}YXR9JovBRutW(3ap%xVb1tY zA+hD|IJFkE5wv-VZZB^sT;y&uUpogD+(bSK{_M(58HrQ%HJ3L-?5)h#p~(j!$yU_9 zl=%&_;F15K)2hEuKU{Jq>F>IS$IJxnqVeMr8`L`0tIF@z6Rc*Ci zNb3IM;Kz9%{;)3Ge5+5@VqEKPlRL)dRQI2c4DAs|gKKGA*3tQg2r=d0w2a_RM$o}& zE5!Tr2&tbCF(A%v)bQ3v`kQu9xr8@^CS9(H8`{jDTl#azS2o{_U;B2|!Et+geww5D zY3k_hf^N9|G3KkUjvr5cz5I`qUpAjqUHbUV(=}_>Ec*Mz#VqTZv44Jjd1GBt^=C_7 zpBX6TM*SMp=8hH;UhFh$Ut?Wwk{#`s-)WJqH@3VfD0aC2h0{Ix-1QZs)=w$;k@2mx zY*+&A8GuB`K(VEs5yU_KAJ3Za^y=#7*zBde-NR|@V?hxP^U9B%3bZ!KbH^T>JNv6* zitXv@euiM}&i&_hup^!i3D8CBaV96<$4-x(O}ds=G!sGBtbDX+L&n-2^?&SdAYCV? zW6eCB@kMSiC%bE{Q6U^z63Es)QojCE`Tf2PI-lrnQTdal!r}Dd4Ec9rxA6g$#kobC z*<9gsEm5I4;S$+BcIiOl#-!Qnt7xT#jS+T!FN*4urnF5IWz@bLx;Hj<*Lv}IU6V7X zC)Fg)2_bzfxb^uhL5e_m+(}A1I`Z!pk@kH&T8NFHOV1Zh+-pC~I=h-RoK^G^6jT)9vrJ=Q2*0RNld`94z+kDt164{v2(%b!(5&p{nVZn%YNA1knqpS zwa03Lrsz%ug725Rp)kBc)D^*FyXpFMcyxB|K93Qf-sBDBFp+-n zf45WZ7iFgye7dvkNJCu79d8d8aYPX>?0#$AhV5ywT?w}b6wW*Rr?eZ_9cw&Th|6NO zs<+>9wE1^ahwR+6LJ(J6NadRl$Uh_fz}+^RF4>N2#Cl~ zNll>BF$j9)Pz{*R(2R!dKmyO>uYnDYns71Uc za=jY|kb(f8rndoTI}8*WnL2=FuXo{O39s_j1{ndO23WOC>ed!z8=2zrUo(mEXjNJr@e+K?!$4QC?yQ$AL7G`LqB?6Vn1{8xZ+SNhW0j zR^}^N%{WR+{TQII#AYD{)gaCJh=B;=P*jjvhID8@v}6`! zg$OmY$?<};SY>?!>0c5L+X+Z^2{T-nldjgVWtasbLLpts4GFjsc^IFu^0K_yA z^F}y}qXMS07Uv3=GxiM&mtSlZSwdD=;TN8<_7`=72-j_0bd~#?SfphIBse4}X9c4; z;%CqxQ!$G9sS&o#>J^E$sICAs1lpG})J*8RW;SNTf#3lGQwS(LPNO2>2&An6AqHS& z49z4rCNWb0U=E>25y?=8r3V;k%ROTb4tnA-5E!*-d|-UZrV01NqqDUk|T6$co#1ZD>vOM- zijdG#+Ixm2fC{98M8I`xW!d=-?K&}q#9u-GJwf@Ep0eqN=$wE+zCV3PI3+njOL5h@ zYPaGCZ?xnZ?Qp|Y1ucLcKm&}VmQuy{t%Mx=ar`*G-HwsOQ21(!{gInb$B0BVDAv}C9Af`@t8l8^KWs9}BpdK5LfhlJ9F#`$98k+y6c zZ=5bwF|HDguk(rzvB`OS_8IGMy#i3mE?Nn(7VAbr@k!J?Jd~R9!cH2B8w&8_T9s8y zaf5bjj zCm8WtfAFybt2RH!!LV>L-t|Zg{HCRYbQOF7`M61+L89&XG58Medgheh1A*E7lRl%| z5gUVm~?Ey3~RrVmpgSO_&*psoFtgl4NyRXf&+C)CJ@%wq7%@k$ED4Uii z&s%oI>4%e#iB%A*k3@AQ>zw10>n*C{s-)Se+3b9aH$}jRa*J?$);}EK_&7L7w0^Nu zorE_!lr?Ji#DRc;0N`<^F%5dYty^ScLB_kt%({Us&)8{&!1DY(t!-1-SY;94{WHhO za8|Yop2Nv0+)JhKN3+TGQ)GX8+I^}oRE5**e@D*!R^qp{MVC7#!Xl&Ep51X4Dj98v z?V{dHZ(*V+s)1L_qEpmU)$fQ_9Ag!M%8|M?)5claA?GrrIFG5(%Ec5Dk0oH0h2GXl zyN%nZ6qABytK|dX1xh2%kpWHy9{S?Wqe=FqLt7pA&)rq$LpN)GaLkD2U`I)8`vc(>bo;wG5msyaX^{#% z=>QZDV`_ADbN(ky{lBJYzB_?DbAI;sHtu}q;yuO95Ge)1&LRL_5me_a22 z;k$@g1GOElbbGqZUHL~>_tGQp%)h^Wd;jg=dwGDBULG)8Lg>R~?Ek>IDy>w?53fVV0RSBq>WO=&j_he(S| z-ax$p`?im!(1yW(t_uF$?o<40wRSuu$67XIfB5#Z^z+IOroXFvw*NXvAs;^#Eb7|o z_oV{^z4b_e=_ZEdo9`a-rh5B2v?fhhBsG>ZXFIjH1z1n^5DbM zmxWEif9kmRrh2L>e6f2&i;E2+Z0EXnXfDM9v1oCi8#AQYy+a!y?9e6_(6}1*I=Fs4 z;dh`>cue@8DCjixB>_b!l|^O;8hi^F8gQL8KqA5rDR4Ub^)5Q8f#*sw5Pk}Dh7ImI z{bu4@dxMK)7CN7>t3>22kH>&y&IyXz58p-E&M^7oC6w(H%>~?V>deoFl{SWIboWDv z#E>X-q1qKlTWjWGE_V4Sh%oCSC3-#!pzNXB$Dztk7ZgZU8#)pK%tiUGI}~YE6}@Xf zqRHp5&ymlaY64ht4wWp|C6dBmxFiT_fKU#E9u$=%Qg%=h-J%^M02QncH@?;AD9D=L zTA3G9z!U-)xh5kyT+;^|ZJnI4R9Z_PIX?d-x<>n*^sl^+3fjr>8IZ&^8L7|B&s#f{ zgCGG_6>eF~Ud)pT$I2TJ9EiCF>NGk<0B)2;762#Glq#>4_+hSgR1OJDv|2TVIb(pY z2ZR;cu=@V1*Avb`mcQcbibCG{yNe&;({9{?7m>9mDlj_60K?}EBfn8LmSdC4bNshj zZa7GvGV-tR99uyR_Z4gN?tIj0A^^k9vB`(@=SJaF>X>|BDuVV0#|9@YGT4 zY5$X*rW-R~_&1(*)vtMFkI?1GooiYWM>i%#1MBk)DFVGhP5A|pECQ0L9*D8bvz+y= zWYMrUpna*nFd`wJjEvrP6cvj18= z(>}#VMN^L1_d$MAG8{YL-(EK-?eq4tPR#?MPEBh=5^#VM5+1)KqxsZr_c`(HxSWS} z?Q%o##&8}L4J)(ZIcqgqB4z*kGq!t_;Z>oUdbJT40aJ1lKcs)RDg0!cx~b{HtVa#w zOIL`S7IGGu-VBK=MlwovmNkqE_~@hR8=N_J#9@8rh8>ZL@^blx2FTKNG>t1er1L!T zqA(_kF`NulfWm=cRax~wfKy#C5)u{~qZ*iaO)O+!sF%yKc9-7Yim!%*b+D5FEY2iA zhoK1>^}6@-0e{G2=Ny3X*lM9trOAQL3$f+M+u`K;Ezg|We-!TaZ~s=57eY@CtjdjS zWwukLdY%u3zs)ZYQJ#XUTg|h55Z8_96GJ3YwloCiNZ!Rw)82LHx;fQG-Lm*|_72&e zQ8T~l44~3JDIVw>FL%>-?-=;Ox$K1#Bs;pYrrcAcv1-I|eS&daS>qtN7)`{M*RL!e zxaF=n(?wQOdJ`+%a-Mh$iQ~$TH;ZUJaplL1GKPBx_3)H(#lmvPI+h=+ZE7==woFjB zWbiGSP0=?Za${l1gQ&7&-a0{M5h9T+=1nTIIS+lOjZ9COf_NdMeP4~)Oy-}@9~uAH z-U9ha2Svq>8nozc@nC=`w{p;Ja{Av9*R6XZpVFpnTXXz9N;%m*h7z^z!pcq|FSo)V};)@auu&R)@0 z{qF4^-_qyj4!sY#{k3q`+|JaYoC@xgw}su;=dpW}o5OQo+rG4ke0#F6AETuN%^N#C zcVy#hu7frLsRuy@wp1_?iVH{IKNQ5wcPS8f3z)1#KZzedQt1_DTL+EnARW9y86>rJ zNZ+g(KUn+Uf~D>>@V1(tw{KQt5cK%t0MJs$c1A7$Yk`^Rw5Acc)`d zbUyn2T)Gp#R(p4IP)ARDNhCe`jU@a6pwH#=3-3~&fOdo1fa>#O@-Tuz=oN6|6)t7YfR984yyGubSE1@VP`th=M z*OKJ<>7Z4TKu^ul2gTZyF)kxk_FK!=W@vdoc#rD}tAwO|he2KLPbB4meri1US7*<# z-vyl*EBE1Ih-xH+^14K!tuN;0Tq-Ik72bV@&WnXRG?9`j0*-`Sn zBHNTfH&B)@9mvivI`XnJW;iNmIJT%|97A0!GrruFF;~_U6j$DmG^HWMzNAvrwf={q z&Xv_|Cyoxw`}2mZ=>G`eS?PrJf6fk9gcHq zSOAFl^1f3+v9)@>`*h6%2m8entlp9ss>=HZ`9^kz=*C#v5;pe3rg=99vFo}JlWXMJ zhSd|2-hZ4w+q+@0@0~mHnSJ-x9j%_8b7h(5OPjZkFDZ$E-n%#Z-VJ2*nJ4T!QF!ZX z@$K%k=iyVkUpjW%+6_9AtbvtH zSqkCvnw#j$mYLqbLg>m~FcLLfF_=W!w|UfN;QuZ?Um5(s0YT2L7RRcF0wAY*kgAJy zP)=oyPZ_*fGY5VCBeCab>rEuGr>~cRiE(?fERV3*(w!HHEFY8=uCHX+^?+ z+LL>mA1;|S*LC^O)CcqSU3=6)x3{->e0g(uQSqC3)#1y7%FpRe{kbemRyub2hAE$g zEBnkFf@4;{Jvn#YZHLv*(uUgJp1k#P)!UQD@0r_*rjJll8>))j#XF z-#j>Wn@FFb=SDN4DcvpWj%y!fEfuS})>Y=76RU<+S-aDDqG)X(l`p51H3W~0l@Ask z7@N7Zt*fva{EzVPU&kM1&=RU`kkTp!Iz_FY=t2LsNn6YCYc1iswA9&_G1M}U7)4(z z#!?X(QRT>TjM~_}I!8MAiY?b-wvc*M*dpz2RD^*@C?WJam~O@mSZspY4Gbb~OeFqx zeNhPJcNz46pm%N#s^7@3ZRcq+yxc-^Ba_pvc?W$P)bV&3vsOD$Oq40oN@BF2HdWks znO9#tP!u9_0~7)W(^S~BY)akAQnZ8k71{0AZ;VB~!{DecY5JfLpf8QUIB~hSM$bPH z^vuN;i59y_(Np~nF(c4_AqPUvzk&;7|9z;Np7Ck^1>i(m_i(tC3No&t&W;kVh1>nD zy-A|9)y^>hc<^bg*s*BG>FNcqi%u3J$jLmMMR|dks#eb}^|8TOC-)AfRr#nJ(tLzz zRZMiY!iBE(AQW~iyBV_XamV75TT2KfqE{<|5ZkQEqT289cRw-JVHG>%sR;of_z)49 zz2l@HCdx(SsgJaPQta<}+;NPk7)9PlP_nLU_eezPfF-Z^a8$nx6U4+cbw&=WO8dkG zuq7^ zay#g0eSL1^;5efuPlL;2C2q_KTl9NDq11_BLjCxfn>6_O3F$eSafM_BXtl!fMEQOv zMqQYjy1VfNZ#;Dzl$m{6Nj1tZ>27=ND16T3?PL(X}H|?yO04uC4R8 z8%SJ4>kWvGIUSG}0AeusMCEDMlB67a23=m)0ef|z$WmLUHx^lvy`yD`(Kz1)%NhmA zN@FqDH=|X!g!U)0^}BY@1)3h@!f&`$Z#;t%ygIv1NuEE5UEnx^ka`|;#hIeUH9e#p zghqjykcTyF#u}3zafcx}1FdcZ-Ze-903~y~I}kJ5?5+JYSgp;}6F%xU7!>>U4wh8y zZ`;XrCOmk!K2meCW(KUaz?1MbIsi8<`ti~0Q95Roe5jz{%YY*dhmJJY-T@Bmu%u^M z9xP3=uw$iv!7A6>q|{ATD=b?Bw|>0>ohbnxs4faXNNe4pMr28a)x{*KCVYl8gbSrI zA?^P;{!*{1lO<0rsg0?%Zc9?$G3V@MzO$2_x04>Tdk0b`Vx)d`F(RhZ&Y{Y_Nt;e= zK$Q|mB%-UgXYwGEXp*~83U;fFyb86EO}{Bt6F;-gv;IO9tWh&5gm_5?lcb-<63Zqn z$Y9Zr9}g6)j+-;0$peuGP)7ooHfmy%eDtt0MKwt*WcykB+{<6DLz{6eScvUiLYZE3-aV7ko5$8ZLwON#9|_Whgdkb;72H2gi5R< z+~vTe2avu8Z1dKQaF=j+AfqNR-TEs6(#!yz0t}UzJ;A~oMXbU*f}sQv0ptXYCPGuq zTBR7}C0LM;UZPUNStiK@;#NL#o80avw_DV!bX$hK8XY&AcJ+VuPs>5;KPoJ%U_YU;G9J%<9K)VJz#!dWY>f}fejtD?cRTdsf*hv4qlSAgDX^{z3ZrO-h z4=HFjWHE*;!R*v@1Hn{E#vq$8$WBvh$uzZ=1^Mb|L+TI0(Q6+sQ3RI+GULTeCJKRi zMZs|#h~aMriLnamu+hd10LSD-)&r?Z{^Helg72^T2+8NvNsu#?2j2;}6F5l)I+Y;x zGRZmpBV|wOqy>F4U0%gTadTDt)V(QwXBrR*pG{)Ug#(iL z({Za?v_xqM4|*7IyPm>ds#~$N;|Q@z@sunkfhif9Ll~AbYIt1SZEKzI*MW3#$oGXI zjUPg;z&$JE$~v*V^||IsUDjaz<>5Nmdy>BOUD9MXY_Pt$o2vTQ3`MAtH*jRo;mC0> zA4RlOgOMkIkw>2XH`W#ax2!;OxF0L<*ZtM;mg2x+X0igW-fk6qp~Nrc3f+2Sfd+i3 zH1!Mm1~*S$H*&^wnTAwq8ngK`{?!_*16jStq5Z<}G!wnpJJ;sYP6w!+#L z7KsDg`}Lt__G{#p+QwzrNj&y0|u<;gHy^9^mp>f=D zh2oa&h)~=pSqz<>tPsK*pg!nZhw46%_C1Zc%2>!x9K@HQMqAWKDx0C@b3pHep$=$K z1Hu|aQ)Y6I5+TYp5eySS7qIDkg1jTdB;+xRs>F$F&T=_yOqRkUKe3R747YlmoT^9WdBS zF(eXBC&0!r8&r+puu>j|AOwQwsqih;4lG&}|1gJ^%VYHGeK1>+aL9Xwq{!X@hCiF}GhvI1cZ1Grp$=HjQUbPr6P3yHpoX zn4KIdF)KB;V%?6UQHL{{QR0$aZA>A;b$qq8Vx+Cr~lOStmtdn}fha z2*NgtFysqJzOU0sUBKR-E(IYKDFH?sM&Rxg9Gjm#*MMgsYcaGDy1RWV-yO}z(5vJw zK?qT`J54$XL!B_1bO}Gz_0*Rbi#^ZGw%77yI_V^adL0#cOa*dfkBo|^#z<;qXQ}>+ zFjtEthPNB#pd5)_t>49Bvz*{ZhV5iWd$#&Df6|1e_(rOu_(~kE3q2#`0qYeT7OX=g zE~;4sZjAks(Tx)`&w#vW zxc#F?2FY?nnu$d5X{=zBVx-b4nx9^rRpEWX>3X=gThA-TspSq z*Xo~)YSgu=ItP5w&YAC_3dBx6YH6|=*g#_Z2DzjgIHuk24{msJ+m?>o?!aR06WD!1 z5(@LL4a*Z6VZ{tPgu3zOl#r$lIw;fXg_D(j9=yz~^(06V*47NUGV|8QwtWgEdtlGz9ZcN*@Yw+n?hv6C@@T)K7Arl)^YT|M$A?_b3=o5#2m^wooCz-s>~y4sfx>jOm@ z%~tDfy>aa zMD|F+MbqwWU0pVAlBzpa69dx!(Ez5Pm~{$}XpF9Rg)j$Vox14U=z2GJzyi{X#d$&l z-<6Z#qQA&Va5HaeT(%a33DvjFs(w(K$}DO%6H+m6CTBg_f7=C{q5iw{p`QQGPUyei z;NUhFG3uh*6+tf1AzV%>+)3<6f*W8183vgfi>daKpywht<=X?=b`vZS#fpHmJ%lBo zna2DEqZ7O(+~gpm=c<-3tk$RGg!~HTWEtE8Ye$XB?15e$XdFNO!2XGW-skMq%Oxou zZ|yaSlI3)(WVwC0@eynd62X-yjLOm$8#4z6IC%(-Z)mZxg40Rfpx^dSeuAUgZaCN$ zs^iO#TCZjps~>a5+zELytz~a9yoj;u)BO8sA<)wvh{w#bW`N2T?<*ZHfNP~p=UZGE z(HUt`2^Ok}JE7LcM7u=Hr)9e)wwfk9{w__zphy{aLTPpyN>}xVu1cgyBcLFOr`FUC z?mV-q$&MXScGSA_?mtk_^@O}*o@XEbV_E(X1F*c7fC42_hD1D!^J3!bm1!)yl{ABtO%Ywy-s8-CV$FtLtP;$!y63+y9}>qxGS4rz zD{TRg92J!!@0#GQb=QU~u1AQbkG>Gsab1+1SkZS8vg;c%>MD?k%))Xmr^zl&3UqOpqY~?187pvCy$R1prnYq>)EM z_P|P)k|J8V>DqUaKam9_iI;{fK%_KDF;;G*!_Evu4vA>iTSN|Mt8|KTmgF{VL!Osy z=t0T`mdQurOG$^9za`%?lYA##j0D3fJAS10C{0>TrDy;WMGgj_%t|H!lx+n+!kQMa zMu@^4gUEf;u;ekQC>cSh_8V9$V-nJ8BA29Og#)Ip*+lqoC?;IWA8jlJwN8u7R--}G ztrA$#StnHk7dIaPK|q}hCTiAPI^(&z(-b2i`>$1W4SNCocdze3Q5oQ`^vo9ZazIk$ zBSt_`fjA^x4_@rzWD)_HI z9Mp$Su#+5D-SmBs`ULfX^??+Kmf%)#DK+XCnj7L>M`0qh_}?g99W43M2CDoI#y@>1 z4G)#GPIpmhW3~J;i4@#hDxJW~zzcQ4f@t6yOGxLqmCuu*{cqbynJA=zKM3ODy6`)1 z3-@3Mj1VD>=>r&V+m#A&b}ipYFaqu8HgIcd`uw86aUDWy!)K3c`RW zR4pVJTv4ea1{athDz;Q{$Ci>62#bOXmb#*~xS_`7SFuu=ghfInQ~X?_ycA2lr(Oif zJpqB`3Au(<*N{*p!a%Y>$ZnIS*sakG*+g+_K~)_fueE;K3?P48?Ywmj8K-0&q$lqL zDGttL4wXn95>^N`%}QPvo(bNz!`H4Fe>O40`xc%#JQ_Ci^w)e1LRx4oN*8(2o6(Fg z6(OuIKxGAtZd?J(^NAqp%Rj}~{@hJqU9)d?;*CHc?dzxjJ6dqahDD%C4c$5gR77nR z6+=K-W*ivC*w1#*f_$W6Dj5U5!%DKRBMq`ov8_Y+x@Qrg>_^CZV>`o+hnUdMV}?2< zFm9^|JcDP}C<#9R)lQTGST~s&Xo{%-oDIlMAxVxCo@brrMk9G6b=2)h3h%247!U=-q}qk%&U;X7G7KoGLD+8H7b!p*r<@63ma_a z-ll_CQ|Yz>B(Gbh61o0%7aomn3vg^a}#972O;TsCq;lgO?g zlV=$}NZ6NX>(FGWBmHiCUyZrWT!*IWMa-|g#c zIk+{r`6TE8_E5|afx$|U6rr5IWwD3G=cqa8eWYxCci3_5Q2#~I1AfZ^)(nm!C|JoR z8737Q-$sMZv;NS_1rGq%22X&N9jl^4F1RNJw)QEDq>UI3XZ4$@U|Y<<9-AOxrAHbq z;tUsz^Iy93EI)(XA1m>3z{Mt<-<3aVKq*<>UfnrwU|#=O&&9Av8kp~?993R0 zworiq%(u+VC@dyo5iuq5_Rj}Biy_&?K>s?=+)=jUptZ=EpWl)#Jqe4kb@NtdF@h!5MD`#E zfr1eAe*e0M>sF;_tyCRHU&^SMcD}fa=VEfu;3P~8>skg9-szbw>C3ya;ebkxLt%ke zaK=-w@yo?0GxIkr!SEy}6V8GbqSmy^zjR*uvS^Wi*(jZ)-U#ihaEL06E%JsACZ)(8H6w#hfLOq^~G{pq`<+Q}Fl zkj;5|vbqAJmmT&f&SuGF9^f8u(3IeTG=;$1*l?q(_y!qwjGa zfW9)IxFJ^0j|v2tmGp^F7a1yDcrLC!odXPH>sL4Cff%U2c7D~_fxrQOpg-pcXM-qX z|7ia`v4VLW0;IKcE%{aZKn|P`%)nQ+Fgh>*dTDVS>zH40${$E^pg%K-RZNH9##O)w zP=VynVW+e|M!GeCU)~?rxj=f_&r##3@zDyD)#a9B#dHR3jH};MR{v2?SO1gE4&B3z zfs69{Tk0FLY!;6I9lq86Y~PG6LSgXqg(nM0y~UgxRKg}$?WGp0Ku^5^;HIWD@0b}hoR@Acu?sQoQi|Bg|3T9H%l;5v`Vz^gaL zZM(N)+ucoJfi=654)QnmE?!;pd&@U>{Ev-G`zxuR>~oghoBHT^qpu%6P_e$E6>?`sbdh0heu1vFor8rh)fs$DmsDC}!o7YF9o&L$c!IxYsjRpSa&KMb^AJLoZaG@$`pJD1??vWOx1 zgF!q+HU*lD0YD9TUytNrns&P}poGL=5)xU&OsWlK6$pJ^dXMf6(g5#L0YcSigq2Jt z0;n9Vo=rH202$2%;|X?eyp`NBc`;{skg9&np6~2>0&yjiOOLuh1cLk)pTyDr*MCh=lHmS~RBx|<_;Q^H&F^vcL={fEkd6TAZsyH8)>QS-1n*X)E+Ji}5 zk8Vv+;Bt+oQHb~7veilW=hEVty1T}?IW>`n&;sRJz$^nHIZX}AC@%dGh zliSN<^3P-T9wFP`p&-kP3yLY70rJ7%-mC~F#0P}n?5twep-jqGAY0P9k+o$o7ym(K zzK@4Ia5*c`36&e2elhJp9f)5R)Bc;D*QlUbHIk)>*t4!F9p+(1gcSUYgMgHuB4mxn zS*c(qwJl&aOsFqdm0#v*zr(kFn7@gd6`wr&1-&nO1hHp?x}j=ND%EpULIGg}wNuF7 zQs!W(^xv5)(ph#@swLcBm5lGX2xInkb4{yak8p(9z}x|4aKkjSPbo2r1Ge2@u-R@l zSXC2jK5PDmqzAI`?ZAZqZv(zhA2=Ky0DV^;$k#(qlhk|}V0k$duP^!b7n)d1C=@ZR^Ikn%7D(ty87x&Q>wD15N&hCLC3^n@k zCmhbO6@roD1ko;_CTw5I6o)|J8Yjnjq2@+aTWmXMvoBs%eg_2FD?m((CcyS0_B{WD zlPLwl!H~lSV~Il0qy^ItSXJq9AS3y7c!O$LaZzaC0%B!g9=L-59(f$ozBeTOTqb@t zhRu*1L7;s-`6ra8xv45KXB7p;Dd$!Cd0*Q zOSs68CGNrr88BevdQ9Sj4dZGZ!#xzZ?JG_s#jR(!W&Lr+W`stT1PBpi;tO9Z4Vg&x zBu{`DA|UAt3nh;ASH!9wduj7Su4V%1jy#>v8;9(_h6t!&?j+<;BIgMUSx1GscgT%} z6#fz8Z30kh#iDt#eefnu1>V zqz3^lMfb5YXtWDlJp&EQ>Ze%>d@krCovTQ`=MMAlaZFN7FVU!ZZMGef)B$H? z+rNKK(YZ3`;ij%5k52e!dmzKGV0I=>6NrvRTZ&am@zLJkmSwCaRKQ_na!60)W2EnJ zJeHZ6I3CBvQNq=01iGkHe2$Ow;Lu0Te+q6`|KTG?Mz)I{{+`MGe%wC+S&iJgu5ue{@^-^q;HFGP>9IcMdO8Q z-Q<$=yRSt6zDsqymgH{)6YZW1gCs#v-Xs63UO#Af6dOW{S>C_>Ync95$)7cA!6@hZ zJ#RV6BH$nf{_2^ArRC~HW8ZW;dh?dHd(zy1@s?Mv6KhO2J%4N7c|>s)k-O_92M*nc zZ?K8`#5v(zK=qHf_D4ja_rPBeRlMaWmlTLwAji0MuxxN;-TgGrX9mf{a$tI2N?YQp z-&20MKOmM$?=1_MQfK*8RB3xvtTrP+^ahEux_+d-^>w}Okz{H{IS7TsuC>OL2KPMO z1@NU+{QaW-N3a-pAy@^k4c`<|d)=d`!8B;$a5ZicUBF_C=P>Pieb^a>pK`8ZOpGVw zCs*M2;#1UDR!2wh^R^j|j(l`(XT+|yZC-ER+;F6)@6mYwk3N1sQ2ly)L;0M%f+d=_ zmq!&w`IEKd$6Y-%W-tnOpRwa;-s2Q!yVH3jJ~|>{E^vOQt;wAM(rYV5O)pCY7^2`p z%#4~#GaNtoIA;``+IH{5D`~lJnAcsA?|PBB^a_9^!AaGa7P>~c^GezD`WJZ-3CgwZ zYZgXa0cnweEY1nd&DF}(h^jSyYhPr)VYrl5x>GWZDPEk4NZoemLez}183pGWym!oe zWcuM)v^vd%F@2K(+)QybXr}B$MqH)EE$r!ZxQQi#fl1>&G#YVX0Fo<*E~(I7z9I9@ zca0hTtV}wbT}rpI@pM^xFB=J`#r`;7XEbN=;a;2h8NYXezV*SE!!}E}nH_EHj8K?? z!`U;CMU)Sd4)dV|EJ&B=+15DBsIBr5`r3p@zQTzMP;rJdjEO7|GeK+xRdYSpB29%q ztjb)|?y{N%#zw_dg)F^<*e{2T5OcC(wndr-zs1!KQU<>YLQI4+|I!gChPoRRW?hLw zrpS;+3Ui@=%c?oZ%)hRvpHM6EUc=S!7+#A2_=8PrO(5}(vSns*Jtda9I}}0}$|gZ) z-JP2zZX|tRUp-{?WQ16N!>tq{*C1yJra-BbopdT=z9P-N`@pkWeUjEdEQqDDUZ zEf8nfb?dTFr;K3C8v^!pki2R-huwzblig*p6nM~SNHV~8jN2;~S%%-F`7F)3bNjSQ z4*}%m7T~i$;Vc>AC1#R!f_D?l-{7>C_A^XMo{R@B^XMw(q4GO`U!!2zYwsw7Je9f9 zYyvH*I6d`TPdT8*WfL0VWq9DW2Gg`B6n&t5Y4LEBiLXkey}U!>_T%A9H3+g>CU&{y zXG9W1JcfxaH!#;z-)=7w4Y54d9{@O82$NNFf$A1;%MZXCmB98A#sqpC(gpr_Nor87 z8YDRCX!Hf0w0lCFys|F|j5k_ag4zdLQfhO@JLp3euy{T!v|I!TdOdNvz%b)u6avLVgd$E8mS~0~C85UKz7EBep~GW0v0i zo)i+J(4xy8mMHE=e=KY9Vnh_Rk2Qhq*ARK5fRb-g3Uaf#qCc}RsYwh73tcqZd(gxc zHE2;K8#8f-0m?kL1Dt2!_b*F`UqW2J zKZRpJ;|FWx6 zTxP>x;KB?&d%xN+rFf=+BW}h4RrMSN{?Zn!@wrR8W#D_LAX4TB|6T7L+ss;XlX<;Y zynwCjc7UyYz^<94RsU$(9lAT!sa?U~UzZ+|nMksc(B)iRc!$W)^xy5f z!gnbffO>P6zaFRVi#b z+=nrg$wV@ZKLJ|uBDTdoPM{W>@1YYeZ**BZ9KsOV{NQ!F6&BvmRPgmN{sP-TJI;xb zPOu3dSi(MoP2sCRnZIYgVb7Vb8@A;!=uhfo-0>>c<>%WD{&3m07NoU0=cHY>|0*!O zpREtK14GvhvB@%>HHpEoQP+5ST!h?y;;H1$Nt+QtW(_9=%M+eUIF21SMjLOJD_@E7 zThzvWNYnfw>dC>sT29%{60mzUwzIbMzG#Kub2_G3GOB^jr z99xXD>mJ3`?24V0R&rrr4Sxsd?JQDD5_~IOg&-HoGo?RNWSrd;PxH(nY$*qDqjtgJ}6-Ec4+P zaMma>k_s1aTxO{ER2o$kV9!B%n0_zF!;IYxuSc7*sQ~);&uR+K{4il5ETmEFIGuW zEJX+@CASBdF%lj>30b8g?pDB_p;(kcK1cxCfd^p9F$S@wlhEn8kcDc;jHFR-r}O|) z!;CTbRwn;k;2N<7y^7@hzGK8ho&o&nI>{|!4nXyAIhH!~&N?X{4(Q+Bda>Q8A3D6D zN8?Zoh6|}!7jDGm#wb8P%C5iD4zN0?D5TJq7|bLU782>l-eeh+P+yn!62j#i?40yV z95Z=_`J`#CQf_v_`xy??7!2VC*>g;LU&w(}6u{;57hyCuC6Ah*)kBAVs@Mqq6!n5Z z7dkIoQ@_AO0K@MCcP|ip=l}>OY(9u@^m;lhRV{{fB|ZYC;&#I{21DYqL^euS{0F#Q z^hUO6%5T0fGkZf}5ZR)q^%%X#RS8UlYpN= zX&w_LGiI(;ZXB^dt;dj|O>91hqD}aNKFFV$bmws*n>fW3>L8JNBa5WiCXy%fgS?Sk zfVC=EG9z0n;K?;Yt}M(6Fj;~H%E9m_AlU$Wo{G{d&KsOK_p(;Ak}Ib?Ymk-HO0-)j zX{CyUkwS4%wlFf1bF$dGt@&fl$^3kHYG{itO%j>JSryIvtuYx!+#f4{SRxQ|NSs$7rlt!!HNLjWT!x!G|8zg3|OEB zK7dvev8Q*KS#O6-^96Qa6eOE(Ex9(GqVQ%Qr9d*8gOH@!?bjKdUnkt)o<(JW9MHW*JTf#-pm>B-F}H6u5(?G9~7Ns_l9CW8`!ZhaOpn+c?`?eIO=Znz_z&xVy!lMz}zR4FK z>2lo)xu$P=UUdJP-6MAH`256CjWTffy80u8U`OYBylFNC(RQ-HlgSaV| z&F8G68+tm4)9d-C2<@>+^Hx_!>Q*o!D&3L8Hbm;4!!8Wtkmrn7*SO7@Nh~ z`o=p$AF;ks)$dW8XRGFY3Hh=rLiZE2ufi3a#s>W4?UM0XyxYf49gj*k6d5`YQ)psD z^b4_QF7^dWTY`){^0iORBjo)7xEeGy|g?x(9)8ZHRS7Ha2wLo_;WkXmR(|`>Co^LZ>#kD;GYR zi7dj?RMq`iMALM`k@%^bY}Fk9fkeLA0V4I7R_~8oID$q96MdBpQ>X6Ynak$_=w@?Ogro34fXm$mEOC2 z-GvGf6omkD3geo2eLIsaw_+uroeF7mwXMPup$THZbO+YA55iiiP~}kRapW>`?J}Z1 zupVqIpf^KSW4*njxT6^Ss~JfK3v|=+J1!S8$qcn6q!4)DB%KOSZISwmvhd$vF*MGawsDvj;7^-Vb2^0Plv1$%SXpD}>&!M{J-e0lGUJT4&3wq)gI1pVk&!K*x!)ee*Hch^gSB2o zFyYS>!r*LxPw{?x#}NIm!;4S@oQI zRv?`tf^rRwhjREeNRTct&sNDu`7sqUJPA)?#yirnxD?J+3Gf>2MZ<;OmY%)Bc3{a`NMt7uw|p%!yB8hFJGrWNG;9jyJvqfaA^TR)VrU4zxg^?(hp|Fw-3&% z?v?`gz^(erXkqp!CNBjNAUM$TG<~WPIH+PMLtcs&g7yaC>%V#)z%GEODLq|r!g9sY z0@x20)`n-ebrsA5U9!hUZI1<6i@%#t zQ1mqWo1p0BU^H6*leGeKFVv7vM^2pM#?cGXVp8mNx#y1h8x!?v!KabrZsCi~hzpuOhw zy3n`Q#e)RK>h&-}rafDn-uD^Q`33W94)9tp!YEpD5vujQVC*~qV|Tbga|(iLCVHMoX9;VChQY^rZEc7)*u>ud zWCXp18pXbpjdf{`k{h}CrVZo~kL@1YxQW@%AI6-%Nly0I&dT&7Co^x<=)rzN31Kw^ z&P^VA4|zFd*(Z#)M5G8u38P(l4=B?pn#fZw&ZxOIpH}jabS}07^>YeGqJD5Sh{}Vh$dSHK2aj4$hE2&QFok(Qk+H z&x=^le^*5Dke=V|1*FuEwj7Wdvj{(%;IK__3gx9qlnRLFcq&0N?g8;PLv!)B(ubrF zhe4>l=OWPUck8sl6;DaL|NYQX*+II{@)(VNGlSV|sYr#ClAHD$zM~)$n7FM`}tE z0+8eu&Q&_OGlNs>MNfqN8H6SMY&`CzdDi09J4{~R66g8Pj;0$=y=voR&&>6lUJecW zw6(wuP3l$HXCg;)|2aBAFG44ncVoY}osjR=Ct1HxYd<#!s`Vk3Sf{Wg-g;RTXjnW* z006e^-_(OVOT1CepcEbfC>;n6NiN|d0VGGQ;&@hQwI<^`4l>CmxGf!yGV#?w)EVsF zJXjw^4Dw0=|GWd$Nt!@+F4&vn_N}=JVa(vcWtPF~O`y%R-I!{nLC6T@*r{Z=Laj82 z1!5HzNh@fJg1VRFh+-UrzSAoeV{L*y#bFe+NKC2Uu@0jg2CMEV(8CN1A9* z@?nt#S4b6HYiDU>vxJC-60i-jD54rUAmDSrwFUP@GAS)DWKae;;9O_Dq$?CAsr-SmkBQk+bv@pAqNOy(?+G69x2UE zpYQVDZ`Sz2I!TX&6C4xe)$7e^yKj-v)`=!JwW~qfMXgqbW&>oP+l}>DTYn|E1TVJ6 zKcp{vIdKsbF^WRKr#;nfWCCeMH5P*E%f%G*>oBJ35aJ-7X)dd+=6eKe^IY!+*=V)P zq4+=WyOGs2OoI@~Fw=Nn6!@AjY71(}#Fp(&*)4($bj0D&@tRhCBcE%oQ;<6m<+;a) zM@y7BJ3nI6w33R&n}Ea5s#5Zahzix-TT_Nb=_L@BG-#`f-YX*bP)KUN4uBniF54>k zCv262q#dHUAh*9OLzjkPFQy?nJOK~GG-PS)r#K(VH5IlB1|Zb;**yR{T9s)sbI=Yys!1tGdNaz2OK34d*1RhxOuBU?H^uW zNd%(irnfh+mb(lxxj_aA4>vvgcN}AZECOtDh*%b(zZnM)hGUI4Ypxc32;!FGJ7dKrD5eqxo^Z$J^a8#;uG)pHL4$RD`59FFZS zC<)t1#7jdvLO5RLyIy3cc&s2q)6p8r;V@(XIYTu03v}Z0N-IscP?8d|G~Sqoq!66VGHVdkU4q5XgGCGt0cVK8X<}R&f+EF6 z6lEa>St^RK|DC}An;=Fu$QNr6iNFhoXT@+BtbBAkViPd^*EJ%R-H$Br?~XV=&{cds zItn8>7BO&2g$U>GUF;MpME)jN7~zcA1o+<^2?6uEB_v=5RbX)9&6SN=? zKK!fw_f^06A}27-9MdK$bi>gmKIv%uShVHk=$bxv(XJ${eXh zDu7M#C*?uOBou!Tte{+fU4+6!2y|2!Tw%5WZ$mHWscyw2X!^LRVa8CKHJQ~>Af2p7 z_S*zej6^mUfH^>?;mQsOMgS%fXEWaeuW?u6bsbnBnMoB#$%^gnWW3TRbHuI_8*b;M z-WnKp>(7fLtMej$`sIDUrP-s#R%R&L=V)mkfkZI1miW9GLsjBQa?aJsByU*8tCDwD zoN@1dGo;b)ZA!jcFMrfzL;TF*k5M*l3G-)9u5O;s*|UW0;Xp^y3JzCG%e0x(uwV+` z`mQd-!&W`fsH@+-i`!f8o6vJKJl*l>hv9F#Bl?+{8u>( zR!kp#Snx7_a_i!bZ?F3O_^z_@z-E5KlXJh{`)K~#i7o4IE)`$beK-2xb>Zi8nr953 zUvc!?ANPiDUy>e~o4v7Oljr19O#8kX8OnW$tg745v|CEt>FC^Z(Ca04?6_;|JicF7 zld`{GaDh25Np!SgQ}nyJFE5RG z`@037$7MVm_m3q(hjB4AZzmozh4Z_lqUzZ8oU$fmmNbcI+}(7bWBV5y2PE{)ncX!3 zzs_KXCcQI%?d;>X-sswR;^vFAYL@-P^fC0RqUO~nj2r=E&Q%91f~SgY939%VT2?Mz zjr1HV->_l4^x(w_Z~6yrSbk&f1J_fY>$e$aQ>yp5Tt@_B_Pg%HeDh)Y?!@2t%l;p- zSb{I!s0u>xh3kxS$KtZ8th6J>*_o4P-G8I^AC}Ukr8_TvdCfr2orY%q7Unrj8OpZyzq+~p zo8z(X#JImXcT;M^=f?5R!+QJw(fre-_n(WZ25tDxNxc!mOjGh^H-(B)k#+Er|58ro0 zAIG#ze(%DH8(Hs3nD)IMp5k|xy|nJ`KJlM-1K!Js_wV&TnDySe#gkN@26mRN*@ey@ zJwAMk?5Lth^b>EPVPJ`T=ZDR|6_ST4XWUwU{?qkuf9vs)^rP%CpRHLsf8_iby{p1H z_x}9kn-%Zn7G7U?s50E-mdF|z%z7E}NN{1VN4zva+dl@aZ#kNOICI?NgHvy$ivovN zb?^N0deGiqm+jr`7nQ@keQwtNtGDjf_&001wly4c?tC!s&!uI@9?x%>>+Dkh2E^8cZ}^kChmuTRgT-aa;cKlJv0&Pdti7PI5T{G8Dq|M%h3e~PC5|G4|V E0Ae=cz5oCK literal 0 HcmV?d00001 diff --git a/lib/core/config/api_config.dart b/lib/core/config/api_config.dart index 05fe8c2..aa65fe4 100644 --- a/lib/core/config/api_config.dart +++ b/lib/core/config/api_config.dart @@ -1,5 +1,3 @@ -// lib/core/config/api_config.dart - class ApiConfig { // Private constructor to prevent instantiation ApiConfig._(); @@ -50,4 +48,11 @@ class ApiConfig { /// Headers: {'Authorization': 'Bearer '} static String editDiscount(String id) => '$baseUrl/discount/edit/$id'; static String deleteDiscount(String id) => '$baseUrl/discount/delete/$id'; + + // ========== Order Endpoints ========== + /// Endpoint to add a new order. + /// Method: POST + /// Body: {'Discount': discountId, 'User': userId} + /// Headers: {'Authorization': 'Bearer '} + static const String addOrder = '$baseUrl/order/add'; } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index e0ffce9..656da74 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:business_panel/core/config/app_colors.dart'; import 'package:business_panel/presentation/auth/bloc/auth_bloc.dart'; import 'package:business_panel/presentation/home/bloc/home_bloc.dart'; +import 'package:business_panel/presentation/order/bloc/order_bloc.dart'; import 'package:business_panel/presentation/pages/splash_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -31,6 +32,7 @@ class MyApp extends StatelessWidget { BlocProvider( create: (context) => HomeBloc()..add(FetchDiscounts()), ), + BlocProvider(create: (context) => OrderBloc()), ], child: MaterialApp( title: 'Proxibuy', diff --git a/lib/presentation/discount/bloc/discount_bloc.dart b/lib/presentation/discount/bloc/discount_bloc.dart index 3134069..345d67c 100644 --- a/lib/presentation/discount/bloc/discount_bloc.dart +++ b/lib/presentation/discount/bloc/discount_bloc.dart @@ -1,4 +1,3 @@ -// lib/presentation/discount/bloc/discount_bloc.dart import 'dart:developer'; import 'package:business_panel/core/config/api_config.dart'; @@ -6,10 +5,8 @@ import 'package:business_panel/core/services/token_storage_service.dart'; import 'package:business_panel/core/utils/logging_interceptor.dart'; import 'package:dio/dio.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:intl/intl.dart'; import 'discount_event.dart'; import 'discount_state.dart'; -import 'dart:convert'; // Import for jsonEncode class DiscountBloc extends Bloc { final Dio _dio = Dio(); @@ -156,6 +153,10 @@ class DiscountBloc extends Bloc { discountId: event.discountId, ); }); + + on((event, emit) { + emit(state.copyWith(clearErrorMessage: true)); + }); } Future _submitDiscountForm( @@ -224,7 +225,7 @@ class DiscountBloc extends Bloc { ); emit(state.copyWith(isSubmitting: false, isSuccess: true)); - } on DioException catch (e) { + }on DioException catch (e) { emit( state.copyWith( isSubmitting: false, diff --git a/lib/presentation/discount/bloc/discount_event.dart b/lib/presentation/discount/bloc/discount_event.dart index fa28db6..2d5df61 100644 --- a/lib/presentation/discount/bloc/discount_event.dart +++ b/lib/presentation/discount/bloc/discount_event.dart @@ -58,8 +58,9 @@ class NotificationRadiusChanged extends DiscountEvent { class SubmitDiscount extends DiscountEvent {} -// Event for updating an existing discount class UpdateDiscount extends DiscountEvent { final String discountId; UpdateDiscount(this.discountId); -} \ No newline at end of file +} + +class ClearErrorMessage extends DiscountEvent {} // <-- این رویداد جدید را اضافه کنید diff --git a/lib/presentation/discount/bloc/discount_state.dart b/lib/presentation/discount/bloc/discount_state.dart index 769ad64..15d2b16 100644 --- a/lib/presentation/discount/bloc/discount_state.dart +++ b/lib/presentation/discount/bloc/discount_state.dart @@ -1,10 +1,7 @@ -// lib/presentation/discount/bloc/discount_state.dart - import 'package:equatable/equatable.dart'; class DiscountState extends Equatable { final String? discountId; - // *** CHANGE IS HERE: Storing image ID and URL together *** final List> productImages; final String productName; final String? discountTypeId; @@ -57,6 +54,7 @@ class DiscountState extends Equatable { bool? isLoadingDetails, bool? isSuccess, String? errorMessage, + bool? clearErrorMessage, }) { return DiscountState( discountId: discountId ?? this.discountId, @@ -74,7 +72,7 @@ class DiscountState extends Equatable { isSubmitting: isSubmitting ?? this.isSubmitting, isLoadingDetails: isLoadingDetails ?? this.isLoadingDetails, isSuccess: isSuccess ?? this.isSuccess, - errorMessage: errorMessage ?? this.errorMessage, + errorMessage: (clearErrorMessage == true) ? null : errorMessage ?? this.errorMessage, ); } diff --git a/lib/presentation/order/bloc/order_bloc.dart b/lib/presentation/order/bloc/order_bloc.dart new file mode 100644 index 0000000..3f2796d --- /dev/null +++ b/lib/presentation/order/bloc/order_bloc.dart @@ -0,0 +1,46 @@ +import 'package:bloc/bloc.dart'; +import 'package:business_panel/core/config/api_config.dart'; +import 'package:business_panel/core/services/token_storage_service.dart'; +import 'package:dio/dio.dart'; + +part 'order_event.dart'; +part 'order_state.dart'; + +class OrderBloc extends Bloc { + final Dio _dio = Dio(); + final TokenStorageService _tokenStorage = TokenStorageService(); + + OrderBloc() : super(OrderInitial()) { + on((event, emit) async { + emit(OrderSubmissionInProgress()); + try { + final token = await _tokenStorage.getAccessToken(); + if (token == null) { + emit(OrderSubmissionFailure("خطای احراز هویت.")); + return; + } + + final response = await _dio.post( + ApiConfig.addOrder, + data: { + 'Discount': event.discountId, + 'User': event.userId, + }, + options: Options( + headers: {'Authorization': 'Bearer $token'}, + ), + ); + + if (response.statusCode == 200) { + emit(OrderSubmissionSuccess(response.data['message'] ?? "سفارش با موفقیت ثبت شد.")); + } else { + emit(OrderSubmissionFailure(response.data['message'] ?? 'خطا در ثبت سفارش.')); + } + } on DioException catch (e) { + emit(OrderSubmissionFailure(e.response?.data['message'] ?? 'خطای شبکه.')); + } catch (e) { + emit(OrderSubmissionFailure('خطای پیش‌بینی نشده: ${e.toString()}')); + } + }); + } +} \ No newline at end of file diff --git a/lib/presentation/order/bloc/order_event.dart b/lib/presentation/order/bloc/order_event.dart new file mode 100644 index 0000000..be182dc --- /dev/null +++ b/lib/presentation/order/bloc/order_event.dart @@ -0,0 +1,10 @@ +part of 'order_bloc.dart'; + +abstract class OrderEvent {} + +class SubmitOrder extends OrderEvent { + final String discountId; + final String userId; + + SubmitOrder({required this.discountId, required this.userId}); +} \ No newline at end of file diff --git a/lib/presentation/order/bloc/order_state.dart b/lib/presentation/order/bloc/order_state.dart new file mode 100644 index 0000000..756fc8e --- /dev/null +++ b/lib/presentation/order/bloc/order_state.dart @@ -0,0 +1,17 @@ +part of 'order_bloc.dart'; + +abstract class OrderState {} + +class OrderInitial extends OrderState {} + +class OrderSubmissionInProgress extends OrderState {} + +class OrderSubmissionSuccess extends OrderState { + final String message; + OrderSubmissionSuccess(this.message); +} + +class OrderSubmissionFailure extends OrderState { + final String error; + OrderSubmissionFailure(this.error); +} \ No newline at end of file diff --git a/lib/presentation/pages/add_discount_page.dart b/lib/presentation/pages/add_discount_page.dart index 351d1f5..dca7d8b 100644 --- a/lib/presentation/pages/add_discount_page.dart +++ b/lib/presentation/pages/add_discount_page.dart @@ -10,11 +10,39 @@ import 'package:business_panel/presentation/pages/home_page.dart'; import 'package:business_panel/presentation/widgets/custom_app_bar.dart'; import 'package:business_panel/presentation/widgets/info_popup.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:intl/intl.dart'; import 'package:persian_datetime_picker/persian_datetime_picker.dart'; +class ThousandsSeparatorInputFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + if (newValue.text.isEmpty) { + return newValue.copyWith(text: ''); + } + + final String newText = newValue.text.replaceAll(',', ''); + final number = int.tryParse(newText); + + if (number == null) { + return oldValue; + } + + final formatter = NumberFormat('#,##0'); + final String newString = formatter.format(number); + + return TextEditingValue( + text: newString, + selection: TextSelection.collapsed(offset: newString.length), + ); + } +} + + class AddDiscountPage extends StatelessWidget { final String? discountId; @@ -49,9 +77,18 @@ class _AddDiscountViewState extends State<_AddDiscountView> { final _descController = TextEditingController(); final _priceController = TextEditingController(); final _discountPriceController = TextEditingController(); + final _priceFormatter = NumberFormat('#,##0'); + bool get _isEditMode => widget.discountId != null; + @override + void initState() { + super.initState(); + _priceController.addListener(() => setState(() {})); + _discountPriceController.addListener(() => setState(() {})); + } + @override void dispose() { _nameController.dispose(); @@ -126,7 +163,6 @@ class _AddDiscountViewState extends State<_AddDiscountView> { "تخفیف با موفقیت ${_isEditMode ? 'ویرایش' : 'ثبت'} شد!"), backgroundColor: Colors.green), ); - // HIGHLIGHT: مسیریابی به صفحه‌ی اصلی و حذف تمام صفحات قبلی Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (_) => BlocProvider( @@ -142,12 +178,15 @@ class _AddDiscountViewState extends State<_AddDiscountView> { SnackBar( content: Text(state.errorMessage!), backgroundColor: Colors.red), ); + context.read().add(ClearErrorMessage()); } if (state.productName.isNotEmpty && _nameController.text.isEmpty) { _nameController.text = state.productName; _descController.text = state.description; - _priceController.text = state.price; - _discountPriceController.text = state.discountedPrice; + final price = int.tryParse(state.price.replaceAll(',', '')) ?? 0; + _priceController.text = _priceFormatter.format(price); + final discountedPrice = int.tryParse(state.discountedPrice.replaceAll(',', '')) ?? 0; + _discountPriceController.text = _priceFormatter.format(discountedPrice); } }, builder: (context, state) { @@ -185,7 +224,7 @@ class _AddDiscountViewState extends State<_AddDiscountView> { context.read().add(ProductNameChanged(value)), ), const SizedBox(height: 30), - _buildDiscountTypeDropdown(state), // Pass state here + _buildDiscountTypeDropdown(state), const SizedBox(height: 30), _buildTextField( controller: _descController, @@ -202,25 +241,19 @@ class _AddDiscountViewState extends State<_AddDiscountView> { const SizedBox(height: 30), _buildTimeRangePicker(context), const SizedBox(height: 30), - _buildTextField( + + _buildPriceField( controller: _priceController, - label: "قیمت بدون تخفیف", - isRequired: true, - hint: "مثلاً 240000 تومان", - keyboardType: TextInputType.number, - onChanged: (value) => - context.read().add(PriceChanged(value)), + label: "قیمت بدون تخفیف (تومان)", + hint: "مثلاً 240,000", + onChanged: (value) => context.read().add(PriceChanged(value)), ), const SizedBox(height: 30), - _buildTextField( + _buildPriceField( controller: _discountPriceController, - label: "قیمت با تخفیف", - hint: "مثلاً 200000 تومان", - isRequired: true, - keyboardType: TextInputType.number, - onChanged: (value) => context - .read() - .add(DiscountedPriceChanged(value)), + label: "قیمت با تخفیف (تومان)", + hint: "مثلاً 200,000", + onChanged: (value) => context.read().add(DiscountedPriceChanged(value)), ), const SizedBox(height: 30), _buildNotificationRadiusSlider(), @@ -253,6 +286,30 @@ class _AddDiscountViewState extends State<_AddDiscountView> { ); } + Widget _buildPriceField({ + required TextEditingController controller, + required String label, + required String hint, + required ValueChanged onChanged, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: controller, + onChanged: (value) => onChanged(value.replaceAll(',', '')), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ThousandsSeparatorInputFormatter(), + ], + decoration: _inputDecoration(label, hint: hint, isRequired: true), + ), + + ], + ); + } + Widget _buildSectionTitle({ required String title, String? popupTitle, @@ -287,7 +344,6 @@ class _AddDiscountViewState extends State<_AddDiscountView> { return BlocBuilder( buildWhen: (p, c) => p.productImages != c.productImages, builder: (context, state) { - // We ensure the list has at least 2 elements for the UI, filling with null final displayImages = List?>.from(state.productImages); while (displayImages.length < 2) { displayImages.add(null); @@ -296,7 +352,6 @@ class _AddDiscountViewState extends State<_AddDiscountView> { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(2, (index) { - // *** CHANGE IS HERE: Read from the map structure *** final imageMap = displayImages[index]; final imageUrl = imageMap?['url']; final isUrl = imageUrl?.startsWith('http') ?? false; @@ -345,16 +400,14 @@ class _AddDiscountViewState extends State<_AddDiscountView> { } Widget _buildDiscountTypeDropdown(DiscountState state) { - // Create a set of available IDs for quick lookup. final availableTypeIds = discountTypes.map((type) => type.id).toSet(); - // Check if the current discount's type ID is in our list. If not, use null. final String? selectedValue = availableTypeIds.contains(state.discountTypeId) ? state.discountTypeId : null; return DropdownButtonFormField( - value: selectedValue, // Use the safe value here. + value: selectedValue, icon: SvgPicture.asset( Assets.icons.arrowDown, width: 24, @@ -411,7 +464,6 @@ class _AddDiscountViewState extends State<_AddDiscountView> { Expanded( child: Text( displayText, - textDirection: TextDirection.rtl, style: const TextStyle(fontSize: 15), ), ), diff --git a/lib/presentation/pages/barcode_scanner_page.dart b/lib/presentation/pages/barcode_scanner_page.dart new file mode 100644 index 0000000..78543ae --- /dev/null +++ b/lib/presentation/pages/barcode_scanner_page.dart @@ -0,0 +1,124 @@ +import 'package:audioplayers/audioplayers.dart'; +import 'package:business_panel/presentation/order/bloc/order_bloc.dart'; +import 'package:business_panel/presentation/widgets/success_popup.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:jwt_decoder/jwt_decoder.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:vibration/vibration.dart'; + +class BarcodeScannerPage extends StatefulWidget { + final String discountId; + const BarcodeScannerPage({super.key, required this.discountId}); + + @override + State createState() => _BarcodeScannerPageState(); +} + +class _BarcodeScannerPageState extends State { + final MobileScannerController _scannerController = MobileScannerController(); + final AudioPlayer _audioPlayer = AudioPlayer(); + bool _isProcessing = false; + + void _handleBarcode(BarcodeCapture capture) { + if (_isProcessing) return; + setState(() => _isProcessing = true); + + final String? rawValue = capture.barcodes.first.rawValue; + if (rawValue == null) { + _showError("بارکد نامعتبر است."); + return; + } + + try { + final Map decodedToken = JwtDecoder.decode(rawValue); + + final String? userId = decodedToken['userID']; + final String? discountIdFromToken = decodedToken['discountID']; + + if (userId == null || discountIdFromToken == null) { + _showError("اطلاعات لازم در بارکد یافت نشد."); + return; + } + + if (discountIdFromToken != widget.discountId) { + _showError("این بارکد برای این تخفیف معتبر نیست."); + return; + } + + context.read().add( + SubmitOrder(discountId: discountIdFromToken, userId: userId), + ); + + } catch (e) { + _showError("فرمت بارکد صحیح نیست."); + } + } + + void _showError(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message), backgroundColor: Colors.red), + ); + setState(() => _isProcessing = false); + } + + @override + void dispose() { + _scannerController.dispose(); + _audioPlayer.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text("اسکن بارکد مشتری")), + body: BlocListener( + listener: (context, state) async { + if (state is OrderSubmissionSuccess) { + if (await Vibration.hasVibrator() ?? false) { + Vibration.vibrate(duration: 200); + } + // Ensure you have a success sound file at this path + await _audioPlayer.play(AssetSource('sounds/short-success-sound-glockenspiel-treasure-video-game-6346.mp3')); + await showSuccessDialog(context, message: state.message); + Navigator.of(context).pop(); + } else if (state is OrderSubmissionFailure) { + _showError(state.error); + } + }, + child: Stack( + children: [ + MobileScanner( + controller: _scannerController, + onDetect: _handleBarcode, + ), + Center( + child: Container( + width: 250, + height: 250, + decoration: BoxDecoration( + border: Border.all(color: Colors.green, width: 4), + borderRadius: BorderRadius.circular(12), + ), + ), + ), + BlocBuilder( + builder: (context, state) { + if (state is OrderSubmissionInProgress) { + return Container( + color: Colors.black.withOpacity(0.5), + child: const Center( + child: CircularProgressIndicator(color: Colors.white), + ), + ); + } + return const SizedBox.shrink(); + }, + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/pages/store_info.dart b/lib/presentation/pages/store_info.dart index a84f70b..2e4a561 100644 --- a/lib/presentation/pages/store_info.dart +++ b/lib/presentation/pages/store_info.dart @@ -8,7 +8,7 @@ import 'package:business_panel/presentation/store_info/bloc/store_info_bloc.dart import 'package:business_panel/presentation/store_info/bloc/store_info_state.dart'; import 'package:business_panel/presentation/widgets/custom_app_bar.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter/services.dart'; // **ADD THIS IMPORT** import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:image_picker/image_picker.dart'; @@ -127,6 +127,7 @@ class _StoreInfoPageState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.errorMessage!), backgroundColor: Colors.red), ); + context.read().add(ClearStoreInfoError()); } }, child: SingleChildScrollView( @@ -181,6 +182,7 @@ class _StoreInfoPageState extends State { child: Stack( alignment: Alignment.center, children: [ + if(state.logoPath == null) SvgPicture.asset(Assets.icons.addImg), Align( alignment: Alignment.bottomRight, child: CircleAvatar( @@ -248,6 +250,7 @@ class _StoreInfoPageState extends State { _buildTextField( controller: _addressController, label: "جزئیات آدرس", + isRequired: true, maxLines: 3, hint: "خیابان، محله، ساختمان و ....", onChanged: @@ -263,6 +266,8 @@ class _StoreInfoPageState extends State { child: _buildTextField( controller: _plaqueController, label: "پلاک", + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], onChanged: (value) => context.read().add( PlaqueChanged(value), @@ -274,6 +279,8 @@ class _StoreInfoPageState extends State { child: _buildTextField( controller: _postalCodeController, label: "کد پستی", + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], onChanged: (value) => context .read() .add(PostalCodeChanged(value)), @@ -305,7 +312,7 @@ class _StoreInfoPageState extends State { height: 23, ), label: const Text( - "انتخاب آدرس فروشگاه روی نقشه", + "انتخاب آدرس فروشگاه روی نقشه *", style: TextStyle(color: AppColors.button), ), ), @@ -333,7 +340,9 @@ class _StoreInfoPageState extends State { _buildTextField( controller: _phoneController, label: "تلفن تماس", + isRequired: true, keyboardType: TextInputType.phone, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], hint: "شماره تماس ثابت یا موبایل فروشگاه", onChanged: (value) => context.read().add(ContactPhoneChanged(value)), @@ -346,6 +355,8 @@ class _StoreInfoPageState extends State { _buildTextField( controller: _licenseController, label: "شماره جواز کسب", + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], hint: "شناسه صنفی 12 رقمی یکتا", onChanged: (value) => context.read().add(LicenseNumberChanged(value)), @@ -353,18 +364,32 @@ class _StoreInfoPageState extends State { const SizedBox(height: 44), SizedBox( width: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => BlocProvider.value( - value: BlocProvider.of(context), - child: const StoreInfoDisplayPage(), - ), + child: BlocBuilder( + builder: (context, state) { + return ElevatedButton( + onPressed: state.isFormValid ? () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => BlocProvider.value( + value: BlocProvider.of(context), + child: const StoreInfoDisplayPage(), + ), + ), + ); + } : () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("لطفاً تمام فیلدهای ستاره‌دار را پر کنید."), + backgroundColor: Colors.red, + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: state.isFormValid ? AppColors.button : Colors.grey, ), + child: const Text("تایید و ادامه"), ); }, - child: const Text("تایید و ادامه"), ), ), const SizedBox(height: 34), @@ -391,6 +416,7 @@ class _StoreInfoPageState extends State { TextInputType? keyboardType, TextEditingController? controller, ValueChanged? onChanged, + List? inputFormatters, }) { return TextFormField( controller: controller, @@ -398,6 +424,7 @@ class _StoreInfoPageState extends State { maxLines: maxLines, maxLength: maxLength, keyboardType: keyboardType, + inputFormatters: inputFormatters, decoration: InputDecoration( counterText: "", hintText: hint, @@ -577,7 +604,7 @@ class _StoreInfoPageState extends State { child: DropdownButtonFormField( value: context.watch().state.activityTypeId, icon: SvgPicture.asset( - Assets.icons.arrowDown, + Assets.icons.arrowDown, width: 24, color: Colors.black, ), @@ -616,56 +643,4 @@ class _StoreInfoPageState extends State { ), ); } -} - - - - -// Future _pickWorkingHours(BuildContext context) async { -// // ۱. انتخاب تاریخ شروع -// Jalali? startDate = await showPersianDatePicker( -// context: context, -// initialDate: Jalali.now(), -// firstDate: Jalali(1400), -// lastDate: Jalali(1405), -// ); -// if (startDate == null || !context.mounted) return; - -// // ۲. انتخاب ساعت شروع -// TimeOfDay? startTime = await showTimePicker( -// context: context, -// initialTime: TimeOfDay.now(), -// ); -// if (startTime == null || !context.mounted) return; - -// // ۳. انتخاب تاریخ پایان -// Jalali? endDate = await showPersianDatePicker( -// context: context, -// initialDate: startDate, // شروع از تاریخ انتخابی قبلی -// firstDate: startDate, // تاریخ پایان نمی‌تواند قبل از شروع باشد -// lastDate: Jalali(1405), -// ); -// if (endDate == null || !context.mounted) return; - -// // ۴. انتخاب ساعت پایان -// TimeOfDay? endTime = await showTimePicker( -// context: context, -// initialTime: startTime, -// ); -// if (endTime == null || !context.mounted) return; - -// // ۵. تبدیل به آبجکت DateTime و ارسال به BLoC -// final DateTime startDateTime = startDate.toDateTime().add( -// Duration(hours: startTime.hour, minutes: startTime.minute), -// ); -// final DateTime endDateTime = endDate.toDateTime().add( -// Duration(hours: endTime.hour, minutes: endTime.minute), -// ); - -// context.read().add( -// WorkingHoursChanged( -// startDateTime: startDateTime, -// endDateTime: endDateTime, -// ), -// ); -// } \ No newline at end of file +} \ No newline at end of file diff --git a/lib/presentation/store_info/bloc/store_info_bloc.dart b/lib/presentation/store_info/bloc/store_info_bloc.dart index 050fe3e..4811519 100644 --- a/lib/presentation/store_info/bloc/store_info_bloc.dart +++ b/lib/presentation/store_info/bloc/store_info_bloc.dart @@ -13,6 +13,10 @@ class StoreInfoBloc extends Bloc { final TokenStorageService _tokenStorage = TokenStorageService(); StoreInfoBloc() : super(StoreInfoState()) { + on((event, emit) { + emit(state.copyWith(clearErrorMessage: true)); + }); + on((event, emit) { emit(state.copyWith(logoPath: event.imagePath)); }); diff --git a/lib/presentation/store_info/bloc/store_info_event.dart b/lib/presentation/store_info/bloc/store_info_event.dart index 7d0bac3..2a3f10e 100644 --- a/lib/presentation/store_info/bloc/store_info_event.dart +++ b/lib/presentation/store_info/bloc/store_info_event.dart @@ -1,7 +1,9 @@ + part of 'store_info_bloc.dart'; abstract class StoreInfoEvent {} +class ClearStoreInfoError extends StoreInfoEvent {} class StoreLogoChanged extends StoreInfoEvent { final String imagePath; StoreLogoChanged(this.imagePath); diff --git a/lib/presentation/store_info/bloc/store_info_state.dart b/lib/presentation/store_info/bloc/store_info_state.dart index fc82a1d..ca4a40c 100644 --- a/lib/presentation/store_info/bloc/store_info_state.dart +++ b/lib/presentation/store_info/bloc/store_info_state.dart @@ -43,6 +43,21 @@ class StoreInfoState { this.activityTypeId, }); + bool get isFormValid => + storeName.isNotEmpty && + activityTypeId != null && + activityTypeId!.isNotEmpty && + address.isNotEmpty && + contactPhone != null && + contactPhone!.isNotEmpty && + workingDays.isNotEmpty && + startTime != null && + startTime!.isNotEmpty && + endTime != null && + endTime!.isNotEmpty && + latitude != null && + longitude != null; + StoreInfoState copyWith({ String? logoPath, String? storeName, @@ -64,6 +79,7 @@ class StoreInfoState { String? endTime, List? features, String? activityTypeId, + bool? clearErrorMessage, }) { return StoreInfoState( logoPath: logoPath ?? this.logoPath, @@ -76,7 +92,7 @@ class StoreInfoState { postalCode: postalCode ?? this.postalCode, isSubmitting: isSubmitting ?? this.isSubmitting, isSuccess: isSuccess ?? this.isSuccess, - errorMessage: errorMessage ?? this.errorMessage, + errorMessage: (clearErrorMessage == true) ? null : errorMessage ?? this.errorMessage, latitude: latitude ?? this.latitude, longitude: longitude ?? this.longitude, contactPhone: contactPhone ?? this.contactPhone, diff --git a/lib/presentation/widgets/active_discount_card.dart b/lib/presentation/widgets/active_discount_card.dart index 51616c5..32ad109 100644 --- a/lib/presentation/widgets/active_discount_card.dart +++ b/lib/presentation/widgets/active_discount_card.dart @@ -1,8 +1,8 @@ import 'package:business_panel/core/config/app_colors.dart'; import 'package:business_panel/domain/entities/discount_entity.dart'; import 'package:business_panel/gen/assets.gen.dart'; -import 'package:business_panel/presentation/home/bloc/home_bloc.dart'; -import 'package:business_panel/presentation/pages/add_discount_page.dart'; +import 'package:business_panel/presentation/order/bloc/order_bloc.dart'; +import 'package:business_panel/presentation/pages/barcode_scanner_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; @@ -15,8 +15,6 @@ class ActiveDiscountCard extends StatelessWidget { const ActiveDiscountCard({super.key, required this.discount}); @override Widget build(BuildContext context) { - // ... (تمام کد مربوط به _buildDiscountCard از home_page.dart به اینجا منتقل شد) - // ... (متدهای کمکی مثل _buildCountdownTimer و _buildTimerLabels هم به اینجا منتقل شدند) final remaining = discount.endDate != null ? discount.endDate!.difference(DateTime.now()) : const Duration(seconds: -1); @@ -150,30 +148,42 @@ class ActiveDiscountCard extends StatelessWidget { ], ), const SizedBox(height: 16), - Row( - children: [ - SvgPicture.asset( - Assets.icons.scanBarcode, - width: 18, - color: Colors.grey.shade700, - ), - const SizedBox(width: 10), - Expanded( - child: Text( - "اسکن بارکد مشتری", - style: TextStyle( - fontSize: 15, - color: AppColors.active, + InkWell( // <-- ویجت را در InkWell قرار دهید + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => BlocProvider.value( + value: context.read(), + child: BarcodeScannerPage(discountId: discount.id), ), - overflow: TextOverflow.ellipsis, ), - ), - ], + ); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.icons.scanBarcode, + width: 18, + color: Colors.grey.shade700, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + "اسکن بارکد مشتری", + style: TextStyle( + fontSize: 15, + color: AppColors.active, + fontWeight: FontWeight.normal, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ), ], ), ), - ], ), ), diff --git a/lib/presentation/widgets/discount_card.dart b/lib/presentation/widgets/discount_card.dart index cc36b6e..68ee0bf 100644 --- a/lib/presentation/widgets/discount_card.dart +++ b/lib/presentation/widgets/discount_card.dart @@ -214,7 +214,7 @@ class DiscountCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( - NumberFormat('#,##0').format(discount.nPrice), + "${NumberFormat('#,##0').format(discount.nPrice)} تومان", style: const TextStyle( fontSize: 16, fontWeight: FontWeight.normal, diff --git a/lib/presentation/widgets/success_popup.dart b/lib/presentation/widgets/success_popup.dart new file mode 100644 index 0000000..70924fa --- /dev/null +++ b/lib/presentation/widgets/success_popup.dart @@ -0,0 +1,43 @@ +import 'package:business_panel/core/config/app_colors.dart'; +import 'package:business_panel/gen/assets.gen.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +Future showSuccessDialog( + BuildContext context, { + required String message, +}) async { + await showDialog( + context: context, + builder: (BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset(Assets.icons.tickCircle, height: 80, color: AppColors.confirm), + const SizedBox(height: 24), + Text( + "موفقیت‌آمیز!", + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 16), + Text( + message, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16, color: Colors.black54), + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text("فهمیدم"), + ), + ], + ), + ), + ); + }, + ); +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 0dd74af..52647f4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.12.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + sha256: e653f162ddfcec1da2040ba2d8553fff1662b5c2a5c636f4c21a3b11bee497de + url: "https://pub.dev" + source: hosted + version: "6.5.0" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" + url: "https://pub.dev" + source: hosted + version: "5.2.1" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 + url: "https://pub.dev" + source: hosted + version: "4.2.1" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" + url: "https://pub.dev" + source: hosted + version: "7.1.1" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7" + url: "https://pub.dev" + source: hosted + version: "4.2.1" bloc: dependency: "direct main" description: @@ -273,6 +329,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.11" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" + url: "https://pub.dev" + source: hosted + version: "11.5.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + url: "https://pub.dev" + source: hosted + version: "7.0.3" dio: dependency: "direct main" description: @@ -797,6 +869,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.9.0" + jwt_decoder: + dependency: "direct main" + description: + name: jwt_decoder + sha256: "54774aebf83f2923b99e6416b4ea915d47af3bde56884eb622de85feabbc559f" + url: "https://pub.dev" + source: hosted + version: "2.0.1" latlong2: dependency: "direct main" description: @@ -901,6 +981,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + mobile_scanner: + dependency: "direct main" + description: + name: mobile_scanner + sha256: "54005bdea7052d792d35b4fef0f84ec5ddc3a844b250ecd48dc192fb9b4ebc95" + url: "https://pub.dev" + source: hosted + version: "7.0.1" nested: dependency: transitive description: @@ -1234,6 +1322,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + url: "https://pub.dev" + source: hosted + version: "3.3.1" term_glyph: dependency: transitive description: @@ -1330,6 +1426,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vibration: + dependency: "direct main" + description: + name: vibration + sha256: "804ee8f9628f31ee71fbe6137a2bc6206a64e101ec22cd9dd6d3a7dc0272591b" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + vibration_platform_interface: + dependency: transitive + description: + name: vibration_platform_interface + sha256: "03e9deaa4df48a1a6212e281bfee5f610d62e9247929dd2f26f4efd4fa5e225c" + url: "https://pub.dev" + source: hosted + version: "0.1.0" vm_service: dependency: transitive description: @@ -1378,6 +1490,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + url: "https://pub.dev" + source: hosted + version: "2.1.0" wkt_parser: dependency: transitive description: @@ -1412,4 +1532,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.7.2 <4.0.0" - flutter: ">=3.27.0" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index ed506de..19f86e2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,10 @@ dependencies: firebase_auth: ^5.7.0 cloud_firestore: ^5.6.12 firebase_storage: ^12.4.10 + mobile_scanner: ^7.0.1 + vibration: ^3.1.3 + audioplayers: ^6.5.0 + jwt_decoder: ^2.0.1 dev_dependencies: @@ -83,6 +87,7 @@ flutter: assets: - assets/images/ - assets/icons/ + - assets/sounds/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images