From db5b3e3bd39280e2c7d7ed00e1b0a684a55491f8 Mon Sep 17 00:00:00 2001 From: appflowy Date: Mon, 8 Aug 2022 17:12:34 +0800 Subject: [PATCH] chore: update ui --- .../packages/appflowy_board/CHANGELOG.md | 5 + .../packages/appflowy_board/README.md | 94 +++++++++------ .../example/gifs/appflowy_board_video_1.gif | Bin 0 -> 70273 bytes .../appflowy_board/example/lib/main.dart | 6 +- .../example/lib/multi_board_list_example.dart | 111 ++++++++++-------- .../appflowy_board/lib/appflowy_board.dart | 1 + .../appflowy_board/lib/src/utils/log.dart | 2 +- .../appflowy_board/lib/src/widgets/board.dart | 86 ++++++++++---- .../widgets/board_column/board_column.dart | 48 ++++++-- .../board_column/board_column_data.dart | 18 ++- .../lib/src/widgets/board_data.dart | 13 +- .../src/widgets/reorder_flex/drag_target.dart | 96 ++++++++++++++- .../reorder_flex/drag_target_inteceptor.dart | 20 ++-- .../widgets/reorder_flex/reorder_flex.dart | 63 +++++----- .../widgets/reorder_flex/reorder_mixin.dart | 54 +++++++++ .../reorder_phantom/phantom_controller.dart | 28 +++-- .../appflowy_styled_widgets.dart | 3 + .../lib/src/widgets/styled_widgets/card.dart | 37 ++++++ .../src/widgets/styled_widgets/footer.dart | 46 ++++++++ .../src/widgets/styled_widgets/header.dart | 74 ++++++++++++ 20 files changed, 615 insertions(+), 190 deletions(-) create mode 100644 frontend/app_flowy/packages/appflowy_board/example/gifs/appflowy_board_video_1.gif create mode 100644 frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/appflowy_styled_widgets.dart create mode 100644 frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart create mode 100644 frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart create mode 100644 frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart diff --git a/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md b/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md index 119d25f89d..7cf059202f 100644 --- a/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md +++ b/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.0.3 +* Support customize UI +* Update example +* Add AppFlowy style widget + ## 0.0.2 * Update documentation diff --git a/frontend/app_flowy/packages/appflowy_board/README.md b/frontend/app_flowy/packages/appflowy_board/README.md index 922cdd01b2..893bc3ed64 100644 --- a/frontend/app_flowy/packages/appflowy_board/README.md +++ b/frontend/app_flowy/packages/appflowy_board/README.md @@ -6,30 +6,25 @@ The **appflowy_board** is a package that is used in [AppFlowy](https://github.co **appflowy_board** will be a standard git repository when it becomes stable. ## Getting Started +

+ +

```dart @override void initState() { - final column1 = BoardColumnData(id: "1", items: [ - TextItem("a"), - TextItem("b"), - TextItem("c"), - TextItem("d"), + final column1 = BoardColumnData(id: "To Do", items: [ + TextItem("Card 1"), + TextItem("Card 2"), + TextItem("Card 3"), + TextItem("Card 4"), ]); - final column2 = BoardColumnData(id: "2", items: [ - TextItem("1"), - TextItem("2"), - TextItem("3"), - TextItem("4"), - TextItem("5"), + final column2 = BoardColumnData(id: "In Progress", items: [ + TextItem("Card 5"), + TextItem("Card 6"), ]); - final column3 = BoardColumnData(id: "3", items: [ - TextItem("A"), - TextItem("B"), - TextItem("C"), - TextItem("D"), - ]); + final column3 = BoardColumnData(id: "Done", items: []); boardDataController.addColumn(column1); boardDataController.addColumn(column2); @@ -40,25 +35,52 @@ The **appflowy_board** is a package that is used in [AppFlowy](https://github.co @override Widget build(BuildContext context) { - return Board( - dataController: boardDataController, - background: Container(color: Colors.red), - footBuilder: (context, columnData) { - return Container( - color: Colors.purple, - height: 30, - ); - }, - headerBuilder: (context, columnData) { - return Container( - color: Colors.yellow, - height: 30, - ); - }, - cardBuilder: (context, item) { - return _RowWidget(item: item as TextItem, key: ObjectKey(item)); - }, - columnConstraints: const BoxConstraints.tightFor(width: 240), + final config = BoardConfig( + columnBackgroundColor: HexColor.fromHex('#F7F8FC'), + ); + return Container( + color: Colors.white, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), + child: Board( + dataController: boardDataController, + footBuilder: (context, columnData) { + return AppFlowyColumnFooter( + icon: const Icon(Icons.add, size: 20), + title: const Text('New'), + height: 50, + margin: config.columnItemPadding, + ); + }, + headerBuilder: (context, columnData) { + return AppFlowyColumnHeader( + icon: const Icon(Icons.lightbulb_circle), + title: Text(columnData.id), + addIcon: const Icon(Icons.add, size: 20), + moreIcon: const Icon(Icons.more_horiz, size: 20), + height: 50, + margin: config.columnItemPadding, + ); + }, + cardBuilder: (context, item) { + final textItem = item as TextItem; + return AppFlowyColumnItemCard( + key: ObjectKey(item), + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text(textItem.s), + ), + ), + ); + }, + columnConstraints: const BoxConstraints.tightFor(width: 240), + config: BoardConfig( + columnBackgroundColor: HexColor.fromHex('#F7F8FC'), + ), + ), + ), ); } ``` \ No newline at end of file diff --git a/frontend/app_flowy/packages/appflowy_board/example/gifs/appflowy_board_video_1.gif b/frontend/app_flowy/packages/appflowy_board/example/gifs/appflowy_board_video_1.gif new file mode 100644 index 0000000000000000000000000000000000000000..bf1345608e2e81cfdb3b8b329fb0024e5a2e2a89 GIT binary patch literal 70273 zcmXV$byO72_xG3DU1}EvDUlZZ03snN!qSZ((gI62EL{pqBi$vj(k&oTqArcJbhC8B z(#`(-KIgfA&Y648oSAvQ&&=yyMP&t15i@!MNBj%G|ALGR!VRH;Ja|+<38A8TBuK?T z|Cqt^u?Q0bs~qFAP$s4)%xuiePrk7-u(Gi~W9MLh%JrN>ko_4a&wm0oTwGk-JjUFj z{5-Izxf9w7aSF#Sv}eTXpQ>|w?}AoH3e^F%ar);#NXW>z-S=iTR@*IGZ98{}9a zb82XEPe}7_#_}5H^5|Q>e9*|B`;uSSk#FNufG#Sk2q})XEE!{{xL2*%$*jl?sO+n# ztmLl7RaVz!e*HyR``o$Kx2~?fqpqyJz9p<-%Bsm#v>CV5-09r1|KrCm%~rTh>rF=M z#BJ+~(VtD1Kcz6g2M&LmEumv9+wQ~LwhP;*+B>>LI@P$lWH`E|YP-ieyZh67wqgCa z{{Ddv1GwUWjpL!&AH$17!=tuixZ1J3@$m_r37ppiu4LjoW9lw^8do-b-7~#@J+n4D zJCB>+>s>hfv3NGWxIB+pUs+krS;aN3;*M9>UDk1n8++@UJEhyW?(Mt#?bF@;!~KJ! zqr-z1EN&EwJH{R!9-ShOaTzDL=u=$xDemlFoL^sE+@4?FX5Ha-?{H=JxWRke+&ymf z9#@aUwc>D{INTWSfAR0~v*1WId&6qy$jdQ6qgTb zE1miZ??`IB!rBjfBh)&LWNE0HYecMycOonz7Ji7d2p9U(d^HLGaC6Yh-B`2S0UHdK zyUr)3Am&SE%~flzv-<2wG#C4Y*3Eh_yEcHD;ks^ntdzza)~16WPRR07i*zdjxH^qV!JEm-C#`WPr)W5X8Y3fE=ac8AhK>`6Y`S8CS9G5`;@~k{0@4yeH(7 z`UrA49%im2O-6a3nI$bt{U=RES;NONK~q6li%{g{-wx6j;J<_}uOp{qr7K=f$psnb zQ?z}0Nd;idRBxpS0v(!v62Am>a_W)aouQbuMuVMM-5qapD7xH zeI53j`pq;sqm`93nGyjCsQ)PVY0b(4Set%qXFb#;2~87jAYC!sn@~AG6FSJXRr24a zjCDH`J&c*kgg-jM-*ITca~Qpa-Usb8*t&I6_V5V8 zp&Sx=gM1;MP6i~HifaiU*pg8aN&N_?04nZ2eQBwrjWX2>d~intTt9O>n;=x5c&9z5 z9M(R`?-4sOWfUEIHUszcN*wFX-#?!Nl|4_L*Z)g?v5;n#DY*FI0=Iv$^i~CUi3z1~ zZgR7HQhm7+BXV%LnxM#gwU(mie6^loQ{600?|pE!iHhL8-YQIYzTPe^slMK+Y&^K$ zt?A*t*{h#&zS(cutiCyDy*RizM2kEh!*$=Cbqy+)?C8*I-DsoR}AIPUIp^DFND_TuoLn8{E8 zkP;f79*YF4q3~&X(Lix*5Je1%kW;CR#1I=yJAfi|XA6d;aq16qBTrZn5@ez1kY+pP z$GW}k)GgRBzL+m$HcB1zv)FLafiDp6-VVlVYy^xfp8^-5)X7SJ6se?^|1iC`lS7>U zlWim`Jqi%Uxds;$>19o;N$KM027r&V+Nn@{M0}{ANH?Vd`l;S-;g+LVL`(t0rc#gi z>`|Q0Kmp@LZ;#~lQ9P2YkQt=hD?@*r5UEzkO7llaE^Rr{Dj}FEJRyX(6iJvdP{<)z z7R;scmNfpIEZsN?D0RP_QmR(OrT3>#x8*psCZ>qTM!DZ`_BgF+porJ|Pru3aaXOl; zm@h(kz?}XhqgSn1Ai_7|6-of&%1l6)0aI3W!(>elaDe{p5QBs?iCO{&9V)ZWdc}=m zORYqF>d%l@%SjG4rbJ>>dDw6EB=>TlMDpSfZaCojBo9Dd3InN(1k<0Q!0M$kG%6!9 zg<}v3^$?m;*dRkanvf0y0}A*3b+ASgK1a7vWWb1C_^^=MAxoc(Z)^IApHlF}mZ{mO zjAhN97K;v+X?XXICZ}))XU$d_l*~epOtCGmcJ{x9#2#7$RF=) zlNamLiOT{MiqYCBYmsPqkjPNHQ?D?b>YHq6Ijcg%R=nR-nQECmt5$k0rwVe_iF)f+ zVZ6_!B(E}>Ts4&MM@kNDT=Y%pMjzsxa%1njIfKZ|YUA zc1!P{-wi|YJR1ASWMZvHTX5)qJQcBI=!JG$JzNC$ut>yifJ4n{2s*dV;9 z1Y<=<6p(|u+U_vgl=kseuaZVx(vhDo`8U1B=hGXpPRo&deFdP`%spPj(JN!r-hbeb zZy7ym8()Ndy8|Z5Q`UE(zEmu@{^JGNkcis_fw`+8pP`e);g}(E*-QfVX<2T~p!K~Z zS;l*?ELqbY=CE9MI3cdSkn^>Pjq*2%CTWcYj45f`+gOk z6gjpEg5pPxMJqq(I>1fnh&C7Hkq*XY<`9hEhBs*nS9UjQ+1V+-ui@PXkA3ik=ipH4&1(TtRZrCLq-5&5~Z#@gG01ktf!G zq@uKdFh_~1OG(k;Gc%}vO~`}sV*9Q$X{h8H3kC0?Pt%Lnn4t#LniQ z<#5aFr!0?;*bt^rK0h@ha&4Dks@ClkyDqCZh)3({ldYx*-o6B||GX@%;Rpe1KKH6N zjX_JCqC>cy%3+IlJLAoWqR4eXd;liou}y;i!oJB^Yk$gtM|@c$=r36@r3^&h^hd_) zxNU=}Sz->CZ&K$NFrkS2x`%GkQ$7DLeYd>3DAoXkQ*F)c}}h( z66%e^I?@S~Sv_;##}IpCG);OTwFt!D1dunHWRD`W7DaXohiYGf_?s+Qd(AkQ-9Bv) zU?+(lqu|LS!Q2-bMpaCHrGiG?^IL3yPf&Jl4#6rcra_t!npoJOdxEr`k& z%J3h=ih@+#Chkr%>H-B_QH*J6vN7UxFj2%8LRds9km6$qBdy7W5MC9yv?Osc&ua`Y z0SUcA`nV~EY?YEkp-6K<-mYkp*AdWB6gV3Rft8WkP=F?VpiyX04B}%FMpGITrvm`( z)wquUQdcm(Eoj2RUg$>{uy!P+uLs;(O8OQwnMd?*q^_!91r8)k4zFLwy3zY;TRz~hCviG2_qHApY{Z!ZA`zGC13TDn_`JpkkHi{pf4Xd3=O$J zLNl?14k)4#KuS3#?aws%8!V9_3VfjnU4Vfs;jy{6o*;Ea=*&f`fxb^>Gm#Mr=z)e_ z@j(nR#L2`YmKZV@J!n5)UPejA2W;#pJfVZxBO8;B6ic|^gP!muy#|q+*8r{1Br=?2 zIutqCBhV`VxfL2*NkMAQtZapZT$cfDG~@5E*+y8RY>d~a<`+XWaZVj!Zu7+$B3+WYsL}>yo{Qm70ky8T57X`f>A@AKL@}(f{ zLXi!@p;uTkQ%&GKxX1wpwn-?uK#`ckKt2hel?153Y?iFOrO34&2MnKP#9!>ZK&Y8I z#@g@)NeEvF^dbQgxaoBUCzBTeAB;d3uqZzSC`~yp95Ctu-A@Tr#>8G-#R$Kz{Y=iMp;EcPZA*FwBQMqZx{BU z^mOPCOw~ps(Fb(+l4jjzR*ZQ~MnXyzwy#);o79xK+6xwSrwOu5AiYCE{Acq=DN?o) zpeq-ZfS*b;`C??hR_k{(y&Fun(f&?n{0$4%9jJvw&Wp4AUXe)`2s?{KLHIfB-B^GK2Q+zPeI!g z%ISie{RtF9;eHDzGX#TIMzXH}#HMiKWCQ3nU&T0WMzV5OT@8^F z08FmbWS#&{1Vm+%km)3VSEHe-v&0s@P*e?iX%-lYCSRCorpo^1oDdL#gM-7?=HWfW9M;yAh~W zi@PBgkjXAl`KHAA0^*7Mxn&3nuJF^^`=@Yd(U#YiCddI@H$+izqY71fOs0db9kvF6 zmAf2k5?+$j(%KJ*dqcO7Kxb?@YzE5ETRBS6;G$eIl93nwzRio4v@6?Jtud=ywU0)j zJJ(5MDjhF>^G?k;!tFR$|TG5o?gtuSO0Pt z64E$5s8uuH*pqpp8h11P!o*6uF+fr}5Z%77k_6<0Z_FM;7M|5Rt_$|YjCe$)w^QV$ zydPnkZ!vB(n)0yF7$N9E!N(KuxoT`>nTagb9c{b7p*6tAH=qV9@TU&e(;P$gFR3qjgo+z(E1n(=!C@<9`K=ky^LfJXXLS2)MXm**A9C}S-o+#l)B4$#%XEOd11`J#U| z7<+r8$^rrX+cBk9%>UAUdZAN3{ppSm1@*>i1fj~VO8kNVU_VrEBo^cg0O1YQY3YH9 zhJGrDfFZvyT0JDrmxVX}7%(FUfl^@mwS=sh2*+S3On@A}CIVqV8`P59KYxk~1P&o0 z0bpqL5~8Lrzh{xk>ko|%L3p^We+^znz54SJeAvA`Ra~HAPl%=l^lF4WqZ~9x3w|g{ zwz-vuOjw^dUmK&*4&!s$(en<%f=us0mpzC#0UM)Xff^scM;S3uzF#dgHpUuMc4#(2 z5;hzbH*Kp`G^_QTB((!^xhigq>*|bV*OmB^%ssD+vtzVOC$`GM4TQoBY60Zs ze6uvw*^mk1Aqw&+WcFXh9f2Y}k*;maUwYMxJKrGM*>SpiBajgQiR1jj*Ej>T$!-Jh z?wE~H8{>xWP{oQx-t^V1zJms5uz0|VSL&)C8l(?dIh8DwamJsa(kB>p zg%GU^f5+l8>ACQ35xaalblyLaJURpJlE2vVAljq--9iL4q{AMun;xIuAE2y`xqct- zJvpWsKIUUO`GJc*e*g4DIPioJx~SQ0#1?-di96v}I(ZE}l`}o%-8@#vJ5_O+5`W_? z`}CBhf;d58i+rTsZH`ebCTMSdrOmJsPUVq?H=a#?C>p8Wkx(JIK&K;M|opI*~ zrVBTz3lGx^ufPkRybHhI7ax``0&o{drpsWd%TUwH@W9K+yvwNHmoZD1PQw}x!p`5p z!6~L!X@OT6c~@D#udq67(;=t?DyzBDc*Og1x)wt^#rkgscn|jlm zhQOPqyqlC^l@WH0F6<4O>9$?!w$t>sJMgwQ@3!yv?ZDFQ5bkz_>26f&Zrt>4GVpFX z?{4<@-8^pTZV`8fVY*+Dx?eNBtw!8$y&&KDeZRMKe}KEkGU1M;aHpoY^FZ8X9`5=# z?sf@x52Y9-gs|(6V5CEdAMsjFk1WeZK6t88sQ-6GK88-nd2Rads$%G{TpYW>=$dj; zcg#QU9b8w<;I|qoG#J}Z&ldAMSeqH!e1nn>gFH1H-_j~nPUW?l9pBb2)hg1+5gX(Z zsO&{MteZNa#XU8e+%u@Ro2a&$o7^{Qa$O%PGMYLtZuLDoSf86ZT&kZXgmS!} z#+tRyQ0Z0Z4jozaCO&;r{C?)xY9Ld{Wn+Hk#Af7+d_0HoKOQ??rt{T$VfM^ny4K1a zg43HjcbaeZTwR>>e=LD%3xjf+%wM{${Yiacvp9d{zV)~0O^M0EwddZGs4|o2A7&|I4HG;S$rO`;WI*XNQ|hOSnJ)0Tlvk7VjedEo#Gd@lO_}4f$FcIWf)qCuu8R6&6`pV7ZmPULPLEaj zI;h;$1o~f|sJ$F}=dLb1=W(Jgx|-syA--FGq9JiI?*2yd=JezZjNp-nrVOdjsU}X2 zQpZC};j!nbmJ(a4$6FQd@279o1SdSSHC~;aYHP|q@_hGJRp{*9J8d0L9bNV8O&xuU zR8L()hwo>)@7*Un^-TQF&h+3RgE}y?XrXg`%VZs|8Iw;|=LWU~sa~@-zM(x#_TMJF zjGP-|ZF29b<-Z!a^$T6R*Z)l}^4eq0^TODE;^zZKpWW{lix%rY-@N~DbJl43@d?9) zX)x(85jfpfAs@5wg;t7;u=8*#}D!tep~y=y+MK?rfq7?Ew$ZBg{$b^i-Ju@jHi8eiRqX>j^e zgM(!p8So5#(CblE8@m6;LOlb`(_e44x>V(OPMP7nF4CW-pRK^NU@XBP+DKR^M{rQi z<&3`+!_WT@NlS7st~Gm1d^d6;Os?nuiP2-}?mx~?g!oFBAi|A!9Fo0H+4L@nF7Wd1 za%MOH51n@(-t#;BYWO2InduIwkG^A}u)zVR?mii2q8Y$|%)l}alCYsdDU;+K-M_l8Iv7M~ z0VgEr5WUllM-tD_1eQKkEa5}0rLqhUOuxr@@5zl=HA(R9k9O-xb=r@mpDfQ^q>+o*_n)6)=ys)EFvn zYsnwz1B%o~y6^G-2)HhO3l?9u>#3U=bbLFWgs;x3kkh1P|7*wa3!qHt+ZL2DhQ-@8 zWnJ8sA}BcuP6mx$RevJV1oIQB6cJk1aL>|?zN11taMI;rvK|*?71C98;v#qF3OEYOY3+NJ)(duqhs5LlB;TC`kDoxNuLf`rJ?nN0%JpdBeXj zaNz_YP_L!gwawe(?-}Djga1;Kr+33*rISx$sn~@UJV1AE*WgZ?%!ZMf@ayXHb4HlV zX=n#jp$1>J;zul~lIr!W66|5Ue-t~r8C9*&Oh-F^lvZd56Q{;elvbsA*9r+E{C$L2 z3-KF1j)~-2GAxow6AobWsUvORCTBMwm*T^%k}G2@uNlMVRSSEdAifCU+XOP+R=oGj z)cg&M=TRK&EOZ5;@-AQJL!UVa5bG+1dwo9wvRz6Cp6K^HopfBb%Dn92b>@#Yi>agw z`P-e2J`(p};AuXa&V)4-Bru`iz}!~W-jz$wUDG@G?yGdW4)8-{Y6>N~BC`jokN`@s(kC z1hd(L>5`}cbh9m?IJqMWm<7NZYru7ji4gFAJbzE&otapqEX5HI49HF1Re;($jS?#=p(@NW2X)r>b~GRr;oD=BaT{ zw>C06Ug5d_rKG{{n>nVo8k~q<7IQEvUl2d-jW8)>{$>%v-1meD_W3zxc>#oOE}%s# z#2O=oZ#s5Km;l86N8z)C@XmYv&;ioVFNXXj@ACD2@5X!kjaTHJ9hAKISnYi=!>xa~ z-F#Qze(1sWx%`O=^1(L%3oPEURN|NvU;=|uVVYD$0pL|UXu>al5j9{t12AH@;-u~Dbz_632db%IgQY)5k~li5Mo0_ z^a+`t0enisL9ifhS*VYuFz-kW*UL2a_cBj|+c`Qr*|JmrYYkPAdpTVoNHx+fxYEx1 zL7I=JPP-08s6^!S44Hb|L2ybyQd-RpzzDoJhbeQ_K z-ZEAs=TWRU#+(ub(3W5`Zq20|ctVe;)_4_0So&X|tAs~OnurhJ*dOVsSq#E_*HFv) z+gx~WNNq({%I(pOKT>6zQkGKQjo+k@LMPkCCZS#w%H6^zBZO00kNDJbbmfx{S8YFFph{ zxQezB}~9blCe;{XQBw=Dt=6uHZ}xAf%mYEiyr=j9))MQ z_c_o|0pV8hh!T&;h8f7=`lfZW2Fexuw`#J0Vdc994-^n9wjzXnG#<`1_Fk4gJMU{3N;P2 zzQUElR8c{ABQ+eNNNqUq((c>#JDOsKK6ZGx9yo|({t0uAa@D+wx>Dg1Nf1sR8$v+~ zuq8u2tLThaCL-Z`6*g7@N?^~iZ_PMneh`WOu!Y)5vjIFERD8lP#wIQB=&hjq7{%K$ zdD}6OSnZS`t}+tf1LjuErutwiJe4Y5^WJwNZ0}?zsx52qxl4g8m?kZXp#Ht8^-lms zQ6YER;IVu_{|`bEFdk`Su+-a3IdV0Xd}Z`2?GGNJp2_1qx8tjA+&}Y?XDv!TNT$ls z8hI3+r(H0?OJR~{oGh1uGsKh3A0NE;&ENn7Z0nKEbx4sabnZBVJ4K0B1@KWcPY+*& zM>eDXLo&qjQ)_lz^eep&@1|7YQ#~G2hQ5Qo$Os>u^4OHrsn#z(w$r$sSr#XsDqk$Y zt`P`Q#qS#%;fSn>{x8Cjg2oqC;z-+0@NjbHO8v=0eH&K&kbSn$qDSt}B0hI<yl46Mlq$P+f93n1VS&}V!W>Z1 zM>UU}%F>ThTYWFmW$27Y8CsQjj#pdK3h*B8OnDg0VNP<(tqjIo4Zfcl;^WT|KAx+? z``VoIRVDRl{=_VyQ+L&#C^an-|DQwoyzH^U+ok0Cuv2*OfvwYt`{z@hZqIq^6%_t6 zKws6@0CA}b`Gubo+1cVX{5AOMe5i5t%2k$}&Nic)sG`?IH0rkVxSo&o1hfmIPk;9O zEncW(b|tACED|$v$i@+eu{Q-#!0{?bi3pBCzFTP%hKnlu?#qMnG zeQNT|WCfdT&Y5P;9cRuqWX@$`#(O}=b8qe)WX4~e&ceHj%l9>Z$w4Dvvg##fAtG!c z6t`NOX(295Bl>Gqq`~5KH?QP@1--R7ENzvKam~)oLUt%kLc`Ko+EPK+toq}cw}GW{ zF|}f~Wy_qoS{(h8#WmQZ<(uLH8OZw0Bg?ndt6Gv)T5480=Yw3%>u++Q`sWt9xmMh# z=0?2vQbSfcvsNb7CffJw@=wjo!Y$2tH{J%Un>eSQPp1x;S=*djSyscXCZAe6Z+yXD zuTW35tzNeqvVKq9;@}L$b9&CNuunE?+O|C9X)lE$k=1uu!mFl&X5NDUsX`|a_*Os&W!0SP- zLMrNh(=T8vJN(W<;uA8<(#TSV@F3}E75wppnI!ga;tK~w(NeJ+IQ#eC;QJP zk7^HgLhiTHi0u+JcKLX>t0e8d0jq%%5XG&p3&?Kw^(QFFX)jq zbXgQGB6heEZgGD(x|CFSi;UQ;e|;9paiV*$9S*&bWV;Q|xYLFDz=K5QnK%6?uNIxI zC!ywG0844OQ&T7a({x({*j0_!_5lzbR^T9rj_R%J3@piSEZGzkVjAy+L6~c{A2}z3 z;`hPB@xt$*#iQfwk@ydm!sX%NJhJhce8D08AJSblR2m@Q4X| z%mR{QQ#?Ln29lO0b8hZE{vGf341nuj{q2Ak4Cs}wA#KG62R+7ouX?2tt9qP%46=SE zE8**Q_v}PaF@6aP60$i-T?rG3KZ(W%h)V%p1rkwX!k%I9A9y1G!_NC0Q|sL)1PQ8y z7X;w`hSk;Y%@PPH08+prULx=G)$+;d;^{dO^hOb?$q8Jm0KOGDI|GLS;CM-t>Y~K% ze;D0w;6Od^_<>`BGw~XbY&?`E-3@+vuH?!2^>il=YQA~Oi)*k{C*Y^AdllsPqBwN)c+ubhOLsdRh#X3cLVCUid23 z__@G8L_p2;&V=yJJE+fb&rDlSFGt-X2j5J|hbTg2IVG&Ip6{kkbQHZ0ilJTF(5OZq zgPzN_u}gf6PyCyBRa@-t=c90%tG)0`N+^IC9`1y0BY5fYx>MLMq7yIHl^%fnFy#>r za{r9;{&#vghgSrbSDW*|FT*|_Tz)2^*9stj*om8Qp@-L!<73mEtzS?RFdjwHmD>lT z&TE)?>Ce`}Ad%CMWn-xIzxV0lN%8N~Yo(j-Z{q(^PJ-l{=E573R{xAZ*~(~I`a^%P zh`;CWczZao$D0XEncg4xpQT^?pv`qHjT5f)w*N)Mz@{3_;jNwxFe~tO);|+!yA|FvNN0aVYk|AZ1UqDJ%_rByGdZcTUVc7i+2+W0ecG0Uk*OJ zcoqbFoW|`v`m(QC8_?c;zbA6P|Bs;_+-*eMUw{MW-t3KtJUXOtK9veshFbrY!X>^5 zJRYW6k9XJrIblJOzWAVrr#4vT_D~WU0mm&YOK0Rm&bO7eM=LVH^rUa+w~p9)6Id0K zcRx_2da4%5ZZTSEcfv80&2QtyP(Cws)^yeGPP%o~yc{n% zT&+=PZ>ShL6zBBA-YKxc`{bbN^n5-69RVH6`7C<;x%;D_%fam`cJ)um?)&>Jaj!3v zdG3e5UR`hOtwcD3#O^=LVWB_2xwz2XdY&G;r$tEO?z^rJE=~eTOhp6m4hk7f2}yh~ zc1QB#Od;elMA;r-lF}uGhlaFSLD6DOED_J{Y!<^<0@A)X0HSPJ!k&~jtw=D{U7AOD zwu!C=2#or$h48L4S$GQ{U#@z;#uKx27k}u>ZZFB&TxhBya8(GWkP)*=vDETi579Mm zE((0>hP$%T)(95k_(YrJyP=|x-(37DsrHIP(Xd^tM8STPxFo|nF0&-pWp;)$>jQpN z5h^6V`}AwJtem*6rMTi-njv=q$xfDWWur;v?pMlVdWOiZD4NO|?S~b6AKPSp?A5ji zJ>GG8ph5b%e(r70-gh<0-F=^hq-chwWmd6+=IPA2>e4?gWe4ed?Vsm=Ty&@L{*;}q zIQ-3aO@g((J-@L)6CN5>HIQ^zVcTAD?D5%My&a(Ie6S>X)K$m--l+!~Sb0=)G)q(G zL|uT3(eGyO=soI-En{$ZW&1()bl~M<;cr9U-#?s0iCx6d49iAKAcA00|JDC>eYD`A zFU38`GN$?MtbW2o`Ste@9h^CcZvm_K zp|~VDs!rPaLDm7pQMj~>S*VC3+XwJ+Fa?B_kQx5Q>%$N5?V93mz{;Dzd(1y7L4n4j z#`=N{I`J^B396-s=6eoI?j(9~`0t@Ca}+ETBngDfgqTP778=gcj z1w9&KKl7^hU$0*R&2>n)VC6Eody{P7DK`cp+Aw@s)N03A(s)Ik`#g%&psjj>h((@*sNLyP=z4- z0NhHLi=;K0`#5C&2H;&j>3e3NJW-ku7Q5O;fJ`8yM%D$S`=F&UM@Zvf2tZ|=ZIn=$ z>l6D?#-I1rg5<{owi=YtBNDv?i?jk}5J6@CRW?F9WWq@5>Z5JJUVIxml@Lr#zyKKe z%lB(dL$4d$Wjjd$6dAR#nheZA zqU>LoVoHXu+d_aZnSn2C0Z8MdL=fM{Fd~CLf3?jDeOR8@;i+Y{I2FFkzPH_0wGh`n zG5~-lO1sHIx= zZPCoJ_SDIc=+*skJy8=k0k(SWVEDX7*Z#oSgJ*<6XV;#)qI}C8R0b4O`6Sny`nFD$ z>WftYar~fjLr~QIgfKV$%s^U59q`W{5&tW(PW-RsF%yqfIVXx$Zj~#qf5WqrufL{2rgfe<=90-9EsECUeY%IE!`=hL;3A-xIsU`O&sN5gz-b+M0`t zn$96Nsg80nGCS+0t%pn^qW$Go=%)%Bgme*c$-!of+}>VH8_yGII#-yrt$XFpU1Nd< zEYn0Ydo*=xQ%Wk6xNsT2l_v*czP~Sg5}3(n!uw6wCEJ=mI&r`@jmlfraZO~Ewi|9O z7_gvbBSxeD=Vilj(orQfuj18!ze#0|9?zEWZ=a!v4Z+B}>=h9k-@o2xpNj}8%hV_@ zdb6wF_DQM_s7L#a6!M-~a)s?&`1ws31lL!nFKp>w5DnEkf2wkMQoO(8H&u1unWaJQ z_^s4$T86rzwk&4P^0%MafTU`5-V%Z`bHQS9{>~XR)FYsS10{GH0N4;jd;Ab`^Z1~&jI2!>4(8MMxS0blg*|#v;3@Y1ZL$c&y&CX z+<(e?xwL)n+C-W=PsDbWe&W3RQ+sZ2L+3V_?u8Sc;HqmBt|Ct+;Xv8y%W+?c zQ#l$HhXRSozh}~v9G?8y>v19RO{(vws?S&7m$7TY8OutO1dLr_@q)d9OEzIYUIl|P z^PX`x{l33*2_byREE~NQugZYD2!G+TY?ilYZX)=-^+P`Szlsp!H3b+}X#LV90p{2s z`LliMS8dN_pJ4HWybzv321+{3(D%VW5DL%UDejMuNyAUmrPp#o14}Z9Bu=`CXQ@fN z&rwXRc%(78e-Vox+m>ov^F;om6s$@S%^A~5)s}6Q0j!OWz)}8o%r!rU-(ShfE+k8> zY0DjHGv1Be5B}Y9lEu5#B7tDm9$^kYj=vd<*Zh*AGJ(TrHB+hm8RyNx2NXUKoQHwc z9vOen!_v_-(J`a{n*SP)IZ|Lc4jiqty-Y#1|B1msd!FX5?l z+J1_+dy*={rv*5Oe`L5c_a8lt^D@Qam!+BMGooBfE&Qt(qfZ&eM=ZZu+T2^&+)Hbv z${^pX3N^Sq;+xMjc-xWmA%Fj>##?77E@+(|dJpQgY%&e3z zv$}7yS{$*KU#+;}73dIKls#ZW&{-(56qNpF;lgk6N^G@6y1+?!l~0SU#9-CY%;Kq? zMX@Uz#>av-sG#dl{!$_vNv1{r7mK+vi<_F&fo5C*x~%})YccfKf_unpZ^DdiXZ6dB z#U^HT7;i0?cnw2g`InA8hsAR7+1e;Sd(qMA`PJ&~(X7L&(i zMTgJLE!>`R6|-U=%mp~|bBgAwir%Oci=M~IwySH$ zEm(UQ*c4k@`&rvY74H(g*Ol3@6IgekHgx>8=GbSg*I*@XV$B2OL_kU`;#Ta_)E$bA zUCc9G7guZ^!kuQV-Q5P=cZ@y!<~+{LJ%P)fQPy4>gI>HQ-qrKo5DTBtWuLc%>+f@A z30EFH+;p<%aGNZ2OR|;=UEF^!x6LmnhJ}#aV_bG@ERQ$*WjfE9YD=8Nb|0qcM7)`pL-pn#cSfKEuLz&pCDIA-&whASn8#ZQgQOJzKg zN!v65NK>jAr<>auw<)8!HT1=m9>~V>#?ZZ3bQJx1$(zHp^BU+GaS*lPp z(Q8;Dy{+;2T6li3w0IezFiLXfA#dhGt|x?a+i%1qU_s|0AgwPtmi`MpMKcyM^w*5FP{Lfc|ImSta-vK13zzT^{I#e=P7}&C0?+KEKbtjS0 zDrs~hpxsvPGDRtI{J}$=hX-R1IZ`Qe6TweWP`|Q|N%^`QrN+YLx~{2uK@+s+6Q)I9 z3~O~{9c96tWq<%Ko$zu5?|)?)a*V8Ub~)7FIYOBvJDCEs@+DXEbqlKnHXC#;8dcUC zTZ-!9o_^yf`L@F08@?+X=b)d4b8yYAc)!7!R9!)nww{fzo|9YH`m5kaI9qG``tL~# zbZ)`VV6I=~`A)N3$&$t4-L}h3i%tC^*{b?oS2pe>|`36y6;R zwrh4`Fn(JV%)0-Rt11?MZ`kj7b<6&;Yw5HA>r7nLOn&89cv(`z{@;hy&h*u#2Tm)q zRZF9ef)E~4607CvsutFR^>bX+Qn}L%Bk$^Qm5s;2k_=C*{lUtw=iAm*m|)%w=Yy?k z-oF9PtNG3tznDFhgO_is;fBsTqr7wB2djRz2glV-Ue9gKpYP!xY?B-wo5$?c@t(Bs zVj*95lBy4^3y#c-kMEs#C#z5VUK~_Ap9id-Gum7%R&T#OyofruG(WszhrG9a&dvF`4rl{jPf-GBgjq=#00LuxdkBGkxYJ#f3XWC6oHMxKI+F3&+!%b z)sC!SsRfT7MmUjv;~SU6l90Mme?~k|b(MR2L=){wxBJfrs&3ql9{2NKxBP3)=fe-- zZzic*%y(r>KB6$fvJf1To$z~#yR{m+K3R38a5!TBrbZ=u45D;99(849b9?;ih)LF! z`Q`|0bj&H}M)O&Ku)ysZB|pHSF2B!>#Q&I^+wG|Z_F0Ace-%gMH^yCj%Yu-(o?#yx!2);JATU4&G-0Z!odBHikpD;G3gIC-X7-{-&|=4PDDD~pkv2J zGT(gc-T4IFUvcA3=w6+0C7(>FoCsUEiTb<09&=ylJ7KI4G|v~jm~|6dbrab=ezJNZ zICnB6;2}-w&OvYrJ30C9hbxb&2a~o3x6!GX#VMP=2Rr4dbhMj%@~J@bv5dp1_^VT; z&)*25Pb51$q)ZQ`zIm|pdno_#kk~y@Kk?AmJ(8dE;H)@(apSH@dB(tYCi?ihQm3qh zMExt5Q0*7rpT6-_B{*|{2}#U>blB=EZG~QZmJPxs!vO^I-%0Mi@bQfiOCIN7Ra}Vc3IW$3;UT*Je!A>OW&-RmJ}iq&y@>7OP;Ga2 z4wB`MUqu3bEcy2|f(N}n+E52beGh09@w=Z5{MJ}S*0fC}`f=_;n!TxL@4{3>bTX|& zKO#ifa!1(5M>K`N_(JGk6AOOE13@_?kzEHZZ4i@MsxSqG*$p1w&)}WTT9zcy$Y{|| zpD)dR;~JyBHAO!rjOiDRo%4~vh;HpQ{XV~ph-fk(y81+U6=gG;cq5xMEc$=ky=Pbx z>J}v&La#|6Lg*c7(m_x(p-OLpqS8SH1Su9kiuB%l@4ZNs-U&TaMUdX5DAI&bGr@E2 zz2}yhZ|*bmXTIOblRt0Xcdxzn+G{0fTM?~H% z1C>WeqUA*75$<+A*x@E60|11SlO*%xYKH)XBH^QegZkQ@&g7(=ljL%jl(v(sL8-JE zsdVGf^y8BZ!qd!$a#{3v+0}1{m>%U6oa97kFi?Zy4?&%G$ZdfcbhkZEv0)W8+u;|s`{Vzbv1NmoOj(jM?O4fXplxx z$@S(OUoypY-2R@W8huR~!Fyf{*8*ghYf~0#h-d*sD-hyh9WZ_%$j13?N#0n9^msxu zoZtP^Ht6$N99XR-?a7@lFYZjbUI;w4mcJj&DTBDw&T3sVZ(Todwm9p$r3JvBbz7!E<1irH17rbc z8F5Q*hAvW!g5C;J@rzPmJF0g5cXUZE4K! z_XXb{z~3J>X`GzhwYg5x>~wp27Xaw+F~LP<6C%$~4i4I7eTDayPh8^`^<>z^7EeNM zg3J}e=%wBsq9GPaQS8d`VlI?x&E9;EY7MN$nWb4}dEXxPuBfB@fy8w3g^^iufs{Ot z)m)%fI(e!|-mgz$In|QHF1{j*K4F+gya}lUY|uj4Z9&%rCB$8+Z6ANs`da(ujC$uu ztW3#Uv4@o(+8oLC;!7TUSnPIQALxAYP|Mb=JA~FX^lYEQFv1CPyjF5-x9H+S%KTvl z_S$lco%x{z&sPDX(077o_%^Ykie35lCTrcFm|M*?xzwqtK6%>X?=?s;f~IwHT+4ie zQGDrkpyJ>AO0dqu(CSr9I~-J}&v5eU`$~VtcXy^1gPLx?7Z!aU)%NJM%}FPNTR+^6 ze%_?cc*)$pUvpa(eth`?l!J?FP|=jvb->N@9rSi6$K~)(? zTJF!j>v;yGZ9HXS(f$1HP0@qX+21MEVq3{#)Tlm6-)|O~FtA36&!Nf&Zmvm_42gbg zzK@dJn>~=cb8;S&bL%5s>?q=b|8PhGqIZ9kpGHA<>|REw?zjN`(<&VV&rocix>Vf$ zI46AW(F$@b8<~Dg z$s5^Wcj-2B37@lnNxQ=$>9up$ZN)_dp(SH(DXjejMV*s z8*K~+DDj2VgW)?EhQm<>=Cs3cbvWa%&w9pbzrGm0XFQsEnvr%iZQEve(EDZ~?RefD z!-!t=V@^jehryXnR^yD*PcZ9g@0m_F@-os-x60bm4Q%tjrl0M!WDumjd(f4BzRyhm zltj2y|9zPFm-h=7$8B2p%zHy^%$WEAS~)U>4HjRD*X_8hM6h-o%1E%a7VfQm?TGU` zyB

a`ebbkVmEu#XHLYeBnYFig7Q{av^qoS6_C^50~86k`TTl94=uQ=_lZ8N@_es z;kN4I%k_EbKr4{Yb(WoZ(;k<314o{fax75J<{sR>Uheunm>jzYceBwnRD8n}DmVUg}qDZP@^kMCsX&H*{p;! zva_>OMiI#_;($XfeKD+%A|>E}SGX}*Ztp0eKfeJbS$s1Gm$;R(&Xip=&34x9!%C0? zOLT0${8OH8nLwp!;aFNeLfGsmA>Q(mCmxa$NX$woB?JzwbkhetbOybyVFh(dKNERW zkGe&0Kan!Vgnk;kG+ZMG^0W^SuPjo3`Ru-LW+)f(oj#JKQ3kXQHM{9cYL{|A&QCuFQe#}HJ9~D$+2VVxdVQx^vD-8M4~- zu2gxpIIlY6%hcCDE3V!C6v=1c7bEL%r4K{;zZS4U1Ink&wltf2l=3CSbKtX;=LGxxSCnSM+EMHkJTImvkB<_J56XJIX=hCcMPaQ(wF&0zq+k0VCsLG$R`@O` zT&FmrsR3WJwPaR#{M`fAt=47p(j{d)=IE&IqxxR-7uLI!U!2`;co7P=N14CZWxv)N zL@1f>CC6OnOWQxS8#J$t_G9Gx9c#AOtO(a#nh~%H0Tfz! zKQ45cxY13vJ=dLU|AHvnuR zh&v?QZjMa30Q|c>IDQU=958jTF;@XOfl`GR)WpOG-D7f)THVoyE_BDgd_nH}=|+I?R%C>O&C>R8cOW`H3O@?d1Zr%* zH}_M3z(KMu_vYcPsW&o26*=bQY_EOz^aI}+nY-+d)jbqxAB`{@U1CZ?H*q>TfJWMK zn6qho+|@@qKPSwQA7NGcKPDrwRxztLb$xTCOphHt%P<58@KANn8|c{svwK)icC*NX zvl==n=nicsawBkl`kkJdGEXCDW)XC!US~TpbVvYWX9RS1abW=TtB2y^B{vn3DKlp4 z`CQN9*K`&fDO*yGp=;kqj(21ZYXRA(AZ800NTL$peFQ~pR>Q6-d8Su0bp=j@mQi5V z>8T7&t|!%EFqOd))4U9s0S;rR4B08*dn?P)mv*XYR%X-(k470%$t?OFR#-j@RbnAt zmMH^E2f2h00eqASX-Ye4Le8>9n#f1!H2SNxozQEv)xUx$Y81S}NjtT;fG2b$A~U|< z0h`VwuhAsk%c2MbGl$BM6R|QYmU}FI0DWmEZ})`mcm!oeSDbC?;2ec?mgLZp~ z30mG7Ou;JGhR|-R(y2Ri?2?}bLzsHIhd);`b{8j{cAP?(H?=!NmYG_t+zLm{nYjZ>p z&Ca#Y0td1$Az9_Bx)6@42H9*m*_vu1oO5iO^5dMwlw9UITo3(epN(@V`_sP6Q;#qb&q)L1Fq(8y6qQ~6jtQKDaMJvZ>NL_^Z3E0|HWw)AVY<)>mp z(pT(P?o|V6s&yacnw$@|<_7D^7uq~=Ntx8@vF=rYRD4FL35u28NOsjL_o}tQM9~-X zsD|oqBbjm`OzMp_Sof;?B}PMyAGaqf%m>ren`(c|)W6!EA8M-mxzLWq(i+Y6dnb8p47W5L|6J?}!7yvKHlG}9d@3;>X>B<>-d!2Y&}?hH zI71(7FO0OcVZDP0xEDR~s4cPXRa}LO-eeLRi#}jQ?j>L71FU-$>{;QGKf~LNr2rOx z?&Uy^c+2G=?)<{#VE)>T2a*V(QwUiyba~AVpib1Hktm& z9H`6yL^L^leN#4bz-lxFpH>DeNK}cj!lSv~?ZTmAuDdmpphRHLsp|3UwVWKZ)S9*& zgHGmDA{^b-s!(-M;mQc540sAu;zHaPAvVmmk(uyT9j)*pvCxpqCU|RG%lLI#OX#Vo zi-6;S{X0N3WFz;6!l>eFYl~kbuYbZ%99IS8Qnys1lQCq{QIQWkPv$!qD~lsb$gX(; z9qmTtQSjYT^T=F~REc4+Uhu4T2b;p)2sb|Qtx*ZW4(Z4&GZFuN_+Ft6B$8h!_JcCD zv2Gb^kjb}N<}Tm8mkMVhpI^&B1)<_Yc-L%4VM~!mhw|mp@;EWl$eKQx=bG|3nHJQy z4eLeDWS%P!5hI>%&5pMRZT8lQMbNms>51lS#o7h*z|1M5}l5lP+)wN(3W-K+W(Z|z4#hURiySMFDN zydQn#UUjp-U&Fc=ctN7YO&chN<31SUy{V6*Nt>yoF&g)dFLII6nFPmO!?#qP^N|Tp z5b{`c-x1Ec=b1P>_g5HGW-L>uUcaaA)Ai3rj`OfKb;7|1tOl{g`#6FRxv zPX~5%tik3IzKJ|Dsiav+0DVP!|#r& zGs(*=@2O6YCm+=ms+U>+)xAn}W*RenR9in(_R?u^X8!c3uATgYeSqrhGTre%+^dco zM#Db5E>NA@dVJhCg-ThV`W zuR3l6P*ivlsx1QP(d|SU72eoQK!V$e^1j?ejF(;kMZr168_pH}d9MO|VzY{aOxU1! zB4(unSuECC*p?uZzK(m3sS9%NX8wl1U#X15^_^YBvVAuRDW1{YVZqx3l%`G_>>+Mw zLUzlp7zf|6?y|A2YdA6!bX3TJChbb74_W93uB2k!d3*4j3>XpV!oq)-Hy*!geRg&P z9$+9qB=X_(>DN^`)?{!s#CBcxI;jN-hNT<2tsOzI@Mt8EOT)jKWW_7ct4P$SFDvZU zCJ%AZs79LDj~W;l@OMW@ERlr+0^kDxMD&2Gn!0Q3=}m+WWN{W+^6~}a?+5qcXSI3$ zWoQcA}R9+wg?7C{ckv9_V42nc#&u}Pwg&s zA-%-!g>th(e2A#oO!U!Ts|x?X7F%HaPX$_6*y7DfyAPWdAZQt>3}L2sz6IT58{pKf z=e_pzM5rW6I7Pxs2IvC}DZauMs^eBFWOrmAnf6^dV`8xd|4es3gflY4AhT$#P*8&z>aK{ndc?~fHvv?~Z z$-Xf9%0QDG9u5Zc%~#dEH_H(WJsl4bgx5^6r^Y_G6-}g^&L&S;6{YPQLnCoxzOvMoqv_f);Q>cIYMBj$r6z#Y%2pe#wp;w^pQYtTIQ3 zHHgiGC(l?6fP;)TFKt|fcXI7%QdNIA4F+SrN2>606Yaf-D-UHw(w9T)cDiAL1*McI z3Hs$~`qv@xAE(TkY(w$0Jh|(aPGxrl*Y^|mP%uK8ZoYz5>0quqWCj*cXdnQuq^Nni zoU7=&Cr&qzO6|q?qp)Za_qJ@7kI^}y`~JpaPCq^xDcFA(4!i}{%1)m!da}>&-Kk$l zWWsT3nImwfqk`J-C_9em;KazJYD$x#f#V7bo?aE&=jIAR_pYnse8QzcSyNF0q6T=# z;V51(9P>Jm)CF+zf(1qYDxw6H9p zO$Y_GhQL^C0kT;HibiUHUq`m$r~Y7J2Xs(!JKRv=&+?z`3?{WPA-MNd-3i|Q1l+Hr zR2aA?imx38yYV@vlttYtEO&vGR9P95R0$;Q%jKGLPV@$v;OglOq6*kb%Gc=$hJ zi^*$ve{;s%a4&QI^xt9&bO$*6PiIVYCv3RF|0ZgQ!WG@cLQxrbPi>hp1&!p^s0{wo z8B-{{GW5CH3f&C4M{>9_+zGY9bcXIlP*g<*sI9X7>5Q3xTIITZ(ytX>6;x>U-6998=C_H#F)RCx>?4Hl5AwhSxQJ z!wMB=&gRj>b*;xkKRwRQ76737c0!F^Z~F5^BF*|vn&Dml+viK*hwShz8Vijf3Qw^L5FQhGD1SgY>iW zZwOH1Xn@9HHvPqhl4j$0!ti1K?TbyVh{n$a8o!F4Tx{u&G=8ZY{`JB2V%r4NG}Wtd zRF!h^-BPn@dUE)vw&~)BeMHmjH;v=QnTs9gk*4|M;p5h`i=UpL=0!qHbSM4gZlGrK zGR+9O=l11ZWJL4ob)5zKU+2wINsC73$^L&{egYMI8-A@=fU%!nx8I5Q?EYQ5z zdV)Ef8fiVM8@bqV#hfjI+A!!|&CC51%=w09+v()UH9mVL681>xg*z*o{O_qC`)UdQY+`PYSdr80JMO=>^sIqIULzC3?}-dofITF`>O! zVBTz!-W>YgT+ZIyiQc^R-uzSE0%&g`n2(60kC?uXgtL!iqR*{*AL%KdyJ#N-%vV;@ zS6<&&(b-oi(O0G3S8d8y1MRB?^V5;^)7AHT;OwWL==ZqZ&tS^W2<>MA^EZ?9x6t>0 z=In2o=x<%`Z!_ip676pf3viSScs);YjAG2mT&fZJ4n2RgtL7U(S*=&K*-?;IGI z7#Lh17&;XgjzI@T!h)hDgJSiA;+=yM6N8fLgHor0($PVgu;6UT;9ULSeCOc8#Nguk z;L@q!59r_uSV)y*NR56-t#e3yVn}0sNb^)kD>|ed7TPHpiqsG7aSrWE3>~NsMNNec zqeDkwVdIivpY_AOIEPIohE3Op%}#~Qqr(1TN(BZqV zh<(Y3L;Z*&=LmFS#A$uR`BcOuIs!l&36zS&dlX6F5=oR4NzxEW_BE2?Boa&;MJW{p zeH2CQ5(P_&qHBm^_!`A@62(Fr%_bGi@hF*rON; zml#R5q?lU`G16aS?w-UTXk%riV&xyjD!RlfCB>>F`H9&F;o$uLza99$-phyrJONTT zA=thN^SjUb&)g-&w~J4(a+BULI^n-K@Ap^b{pKz?P8@!7ms>ToBK^a^o%c^Qm1Lpv zSKOr-XJtONzj!*(KHH9m=~TbsE{BQmI^JQq%kP7_7=9R}?}Z7s7c&U_*}4|XT@v0{ z{FA$EIlW)6VYAp3*z!dURlldMPDpb*UG2n7$t!`-Kh~h3`gsWBQpcIv|BKw^5w^cT z5b&%<%2QjdMk#R>twyU!Y_7&=De|nv>OQbqi_?Erv=(nLuSYJrrhhmfL8M2OABW^% z^@Ye9=tP0bVXp_0W%+fUiX+gK>QdzZ({J*MH40!sX{0h4xIn940qR2{vkq_OcO(`C z#=RPlNNC32WV7RB-9RLug(I2m)QCaZ_+!8PGfM7ulYy1(8}=S>OE>Q+LS^0FX+hsB zHiUsS8Dk3(9)XX;z+EqK=sIp{V!nK!`T5BZg1bSN3eR zsEZEi2eIRi_N`QuJ{@?B8})auT!JGry-hU*6Z?G zPlNMZEI-I z{n=yxg}=W_p9p4rQa#&d3oF^XwPE)}yh>v5gcgQhCjUMm8a7M4mJ!8ax72fZtVmS( zexjbJl3o~^;Jq7d&+{-m#Hd3k2**@HXrI$-Lg2x83Qj> z)F1A9zWJ$yp8t4jfoDn>}ur8p08G!VW40 zr}K}wUrmo39D?D>sSm@zU(ureT-OB57U<8&<@JLg#qx>Z=Ggl~^ax$51^O8wD*S-G zv;-D#c@2T@yZjqKJi!SDYQtde4)$zgZM+BS#@g*)mgut*_4B|U{6Gswj6n}uG+)x2 zTz3re{xjL##5GMTlL6>o7#?Q88ej~d{O19le=GkF0~m#%1Ce3+e;>eD?PvCc@&9cA z!(q7mM|_Cz8{k!Z$XJqW1ojcrwHm*A#1yQ->z}a$7;m@Yk0lQa4cdYkNyYfF0~o7h z^}$-(!Qz)I14G14*hdUFxjd)(KyabW@B+vrttYh9rAE$fWS}d&(tDRtF5T_E$l9RD z4fFX_9no(iuSp-{tKWJxm%LVN8`BU5>$5FUHU2eQi8X*x&gNL6oZ*mu57bmx(yy3J z;nuF#jkyt5i*R1cZiC2{&Pu?Q-^K6~o-gnTjN~L>?Gw39v$=N@eeRTrG{T?B8eM}* za>cgW76|OAU?O0m&k{;|)VLFL57H28=e6pTsM!Rh;}&NRlgiEo`z(BxHF3yQu{leb z(&N0g@1>pY{cs4zV4r}-?i|<9=^AB>yc9%*n@*LOB$rL5U4alJ-Q3Kk<%2@|JdxN# zRulQdsy3orcjZlu!&Idb%!6-G96m8BOA}dk^Z;{!X%b#L4Zs*Xs>$u`ER^%^Q>DLN z<5B+HGsh>3uI!@+U9JQ$&nbVM^I|C5yiRl8;u0Hou4gVUV%8~_4_D>~GKjig5~spF zn#bnS88|Ih>4SK!W8-M^E#-NOT1=7Fd@pyp)S!x<#D$1vK*tsjBSow^x8knO(0j3D zmDpD>$8Ql*9z>=Jlw_y4BczaQ9x&@mHysc4F9~2U8cgS})+Pr_>KYAK(Z6;hYB7@+In8MFw@JA3!n+4w zFuPTKY+12#%${;BGNCeSsj`a_=(uY})0t9`62?*S-lFM0R)-J zWN`?O!jk(WSA0DurlO>fy7w(4O&tj{m3NkODWW+P9Ec+i^>ZrTd{KI`n<@_h3rF&}kLo=BnXl~D zlnY0pJIpAj6e~FdwS{;JW$f&6t~Wrpg3}|IJ91uXj1A%1#jYi?+gWKiI+eqrKxl5Sd6}JN+RQRd8tFEJLB%5HFq$6%k&83>)EHu}>YQL^{v z^~0KN@ka~HrkAI^z3(yGC(M9B_I4bP0#5=I1&~TI4PT@}io$KtfsMOkQLDoHCw=;E zV}Q)ML4{Av2@BCkK}TPnoi8;%Yk*{6Cz(pP?2XuPB6CanYs>l0Vq)Y9!IU6|bs_)d zv!$0nAn+g00s{Li%>C_IxJbEIktyQ?{C8=BaL>$DWpoCDf~6BH{|wgJ9L>0b_x=o0 z(Ro+WgsoUu^>ejkF{i6%!BQbZfEHM0RfB~#Qc&mq-C0Si7 zV69dSaUKibIyIAi)#iGOBi?uLC3^z2l}zB^8jXK>v;l?$YbdGSxIcb9_Bf&i@oTrr zOKpSFsKF7lh=WO}F^E;Yf6^5SF5mqIlAclH1WTXCbuGfzYz{zz3p~2v5d2FyL&^1z zTu+quELPGHgv$vTJ~*97Rm!!(zCo`9`8`c&>xEA$v$D{^2#>OSWgn*!01N@z{V2^;fpx5K!^QFo3`C<%?y9wol$?rz^ACrm{t6w(|^W#j-d z)G>|b8N-5q^AqELETvn@#A3&$06HI`Z1gLaYvoHaTx>BPOH?hLrfEUL3CSyv* z@U`M%RAe&BXN`kOvt;jMa-QtzNsU z7a6z<(-6U1O9-nm1imoIA?RE&*O@Wd0MQiG*RVx5(V;ng*6eW#WFw!|3#fR9okmCv zlGJ?jS+AgD2v$Bps=b-Ku{_SFxZ9&Z8U6TFr%R+2(w$1GXzI%>*`~dMgq`49Hvjx( z6)x3J*T`8Ls8TTVLA()rG0S5=<@=-BeT}*Gc<=q-E6#;v{Q~}BTt_D3Ch1eXi2p7wo`OoN?;5}TLcSU>aT=@jGR_; zWHh8qmU9&loL!S1}vYMQ?Q7W9P=^rO~#?e+e24}@uB$o6TiTTm2j0=IT_su{5$ad~LD$#r7C zcg1b?yOKSkG_&mL@ufE&h*nuXX_&c@1MX5n5eYh@LYue3s%C4KIaeSU(&O^#Zu|c6 z=PA9Ae1&6ROmD=`2#?WI>WgM&kCa2WS%R-l7-FrtYKUj%!l|EU&0cs;p@83PjE&Oq z-nc{-o^!tsM}G{NOWKP%ML)>82JdE z9<+)jhTkzLLYYH9+!okkCLaxJiNHOuQMqvXF_g2+EemBSxx8JoZ-} z4?&k%8Yex^*S8WcZ{GL!osjR~64r)3q)(^T3$V`kY3QA=u=^QvOcGZeT|0zRLWo-OPG2ATWhT_cIk{hBZ7W9Y&b!-Z! zD=?^DAoz#1HBTOO*Y#1yp|C`}!rE(=$ds(AlM#9>wjZK*GPB5Mo?!XD)?U`SHO`k6 zm~zlbPFf>?OiPMcyJyjPn?E$1iTDY3X&P{bQ|qkHuYaZ)f=^Q}892s+=%ntZ7yCwR znWSd-O4*^=rcRxR8efhHi6I&am0F864|lj>9b7qY(wbE>t~CelArM<`p;0ipO;smc9`?SNxLlR z%$YXoOD}x9e^g9+z!G2?0RJnHcK&BEA0)dY1b^kCUiKkL?y~E{7%$KZN_zyB5O(H^MuK2L{k6U|NY#aW;G9}vZ_pN37 zl<)Vy67&7*t^NNH^VzmEQ{N!b`>;iCMlhH*-SXq@YP9SHtN|LV8fy(y9*@-k8$lQG z1|60hx74GTduVeosV=286}Z3p z=`e^6D^v!$PuOQIuwB~3Eb^csNHS5}%_C<0iTaz=Z^pC}Vi3w;b~eX^*lKlX!PO15 zdrbtQv9rPc2Ng}6#w3*Ss9)&xbG*$=y(9(#u8%f_h;kR43;fY_J?Tj(|2Q?$Jm_KH=(ZzOq5V;&CQO%k@#^= zaoJDPFt1nn6cP8vInN+5zKWt%n_S9RTpfa_;WF2HD z`fT{#IGf^kY)OMzPi8pjKQKp199+niFncU+u*;MKI=b1u-zwr)T=VsQE}oT3&yfI3 zb3}wZz^}JZMjALO^Db354a83X9uD(uvoZ|utnG@FAyCcP*1QGHj)JYHBuK%;5gq!@ zhbw-GG1;nSq}zRZYa!3`cJxSg;r{;O)q!KQxvSZtpUC3gV|0TH-~}elL-x0?zo@a2 zXsfg4Mx|CAd@jy=l2&W*mtL~$XxgR@8%1f~d*gWa^`!0bob$J`V{8;97#l_DNjRS% zuVb}3su=RuOX3Sy06fNV{OcvTQoJ(2Ml$}9*BU@dZuGaj*0(67KQDZxYftfN7?4s zZqs$NM5n@Lg+f}jZbzfR`9SuKE41`4idT;xyaFY4l`AoBVSnVcwugvnx?d??QxdR> z*Be^Zo0%C{#jBxx&Gux#Bdp@}w)pp%`in1E#jDfwY`gIplSZ@iz+#^q{gvXieRY)Q zc7}$#)8V#%$%}mpj zC1Cm+^IIHh^t|UJt)!7Ou)Awf1w?mf;#gRMo0K_In=YteSAd3=s)Aq7wJQp z-j^BF8kET3zNTAEt}z4l(UKB<+ruUUsj6QIsA&kCb=@_<`XnjaN13Y>$(w3PdB|fQ zTq{^eEkhd`chsSX7FPhalnnd6D# zg*gNvW>OuA1$T+$$wW~GBIc4g20BB022kB$nAA%ug}Cq&XRu!?m+m)nr{xo<4xdym zM4l&Ye;u(Kdk~Rs=Zm5AV+OjP^svn&imq|mVNIK4Q5;LWsrc%JPYWJX&paut595te zfkDI|D$Q(2GC^RR+&VnLUV8(cY~PV7)yjo!ud|#bOe1iOVcP}N@O7#lgvv6)0B;~|gg z&cN8jAO~?zaDKr<4AitfX54vFJi^3YMmH+6z;RT>gem#?)vDm|sgR_AnS7kb??zkg zB7&KM9%GHH3P!YHNvRFye1?bM_k z;u6~VwRqLVDwVh&WA2AvGq4xtOpJKLaHH7t?96!$I6~cEd9?ri^N$3dvIdoPn;bM^ zt<-X-Vt-4oYKQfS2Je`S)A8BU4)b&gMdW?Z+ap-DyUWbd?eMdanTP8(zjn!BtjdQtBi+6UYr zGo!$~Ct_lybMTR8KtU1;TQ~^(gS1i;sBoyddDqv(^bJC<)%C^mzl#zuAY0LIRZ=Vo z+zXXSC8V27lohYq3&TqB|6^XOgn>myB@y<7 z+*~dUBwqa;rEUhJuXjgE70H2W^CjMLv#LosmRQ0-;%Za@3nE<~2ls@>{k^u0z zHaWF)JJ7DeQa&4j`yRQGgO_KaTC43DqKL=DFVrcKxCanG`@rz)JC-6yRRn}?fmxscA|wj|Isd3uB|%In zH7lplrn1q&V_m|K_$q4C~e83#1>&BV(dV0-FQ`2Da%5a&fylx!|TLZCKIHX_v4 z_n-(ww9Uh0^3{>Sq(a&AyByOZ;SlC!Z6=!(_hUvjUm$t~ia*LFEt{!N>Kj)WVjsv{ zMF%m$y01|XD*)cUm@#nt!nO~Uy#oTw+4{2JDPrC0ofkph{3n<_006Kv!UteM&2}l~ z4FN8G-rC=9q$c)*`0n~2EDzZ?FMgAt9(x#2IPfxIw^06&&$nYbAs+2s;VVbEMEqpR zvc9Fl-%w|X+|}8=_`D+U&iE32Ogvg$5-2|sNdT7&#DxH zAyY0T1mGX|u@zeyS#Sb=m&R{@EQ7dUDE~dmT<5SC%eX`^iw83l?PemP5ISQe+ux$@2*cV332<>Cg@;&6idWzk@4Q=>Ce7?)%Cq$`;??7C@Y?XokbKp zlzDkIP;INiYb!Qv68(O!#(srbnm(ayu8DW#Gz=S9@f!I+!^3U>@aSjwNF6yk$~1&Gj2gebscv>CGpf*Lu=aHAz~~hue2caC?Wxjb+2xTW({Zf|nnT7G@7b z_#>iPI`?-b(`(>zKrAcBm!|UiiY4uEEo%U!o;i|jCbmnJ zG65ODE@zNi4;EJ3rPLHrY_1yj%h%>!a}xV9H625;{@m=IN3#RVy$+7CMRQn;m54eTD8JvCvUncXXgvdiQPT<`Ypqg;2mAOYxKhniD(kcgtm0KeBd{D1@Eb* zl=5zLWPV0*qP*mdhDPSNK8^nCvBpJ>TbV+BNJuG)CDHhI2bne_4`CJ7=EjRC0I|~A zxZ~U+r7JldLPD~G-(4?<@1Y^xYzg+J&&B7tkgD}0Q8Qps^?7{FxUFaYd7%gKQ}zib zE<_;FE-v2!_?)8tW@1IUsufjhBc7%dN5ZQLC9a!dBK_A>5-K`LRR>v}Z|&<-2rKh1 zYvwh_<`fN|$K%e^+kw&kkZ+w^_ar8S?vtx>To-d3gyl7le#V&m*dI)ob53nqcA4_M zhVd1Na4HakbaUF6qx?qa<391wOsBgB=kY5p<*8_gVCYT1>}t8&FX~Xe{J22!%a&#` zF^?~|8Dv{M)V~~-s+9v`oY#_0b!v@XC#^&|%5s8?_BX&@8VoDtStU_hI@g(&mL9zL zvA7UYO@VdhykE9IkjtK4|I>r2t#K!OFCFr|Dfn*FZZ5su*_XcUJhzGZCoiQoN3Jt9 z?xC2CTb@lCf4;ffl%i zPa=Z|9H*Fef}Q7HG7gz|tbI`8Tan(78WW%}5tRI8q>pe^03k6^Cpg^RlM^q9NVBiA zzqGC`!=NP$a>0q<^7&%iovcmL2nq?!k)ay=;Ukp}c=Ke=i= zTAEmV1eH0w@S@iaEc-rR3-=oa2%OL+E*koi84HV{zGFY~vVC~5j znNE4=yNCng2GGa@BY+0ktVk0a6*;g-bf-TITW7^<9fE)`84hZ&-Ufd=Za;ArfbpZ1 zm9xE%pKmsRg5(e*c22Ha?p*NK}>a)h#Ik|fQ-J;9zF6%)$M z+z6tVivjPOaj-=lNJ_z?>qpc!-2!B z*(t{>;gRhZ)Kv=^e)~OkG66Lm%~pz8moBa;x*rG2`0v=!p`KKR*Ka5u8zi_Bj1G5XWH!pLF?hm}u z3-y?1_%H9ij^d z4{s3Dz&{xW869$wyvVtn_iYTHTd3tgk(`Ab7c=yzCQKC=IwgcE;I7;`x;yz6LXyjQI3fGW|324bo<9Th8iH5qBFwMnIanE1%1=qx+#I7`9_Im=( zVoRy;t>@THOlYvqUl{Q(e?9XH`v0&fJC@b<9>{0?=b1B~9wZ3=aptL#Rja?xy!4MV zxBK@q-@iKZPjT%3?U`@cVb7fIVsHBGDD%~sTVv0Bunl|WV``m)nQafXOU1@=VI^(W ziqy7zy?p(g!I$-5zKw`Ph|yj&t;@vMVmRE6=#(_xclV`wPVvRTy>~M_(`ABbyama> ziWG1KWy|kO*i%l^2K64)^}Tj_mJwq=zIU)y74<%3Uj9+q=t7Ft=Mp>B&Qkm4hn|8) zTdwZTQUhJsA*icgOSHV?o^rRbyCU)N0DZ{eH3bi(JcLntn|6)Pi&)|*7NUfNV^%RBrV)~|j^Y=+dEpS3!=9oFR4)reCPl#!M4JCO-HEoIHfS8KM z#+kDO^HLm-(0jB9v%EMsxjJd0fZTvE@vKMOz>`-hR5y0&$Z3Kg1xz#`$HIK)B!#G% zL=D3la_Q2J82#6Nakz!r1l?WFRsPRaSpV0hjm+48TMN$Mzmimcmqge|mj5B^Lctua zBoT6dN*kH4|3>1!rHx{N(H6bQ*xBc7|G_Hix3p0OYBmyf^NOUZDeB4oy*eeH^(k2S zYWAsTF>xj9(kl{wF8b}-bj78$k?>pU)vf4Q~cN<>NP{tGli}Z7qJWJ%7hu2)mX|IR92s zx_+;zHG5p5q5bdH|`pja-qemYJS-0vn6)BjSQZAGElVsTbsbu=kc> zRk!Q9H(k;oE!`mvf+!)~-HmjofKt-k43O^bh9MGz9&{-PN+UT46%iQwk3Mrw=UUHR z@7nLX-~DNS<~ZayI7f2D55t}rqS#@k(M@G zJ;Ro9dv7sl<2mvoHv@(ITdWeqH%Qmb>Cq}z;$(G&OT=ZEMJQ8LrP`mTYPg_Trw>P` zd_nBZ-ZL+$4uV2apK_KsPCVtD#AOE`4=WsGTvlyk}&!eKPW=)pb#_+0j2%}b;)*PyML$cvycAW6HJIvk2N|` z!;yNKU^#@n^rx@O2=RpRi4_r3Ziy5LHSigxg<^RI1}TxkEg_UE2u^z(@3Fi_5iv<) zlgmilm$Tm%C_)+r@Lwo54#LfW_3DEls-{j9cRGlK6qxd)Xq!0Lt$d|-6AvGe5%DVr z9HuFOAq=}lLy&293-0`yhdzb#tocn-#P=Rf3n)HQJddOb99bGs6J4TpLEmvg5Z@FT z9*>X1WZTWkkFM@4Bo?U84w;4!_nwO{?6FVNBjg~}j|xyp417RuIx=3yB2aCWjN+K= z*hG?fT1%78vL!bmmwlA92Re|CqY0euy+kI+KMS6Nr=5ErwZH2_B5lfFWpE^%NtizB zJX!mBf`obUq8HIvjdNBp{~m>ym>7aIA*a0S8FI%{1Rq!X9zul4ocgz%G6gJKZI$bZ zX1C%HSm3P$O#PFjDPnLtlamJgbKefyMwJJ+2qEUB9-Q8$HHx$y8Ch0|9g!z5-BEB!jcfYI9m9%p5S|4280X_3|?HG+B7;IGw6F+R$Lx|Y7q) zqhu4ghzDQnsYK!{s{6DSGV(|6>j<3kTS%zGBN6H`eErj<8fKk} zN|nmb9@eW~_k;%w(p*i$a-7LoM8hprV{1IQ_Ft8oHW|q5jB1IFgIRQzqv7`hHs`WRyJGmF>7jfAf+$Rd5;S9kk(%>apTlgVxkZU$Jp?3Yv_Hs-V-Y2$ zXcy5PeUw+jqREhzDSUVntzb2lmp0~DBp_q+)N_n5^X!6&|AuW`*Iz3eYa#9NlPbrQ zD(z5|)=EtacI^crodU)3Dgr+T)lx&v8Y$^&f$b`txBK`FL)tYDB^|XE6c^jNzSg?M zR~zptE_E$`t^40<%RZ%qa#$!W^}*9ZQSsauoIfd&F(AZ}cBdbhn`(66BM5i6hex9N zehc2D|6Z$s5hC6;^KrK)-R(7-oJ;M2E_FjNX+6du@)|zxv1K|soji9#fwgH(*R1`5 zZ!gFD<+Vt{GAg=h=YVqdm7A--w`Kn?Sf}9Mx$mq~a<7+xm-vuS9q*lWsxzkzuuh$s z%5wnLsVo+8;3W=nBAVj^tW$xE$;HantSp^l`cKc&o1xaUuASrNJ*S`nWg9ZUA!PTh zF?9}V)0NXXG2zxTmc6NLTMF%*^j#dyJ%`%n*K|$+;-h?I6}!~K&grPhy)>c;yCRRa znWVN|I|h|6p157JNlyjJe%5?Z+3lRmtK+YD_Fz{dwQHj6srMVL+I_y}t_6!g!J2N) z0|VR6#kM-WI?o5&Cg)e9J-1&|Lb;r{%(~uJJQZrvt39;t?)os9AlOo@a$v9Zb8)e! zt**KD$kelYrT12-Ba-XbowB=SCqt-f&g96unR4Yg@O#@Pm#gbs_u6`oaL;J%NkIO~ znOmDPd*naVP5;}U4FC5Z{QuQB&w=<1krM&?uNT?1Uc5nP2(UJkzI0fTdc8nMs6-Em zN>ak(rzbvBv=EGuP2nNZ$BD{!-Zn)vDA6b;FvVOaU)QYrNJ)p@DJ?mo6g3b8CNZJ- zQU9^hxljrdo5I0}FcTXX$zW`0_q!o|s+m_jq~WJk=wQVe9GJ>xl`6o!@I0HPuUHB5 zGO^R4HF_B$PA-R`n{?FGXp>2oj=$&y-=s#nwsY`jUvzUMRTXE7-?Ml6pxN7M9w@|z?@q(>Y^kpBG#`tf%S+Ue(avG@7^YS1vbu~2@ih$&-~e~BLm^!foO z1hZn65b$IsCt{cap3GMRY%LWUAky7vW8lgBFY&`2$tXbdB7e5_PDOktSt>Jmm-k!z zpt(ExTl}E6T{U<|GHQPb2UNt5T<>TD+&DEIjaJ=pG)j3+LV#Q_v(a!8*z3+sSwu4Z>mrO%cpSDU|=b-!)b z7J*%-t}^{&Bs@rYlADr4Vbsc;CQHss$U5!N4raU} zkcluR_S=#H0|-WM)fhHSVkFJztaPQ2c4B83Pw}b@E8k^lOkFhVCWiv&2TNCnI!qFe zvGixkgfeGsc?47BUzWvP!4}JuysGWiA}e;C&kNFkWF=A+eFB*T(p<|Gri$BC9MbFX zb6K}%WAMu<*EaW4*78)pu1+qSY}t!0-FWu5rDZ~p1+945j^-J7Ci2i|%S2OMJ4mMC03-=)Uu z>c3sFyMV*}mTS8fOhL{eJ1*I^ZTnrZ^<5}5Hn4Y7J~AhLWPaHXwLY^O^L84#L+|%c zqF+1eF~>6Ur6ML8yN!~)sBs%Jxmk9BTvy69LIWpO0KKkRnCCF3m2r!NAo40_`*BhB zS)JNt7Zaf7r_n~W5@dyf+RINXpER%fstx+A zZ60e(2sa19bDciF_Ehv;cc4z_NQmKHGuuocCH?U!No_56+fOgyhh6q#E;Y?ucMIi-S6g+fT=E>LZIJF#*S3Eq8jIMyE>Bi)sm$km;JDwG) zvsIYu={2z7feCgby~6yEt}Gb{*`_j6@NJI@f?v90O2!__;)`jNNF}-Bme~==nxksr z8p!(Y4pXjZpUXx(i1?1jD+`_l5zrG}DC%#+W{YFwaZxO!U}3Yw4r@2f6O1#~sg(o@Y}D;JIKNJ__NJ*%>9wqx~^pD;da4 zT#*O6QAo;QQZ*y=1q-! zw(LEJg25&S&iXlz*2)Ty!+3Q~eaZ@#YG#b+!?yGYN2i9}qHc`_2>YmA1r{BptLp6W z0e26@*Pq%Z;IV!?KV2q&bmj#; zb>1ubsp$WuJTKv@xT?O+_MmQs?N1X7KH?Sv6d~f@CO&`(MtY=y7VL{bDwG4vlxR8x@rPNEp1;dM-;d;-}TdJQt zPAhjHzNb;ToKww)jS*X2zSJM8`M%EHuy+6;K6%W6)s?ve%K>U@iUea~M|)_67M_rP zhr3FXp|~gW<5bVBCM~w=wF$~vo5~g$xIhk>!0rrhD97SzcjtNWyZkjM`I5t8Zm~GK z@ryU^sz+0REGeJdL{xt1xjRXFJYR|P`i`3vwc0yBzmD_Hq1560Y880y5sO*?$~PY~ z0K~WVF$7C!ufRIYo9S;reENk(-08nSe3=_!?7u*K0PXoVh%apY4#fB0FtcXtO=WPn z+fQfQK4)W=HGe&toQ0YW5SyL&N|@hJ_=!wsJGR*a*yh_%n=GWt59RqNqinmm&iy~i z^8%T#Y@US@ik21n){SoGnMzUE6(`u(+n1#3?%U^qUcad<%cQTWDDigb`ck2f+r?T` zb_s!80s0oXP$@}w-RCC& zFd%6;0mOePu4xyy<458vVf3dQ^nly8ryd*n@GUA)OMExnt{avLGbTKd#2nC49oRNL ztCcU$(M%X*=GQ(OW#Iq7H-a7gk*{Y3_SkI#BRs`@Qe`b}TBshHT{pCjNu}6MKqT!Y3C?-_H5F>CdBOw&^Lh({uuBBzkb!hk~Q>!0HS94%g=a8v;@$!8dkC|wxEG~5bZ z1R-R}ykbyrKXf=z9Q%TeoI6EYa)pczatLMQZT%v5mJ-%W=lTGP6nb)_#u~dveZo=6 zO&GHYmz@zjReYF;4;X`xr`R;2yrK_2w@oUE)HANjNdmg)d!!nSg+kwDP0wznSM1KpzOo&TFp zG|ZVH@U*HxV$JPcP!7^dWVT2GVrD{^pPPJz5k$ft#QX5$lrJ&W+Bv*Ko%i8kB5mNK z4N5QHXLP8E_okKl5mrdw3NzA~BE`|&Yz&EoUL=NJY#I;+J?A|j2&Fynmq8kr!QRD$ zSi+Wv7O^yC`{RG_UcZkFKmE0PEulqMkdL_@ODamyp|dKfj!&!7i`6Emqf)50lhIVF zd$Uj;Wz8nZbe6(pPs}1LGOA8@FO7x$evPF}rEzpqas{L$n>f7OFhVi8G0pl-#6qQE z7oSS1q4h%(%L>z`CjGWJZN5lyMH7)`{UotPx-7qB*{5@9iDFEG(b1LW^kD|mn%hEu zf%rtSH6*DalRkAexyNzkVx|2Be#_^1e3dLuzmvn&0?y9!anD#J&<$xr2rmjYcv+=L z9cUMME(*VDuu8KSl1FP^6gfAt$_npOMtWQn-~Y-gFZV_xIpw0{K{$(|-W73tQ(Ec6 z+EI!5rqVo&t)x!nT_*q#6x}p2E&@w8u8x5 z3l(6$SgqSeTpPPhy6wzWwOx&}%!1Jx!CKkQvmc2L&o^mKr_uOnUoX_`3s5dpREqbr zr}(99TImKDKe{ErEx+=nTn~;k93Crb7(!Myw{kMkDApiT~ieNM&s8 zAc#hZTfdP`?&uvW)N{ol^ArCjctB{3^iaaM-yFHXL` z*H)EWt7&)7Fw3L0VPRUrvm@u%Tdk5*~M+hcR**Lu!5>Rp;QW4%>$ z6o)%nl!x0`Hr)p%y{s=feVL*(UFmyPdj#JieQ{{-VmI!e)S+Fv?-KA%3E$sGkkSyd z+G`@j&^L|b?M*zUj(F2N((pj7VIPMHDV1ibp>CyeZmzN-rO6snrc8{r!y^*Pg;KuO zdd7#mzU}*&kvt@IUT?=<1$HG}ca#ouj`43WLe+LDNhSFld~4M1oDe-a@xL{?VS7u=Fi)%+kx96;G&(-qjty91XfQdRqROHHdtjT0- z3m0w1jTgByQR~#U&0}fps76`cdT^@ZxW_kMrIE=@IEx*Lb2}k3zVNFw4&iiJ+Yr zbhxQ4Ri|G(c&=Jc{K<{^b=QgBSh4RMw{R&_D@$>I$^9zK+hvSbe-8$nD(QR*>hjq=m6 zyl7d=3L?1Dxltl#c8qe0SYNi1=rVRK{RCC*=_N(3>;Ph|{f{jWdmn>M{FCVD&2;@3 z5j8)fH<&xwZbN{k*K5&!H`jNkayReQw=Z`xB=)_6FaTm#7{y$*R}?3-%o3XL5X-(K zP1oT|X{Jrpmok4B&oO!sz7`>OAeFW%Yiw8(PI&|`pqtI7fD??#*1K6u3u*LT$r3I! zIFO2I7^fHsN`WmIot19+bEB)zEP+i4IN7MGr)f}$kob7;9yd}rAu6eH)YTMIENB0L zcsV1WRDy#w)J4Eo%_@!yP4}@#dwEBch@l0Ps8%4x)3GWDS>IiG&|sHba06&|8q)5H zZyimV+(O59(JHc6-ygCySe0Oy+y+Zx!+x%P!_-JUC zqvs;k;L{Vuv8gqqp`~IM{F0sgpp=7kGrXdU@8fD(f?%)|G@9fMv8!rd2MxhMQ0APO zim5xMsroS?Dny>u|0}~3!6q??_6?GTR2(*LSctfPa%R;c7Zz?wD6)vmo={YZdepcy z{O7Qcl;nV3%@OeffyK&)>;0G{&nZz4Y3{)_kP?+L8)yqZ4|*F{)F|O6@s>Xd_u}z| z+#`37Vqrr^VWF4iqIaaEcCx&WsVTx4u@{35(C5XLB&@?jjZXSF%;my>;`umj@~(JZg3X?O zAB9i7Tx`xqz}XXqvrWII(0+#Uq(X{Rv>=QRAk7Vy{gX8J#LV!aW@fYUJTGOfyuL(N zs-uTB6GOn5fK^fYyX$Sv5(|5iyiuaEkqZ`ykS_|wSz5Vc7v;RGY-)b=6a`HJtn3dQ z4C4&7iZ%o)1-uX&plC zB))zTYoW`UA3=*E$YHi^AxMB{hSpb%s$2PkbbB&wWKZAAnr#x`IzNhha|=^8Z{5_N z&t#w)ufl!Nz>qYa!O*z&VZscvD4+bvxyUjPx+UtY{?xR0eTyapUIqJn%y8@d{VJ`CV6L*d&+hmLv8J$=z(Dz)H}rZE&L9LE^tL|`y$;| zbj(Q0LCS<(Bv!G=dq?JwmyclkIwGO=lWw6#rXLP%dtQ>)`6vIR-t1cH9PDm(M0}i>yKSnk#jZ)VOuO zw(B_d*P7JFlxDW>C^TRVB$ymD9LnW0;|#@*rPr>u3w>Hlk8P|nYkh&taH!DHV4SLj z9#l&4)ph+Z82j1tx(rM!p*TNJNeJbf&0jEfSC6NMo!+AZZd!LRb|35ah3G+fSpLUK zD*%igxN^SREWUm5Q}@Xoj6K;v3BJ2JVd;As-FYU9+m)OP@NDTgg>GeSD%LCsB{uF})YZ6}&0T_GY zf-O-H63OM1*iP6B6R}gw_xZ9&x2g0IoNX1RubFf@ibU?uvI3P!+qXFQ0q)*LBEp88 zzSwEWUTz4{nm%CyiXX;S>TOo4naG0EGkmdi3%0dnEao40{NMss>U0Vzdz+V!OaUK@ zid!EPNN-@xPeXENQ)g9?*2m2=W^9Z~v_%vBNGXN?jHyspv~nX-REl+))i}*du@HuW z3}Jh1BWf%th0@F_W^7EaE(qfEh>5J{8GthBrtG8hmrKJHr^NsQfw~~XuBzI(TmZ2+ z1{JVmQ5H(CM@qypo&uhg8X_5D5V&uUlERrtMi6Z5fmP7$V`SJOWWk~SNI~BZlawf; ze=u~;0isMiYicP&tn2T1(mwA(2IcV8J)fd~gDoJ-PIK!yMs`fm81>qU>gCBm#@DNZ zS8)7dr^=;U$7%^(u#`J{6K+X7fraUJ#XS6HB2$o3%xu7SVlFD%3lEzMdl&0;ok&we zpM4?$6Oe@E>Xy{-jelY$P!?G>wv-ln{O%6xz!<_mai8*3@lhqK(JY4oz7;OFA*Hj> z^V6^<8)B==pHo7vEq)E}Kzr9}fV(95dPrP#uP(ZRaM>5V2dAnbC%mgaO+9lq zB-H}&ohE;NE6T(zn(`-)Ge7>g+lQj5?nn0u_mWjU_n3_-{Mf)k->@TRx>r4nve49r zQ-6TcMeg;QmFYe_Vnm82#?43GI1d@e4zfO-5Y~D606F_Vz}N{S%&YWl9_lNkwf&CO_Bi|8R+s-6xPq;5HJcAL9D?9d{&6vux-CFMLuD0U__G z1sl&!B1te~^FE^b4qG&xYRLSoGZaHY5M3$`d78H%>kGs0X!qc>*5~Oo*ZPeSRZ+jA z-5XS2JP1Z-50I(jarjGMDnYz;9wCI)hXKV7A*+@QeS8&y8nM-nsBa#ULzq^U1%$D` z(oXO{Drc-So=@>@p|AJ{jNSiPU*^}EzGu~jCmR{9UEXZY0oBIggiD>vIxOCAt4*y_ zmfl|JFa_LHo68Ws@1r1L_YFoPaLm&F^w>35znLQK!i(wkq zHFk6R1WcABJh>0J%sC#X5&D;g1#2A1+ch^W!|R*`T65E#J(YAphd%8w-Og@AX+~?f zq#bV;Fx)`nMq9TevV#+e?x{cNRwnm5rY0VGH7l8{sxC<^*)`$24nU=L1TElM&F1He=aUH2dnvCV5(Ippj!K}xJP zqc0frjPW&k!uC4e?Z{>u0oolf)r{_h6oNE*`daFF!que}gxZHUag%80`;gqK)Or>V zXjj?x%HQCfhRgZO-2vLrSG!QBJhcLml3m=Oaw_h+=AwgFyVpAglc!IbqZbkPE~xza z6C=1IxpEogxv%D)x(EdJ9oh?fW{RgVCzq-*CMsul$<)~InhTzfo%rVq_{PYzJ) zaE!>>3N`0cD>H;{`7MsLxz(O1(OeLJS=0#hsdpAwS4pi<7QrP*7Bbm?{<6Dw+{P#J z8HxaIsND1yd+$ihBYOd@pDQpSw~mv?H1sm8PcErP@{UXjFWKegPH{UT?&%VS0Kima zISK%n>gEq;p#7KQh7@rfVGtquzgSw%{A-=^m(GuG^WCqC|7&UaNBqh!$Iac+@}KCV zUpl{kUs~QF4F4ZE3s)EA!Y7is$Oa^b<3cc*o<}(40yUWpABK=LaG#EX+|5Vg2_6U5 z7?chUfW*1$KKgln2w9QhGL%+sdJc&pKR?_2VCEoR*p?Nj%!vuoh>f$OcyQ%Cre`eX z8hf@gpUt@7lgdSEO#t@2&BXNcKK;}-4pYqlwkYj6$6BMLKi8$!J{76Y4xbTf;^cLn z*8BbleZdw3UxXVsMFEJCO#o4%-JYkoO;%_oxMEJ4uMc9>ND|q)836jTv`|_<5ZP&r z3nWT^l0ynyNXSp$13YF7Hhq7G7VKlC=Zc2 z@u-s)eG-V4WH1xSWMfBR0pS!Yt61Q)OmBs;5kqlBnp`IT6(*ofb?tpIOMh6K6&AzR zHxQlz7eL%EGU^+Mb*&;_m-8*efNMM_mdd$cQhcg<6+vJdF{Vsx)$)O<%DB&JjqT(# zrMM9icBUv;4*e06yW#8!jCAn_aus*zgjmn%bdb!p=5&ba z#o_5NZ6xQ}2qVbpY?QUU=4_0!{qSs@XPA@U=p1^zAa*ul#jkgMadw>=RAM`Jwnca+E^soQm*beZSdi+AibIs@oKJh{~ z5YBb^IgHfhay^Qr?s6k;7{y&RiI>v_7&idc(pVN0zkzgHms0D3$b>ALWXjt|0|nt3 zJW|~0rk7vp`()o7wsfQXTyGz)>)Ha}+qpU@ZN@!?^}$`Pc2kc6^-myzbwADfV{soJ z#B0HqHs{3w07H(}PJnYVTU_V)R_cAzA3P@XDEP*E8ASKWYqTdRCEWJJ#3WG6rHSdp z5DHXazQJX*gRk+y>u9K#`5SBa!SK@p1W(aXA$r!lpiFc<&!%1kHNtSDoJ3RwyByS_ zq?B#x-TQP6y~x2lES3$lSm+4-;#GaG(e*#$P-tUvw|!0g%IiXj`kq!1K>74L^K2rH z(#aya^u{{lC!;}Whj5Va-(_eQBN!M`@#FT!W->}fKpr6k`??43U5An*;&0#^^o9;0 zYQ(enq2gir1{)G=BHDl<$Pui)86SgDcdb5RXlW)WrESJ&&<+W`@|EMy+d$mq7)Jbz z5r(gxCi0|SSYAC`@EX&hR8E6o{3wuop`!GcR&<|_JJlc>oOC{bR zOz|S?-{V)54HR}1ru^?3*I$VA>#4x2bD(kk1Cb6iuK(t^d0AFhM8*{TEn~Pl&OeQT zba3SCsiJ0bSW5{j7v{d^*zD`~M>Ni%l^@L6=R8MS%IpT%RF8({@_-A?tJ+{*wMn4p z_{dgPrb+|O+g2u;Zl@e>s2M_{Q@BxEr8Ag4({wdoCzM{T82V+(Ni@+{eZf}iC`+Z$ zK{r3^evN5-_0s2<7T@RIwJ#n=n@s66=jblfX_rK>*%)5dx z{8vHv`~CW#1tH@6>EAGI?{Agl3vJbaTy@ zI@IIKig@!(htS%J!|7k$A238S^kRLUyPvCVaO2sXtAQCHCJHr;plD6t1QrMSjAsn;XxK?0RAN^=oWjO%I*t>@=R(6U6VO=mrLNS+x zHBE3RKyYS{YT zruwMuqtC%n`{xLbPrsXpns{&sNk4gsAL z){8>w1l*Bh0}uMKg%4o^_;Q?$Jy@R{Pp!|N|9wFiLaqH>5YC|jLCWeAk9KR$C&f}< zdHya4%y8wqf-rF|p?Fsi*v*W!n2l>x}P|jhu^274EtE6}2 z1h-7?2E4X@?Zx+-snt-thjuT*C<8S%bcoizeDXeVoLTo2UVFd~dBptVT|FI&+D@Dq z^aBSSkbjP7OdHnmHef&tAs3bFEEpBN0;5eFf>_A1&{VjJ#NHjZl zdnQ>(oKz-mL-bggg z1;UVVsO00&VcW!btazo@!Xo>qwCd(cH0M}Ic+_GD7z*Zym^A7|bWbF-@h)j3F9Ah4 zxlke+DxHalsRlw%CLXUC6~>MK2;j17parVHVSmPBTApG-R5#ii-A(jW$;L=n0U}OR z-#rQhi73=9L`t*MqWV*M6m+2e$X=k$VfJA#Vux5&Nrry-5rV3BI0JM(h)?L{LWGfuuECx6n@bODwIoq(FwZF(i`HoHJN3{4VM8r0dZ!a z2S6vdIs>7sF!4kxKrJ^kGl%XbndH#8=QY;an`CqPJG`M{%}z^VBZB#(O+ng3Z&p!^ zgzooGIGavn?@E^o3}&eo+hga7hn5LWiKu$~iE8;jDIfeVe%J3u`fonzU$*$a%SZ&I z85pNDf0%LZ#&WnWWxtv8&V6WktiaL{`h4cnS1}+JktKlJOUM-qet&$&*f}kr>`*vQ+$-p$mkxml=b|ID9eyzv#M8y7-M#Kg2Q{N^D-L z9QYh#PUxeK!=j%APfD{2D|*T32Gh!A2z7;X|1)G92uunYA))^{4gP|*XDI4+hXb%4 zp)&77)_`0Ld5Ka`AVn@r!-7!*8`L?KKdB5O@lO^3Xhe!JZPlQow(i1+vsnms*O$M! zlQCFoVE??6|9Ye+T6`q&qbMG9AGSusJk!5L`D1$gUwpy;_ecLtZTOwao8y0`@(maq|%L&7{tXOK#9r8+LT`g(sR1;q+}PRgX%#~MJ?wyl|zrCv|WWl2m{wPD<) zfba6yam#IQUm-3ESDeeuNH82m@1Q-U`Tt$hO$z+S4*ol*KIHapLyw>LE)#5FjL2%~;kDEfBn$BOKknNF=NE*3eN^~@}gGdp%+yG7XNQ7C_=m;%87RW77( z5^n68nKCFm4Cfkw?!6Osoo6?NX42uOq20)IqPQ9vg-CR0bD(o$lGYgqE2ph=wAc?u zFg!kd4ErJuGHH)CB@;OPveIFz8g4vI!~Iahr$3DJ+V*&JL~uXgi-9hX_9s6$$S3_Q9+272mh8NCpL_WX6uX?FkT<&|ma6c&JDyZcLU; z#@~$TrD$UuW;4wue9a$mU*o;ar?63i<`MdGWhQlza$`*u!6)hGiav0BPX+cJ*z5v8 zTRR(LtIRpW3jZT#_P-%-`<2z;^JN=k9>KPg`8>mZC(Ev^(#D0L70817j~n{QA8KO% zX+yuiSDYlvZYTApk(LCJ9r%Mk{XN;5_**%C!MmWpgu?$;G(Er-e}!}8d`M>c-N=y7 zGXI`zeet|2n(ibjfE-_|NDT=8i*aWIf`$DIC&pSiptV4-FcYQ^K<}jh=)D}eTr2Gc ztNpngIuBR7m5(-7A8P|#dqc!~F8ePU)-%KLICQgo8#hzpX_U~&{hGGp)43gir)=|1 ze%6f+!%&U!mUH=M_psr(_d*N#=Uo@)+=?kd5c7PouWG_W&TYlg>)RqeV`Hvi_#Exd z$pn_1HwtP$r)ZoGZcUj^O&%+DdQ6U!!{sQl$nh;HI*q&Zoo9-)RvwVnE^Fd~n}@Rq7Hg(RTskRF+i(Y%wVwh;cdv;9;z!flUt`)eDMKdWaAz z#r+RVx{x^yL9jLpZAdI5@47e$W{XRz5uBNf4@$EuQUq=75jYe+0%3;-Hk6pkmNOPw z$|<;Ms?yuEL4FHf{&|#5%xz%lR*2(B=?Z<9kmD#tV z+(0O{|M#ordmS*dzR;f(rcg3oi=m_iQC+Iev(?cI{^7xzPJV;Qyl0N6TwN_%vt?SO zGPPZ;+I@*8YGOa!;i)4; zp&XMM2ay@VzN7&rW7##8-MuCTcnmbV4{u9Sam`8LJn3GUZ z#5g#5hAq~(MZU;xsAT8Ot#|ZlzzRm+z1KeoupsoVWWTXUsHC7?PfUDQFo52T@V`Jy ziSLJB*vXn<>Msa+FM?>rUa7XSRw#h^?$?2?&SMB_ae--W5Zo?&P7o@q`tf`UcsWh!;GRLVCu_T>bj zJX-M(^rt_G`V@#U0m%}v-{@q4$IP0Lt90KWN4Sj_py-#SK$pyXwZ}V* z#E4?b1?N=ezH%>>mELQ!wM?uv_+Voc;j^4vJK z|AA47xzOh=9`$4R-B*oAFsWI-`aSEqH3lW>q6oPS~=_2qZGH?EEq!@Lh8x%9*o~o14@jGyHq>x>LzMwCx-w#f@ zM`pvfHwHMio^l%tjx2oz_{&qY{5G@EwM1kOjf6arfZ!3aW&Q7oFPD2gO}&t;wQwX` z2lYq;mhW%nc$ZK73Q5=mW+DKQ?8SG3_V^bI2$7~9PX?_D!iY3Sj3lvQ^i7{5V`)!> zu99O+c_QUvtj1?LpVP+ij=iUdAAK*6Z@S~ujKeR+jI0*0%{XB3{9Z<+!K1#Y_XYuI z0TEleipMq<-tdBw9I;b`b}PPxORI$RJe4I&&}ii_@N6J#S|mj- zIM;VsLD_i~hYjE6g_N+m{p%Q6R-K5Zq1+b-VZx@RNdD-RPPgVm*iKLsIRBRIiyp(t zbQ_;8`+|I-`}+>)*5t-~A9C7M+#WCbl{=lyqZ{*SMQl!q)n0khyA5OksZBZG_7Ccd zEl?9hU^v8h)u%0nbrQEuWCqQ_{gq_hs~HCM?Q$WMb&}()ehjVOv9R0%I!UN_-hwx> z>w7{KTb#>R$ia^N7#xSh12~VN)#X-5hK<9hPn=Hs$!HGMUDsW@Pat*iPTHgNauD7E zn<_E>ey(mRVkq-7M!xdrn#L4^0^e&qRm6w_E=Cl%kGy8E#ov0(X&xwhFDReKwT|l* zx2mwla!O&#fiKV$xcXf1dU$Hn%3Clz}Z4wd#Nz+}uhnF155nkp30+d)F;U*qD z-=;zYvBvQ_ayH`n@|3@B=>u5Q4^w;0K%P8>OIBP=yofAsyx&VKTTcyt7=v*5x?-cOXX2rcX_ zVyj2;f{engr_rd?HIySlWFx4^x6eF}DCLizYvDeWS9=Zfq}b{2$L<()CO{fBdOy_L z6ZG7b_5txn%9?3#ki;}b;K({<3fJPW>M*v9?sN7UQ>?HyWSMHDLy zG#jR;FF|D4|P~Uzadwzz~=%wOp50E|onm6v#auNB`o{T_DvaM5`mBz=+p_I|n z1oJ{G&7r8_r~B@SxKM>zG=&i<-4WXmNHiVksJ!eLXR@$8kUekEBzFa4NdvOy)1Qwj z9y{I5x+?Jf6!N3a(kcX&C1uG*wYxl0b0L}PrV2`mLwpYTHKh-6{FDMFJV3ov&$;`) zj+;|xWt3bnswa#pn=un*jXRYI%n3lv?t8v@FShke-#*pJpa1a!_JZI$g~@Eeuxxpb za#4OCRrxlL+ym_L!Z^*We=R@Arvlfs>@gKU;vB_eUbh;I2-_r&SvInQ5&pF*)#=U zCgHB<%uJ)4l*`@0$khu<9b4j-Fcc0`&en#tf%V%vIAiH17*6k63k;tI#$BJpQXk>B z7=>&@lT9bylzeG`r!ZMD*S8)`)?2_G{kny>Kk5eDgX2&g!!|==Ddi?}>HtDkadW_$ zc9(bke7fnUwMUONk3Z-7!{O9DV$-K`6V>f;Yp|>D^ef!}x7h3F)g|SaZGK?MOz74& z`eFd94rnGw=7;AZeJsA!om3bCWr~H-(@Or#ozHhXrejUAz_{IAkG#cCMIkNZ1VVEd z{&^(%Oy%kQQ4?4kk$R73}`eGzlavD#V_=@JC5B zoLDVUc^IdbkQ^agiu{DinIx2# z`4?w>YV&8#LgD4#ePg48d>|ex!SRbnJVu7Dqh;Mz>N3vdr@}PG&tax&1B^KTBCAgdT_*KnXQQxWcPN7UA5Y)Cgu*@0Goz zJEwGUP@t86wEK}jaV2inWg;^CY^=pPaqetW}g zmJT|{t$l~PaaAO#lI&r}nPe{}i({ZeXERW6gkycabPDW^7i)duT>astw@_)&9&vsD zo85;D6VjFd%66+&6u;PSD&P*?)}d66C50D&Xj7|CDqr=c^W^>7pJRjI zcxriHeHVYR`f+!EvHA_))n1=Abo_(W&)7$V`U)j|0F|^HoGcyi?QJ-XV>ua+029cF zI3=;&B0L=qvjZX^NOASmSrxDaBA9VReNF5&!ZgiUzw&OH5y!UZ;gB*uJX?<|kCOit zG!>n|M&X@jz0D|DAZcu@IVq%(bM`A#N<{?bb~+@q0JeanoHF$(0dAzd4*;vbGl^ML zQ-m%Zy9kq6(%?$cCX2%-V=D(42=NI~Xdw@e082L(CIU46!SEMr)@8O_vp}O^7vZs; zq=n0l&#S?Mm6(Fg)nr#J|77_C2O*XLJuk^^YH0xlGhAdxyyENwO>qgIt%GeWStB*6 z{jHUGD%P?=JOTEsLl{k}!BZ~4fJ`R|qxkzRdCE>Bq5uMxkU0h$GupYo^IRzS1x zUG=9zpn@p6ksQdXu0hUzcB;yxpP;fQxW=!isiVii0* zA@^Rp}GB2inB6v!E7j%9ZF zmW*DV{m*d%2$2Bs9Kq~AT?3DJ@_<-@2Ydo#CuaY=2JU7CfjM`v0@1gcRmPQPjK7F& z|5)7bhyhH<@i*e$e<`_cU$L9`Pur-M{35ocRxXsPs%YLRxn`=({cO0tv;y-k?tv;M zkqoZocDKs9-0B9du@2vJ!qV}j0+R@T6fm z;aVuEhtx#Dj7Ql4IlxXfm7C&PC2l>atXgXN_OnZ9_Pv8GY0E@6>v2d?>o@A2^Y81; zYSA1Z`|FMmTjRy`PSjM*7YFe{dM{!hJoet|?>2+OZ^giudt2jD;^JZf4(Er_-6vg7 zdyaOLf5%>(hl4K>a}=S*O&HwY-bUnWLdbZBSYycw6_l~x_74W%f>VI}I;TuphkM_& zRf$3P|D(OL3aYE!)^xDo8l2$n?gS6+4#8c6JHg!{I01sYLy!Oo?ykYzNr2$8R!@@u z-#fkcsdLUn*QxHR=9Y_9wdR_ijPZ`=p%JH}>wKXg1KCNg#LEPN8s`F^I^_z}rUT_F zTJ-llk;H-6WQuJR%40HZBsXzX%34M+tdMxkQW&!j&H%-X0b_^&s_O-k0IeM$v=Tw! zEs)M#w)|_@Q@+_l8l!q*frNR-lEzRpqr|hwDDqKziqe~o!W{S@?s(!f8Zgq6Xu%6c6SHdj zwXv3?K#v1rDW}N3@aI12%|u}gY$7CawoYFub1Ix_X{(RxJ~Dn`_!-DLZp;3bbFISJ zjQ7W}v&Azo14pw6s$GNaYvRfV^;gU%4X$3V7#kJk7410f`t>VaO||V$F5gP~J-W^s z%K|UILe3~>lo~aRo15jExkfFk$rG;7>0UP5%ZxXMcT@vKq_#Vq@Rnz% zh=5}pSO&)Kr91vG=78@9>c+R1x8J6M9z+J9svmxB&u3I%#=Gn3rP$9G-~rWk>80Xo z(A~28kDL1@NtE`7og4#<`VXDU?jpP)rOjP8ZxB1fxtL`hP%iT z%JvctF>J4k;Eu(EcZL)>6X(2}41qZu^4kXbq(u+aca(@r`VE+@1#ucW^l-TXWAsWM z@es?&aNQ(hIgwyI&bA#rdik z5JQ!A?xL~13li9dfGCwZrmRcw;h>RGYRy- znjJk|tDbZ^lKuuqv98}^ciqz$m$|;hBMItFmDiMR%jnLKD@aR$qsOC~eV8b#^}BU3 z$;Kdv`WvTp*jX%jYU&4$!^Ql^7NU}Lcl+iqnSnkl@w^s(T_!o;tFWa;-t$@;u`oE) z)h{04GJ~efm3_(O@8DM#6}+A;uKOXOI({E#=T;1RHr&^J-p;zuSacsyCkR2RhY|RI z;lgqQG0138-4WTsQ-WTtG_Crgq*D=j;UdhVg<;^$=Y}IEHcz`fOMD*ZR~+EB8Ukrm zLL7}Y$U`1OzIZbuDT=MB5c>J{Mn4!K(84fm3OUTwM5Yf~#izQ(oa8Ma-7N6!vf9=0 zMG3%BNXxL(Hc6#4D#EkC=&MJuIB9P(aX&rQ#t%h?Y2C{{9Z1K{VqbZU7Kk8XjU56* zn?aFJrCnAko2K7*P%Q3Bhfz|%M`vA7Sadg4YEXJ;ZBUU125NPH@`ApyS;pF?u;r?? zT(N!ku3WowldnRg7vK9he{igop?d)d{3+Mjcyb>sxa%=_|vZ^NnL7i)+8as2}k z-d;!Y5Vqt8FH7$D|70coAc!`A=fL|2>XtiZ2}k2oq|38 z`2Fb@d%n}-dFI>3vwr&-u33S>7tBjMZ~}E3U;Nrf-`#o}D5iCW(1?@5=PyVLB%&b& zwUHrcSO!u-?s7p>#Z%9+bddpQMt#7(%pU>CiXe(aeo96($kIj6voG*K=|CiK)=dD1 z5lsPoO58aC6kp0nM_|ofL1m+$H=uE~)^5JBnt7r%e3ge-~NrPNN9`PP+U4Bu*MqF()@eULL zIFOzp=-ZZ^6v5rGX483~ivh`CwJoe_k8Z4bbQmx?Il^s#+5 zw?$vD;erwDvh(1Tn}dbH3CK*CEHCXB_UQWzhg1vDgOojjxD-7{EzF$#mIlU!buWkl z(5y*J4o#dS5Txr0RRV|Ye~z+}k3 zLzu~%YOoNaj8;zNQ!cntE0tp`Q_49^Es^0Ya@UPf58zWzw{|V(JF(Vagqo`{=1lxT zYopK`IcM>TA}*SK?OGs3?M=+{s!ytG4_q3W8m}dYaN2W(X`L}MkJXv@6p?vk&G%(As+JeoC%;R<*oc48&O zA3p?Ux~hvAJw`U+Us{~{)bOR<&c^R@@d16Vp`%dA%nng!nG3sy=XIv7{53~>$d%f} zsCi~mChuU`<>z(eYLBMY>f==z0J=wL!!{IiO82fkE!)sR^ z*TpOc)&8&#E8vV|3TTCzz!+=8doqtswAfRHvk-$cL9H%aeTSf*-Wy2XT3-o1ok|JH zZGuA68pxSyBlog&DfqwjUX`&#x<72w9q|mwDbyx6EzfvM;|(Ehu%$*V?_5NF8!|;XO)vkjOH$4I za?tMWd8YSE3&wHJ0oS7QtOFl|{6|?LUqQ`3H_UBd!#sVK_P=kKd;fu@df5^^w?B@E z_wQsbYoP8R8&H1(Qec>ab>;)AsiN^fP~d+sszJ#&u`yl_E&`CuzeY7;H0;7J zEr1OZsK0%_AnvIX{Ps(UsoI3PvDom$muvK>JT$v)s@dpPf_hKbhVi7te55rWXm$kT zC`StVT6m^_9A)yY#=@=1{&Y!WY_{g2$>Ds>Qit6;hw`rr&4|@SdiOROvn@gFE29K7 z3u`I~#Oh2BoM)R*pV@LEeOkA7n{&jP-hQTWK3Wb51@~ub^IvQY2fQD*>+sOo*_t0N z=JWRUDLkDXU(0ED_2awU8TCi4OFQZ;t?mPELu=6FT(W?!2+zzrMA11zn?v=ITwBj? zlB6o?UmAM!Jnn zaZlOv24b%5AyduAvCJqOH zw$>5~HCfMGHAZRn_@L0lP=iW6P8Y<5DsVQUD*t{2O*K|XE_QH-OJ0NABzcn-6O*0G z_oi4UfQ@Q4D-NGmO~C&7EOkN80DN4Wh%)#tzjX6f>>wfsAN$ZSpGx&msVJjo`Jg6S2HhoA7?C)!@_7M>RQ(dXyv;HY>8ijqIE8G%~gq(XAqc!%u$7 zZmO@oCuOM`Ln!H^_DdrR7TxomO%w#gd!5O~m$5@E zBiDLOE+#pzcUX1t%$l8c&dM!;Lg3()mugdu8QIvG?4ogLI!cQlIodi?H%;vMJry>!6Bxj9|PrCmZuna8m!?KODCG z-v5lG>i)<5rJ%ssIbNKA-w?Bq!OyX3*moBT@`~*aa|Ywtr;D27@2(g8P(fGz#6lg; zn>na&t`2kheD6QDqxyLsj1y-(T#;-0fi6#l)%$XtmP?j%nCyoV>r`5U#YGU zg@j-l2GJMxG;jie5U2sbu0wv5h40ymMXo~S?f?&igVq&A!vaNAY}v&Ja*QB;yG7i? z0?9li(#7H>{=!)|pR96DqT*#Om2}S#%jSZiJQ!670**BFUtBLv<2(|}4} zVS=V7g)EsT7B3kzQLYCRPQ?VY91|HPR4pWCP->(&J_TMNMiR>t58heQpRw++}&Xg0?WvK{ZnSA73LcZu_SyYm>e90ktx$$Lr__Vcb zhP7HvhHH$m9=s;{=4^c{mfH;LE<(cXT&wxpQdLSD^^+mZW-7_5f&dc(*w36x&r&!8 zDJzVHLgye5zZP5Q010r_2h_O}Wh;$e-63~W*>m_Z%Xskv!$ zu0~?*N)eqSok4Kd6;+n2wVjRkQgD1qBfoLMvYXA&Dv|gn7~WM=uqXLuo_a&sBcsv? z^>UAAe~UFol}J1Rp++RE;97dDqeJk@u_*Vason%$=WW8W@It$tlPovHu9Y>d%g@K7 zld&EA%Quj3JD^MLl^}=A8+C>;ZoZ~*BYy|WxT(*=@a&qj-4i0 z4=LaokWyfipDEeGqX&@8MFa891bQJad55&d&VwN4O&Ptv4Xdu6TYix=UNJkhl=*8vz6v`WiXIO zTHPLVLRFl3dh!N(&KdKsuPzI5q<5qc-%Ryj*IQwa9B*P0G>*o)nf=KCx`P;bHuX&H zAN%uZ&X*vVf6>LijsU#`dWnCBcGBa?yJLRK%XY-xBPzH4D!_%aU=6iBNm-J4Wyv0% z7(fvsh~EW(I{b72bSht#%q-PRfJ(Xbip+W0*9zV4$jbfM^@&=`X;B75jmkGow&NXH zh?@DjtscF7jG0Y8lw*HFB4RJkRU75|!=eg~G?xkW#uLbXT0CXl?tRbRv^>!A(`+O| z%>6{}uHJC6D)-R1F3eGo<1#vIPA~ijS zHa!=a7ciZwV?wYU=rWkTa2KltQMM*yp1%+T$dvDUq9|jz2qNe8X0}8}PRW#bFCU%Z zagN{$;Bg?Tev*rwilfUUP+_Fyy_RKyR%EFjNn0gDLb&Lo?SFKZ@kMNhph%Ob3f(p% ztV~n#?*Rx0lu^+)<75U1roHrF=roxygxk)LV2@arO-C3V7ehAaTZlbeyU_PN84;kZ zeo81tCkv5R&$UVmjj6z%lir<4^`n#$!TY};ntdf}=-2n^&@qqm@MV(F<9A&Kpah6G67b|a~LD&=F9q9cs zQ6_{2W|1@Kmkw>`3IuqawWFD|T_v;vRNZ;J4D>xf5tFeOi=bjbr$WzVUNgkVHlY_5 zXFk1OB!Q~V8b1hoTAP2ccTx#%g;I5Z9_&U@gnC5bEJ2qUqB5a1`QSK)nb79w$ec5h zkxhLT(xAsbeSa>?nF;+470 zocNrrbTI?e;V+w*;~uY;-N)OmR>PPdboZ&rM$=BzJaRDgNhSy|dod~gUf!HG?*HmPmzeN-U z5zQdMiSeYPC)Ti10VEEU64BN_bMG*aZO;au$^GNSulXnvIEeD^*22HdJWma{x&onq zZ)L9;G@lx9xI6F^Zj-v5hG=G8Kw8hJE&8mi^WNRbP}%cn-EQd$RsU0teElf@L3PGx z9;9Mdc^`(&c(vhUMDKp}vQn9NZw9>uO15_1PGnz{Y~!+G1@+!4%rN({R`iY*%7>%4 z8ewP6Epv4wcV^{gv3r^Cv~xDSbuKGzoJZp)+I?xg&joEbYP393sLN5bwaIrUUvtKV zMqM@T_3J<#nYXoG_s>-Y5HQMcUM_s;rYFB1^~pbt(oYp~O9RX*3FFl^YuT*)*FV;Y zy?_Q>hmX_G{b|kYe!+I{Q{!*r-iij^4_B8z?>mGHzJLcG9;rzTAW$sV6<~-;K1zcI zn%0BJDd9GPUrI}DgiLUT6NkRkFEa8Y48=_dAxkMii=Zmv`4B|+NeV3z^Q>-wEEi5= zl7ItCZH$(KZHG*nMP@q;JvF%NH71=hAj9QFQA5Ge{ctXUAqW~+48zL;MD*aB1aVU^ zw8VtcTjYjOB{^(N84Rcx$B5(wqX=cpTr>dSweiqBlAz4MrpBpT(J~;kbm}RS0nOSy zBAkkZKrQufpr*_#&7qBk^M`?(#RU5=1GiS02?o!ryQMG!1#pA>3tgs}eW7m@2{FPs zyV9102)*4Q#8qAeAAD+M#I4Go16@8`U5YbvPGz`0Sj?rAe{EZb2TFSMER*v* zHH=&bR~s(n8yPvwgwE;UGDya*kft@aCZal=!aB-)TR_jNc#1avj5zBl1yR(_aslz_)g+Q&lR6ix9?2=T|E*MYsFxcKTmDgMytT4R_Bu}yCc+7 zrge5gZH(S98&d{|Z>tn){T<&P8a6!{-W(ZHr_Od%7t_m~+2m5Y^q4WqYMi_4W0-XI zi^;m2hk=0Q`li-RS3jRZSbQ~al0!qd{6ZZpa5?K`VaDwk#C*ZErkVMwe#2%F(BduHUKabd`+V6}7WQsFy*uAhY0l{7a`rH& z^>}zPTX(gQnu&X)UeR4?vHyt3XQv4Wp&mB#+Y?<(%}c(!{Gk`u?lo#Z{vO(Spz{6^np&z8 zLFY6OcjV)9TB&$suhSqBq+B>jDN#7LnqV5aTm;PlQ52Md5SHj%B#ZFQ7gML9JR_ut zb|u|Jcc)=E+oWh%VN&8`2!ORvjtn`jq=$+RJp>va?w=8w|0MGJKlv|Q2>)ko5W9aY zSidk&PaVQ;PZ&}@+vb|TP{02gFa9-8_PbTct3vkA8Q&Pp%!ijUzv9K-kkSKWf061l zSa5-8JPD|FS`%aQHLbx1K5ZR$ z$3s4m>gn}I08)KE7iyQu{S)d}lH6n!Nb(HG!g6{8npKY10mZN?n0l>^Hsfepg}(Bi zKbO4$ulgovBy=>bGP)5Q%G_#Q(YR zj$$zJh9s%_3;ltF5d6OR4T;-#H)^3|`k2NMQVHRkqSRGdNs&xsGse+Eol+(u?5hLs zWB3q~OyYRY7PjJqS1Af&#j$y(6S%2LP37dK_P5_NzsB22Qu}0D7^P+2GL=H_c3>uB zm`AglMxCs^nQoa-3d7B?1_6h?d5HQ_vam0S`5X`JF3=fK&RS4U#&zw7Xa*3Bd*?j0DKy97u zhane#vM4#6L-}Q$fBgbZdci813vFoYe6?e%l6@;`gZ9^W9n$CtWs>}ri%9h|L(Z8? z=@WBFfeB%>lASgT;XzsSpU;|LormR~Ib(svM<71F!f9M|cI%->8?k*c1aV5_Gsxw8 zaTm9;{EzWE6T}Nv)buZqOwK_sVv8l)f*t%}P{mm%2=Wk&dIm|AbdZtj?m9*Hzz&Yj z5V32%a~)V^`rb*-vIJcS?>vNDoI~9Xmzkcb8N6C7##gk=EVLn}x$gA_zsgvNsz&d_ zOtBY~*q1B;-C;M{uiu~6!}(ra*872dZiQNfdLN_Kz}v-KkR4&Z5FFtUD?wkOGpzkl zX0V{iwJPCNlq9g*K0>mWbiyVw`w(8Nr{PO=!h8w~Mi(cB8@I^ES{(@??OAxo1uaaR zJHgE@_p$QEqC4UhntyrOMlIM!iuoZVp5&)g9!BZ_uwb1@8W>og2!}qIAR@gq}VYZBCF5 z7{~)LGUi6iaZ5`_-_l`Zt|D1rwa|_^)nRyZlnLh3&025Pm-V&L zX@r!{R^HW>>(5eZb?Xe*CSs+SzdE9uE}K)UYpg8rq1P3noo{igt8(k5(EoWSTXx=9 z;rmFh14X~ki1DfB?JEZ3tuigK^Y_tlddGYOQN05)SLq7v$Mn|-2^2bRQS~znIJAhp z$aE2sCQ_=_I zN{bB--PN*BEnX*#F88?02UWGLbDZRE^@!RBPED~(kKei4fi*h2t}PLX*~{vF`4T~; z{rjW2_ts~*^Qorj({^TulX8QN^QJF`Bh3Di{A&=I+)2-?>;qnmNIp`w6yXyzc(W@U`!8WkEZXmT!{Gk`_7XB;HLwjo74$Px zXn<{aD+P-V2-G$R{WxqNcCI$~%nwG0&2+9Y`osfFsM$EznzV5c%ZB23)@`S!Q1_2i z;8E=jGYh%Dwc@!D>STh=anqLnXq{3m7Kjd4c7%Q*d-vmu1KAlICMb^QZ28Qi zTCKg3fqLOAE27;0=42OyUC;pvHKGgS}5=4Gy4IA^PqB zs~iq<-VY=`L@01DKYE%&N~QRO2;0*ftEc z0$o&##+i)eX>wyl3$_hLMG_~))B5z5W{X3~T1^fI&`idgvBO#hyJN|ktxSI=L+K#V zAvK=8`TLxQTm4T>uviEbxkU8!_|q8KUq7(8mtf^2nh65|kH5G9Pv^YW=)E}8Uc;!> z{oi9`PjD{;7v-7^^vCwPuEM|0dBA+jY?-otI_3d0K`N_Z^^=jvcE*;<>UllJTm|q0 zTNB@CydKiUkQd)MnhU#6tRq2QuxV zuvPy1gAIXuW5RMabVbY1{y!O<+1>YPe5)U;W2bu8~(QE<_v+gEyPL2 ze}K?@+3ft4x;$^OrNoFcIH`L?h>?%m#O1UN9pT;1x<{mOTl+aG>nV$vkqub*`8e$` zYyGH@LRWY1`yOE{9TV!J6f!G&7AVvtXZ3Cvfm~c=c|R2@<<+$74b4Jg)1f{*fofdG zK_Oi=59uJ?ByutVWrjfN%pLeMnb;p@cm@$pPL9;J#$EMNm^~ED@|e;rLHWcx10@Fp zJ8h7QWDfd)nz3Fo--^R`cosDD!@!cxR)3`?-E)Wz5HV*jf-l4xV zD*z=e_rR{TnmL6j_eQn%kQ6&v(i4Q5b?Oi;r;R9XV120k!Ai$I8kPKT?&!VpK%_13 z1%{ZF{Bwniwv#&VgmVugIx%4w$ zFaL^Ic)0ORIx;tS*B#mxA-_FcAG4uO>__A0AFTXyGIzLDYfSG%SN(0xMM1&^Tn+u-^! zzEaIES9gj0)M$i|q~g!UL&N+AHi&NqU_o-lUm8Q02_QE@g_l{;ydHuA!G>Wlv~b~m z%Ui^V3|Y(iu&8hd;>7p+eb6VhsE%@{#I!F5UY(+gqU*#3Q$4^6-I1abRT+^0&=w)^ zDSGU2ON`$myAkWh+UNj=+6YKbG8T}sux~+r`0eKnr~~uY3~WkKGs7F-Fi6qa5KP1M z8T6pWL&b-7;)yQ4u5*LXB?CrkV{ydu4>v=*;qxX!@Jw?NAv#e+6l$WpgWnL3?~6%D zn1;o*8I!Gj8ldJ&h=A-2qAV>DlP-#ld($BOaxQd0F_6{ISbT{X)EVw~x)GoKu!)W% z-OmTwh;=F;hXJq0stncH*#+yuKQ4#{)Ify>F_^MKOb%<6oh2b*nxGNhiV-T*zqdkK z!MDR0=|}!aoVhK=S^)7{-dH7kY^I2;8z;6thK=k8W;oGeuB2+!doNXve0rFvf+5Z9 z6GDy#Y7W_^Sf4hrmvAtqjO37#!iQUg2qR?6t+&k%>-oe#n!_Y+J5f=s)k2AVV1-Yp zm6gh>KHdtG2x<&XB_k9Rf=kvOk!gt-tRS43o<*HWG%0cWPGk!jH%Q} ze)DImpB`AaRiC~5d*S}4>zjq>cQ#JTA?weD8?^&}y5RSy%`X>EvWY&t3y(~A!XFSW zKLk9=nv4VrsW^bc{I^ODVOFZquR2)isnxGpB5G>>h_8O2T5mc{1s|neuGwI{MOkrcH0;Rqe9sF{Bu7cyn=a~UYq4(`f zZx3(RX8p1~ZyyoU8slledCSmwm-}2EkYa>bqTY6#`oc+zx^~m2I&;C2?p)_TFi{um zR~gta4l%0xC7Mt98V3{;os@%bz_&QojJ1C8CWzWSR>_K&Z^j9PPtRpi+R%fHFqb%_ko>nHjPP)3Zga8TD^!JV zQ7+-0nPRT1|3pBec5A^*l_a%-PB3DVq+-4?az)U1fuS0M7K2AR9hw`zzgh4Dh#m`F zzK+<b^tO8IsFA*`jW&`#fA#g(0S6#R|x7bJKeY6X0-JKrLmYbpM~|EYN9IIvTEDnxt{eo)-EhN zh=e>*8TTl93ZTwJEinJ4GCpPFUe>!|?f?)j$PF%~|DJu34U!5{>Y2p9{=&u6OmqLE z^e2hX9pZ0U;$Ne;PhpnmgX+JA$y(}v$r4wWX@C;c2fR2sbuY(K)qJcR87w}>GWCz> z)z&-kj^&ykP`^Yr9ylB?gqV*~$z@`bzG-~E_KA@OhpDrCHtpbD^9h|^c|NrHE$nL& zpw$cu7HhjPX!lh8p}H z>!bP<rWdi$YWX`T@ca+w6F*HyU_OjFOW1293mmlSW9T@c07HedFZYtmbh$kU z44x-^!*bMTV=p9CWOyGqUugYBk40HT@EXi&jbrfYaqeoN8=oHnMa$6Y(JXA)?*#4| zl>`~|{7vxJv()UsBjwasg6Y@&xuN?;O*1~K8yc$~JMcY{k>K^z^>DFXRl=Gxv^vOl2rc6EtVHAwNYM|vQ zR{gnLujpSUQno0RSXMMHSpN<_*-m5hJ*wc%1^Hn~LCT_Hk&_eOtiWZ%;bD1AhX~-) zcvrhqegkB7-`x~v;DY7=OKj~)A&yeV|XrV#8J)LdUPYGCLMW4&G^ zGgEFdHG=*w_U8=5?;92FU%#pcoWg$|c)NiEFIDRwFHfCXCo9}ug1u7?}}$bMl=$7 zz%$~IThwY`-P~ZX4r0J`Xy4m^-|>s_3`kBd$QVr6fp#JqI69OXlU|L!kUUr4t^%*q zQkG*dOHcB9mQ>LOzg-P*CdWTGu=scq9<%Xi)A94>%S^Lt-pV(vg0Gux3RZ23O!4#c zs=X%E+~=1^Kf6;k@{>V&hDpa5#GN5sn-X2CgZH!Klo`kf^iQ-)aP(fJsOxES zVz?OCTEe7jdSAn&Z%FYIrckJd9i+_&WAa8Q8%b%TYsf+~!tNyN%Iuh?zCPHnyZ#F4fyQJH4ZKFBAxg+!i|Lc1W2IKl@C|QL=_d2s$0OErE;K9B=kPQ`tg6zq0P)@F>7>oM{dEkGt z%<%s|>%Ym~`hWV1dItRALlOO2`cNMTq)v$={m8^|8327_WDqnl*9Q-ljjo7)-1SNR zSxholD?#6sPJyETWuW@|O`(>+WD$bI;>9%T{3r-80-tb4#ZvHeE(Z>zv&Bz69pK0~ zT@J%(DjooFED z=50#S@XpLRFwd5Rmk2&?H$ z95ngsj~XJ(AGvlvxG+&_NNqp9rwm0Z)!NGe?cnVQnXzr_=i*Dp6Mu~68Zt zfH@I1_yE=Wl^_RfJ3ot3Yg=DjqSkc2ejPv4S{3-B zziWEx_asz2zudu4Imia?v;|$ypd@yt%BvMl53hF(OJT5nbU+W6E9Ej_R@B&fGaj=v zzX51y38?=D@~+dQPO^0xq{;$7-so{p+*a?MR0G`FWhZHB*^dCX^{;V?{~F|-)hLa* zeeVK|UW4}&xa&U>_7d!rUoAQG$BW { return MaterialApp( home: Scaffold( appBar: AppBar( - title: const Text('FlowyBoard example'), + title: const Text('AppFlowy Board'), ), body: _examples[_currentIndex], bottomNavigationBar: BottomNavigationBar( @@ -43,10 +43,10 @@ class _MyAppState extends State { items: [ BottomNavigationBarItem( icon: Icon(Icons.grid_on, color: _bottomNavigationColor), - label: "MultiBoardList"), + label: "MultiColumn"), BottomNavigationBarItem( icon: Icon(Icons.grid_on, color: _bottomNavigationColor), - label: "SingleBoardList"), + label: "SingleColumn"), ], onTap: (int index) { setState(() { diff --git a/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart index 8715a4450c..7fe24362d2 100644 --- a/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart @@ -23,26 +23,18 @@ class _MultiBoardListExampleState extends State { @override void initState() { - final column1 = BoardColumnData(id: "1", items: [ - TextItem("a"), - TextItem("b"), - TextItem("c"), - TextItem("d"), + final column1 = BoardColumnData(id: "To Do", items: [ + TextItem("Card 1"), + TextItem("Card 2"), + TextItem("Card 3"), + TextItem("Card 4"), ]); - final column2 = BoardColumnData(id: "2", items: [ - TextItem("1"), - TextItem("2"), - TextItem("3"), - TextItem("4"), - TextItem("5"), + final column2 = BoardColumnData(id: "In Progress", items: [ + TextItem("Card 5"), + TextItem("Card 6"), ]); - final column3 = BoardColumnData(id: "3", items: [ - TextItem("A"), - TextItem("B"), - TextItem("C"), - TextItem("D"), - ]); + final column3 = BoardColumnData(id: "Done", items: []); boardDataController.addColumn(column1); boardDataController.addColumn(column2); @@ -53,40 +45,52 @@ class _MultiBoardListExampleState extends State { @override Widget build(BuildContext context) { - return Board( - dataController: boardDataController, - background: Container(color: Colors.red), - footBuilder: (context, columnData) { - return Container( - color: Colors.purple, - height: 30, - ); - }, - headerBuilder: (context, columnData) { - return Container( - color: Colors.yellow, - height: 30, - ); - }, - cardBuilder: (context, item) { - return _RowWidget(item: item as TextItem, key: ObjectKey(item)); - }, - columnConstraints: const BoxConstraints.tightFor(width: 240), + final config = BoardConfig( + columnBackgroundColor: HexColor.fromHex('#F7F8FC'), ); - } -} - -class _RowWidget extends StatelessWidget { - final TextItem item; - const _RowWidget({Key? key, required this.item}) : super(key: key); - - @override - Widget build(BuildContext context) { return Container( - key: ObjectKey(item), - height: 60, - color: Colors.green, - child: Center(child: Text(item.s)), + color: Colors.white, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), + child: Board( + dataController: boardDataController, + footBuilder: (context, columnData) { + return AppFlowyColumnFooter( + icon: const Icon(Icons.add, size: 20), + title: const Text('New'), + height: 50, + margin: config.columnItemPadding, + ); + }, + headerBuilder: (context, columnData) { + return AppFlowyColumnHeader( + icon: const Icon(Icons.lightbulb_circle), + title: Text(columnData.id), + addIcon: const Icon(Icons.add, size: 20), + moreIcon: const Icon(Icons.more_horiz, size: 20), + height: 50, + margin: config.columnItemPadding, + ); + }, + cardBuilder: (context, item) { + final textItem = item as TextItem; + return AppFlowyColumnItemCard( + key: ObjectKey(item), + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text(textItem.s), + ), + ), + ); + }, + columnConstraints: const BoxConstraints.tightFor(width: 240), + config: BoardConfig( + columnBackgroundColor: HexColor.fromHex('#F7F8FC'), + ), + ), + ), ); } } @@ -99,3 +103,12 @@ class TextItem extends ColumnItem { @override String get id => s; } + +extension HexColor on Color { + static Color fromHex(String hexString) { + final buffer = StringBuffer(); + if (hexString.length == 6 || hexString.length == 7) buffer.write('ff'); + buffer.write(hexString.replaceFirst('#', '')); + return Color(int.parse(buffer.toString(), radix: 16)); + } +} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart b/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart index 684868a2a3..fc8f3c662f 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart @@ -2,4 +2,5 @@ library appflowy_board; export 'src/widgets/board_column/board_column_data.dart'; export 'src/widgets/board_data.dart'; +export 'src/widgets/styled_widgets/appflowy_styled_widgets.dart'; export 'src/widgets/board.dart'; diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart index 6f923ddf16..b9f766f961 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart @@ -6,7 +6,7 @@ const DART_LOG = "Dart_LOG"; class Log { // static const enableLog = bool.hasEnvironment(DART_LOG); // static final shared = Log(); - static const enableLog = false; + static const enableLog = true; static void info(String? message) { if (enableLog) { diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart index bef98842c0..3cd2a331f1 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart @@ -3,23 +3,29 @@ import 'package:provider/provider.dart'; import 'board_column/board_column.dart'; import 'board_column/board_column_data.dart'; import 'board_data.dart'; -import 'flex/drag_target_inteceptor.dart'; -import 'flex/reorder_flex.dart'; -import 'phantom/phantom_controller.dart'; +import 'reorder_flex/drag_target_inteceptor.dart'; +import 'reorder_flex/reorder_flex.dart'; +import 'reorder_phantom/phantom_controller.dart'; import '../rendering/board_overlay.dart'; +class BoardConfig { + final double cornerRadius; + final EdgeInsets columnPadding; + final EdgeInsets columnItemPadding; + final Color columnBackgroundColor; + + const BoardConfig({ + this.cornerRadius = 6.0, + this.columnPadding = const EdgeInsets.symmetric(horizontal: 8), + this.columnItemPadding = const EdgeInsets.symmetric(horizontal: 10), + this.columnBackgroundColor = Colors.transparent, + }); +} + class Board extends StatelessWidget { /// The direction to use as the main axis. final Axis direction = Axis.vertical; - /// How much space to place between children in a run in the main axis. - /// Defaults to 10.0. - final double spacing; - - /// How much space to place between the runs themselves in the cross axis. - /// Defaults to 0.0. - final double runSpacing; - /// final Widget? background; @@ -40,15 +46,16 @@ class Board extends StatelessWidget { /// final BoardPhantomController phantomController; + final BoardConfig config; + Board({ required this.dataController, required this.cardBuilder, - this.spacing = 10.0, - this.runSpacing = 0.0, this.background, this.footBuilder, this.headerBuilder, this.columnConstraints = const BoxConstraints(maxWidth: 200), + this.config = const BoardConfig(), Key? key, }) : phantomController = BoardPhantomController(delegate: dataController), super(key: key); @@ -60,9 +67,9 @@ class Board extends StatelessWidget { child: Consumer( builder: (context, notifier, child) { return BoardContent( + config: config, dataController: dataController, background: background, - spacing: spacing, delegate: phantomController, columnConstraints: columnConstraints, cardBuilder: cardBuilder, @@ -84,8 +91,8 @@ class BoardContent extends StatefulWidget { final OnDragEnded? onDragEnded; final BoardDataController dataController; final Widget? background; - final double spacing; - final ReorderFlexConfig config; + final BoardConfig config; + final ReorderFlexConfig reorderFlexConfig; final BoxConstraints columnConstraints; /// @@ -101,7 +108,8 @@ class BoardContent extends StatefulWidget { final BoardPhantomController phantomController; - BoardContent({ + const BoardContent({ + required this.config, required this.onReorder, required this.delegate, required this.dataController, @@ -109,14 +117,13 @@ class BoardContent extends StatefulWidget { this.onDragEnded, this.scrollController, this.background, - this.spacing = 10.0, required this.columnConstraints, required this.cardBuilder, this.footBuilder, this.headerBuilder, required this.phantomController, Key? key, - }) : config = ReorderFlexConfig(spacing: spacing), + }) : reorderFlexConfig = const ReorderFlexConfig(), super(key: key); @override @@ -140,7 +147,7 @@ class _BoardContentState extends State { final reorderFlex = ReorderFlex( key: widget.key, - config: widget.config, + config: widget.reorderFlexConfig, scrollController: widget.scrollController, onDragStarted: widget.onDragStarted, onReorder: widget.onReorder, @@ -154,7 +161,15 @@ class _BoardContentState extends State { return Stack( alignment: AlignmentDirectional.topStart, children: [ - if (widget.background != null) widget.background!, + if (widget.background != null) + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(widget.config.cornerRadius), + ), + child: widget.background, + ), reorderFlex, ], ); @@ -173,8 +188,12 @@ class _BoardContentState extends State { } List _buildColumns() { - final List children = widget.dataController.columnDatas.map( - (columnData) { + final List children = + widget.dataController.columnDatas.asMap().entries.map( + (item) { + final columnData = item.value; + final columnIndex = item.key; + final dataSource = _BoardColumnDataSourceImpl( columnId: columnData.id, dataController: widget.dataController, @@ -188,6 +207,8 @@ class _BoardContentState extends State { return ConstrainedBox( constraints: widget.columnConstraints, child: BoardColumnWidget( + margin: _marginFromIndex(columnIndex), + itemMargin: widget.config.columnItemPadding, headerBuilder: widget.headerBuilder, footBuilder: widget.footBuilder, cardBuilder: widget.cardBuilder, @@ -195,7 +216,8 @@ class _BoardContentState extends State { scrollController: ScrollController(), phantomController: widget.phantomController, onReorder: widget.dataController.moveColumnItem, - spacing: 10, + cornerRadius: widget.config.cornerRadius, + backgroundColor: widget.config.columnBackgroundColor, ), ); }, @@ -206,6 +228,22 @@ class _BoardContentState extends State { return children; } + + EdgeInsets _marginFromIndex(int index) { + if (widget.dataController.columnDatas.isEmpty) { + return widget.config.columnPadding; + } + + if (index == 0) { + return EdgeInsets.only(right: widget.config.columnPadding.right); + } + + if (index == widget.dataController.columnDatas.length - 1) { + return EdgeInsets.only(left: widget.config.columnPadding.left); + } + + return widget.config.columnPadding; + } } class _BoardColumnDataSourceImpl extends BoardColumnDataDataSource { diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart index f95f4dae5c..d8981096e3 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart @@ -3,9 +3,9 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import '../../rendering/board_overlay.dart'; import '../../utils/log.dart'; -import '../phantom/phantom_controller.dart'; -import '../flex/reorder_flex.dart'; -import '../flex/drag_target_inteceptor.dart'; +import '../reorder_phantom/phantom_controller.dart'; +import '../reorder_flex/reorder_flex.dart'; +import '../reorder_flex/drag_target_inteceptor.dart'; import 'board_column_data.dart'; typedef OnColumnDragStarted = void Function(int index); @@ -79,7 +79,15 @@ class BoardColumnWidget extends StatefulWidget { final BoardColumnFooterBuilder? footBuilder; - BoardColumnWidget({ + final EdgeInsets margin; + + final EdgeInsets itemMargin; + + final double cornerRadius; + + final Color backgroundColor; + + const BoardColumnWidget({ Key? key, this.headerBuilder, this.footBuilder, @@ -90,8 +98,11 @@ class BoardColumnWidget extends StatefulWidget { this.onDragStarted, this.scrollController, this.onDragEnded, - double? spacing, - }) : config = ReorderFlexConfig(spacing: spacing), + this.margin = EdgeInsets.zero, + this.itemMargin = EdgeInsets.zero, + this.cornerRadius = 0.0, + this.backgroundColor = Colors.transparent, + }) : config = const ReorderFlexConfig(), super(key: key); @override @@ -149,12 +160,25 @@ class _BoardColumnWidgetState extends State { children: children, ); - return Column( - children: [ - if (header != null) header, - Expanded(child: reorderFlex), - if (footer != null) footer, - ], + return Container( + margin: widget.margin, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: widget.backgroundColor, + borderRadius: BorderRadius.circular(widget.cornerRadius), + ), + child: Column( + children: [ + if (header != null) header, + Expanded( + child: Padding( + padding: widget.itemMargin, + child: reorderFlex, + ), + ), + if (footer != null) footer, + ], + ), ); }, opaque: false, diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart index 24d3cc1a96..2ce739220e 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart @@ -3,7 +3,7 @@ import 'dart:collection'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import '../../utils/log.dart'; -import '../flex/reorder_flex.dart'; +import '../reorder_flex/reorder_flex.dart'; abstract class ColumnItem extends ReoderFlexItem { bool get isPhantom => false; @@ -92,10 +92,16 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin { /// Replace the item at index with the [newItem]. void replace(int index, ColumnItem newItem) { - final removedItem = columnData._items.removeAt(index); - columnData._items.insert(index, newItem); - Log.debug( - '[$BoardColumnDataController] $columnData replace $removedItem with $newItem at $index'); + if (columnData._items.isEmpty) { + columnData._items.add(newItem); + Log.debug('[$BoardColumnDataController] $columnData add $newItem'); + } else { + final removedItem = columnData._items.removeAt(index); + columnData._items.insert(index, newItem); + Log.debug( + '[$BoardColumnDataController] $columnData replace $removedItem with $newItem at $index'); + } + notifyListeners(); } } @@ -119,6 +125,6 @@ class BoardColumnData extends ReoderFlexItem with EquatableMixin { @override String toString() { - return 'Column$id'; + return 'Column:[$id]'; } } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart index fe2fca2c92..06e8ff1a57 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart @@ -4,9 +4,9 @@ import 'package:equatable/equatable.dart'; import '../utils/log.dart'; import 'board_column/board_column_data.dart'; -import 'flex/reorder_flex.dart'; +import 'reorder_flex/reorder_flex.dart'; import 'package:flutter/material.dart'; -import 'phantom/phantom_controller.dart'; +import 'reorder_phantom/phantom_controller.dart'; typedef OnMoveColumn = void Function(int fromIndex, int toIndex); @@ -79,8 +79,11 @@ class BoardDataController extends ChangeNotifier int toColumnIndex, ) { final item = columnController(fromColumnId).removeAt(fromColumnIndex); - assert( - columnController(toColumnId).items[toColumnIndex] is PhantomColumnItem); + + if (columnController(toColumnId).items.length > toColumnIndex) { + assert(columnController(toColumnId).items[toColumnIndex] + is PhantomColumnItem); + } columnController(toColumnId).replace(toColumnIndex, item); @@ -120,7 +123,7 @@ class BoardDataController extends ChangeNotifier columnController.removeAt(index); Log.debug( - '[$BoardDataController] Column$columnId remove phantom, current count: ${columnController.items.length}'); + '[$BoardDataController] Column:[$columnId] remove phantom, current count: ${columnController.items.length}'); } return isExist; } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart index fafdcef774..ea8cc91fab 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:provider/provider.dart'; import '../transitions.dart'; abstract class DragTargetData { @@ -65,6 +67,8 @@ class ReorderDragTarget extends StatefulWidget { final AnimationController insertAnimationController; final AnimationController deleteAnimationController; + final bool useMoveAnimation; + ReorderDragTarget({ Key? key, required this.child, @@ -74,6 +78,7 @@ class ReorderDragTarget extends StatefulWidget { required this.onWillAccept, required this.insertAnimationController, required this.deleteAnimationController, + required this.useMoveAnimation, this.onAccept, this.onLeave, this.draggableTargetBuilder, @@ -140,7 +145,10 @@ class _ReorderDragTargetState data: widget.dragTargetData, ignoringFeedbackSemantics: false, feedback: feedbackBuilder, - childWhenDragging: IgnorePointerWidget(child: widget.child), + childWhenDragging: IgnorePointerWidget( + useIntrinsicSize: !widget.useMoveAnimation, + child: widget.child, + ), onDragStarted: () { _draggingFeedbackSize = widget._indexGlobalKey.currentContext?.size; widget.onDragStarted( @@ -174,11 +182,13 @@ class _ReorderDragTargetState transform: Matrix4.rotationZ(0), alignment: FractionalOffset.topLeft, child: Material( - elevation: 3.0, color: Colors.transparent, borderRadius: BorderRadius.zero, clipBehavior: Clip.hardEdge, - child: ConstrainedBox(constraints: constraints, child: child), + child: ConstrainedBox( + constraints: constraints, + child: Opacity(opacity: 0.6, child: child), + ), ), ); } @@ -254,10 +264,12 @@ class IgnorePointerWidget extends StatelessWidget { final sizedChild = useIntrinsicSize ? child : SizedBox(width: 0.0, height: 0.0, child: child); + + final opacity = useIntrinsicSize ? 0.3 : 0.0; return IgnorePointer( ignoring: true, child: Opacity( - opacity: 0, + opacity: opacity, child: sizedChild, ), ); @@ -282,6 +294,82 @@ class PhantomWidget extends StatelessWidget { } } +abstract class DragTargetMovePlaceholderDelegate { + void registerPlaceholder( + int dragTargetIndex, + void Function(int currentDragTargetIndex) callback, + ); + + void unregisterPlaceholder(int dragTargetIndex); +} + +class DragTargeMovePlaceholder extends StatefulWidget { + final double height; + final Color color; + final Color highlightColor; + final int dragTargetIndex; + final DragTargetMovePlaceholderDelegate delegate; + + const DragTargeMovePlaceholder({ + required this.delegate, + required this.dragTargetIndex, + this.height = 4, + this.color = Colors.transparent, + this.highlightColor = Colors.lightBlue, + Key? key, + }) : super(key: key); + + @override + State createState() => + _DragTargeMovePlaceholderState(); +} + +class _DragTargeMovePlaceholderState extends State { + ValueNotifier isHighlight = ValueNotifier(false); + + @override + void initState() { + widget.delegate.registerPlaceholder( + widget.dragTargetIndex, + (currentDragTargetIndex) { + if (!mounted) return; + + SchedulerBinding.instance.addPostFrameCallback((Duration duration) { + if (currentDragTargetIndex == -1) { + isHighlight.value = false; + } else { + isHighlight.value = + widget.dragTargetIndex == currentDragTargetIndex; + } + }); + }, + ); + super.initState(); + } + + @override + void dispose() { + isHighlight.dispose(); + widget.delegate.unregisterPlaceholder(widget.dragTargetIndex); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider.value( + value: isHighlight, + child: Consumer>( + builder: (context, notifier, child) { + return Container( + height: widget.height, + color: notifier.value ? widget.highlightColor : widget.color, + ); + }, + ), + ); + } +} + abstract class FakeDragTargetEventTrigger { void fakeOnDragEnded(VoidCallback callback); } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_inteceptor.dart index 86152ed0de..da529819dd 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_inteceptor.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_inteceptor.dart @@ -30,12 +30,14 @@ abstract class DragTargetInterceptor { } abstract class OverlapDragTargetDelegate { - void didReturnOriginalDragTarget(); - void didCrossOtherDragTarget( + void cancel(); + void moveTo( String reorderFlexId, FlexDragTargetData dragTargetData, int dragTargetIndex, ); + + bool canMoveTo(String dragTargetId); } /// [OverlappingDragTargetInteceptor] is used to receive the overlapping @@ -68,13 +70,11 @@ class OverlappingDragTargetInteceptor extends DragTargetInterceptor { required String dragTargetId, required int dragTargetIndex}) { if (dragTargetId == dragTargetData.reorderFlexId) { - delegate.didReturnOriginalDragTarget(); + delegate.cancel(); } else { - delegate.didCrossOtherDragTarget( - dragTargetId, - dragTargetData, - dragTargetIndex, - ); + if (delegate.canMoveTo(dragTargetId)) { + delegate.moveTo(dragTargetId, dragTargetData, 0); + } } return true; @@ -128,13 +128,13 @@ class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor { @override void onAccept(FlexDragTargetData dragTargetData) { Log.trace( - '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept'); + '[$CrossReorderFlexDragTargetInterceptor] Column:[$reorderFlexId] on onAccept'); } @override void onLeave(FlexDragTargetData dragTargetData) { Log.trace( - '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave'); + '[$CrossReorderFlexDragTargetInterceptor] Column:[$reorderFlexId] on leave'); } @override diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart index 9066c987f0..04de8cae7b 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart @@ -41,16 +41,19 @@ class ReorderFlexConfig { // How long an animation to scroll to an off-screen element final Duration scrollAnimationDuration = const Duration(milliseconds: 250); - final double? spacing; + final bool useMoveAnimation; - const ReorderFlexConfig({this.spacing}); + final bool useMovePlaceholder; + + const ReorderFlexConfig({ + this.useMoveAnimation = true, + }) : useMovePlaceholder = !useMoveAnimation; } class ReorderFlex extends StatefulWidget { final ReorderFlexConfig config; final List children; - final EdgeInsets? padding; /// [direction] How to place the children, default is Axis.vertical final Axis direction; @@ -81,7 +84,6 @@ class ReorderFlex extends StatefulWidget { this.onDragStarted, this.onDragEnded, this.interceptor, - this.padding, this.direction = Axis.vertical, }) : super(key: key); @@ -108,8 +110,11 @@ class ReorderFlexState extends State /// [_animation] controls the dragging animations late DragTargetAnimation _animation; + late ReorderFlexNotifier _notifier; + @override void initState() { + _notifier = ReorderFlexNotifier(); dragState = DraggingState(widget.reorderFlexId); _animation = DragTargetAnimation( @@ -154,13 +159,14 @@ class ReorderFlexState extends State for (int i = 0; i < widget.children.length; i += 1) { Widget child = widget.children[i]; + children.add(_wrap(child, i)); - if (widget.config.spacing != null) { - children.add(SizedBox(width: widget.config.spacing!)); - } - - final wrapChild = _wrap(child, i); - children.add(wrapChild); + // if (widget.config.useMovePlaceholder) { + // children.add(DragTargeMovePlaceholder( + // dragTargetIndex: i, + // delegate: _notifier, + // )); + // } } final child = _wrapContainer(children); @@ -199,7 +205,8 @@ class ReorderFlexState extends State /// [childIndex]: the index of the child in a list Widget _wrap(Widget child, int childIndex) { return Builder(builder: (context) { - final dragTarget = _buildDragTarget(context, child, childIndex); + final ReorderDragTarget dragTarget = + _buildDragTarget(context, child, childIndex); int shiftedIndex = childIndex; if (dragState.isOverlapWithPhantom()) { @@ -207,7 +214,7 @@ class ReorderFlexState extends State } Log.trace( - 'Rebuild: Column${dragState.id} ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex'); + 'Rebuild: Column:[${dragState.id}] ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex'); final currentIndex = dragState.currentIndex; final dragPhantomIndex = dragState.phantomIndex; @@ -234,15 +241,18 @@ class ReorderFlexState extends State } /// Determine the size of the drop area to show under the dragging widget. - final feedbackSize = dragState.feedbackSize; + Size? feedbackSize = Size.zero; + if (widget.config.useMoveAnimation) { + feedbackSize = dragState.feedbackSize; + } + Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize); Widget disappearSpace = _makeDisappearSpace(dragSpace, feedbackSize); /// When start dragging, the dragTarget, [ReorderDragTarget], will /// return a [IgnorePointerWidget] which size is zero. if (dragState.isPhantomAboveDragTarget()) { - //the phantom is moving down, i.e. the tile below the phantom is moving up - Log.trace('index:$childIndex item moving up / phantom moving down'); + _notifier.updateDragTargetIndex(currentIndex); if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) { return _buildDraggingContainer(children: [ disappearSpace, @@ -264,8 +274,7 @@ class ReorderFlexState extends State /// if (dragState.isPhantomBelowDragTarget()) { - //the phantom is moving up, i.e. the tile above the phantom is moving down - Log.trace('index:$childIndex item moving down / phantom moving up'); + _notifier.updateDragTargetIndex(currentIndex); if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) { return _buildDraggingContainer(children: [ appearSpace, @@ -303,10 +312,7 @@ class ReorderFlexState extends State } ReorderDragTarget _buildDragTarget( - BuildContext builderContext, - Widget child, - int dragTargetIndex, - ) { + BuildContext builderContext, Widget child, int dragTargetIndex) { final ReoderFlexItem reorderFlexItem = widget.dataSource.items[dragTargetIndex]; return ReorderDragTarget( @@ -319,14 +325,14 @@ class ReorderFlexState extends State ), onDragStarted: (draggingWidget, draggingIndex, size) { Log.debug( - "[DragTarget] Column${widget.dataSource.identifier} start dragging item at $draggingIndex"); + "[DragTarget] Column:[${widget.dataSource.identifier}] start dragging item at $draggingIndex"); _startDragging(draggingWidget, draggingIndex, size); widget.onDragStarted?.call(draggingIndex); }, onDragEnded: (dragTargetData) { Log.debug( - "[DragTarget]: Column${widget.dataSource.identifier} end dragging"); - + "[DragTarget]: Column:[${widget.dataSource.identifier}] end dragging"); + _notifier.updateDragTargetIndex(-1); setState(() { if (dragTargetData.reorderFlexId == widget.reorderFlexId) { _onReordered( @@ -340,14 +346,11 @@ class ReorderFlexState extends State }); }, onWillAccept: (FlexDragTargetData dragTargetData) { - Log.debug('Insert animation: ${_animation.deleteController.status}'); - if (_animation.deleteController.isAnimating) { return false; } assert(widget.dataSource.items.length > dragTargetIndex); - if (_interceptDragTarget( dragTargetData, (interceptor) => interceptor.onWillAccept( @@ -370,6 +373,7 @@ class ReorderFlexState extends State ); }, onLeave: (dragTargetData) { + _notifier.updateDragTargetIndex(-1); _interceptDragTarget( dragTargetData, (interceptor) => interceptor.onLeave(dragTargetData), @@ -378,6 +382,7 @@ class ReorderFlexState extends State insertAnimationController: _animation.insertController, deleteAnimationController: _animation.deleteController, draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder, + useMoveAnimation: widget.config.useMoveAnimation, child: child, ); } @@ -430,7 +435,7 @@ class ReorderFlexState extends State /// The [willAccept] will be true if the dargTarget is the widget that gets /// dragged and it is dragged on top of the other dragTargets. /// - Log.trace( + Log.debug( '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}'); bool willAccept = @@ -442,7 +447,6 @@ class ReorderFlexState extends State } else { dragState.updateNextIndex(dragTargetIndex); } - _requestAnimationToNextIndex(isAcceptingNewTarget: true); }); @@ -467,7 +471,6 @@ class ReorderFlexState extends State } else { return SingleChildScrollView( scrollDirection: widget.direction, - padding: widget.padding, controller: _scrollController, child: child, ); diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart index a90ee6a83a..accdaa866b 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; import '../transitions.dart'; +import 'drag_target.dart'; mixin ReorderFlexMinxi { @protected @@ -86,3 +87,56 @@ extension CurveAnimationController on AnimationController { ); } } + +class ReorderFlexNotifier extends DragTargetMovePlaceholderDelegate { + Map dragTargeEventNotifier = {}; + + void updateDragTargetIndex(int index) { + for (var notifier in dragTargeEventNotifier.values) { + notifier.setDragTargetIndex(index); + } + } + + DragTargetEventNotifier _notifierFromIndex(int dragTargetIndex) { + DragTargetEventNotifier? notifier = dragTargeEventNotifier[dragTargetIndex]; + if (notifier == null) { + final newNotifier = DragTargetEventNotifier(); + dragTargeEventNotifier[dragTargetIndex] = newNotifier; + notifier = newNotifier; + } + + return notifier; + } + + void dispose() { + for (var notifier in dragTargeEventNotifier.values) { + notifier.dispose(); + } + } + + @override + void registerPlaceholder( + int dragTargetIndex, + void Function(int dragTargetIndex) callback, + ) { + _notifierFromIndex(dragTargetIndex).addListener(() { + callback.call(_notifierFromIndex(dragTargetIndex).currentDragTargetIndex); + }); + } + + @override + void unregisterPlaceholder(int dragTargetIndex) { + dragTargeEventNotifier.remove(dragTargetIndex); + } +} + +class DragTargetEventNotifier extends ChangeNotifier { + int currentDragTargetIndex = -1; + + void setDragTargetIndex(int index) { + if (currentDragTargetIndex != index) { + currentDragTargetIndex = index; + notifyListeners(); + } + } +} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart index 6b68eefd52..266c83d873 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart @@ -1,9 +1,11 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import '../../utils/log.dart'; import '../board_column/board_column_data.dart'; -import '../flex/drag_state.dart'; -import '../flex/drag_target.dart'; -import '../flex/drag_target_inteceptor.dart'; +import '../reorder_flex/drag_state.dart'; +import '../reorder_flex/drag_target.dart'; +import '../reorder_flex/drag_target_inteceptor.dart'; import 'phantom_state.dart'; abstract class BoardPhantomControllerDelegate { @@ -127,8 +129,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate FlexDragTargetData dragTargetData, int dragTargetIndex, ) { - // Log.debug('[$BoardPhantomController] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' - // 'to Column$columnId:$index'); + // Log.debug('[$BoardPhantomController] move Column:[${dragTargetData.reorderFlexId}]:${dragTargetData.draggingIndex} ' + // 'to Column:[$columnId]:$index'); phantomRecord = PhantomRecord( toColumnId: columnId, @@ -177,7 +179,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate } @override - void didReturnOriginalDragTarget() { + void cancel() { if (phantomRecord == null) { return; } @@ -188,7 +190,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate } @override - void didCrossOtherDragTarget( + void moveTo( String reorderFlexId, FlexDragTargetData dragTargetData, int dragTargetIndex, @@ -199,6 +201,12 @@ class BoardPhantomController extends OverlapDragTargetDelegate dragTargetIndex, ); } + + @override + bool canMoveTo(String dragTargetId) { + // TODO: implement shouldReceive + return delegate.controller(dragTargetId)?.columnData.items.length == 0; + } } /// Use [PhantomRecord] to record where to remove the column item and where to @@ -228,7 +236,7 @@ class PhantomRecord { return; } Log.debug( - '[$PhantomRecord] Update Column$fromColumnId remove position to $index'); + '[$PhantomRecord] Update Column:[$fromColumnId] remove position to $index'); fromColumnIndex = index; } @@ -238,13 +246,13 @@ class PhantomRecord { } Log.debug( - '[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); + '[$PhantomRecord] Column:[$toColumnId] update position $toColumnIndex -> $index'); toColumnIndex = index; } @override String toString() { - return 'Column$fromColumnId:$fromColumnIndex to Column$toColumnId:$toColumnIndex'; + return 'Column:[$fromColumnId]:$fromColumnIndex to Column:[$toColumnId]:$toColumnIndex'; } } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/appflowy_styled_widgets.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/appflowy_styled_widgets.dart new file mode 100644 index 0000000000..b802d15dae --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/appflowy_styled_widgets.dart @@ -0,0 +1,3 @@ +export 'card.dart'; +export 'footer.dart'; +export 'header.dart'; diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart new file mode 100644 index 0000000000..b2e5085649 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class AppFlowyColumnItemCard extends StatefulWidget { + final Widget? child; + final Color backgroundColor; + final double cornerRadius; + final BoxConstraints boxConstraints; + + const AppFlowyColumnItemCard({ + this.child, + this.backgroundColor = Colors.white, + this.cornerRadius = 0.0, + this.boxConstraints = const BoxConstraints.tightFor(height: 60), + Key? key, + }) : super(key: key); + + @override + State createState() => _AppFlowyColumnItemCardState(); +} + +class _AppFlowyColumnItemCardState extends State { + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: Container( + constraints: widget.boxConstraints, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: widget.backgroundColor, + borderRadius: BorderRadius.circular(widget.cornerRadius), + ), + child: widget.child, + ), + ); + } +} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart new file mode 100644 index 0000000000..7f5655fe60 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +typedef OnFooterAddButtonClick = void Function(); + +class AppFlowyColumnFooter extends StatefulWidget { + final double height; + final Widget? icon; + final Widget? title; + final EdgeInsets margin; + final OnFooterAddButtonClick? onAddButtonClick; + + const AppFlowyColumnFooter({ + this.icon, + this.title, + this.margin = EdgeInsets.zero, + required this.height, + this.onAddButtonClick, + Key? key, + }) : super(key: key); + + @override + State createState() => _AppFlowyColumnFooterState(); +} + +class _AppFlowyColumnFooterState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onAddButtonClick, + child: SizedBox( + height: widget.height, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (widget.icon != null) widget.icon!, + if (widget.title != null) widget.title!, + ], + ), + ), + ), + ); + } +} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart new file mode 100644 index 0000000000..fdebc7ef21 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +typedef OnHeaderAddButtonClick = void Function(); +typedef OnHeaderMoreButtonClick = void Function(); + +class AppFlowyColumnHeader extends StatefulWidget { + final double height; + final Widget? icon; + final Widget? title; + final Widget? addIcon; + final Widget? moreIcon; + final EdgeInsets margin; + final OnHeaderAddButtonClick? onAddButtonClick; + final OnHeaderMoreButtonClick? onMoreButtonClick; + + const AppFlowyColumnHeader({ + required this.height, + this.icon, + this.title, + this.addIcon, + this.moreIcon, + this.margin = EdgeInsets.zero, + this.onAddButtonClick, + this.onMoreButtonClick, + Key? key, + }) : super(key: key); + + @override + State createState() => _AppFlowyColumnHeaderState(); +} + +class _AppFlowyColumnHeaderState extends State { + @override + Widget build(BuildContext context) { + List children = []; + + if (widget.icon != null) { + children.add(widget.icon!); + children.add(_hSpace()); + } + + if (widget.title != null) { + children.add(widget.title!); + children.add(_hSpace()); + } + + if (widget.moreIcon != null) { + children.add(const Spacer()); + children.add( + IconButton(onPressed: widget.onMoreButtonClick, icon: widget.moreIcon!), + ); + } + + if (widget.addIcon != null) { + children.add( + IconButton(onPressed: widget.onAddButtonClick, icon: widget.addIcon!), + ); + } + + return SizedBox( + height: widget.height, + child: Padding( + padding: widget.margin, + child: Row( + children: children, + ), + ), + ); + } + + Widget _hSpace() { + return const SizedBox(width: 6); + } +}