From 722ba65ce8eac84d572d38b7b1e381939e077fff Mon Sep 17 00:00:00 2001 From: ardocrat Date: Sat, 22 Jul 2023 01:59:19 +0300 Subject: [PATCH] ui: add logo, mnemonic creation step, update translations --- Cargo.lock | 1 + Cargo.toml | 2 +- img/logo.png | Bin 0 -> 45224 bytes locales/en.yml | 15 +- locales/ru.yml | 15 +- src/gui/app.rs | 2 + src/gui/views/wallets/creation/connection.rs | 3 +- .../creation/{content.rs => creation.rs} | 210 +++++++++------ src/gui/views/wallets/creation/mnemonic.rs | 250 ++++++++++++------ src/gui/views/wallets/creation/mod.rs | 12 +- src/gui/views/wallets/creation/types.rs | 115 ++++++++ src/gui/views/wallets/wallets.rs | 19 +- src/lib.rs | 5 + 13 files changed, 457 insertions(+), 192 deletions(-) create mode 100644 img/logo.png rename src/gui/views/wallets/creation/{content.rs => creation.rs} (68%) create mode 100644 src/gui/views/wallets/creation/types.rs diff --git a/Cargo.lock b/Cargo.lock index 3248df6..2a29ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1548,6 +1548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9278f4337b526f0d57e5375e5a7340a311fa6ee8f9fcc75721ac50af13face02" dependencies = [ "egui", + "image", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 194b47e..44cb0e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ grin_wallet_impls = { path = "../grin/wallet/impls" } pollster = "0.3.0" wgpu = "0.16.1" egui = { version = "0.22.0", default-features = false } -egui_extras = "0.22.0" +egui_extras = { version = "0.22.0", features = ["image"] } ## other futures = "0.3" diff --git a/img/logo.png b/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..744095767db96fa9fc648c6c03939fd486098423 GIT binary patch literal 45224 zcmeFZhf|Yl_XSEqkN`?YL}}6q3IZY^RfR|8LitI zMidlavy(5zQ^0Rlw>8p%9}xQ+dN(L2%A@HH?ov@ufSx<5s~c#ktMj_LyF7G!{D6W& zu~51Dd8x};d<~9lVFnlbX^XGFvD%~3JO7%M{nm$S0-@b* z;KOHgQ)<&2j0Y5Mo^D1@UojsivRZudaUF~HCUbieDKZW3CJo5YIW#=UiKUTDwq#Q; zh2CXk`gDa~y6a8sdscM&kP;Z(9KLz!*driHS;j?@mzTLmsgqoQ4ni)QE#7y=eTSs@k@40;pv-ERPf9j zUWbH$wMNUrE0U{%!7Qre@M&}eIMX`A+&hJMuiY_?;)6Xw+hxD-jO||R7RoZ58+15| z|LkdLydBuZW&67$MsFnk+0|e*kdMkcmrf96ttv5T$kK@?mqGPKgZ4NDb%<)w?U5yA z;_AZk%A|Dhkv|26nfqjRQ%F0oKd0<;ZfQ^)pZv>c&W#0rL*she+ymIrp_4C=nnyvqvbc*CGsk7=QGSy`odBG9ZX z5pabKya^0@#;ahZ`jL;<4_pBO(~PK_ypD<7pO~3vkPGo}f7rc{U9gm_k|QOpgy`AK zUsV2~G(NEMbDaSMVN|8~ziaj=B_D)foszLwN&L2O!tI;5Ww`@|XLy9;7S75-%Cb z`_*W>u+M+{v3Y!mj926fqyPhd`S-UfBil&~>4P5;A&+U$Cc4U)cBkaerlE~yHQDpW zeD3|{1Ld@j%dmVZ$LRQIr5yK8*7&Z3{Krk0E$mVGJ<<`;Ki9pN_y62culhC9A#6~DE8&*NWO82~S?%7;DZQeXoi${Xp)B9WZ=8EF`k2bQ(^z5z#Reyq8|Aq=5$2LFN~EE<9)<4uu>LtmoL z1M3#zoagA1uu1AEId!r0b+uyXr%QyDk2%S(@LN0x^L|0Bxy!NQg5UnOB$>9H|DWAF z*;g^9wWzUiZ>P*6@1o8EmsP7-kA-Yleg8wjpuk$j+Zq{u-h!wB1Z@7BvH74Etk4VN zt&ZGybKCp1(4T?vo|k}JEUi-;d05TQ_DG3ENJM{9=4;~V?B1c7P}Kr(VR6paXce2; ztp%9+_?>DQas6%oFBkK-<$V7fj_NEV>bkb5Z%A&j!ku#TL?SfueD=YRwP|*b#t&&A z40vyXO51~|#rE2HOc$i%>_&h`b>|z$ldms*8V>&y@pqj56|o2D__belZ&Z*EEWP9G zjt`bc+;=+k;aDA%tA{0?s`1>vj;c~bk-~B%Ub6nVY0T{BcI958g;_j06OnM6aq37zY_n?EH+{e3@1AE^TJq`4>rq>Ea*DtGWTUZU`md z_Tgu%TUq@S!r8l)lAPe%%lRiG5Yf-@cBwwxpDf%$o!{RUBJN=FcgQ$lS^E1O*nfo} z^fuwO4y*2j!fixpz=4mv|EdSbQ#_M5CKUhume_aMNw_&_MbBpUvKep7xR=g-Ld?H5 z=eY!d3n!?<>SUv!kw3VftJ2~5F7hJ}okpnYWAKH?8KEw9Hy#v>zHQn_lTbq>Yi_XF>?N5yk!3p`W33$}5IQn;gsYPM70k8a}R zM%^Zr)+GJE4kdPss=?coMWXq=R4EttqIEqn&kz;xh4HVY+yo~(7m(4s!7Jg4VP17R zcGaUe%qHfu!XMZJ;8ijNJ@@f)ZC+BAH`<}pk-WP}+LLX`ukf%3o)S}`I#kkmG&*Ur zKzhCss@Umn)&O7OaeMy%9>`ZJ+#Ui^|M+cZg;$_Mtz*FCq0^Q_Gg10Z9o-v6pQ4(; z_TAph&dm{f1ghD#@nWe%C{4II>CE2zoOVd1SG25~pQdxW$44WB6BONgNH*OcBf;qrliZihG897?B<#n&!~Q(| z8oXEB-#;tbqi5cg8SlX<39?A?w*!D8p%|iCJ5!x-gk=pd{yDnC*3_6vHx*$7C z?EjTpRQte`)Ud;sEQp+%cN#c7@_v;g>M%wfUeZti?B5q7{PZH860ro}hq;8CnYinG zsyji5)t$ifzr%_ZpaN3FPkqJA*IYL_6X>pTQ>Quo-X5K;-2Msk)yTLER&FH@S|Y;R z&8OGfJ$B!(uEnNTJ*ZkY|1;J@EePx_20KJtp%jTrV59U2=hruxvv(8?1-;M2Qbvoh zn>}D_4mencO`>!Jm$>M=s4UJ?<946VFUFofNvi+sjDZ$NKMegO+jG$hZh&L3EyuaP-A5_9h4z^oDaYCZ3LalpZQ~EAP7vP zYyzbnCe1U0kGfYw&uwa-UW`(BYS3}d0=!E0=r){?wlBDkX}w#oYBEju6*4YkEQKHL*Maw;)gkPkC!nZc?{ z(of(xJ>!1<`x$(!J5%gzR~3xpF0n%&aO$QN9qqKq!Z*$(;yZ-JCTLjTFQiIB`B#qK zD46cLG*|z79=u@eH(Aq|wB-aY1z!SVPHUcFm{aXI@bLt^c%w)fH4nT4^}N*`Yta7T z_xHv0)$#vi{C{=^M7y<|pV1a+%&|y+%I&*K7uEUbAkFZZDV;Mz7CJo`Sp}tONePew znOJ((|JW~{*z~T#EnuB9aTc5z6?DM`eq7~JdV=5oLjnMwqXXbqzIqCV7CH+i2i^1{11UyVOe0t`8I+JGOJBDI&m53e=BN_~~fA@8D$-Y$;G78Ye4 zM9Zno-iywk35@3)wt2te+N0DtY0tOF@vkAp?3cc@=vn-= zNO`vcDa}X@<(Ggd8N5*qp?|cPlMLSzcM7IbRR`u&6eywe$ZAjba$*VI9$vpSvk|Ey zq5qNs{F%ND>=3oi^qucyQB>8j+h@`@PM<7)h80M^Z&jiBHtlj{bW+Rg+4!$+#*e&v<^FnM7T7o!ww~&3vb$LLQZ42h@Jq%B3hJ_F z3$)-H6xb>%v}t+3p$>gBDtqO|_J45qIq%v>x-h%pEAcKZTx#%7Cs_Pm1TXkpbo;tI zf1s)=829{(hxP8N9Vqy=$bUW~Al923ZA6@~p5^iu(77>nGT9C=CmvQ$>A($w7_qH! z%CXysU|Z>R5YL+a6M;}uyvMzjvLHM3re1uHB8&4^jTgYH;KqSE(87_@`@t084|M2* zY1;J{7vOukMFdJ=)&IZ+^Vv5_mL2%U zu$J;(JM20Ser>x|_SahW1DsPTgO6;YaR@!CeKgYla0^{r4KJyT>hv6XVn%=d{rpmK zt~)_BUh%Bh8c^~!_h3qfy&TW{bD$}(x>N@``^clLN6nNPZ;L~Sw6i+${_AuDEu=x1 zf~U~}mu8P+mT9=(R(2LQyLDs1inW)a?*}RvGQc`})2`_YB{h)rXJqKnFX9+%C()Hn zD*NW~&3=Vj+wY5{bI-fnLQ{-Wm)()O;ikNk(Yf#gA#Rx?CDGEqbhtM(YdJW{Ufs=q z5_|v6;Ki&!R@%DZ#W@o*Kp({b9~rWj+Ql1)HHy87_FT?0xHhUg+aX-YsbulMBgyq1`hTVCA7Vd&Ta#_N1o7fPaq|(RXvEi(_!TRQmqk?|}*k zmMxWNlT*FnbEM$d81||+8F8o zOU-j_+`Diq#Sph8v9>CqsEE|=FHB5QCMw1&XUx`n8}XRVl)AkN3+nM9al(P7`=Ea0 z8RvPGJ!-7;_*OTAe}?2rfx{L=^W>o2|AW zxfVuW`-Q+_RwKnDK$NT-j=A|Wx~lSUPbEGH5;1}Urex11dn@La6OWSCLH&Hei)$M;A(xqxzg%L z)0B@-H}vTJNn)Renj5vbH<^a-vkdsnrvl~UBabx(C^h#R{T2hUKlWROhSNRt>~vrt zXVPfq`grrvw(RAhN_)MgOL66aJru)UmIy_!BJFgVA*G&lk3l-+|N1d>vf{nb!+-m8PA2Cb!^t)SkifDQe zy^;Cgllg(`(UDZ>m+|vWzf=>RhuP>mxXiBNp04y8S6d~DN8Xa^>0({83XWDT5INT) z=It{@)7+Et_(7D#&zpa*3p3+dRBTJ&hNgAu#PF|Ix3>1qS_)hdfyGTZH;QuT`;XTl zeZ_e=!`yu4II1gw2=zT}iB~Z=IG7LnVgI!p_vKxA5RuQRw#+Z__x}c+#k7`f8gTGV z&G|s{DrsI>eX3z;=10B9RcoCLJ!nz+3!A1T@%WUtf+77wn@yW!uNjGeSGB~j+!I(L zv?QkK223bwrjd7ERv+DqdYNtilI5Q>&r6GC(TBg8_z*@4ceimnUJKdy<@L+(^n6m# z*0@ZMa`JkM6>lj?n1oUaA#?~krId5MQb%xmk7&NtP0o=btxw1WOh(uamlbTu`Tlco zp>cd=o86BF70e=Ad~){2Vk`FXHlnHO?}F*jyIqgm0sPF53fSn*InKR3;}xaQ{Vu6PVTmWA-0O24v2J*C?VD*3=n_U%m>42@x8b z0mkN&T#O)f4FgF&;zI7M(rztK*@xkQ7V_zoMtqUV8Lrhj^&SAhI{t(gJ=&eu`TMIs zM$zR7LG!BDr=Ut?Y=jV#uOtVydE2M}2YEr#_bIb@h(QKj%TU0<0YS+c=#W+jCX>Fuan1 zfH_?1pg+Ux5{z$G&SAK0l_y0ai3}Vxb8*RNPY3E`T9iu(?!3Z~5q(xkm#AG-=DG(q zNrO7B1~F3qJQcqW<~hteQUS=rfV;ZQO*?kJJ6^wu;p-|8i?eH>yUzZ+h5a?c05R6& zx8>klR0aV@QsLI9nqU7!{V>8sD+wibr(OtO=lmyxGWx(uez0cAd+?gA&yw+EmnK7Q zW$EI+puib-_MM+Y{h#`>VaDdsM&CBq+Xs5a8)lWO95OzFxvq=U7nYj5UEJ%pnlQBy zP264)Q-t3BUp~sPR!BE|x;xwFuu5n5sI+DMg<>5>)=4V?Ji&BWdTfWW6gL=H2_p{v zZ1hfe7t&zFZuUfmMsYKuv!FzUG&jowiBeG}F{vH>ol#UZsn9pS19Sys>F}f3z^qbj z+gVF^)wGrv6olNE8f`W8#AshG9jTOQdU2Y@?hTWMCRNSI&6kq037k!-=4GltYcd?> zrQ-Mx!?ZxNT=&jBO;$gX7}w`%Z;%+aCwAAe?BALw1Q zinHX_#9&W1r^ zuv$rm&=k*vk6V+nH9Q+-t)jWV>jQejvy5q&|Bjs7T7+Kp_?3zWutQC8(nd++%&*4l z*;j?W$e^(+HO3JxEyX~9%fuZhX>H9Vhp4;=y`lt~B)yg=B~3V^{1&y=eq6YC{~yK? z0wZwQP9h}YxbPLzYZ5OsHz$G7I(gn znM<-4KvI`Vd#ajECpi3fuDmy}{)cf@5^lffiWJd=#imVWPS*FF#j3e?Xb;T6h> z(^pVcKW8KKW2uPftbi#>C>&kKTuETIlE1W|n$CuD^dJ7o=BRPwO+L zeE780?Hg*Mx7FkM-s=@Bh2YM=^Eft+3Lp(fB+Rp3OXQs4%F+Abv3gdBntS@}x$E$C zKPk6CZ{PbXYlD^r-rr=Vxg{D;@f7{-1<;5Fy0lh?#8%*JSK1&8j3%c(Z^+O5#U=iY z+4lv2s;VILgnL<~+ln_`#5gCXe#gH{;)RsdsolLQyXxEgM48MN zY0c*9ttMYph1nXXtsix`K+Vnb55f-GSe^njfX55rW{X6Z3YvSTu!}T156>vKULlbb z3lHqO`^^@8A%GZvr8!=0Y>72njl{z5Nn{U3=ys_MYPIph&go>4Si#?85P+jY z?0a>S{L8g5yIkI1i=Fb$VF^@d3R%*MYVq!V-{bE0P=5y&TY9j{4i;AIiMd<oU{)Ubjbak;d$MFIl{wr(-YJO1S{A+w;c%)$P(Jq$-RRCRE zeClYm#@kZm7|(XD-C8ZWU(lWB!9bOudvwvWuy|{1KUg_tmPk&k9M})}k?XVz zv=6?s@v!-?hJV!@2DN|^viAjOEjNz@JE_Y`noU+x(G{J=wI&^}Q1YLS>L&d=Z6_4t zeWMEt29L=tUC~_I!$Z(1m+-&~Q z&AxDGM;D4y1oUuYS_|BhKqpDgN05FTiO|iyrAemb@p6-4A@?C!#x@6z?O5zrTkKQBV9O zHr}?tQ=jWpo`w~kNnKj69!$Hp!??Pmz06nsRJkGFV@LHgn=`C`L1p|WBjfTEB-FY>i&GjCZ5L~Qpd zVCyklK1>tTsd6wmwD3{_sA%*-(10J~0W}EH$8=87tRCe~s*u(7ttBGQqhGx38N0s! zSbnEwISt(#aITMo=}A|=_uDO*O`N6P#9@-fq4GfEljhI4OnEu@)kZN1B>2_O?Mycu zH5dGwzmOQ@Cm(?*rp%wcj%xK-s2RB1c6wl%^3s!qN>-Jn!x))CgYD$USsN)H07(KP z4*96$79XD7n^%gDka(%<_Bciw+(A3$S8Bw7QAfbhN7;7EX7vZ8QW%50Mm8}k(_Ya6 zFP>)n(H_S(;F4ce`S|T8%n;AMF<&51W~OaiLnXOp72lwU>R?1i-yif~N!lHX&X2)i zxOLR0O|%vI)HmPx{G$T2{x-v@w8!YmZ*h}v?F+U*>_pl3?j6X9rSI{koryZMvv^=Z zn0LC6ad##zFfS%nGORVNJxfFS6?aAlSUtED%eYz|GIcoA0?%nsH`}iBUl82g1iGlp zbWazu9-zrt6+@F*qr{QQ2mA>@J5O^k%>xU!(Rb8eJ@Q|Wj@^1x7p=Tj|NUmzy5FSn zZm&8_saboV!rd{0^U+FAQ{twC?rtQ-qnjDm!4u9lZo%%Ieut;C{fVKb zxxyU~>;aYk!M$pxPE^Vx&d8px8)U-4E!Ebu*S-2Z^haF3N}^VYcS~^v^ZDDxdA|F^ zbeF!~GE-j@E^IVhm`%#jhLB*n#T`2?cKvR#^tO&>aR89COxLsh-?+0Sx&gTHqrwFr+1ioDON=Ti6QOku=j=V zi*q%fcB)GkevB1YT@FKElL4i31hk&%Od+b=ji5O0UGlGAmikQU6oVVBF_Z5qm{aWt~N=3m3vuG46u)n3T=DS zRd#HH$>bJlE};((1PJn+O(3#_z)|ZGGVUNm@!iAx99~d*!K#Eam5h!wRO2!MX{Quo zRqJe+`{VK21a?z!@+kA&2*cVbFtkayY}{*)7{pMpg^ePgC8*q>(M>OLZL%gaG#?!; z*e{d^ZzO#i z_abjWEcgvb^1RLb?PI3J99(E2Pi*y20zO3^wwng%J)pK5W7Bw3aS=N1LjE#sPos;m zWGeJ0-<>rwaykH@EOYTyj$w`K65(zS)%&4w`~;naW_6=Gth?2V9g7%Vv6h594xpmb zeVdhn7P?2065xmQu>cfcTxAblM?a&0!-JyGNNGvBRMGKC_G^9)2s7IoN%fZ=6k^{& zsz2XC70D?D>nX6e@bds8B=yrujaRr2 zprYFfB@1QbQT@_H&!MQCcT)%mqeFOLPu?2)a!vndd#2k6ym#b=6&B&P#_^zb+$28Y z3ng{~g7!$+;LCUbVf zXmg~N(|t^CwslNtV9~Tf^h9%* zt@{B3`_dL|q)&6H9F<%Aa$fd*#BL}{T^450T^t$3g{T(J?<&3syGeoVg9z!06g!^3 znc)uhp2&|wFIX?rC?7XTnKj96>xS}RH>lA0Zo97-L15_p%Y?>Ae^R0OkS^x)$$ZR+ z5tgGEZ$#bUHixOM=ZQQJNK`wgs|tCh`U&hYC$jUBh5ybJW#;i_I*-rC06>h;11Jr* z)v;@c))0Ucx5cvDF>oDkpyCwBEY<*qF7|AQe6oe6KU_1AfB8X1_!pF9j?NyG1T@AopCuAq`tw2BCjoG!InYfD8-77c$4a=6v+ek zqCcm$v*>@~Qu)r$n1yd7!eBhS^pZF2)F`lzA%j-NBu=#JHIku@;fd6seLdET{`&2K zoftsY%EW&R<}kU5fby29*>3%=yVw@-Qo%p# z7Ow^{6u^)mElP`rIT1r&U3IZ}k(|ze;?;m=Iaq-6c7MJ5M2C71U zp@wC|LM5+*((kVCEx85bp5{+srX%a7ge)L-jBA3=aU<%dC0PJU?LPlnutZ+koz#l_ zXp0SIAZ%?a-VXYWJSgbSz5dWb{^WVR;3GAzr!kNa)%V~YQ|Na8Wxr4x`S;Dr~g!N6H z1CKy8eG2)jDOS}5E4$6;Aj3o#LI7jnr-}Yr4Y-z*xOk9QGSPz z@saNwU~0O7r>@d*ND^YYebct>boTi!4!z?eZz*f_ENGk%u%vyz9f7V4a!1Ab;(D`U zT*)sA6A`6yzNk`+-mk3%7PuKChypA~yGC89flexE!WWrqOD0H{wM7-96JvP6*>tW{ z8VlB{6i^7iD;`9feJk2lOI6RURX#8TF@iS-$FECDCthhCp_Jsi>P%t$4YKs8R zu)$Q@aj9O1LIemr5hm6$85vM25Ja18&42HuaKiahkuAc&0Ldbq2R%pog(R6l5oub0 zUdJ*5zLGPNFgvx3^I#m@p)*cy!*@DTZc0EKV#m5R77K)GIXRRAi>A7xphxm1rnxBe zPrL@iN(*M#3`YOwD$<|UF%F=zcxiz{uK8UL!cmW}bGs{iB>*=SKC4?^Ci9XWn?ofR zI8a4b3J86TUd<@2yTs=2BBlT?W$#6w%bb&)Z0f(_01%(xvxKDmK!uQA7(e44sECSA zqxrG(O1N+dY_CR3<3 z-di{l=I~6DR|?w^+e$xdZ`-`@JnY8I1(u}qp^mQ?zPGArcZ(N^RoXT2GU%FVPVhGc4BuB020ACHbxiFx^VQfZxDzW|oU2-J-atD1%?z_2I5 zcgh6b8fMGS0-Vn;*CSk+L=0_vlm>Pd3lq<2yw(HKaj!FRNdFsVyE6Ei&OIs`pZ1Rr zZhY(0{Y=#~p68SFK14MiP>~aUqQfI4EZ%GG?Dz4p(ed^^0;Owx5PV)NG!t|zOsJ=A z9=B;SyQGs?ur==TA%6Y{DUGRpf0M|Wa5!Clog(@5dAWQ!-_#NNkxEf?Rh=P!A>crL7T^L-d<@5o3+DdON1t5^i~HM7 zdd(kYA0?Su(mc%?x%IyG{g528I|aW->Jn- ztP$nKjU}BPJ%1T(0Y68_PC-HtLs7$VJQtuz$7Bb(BoK5|)JCHwp2k*?CW9_{FkHW#%~16%Id?uH(VbJg z;r0sP-|GiY^zOu|T@zXC&f>K3xv|3Klc9ZFah8zf zR2ajgpmXaPk}uDWW5p)F|M&gmw4|BWI&l#_=k6VR;dh}%j)-I2H@eNz+M7`hgHD$ zJ=j~LGK5TjP}2`M6!d7UT(LH1PR5b5|2fhBcs&{w&~lixIPesC;X1tjm>f{J1yXK{ zRormj=``)*^0;pU!bS%uPO=GYU#oZI0VX zG{aYR`ds|WY<5Lzqvv~3D>9dRK*i{`!rY_0MUDV5o+nKgngb;O%F?3d0(5e=wWVqY zue(K=^B!{jVE!RJU?QdPKXi(ZB%(gcCFq#b<8&TqwLX8ylvfqVU<-&{SGz2FlAVTH zGE5s-Zqs95Lk^Sxe>zp~80yQ?=e-^3U?2;vY!i`v6!~VAM>n=oJi=Cb;DbC!?cq}7 z=Tr0xa7)|GZ}F4Qr~pmr?q>Jq(f+KwgF$t>zQ~AgcVM`LiAEYkn&_Y7?HA zvImTm?vGm93(~)65QYbY{)fiqH#R$ecQq2H|{~Jh!*Lsc&#IAc`kFWEgn3HEUV!7l2z|h;9~?g!;}`-+y_4JI2JP zJ-srp&T2UU$Rwdk^7kjE1*`Xj^%EqJTXvSxp|Yn=UUxVvAt}Pl4|bzPA8m#yR1?`W zXP1Z)!l|tDJGW&PoyX(qdnm*oHNlHh(+>m&m^3DC(D{UHY;>_b9588W+B9!~I|C@A zO>ae|G-i3aTjANnuMG)C-{^M*LqwhAH=n9LU;F}w_FpFGyvJziIE=;u?mz7#oT2wK zG(bovv8`F9XBLyo+op*m^0@E@h!Rj&BB3XjDq?VNZx>Bh6x9jq#_$BR@Sj^24F(){ z6_{UC@ehi5%d3}ksCwkoojL0Y1}&zK#{67nKhMNG8CBlNVYGBMFLqPA27S&8R90C~ z(rRJtSk_LH^_dS5=NR+C@chvtvBS7qA!kbiD0a4TXFFJV5$5V~Aq@8qNu@;+T~v$K z$J@i|&We1D&+%t1MIunt?u%W>r?1AYKZRyppy9Iz>C^k}uRIbVts`BIjx;?!g{CNk zEi&CU$k68zTuBb;Z}gvQZkj(oLwxWoC^pW6-FCx^sn$zjjxtdF#mofaDq~<^Y1Nw5 z!(uMke9UU6*nn@!OEx^O&~vX9MP6e|Cd=ef2HU5LpMGl@;=QML7WCtl63=yJ4w4oW zCbiu90cLNPc55kj&&ir0+-CJ?pN+@<>Wl(?p3>zLb0R4gX7 zjqLU9m)|pwPN1u*vQas1N@;j{LTzV5ecT)KjR(*=l(Ho9@u7?S?iY+`>dki_XwVz6 zv>m`zS6o_E-0|emSu1vCMliteH0e1&y$}28u6Xfg! z(P$2L7aQAlF4Dx#IxP8uetwIxqtWJzCIIA$S%#ezFyj3yUD7=w%H2;LtdBQDPdsxv zK5ybIV3(he6=_Ime*Olpj`@2H2x7?Z9{RrD`|7kw82!v22GRCVRsq+&g-0R-Y-QO^ z7=feT@8}j8lk%URSO}-%P2n|HyNxiDcjA9PeSK2&Q8K_OGM)8pP+IG|Ig)}N08eg6 zx1)P=Zp~2kTJ87+B@5yvhJW#wjoF$qKe1cERJkPSf=hnWs#t9k7py4A0g9ak_b3U6 zz)k8#*I$&HyL9#hwDUz)!wE3)k62VuQ`d+ z2#ARgfwFZzrLrQs57Q$nIeItb>6xDi@<63NOMaX|)h_H> ziy7u-=Qr8H$XEP`z0pA>#EK<`bT#v?GIQ@8$^#TfIM3T3?Vwd^w)-oji#+GP`B}9awdfxZ{Lo^j*m&}- zZAk;w%NJboA|6n6DBMZEI88}c9bO>-E@lS=fOOK>72oJhgQdyGm4g>Hw1DzS^-`=n zk1K@|U~w#FTN4cOn9F-%a9lFaHI!%AnNDe1%%z1ckpT}`z0;pn!rd!8&UJYmgEjDlccKs& zjL<^N%$=tsXSQphq0dDK(&v<@^+F}*IAVJgFG#8tzu#*X)xWpsY(sED{_s;?&)gOK zftn`-#g|H4))m_yyx#>XYUqzrZYcRI)>0;`YM+)5$9!tsco}PJ00!Jsu2H}(q}PHs zf)o9&01bKSn^Uk$1g??p)al!=rNyUz3um1+woSBVHGSl93njI!PH%X$ITt*lqJek< zBFX}0&PVo^eqQP!k4DUJA^m9_I;SqK_36;IQa>`foOn4Fa2i0qUxr{+DY2D3gFoPd zJ@p>(E|=_MuKvE1#%oX!DD0B1WQl!_8~0iJ@cM%TH#U?CEj!&iv{^K@iG}ghh1V@X zo7KRSr$ji?L2m|pqN~$_>1KZs`Pmj98HpO(5>%_x_GC6EuIQ%1qHUTxm)U;a%y7g4 z)#x5@F=nf-_|bxN_x5CqBZCbVCDS2I|dqS@h>8t6xz3}7pX(B&k6dpqsO=ud5?p-2t-W_iU z{vmt!=0|~58B&+>et(tOEPKQH&FtSgmtpBwXiU@ySn9a8`gs$+)Q7QVE=tX_$f_jk z7+FSDf)WU6F~c=Zo`7ztq@f!Ej#ViIE%3I75z;Txb2T{vz-pr8j{+)qWl(o;e%X}7m1G-a^x>UrXAy-ee9-E%G$Zqqc zmU7)wZpSp10-15lN_s#|4oB2t;%e;)G_{0`Ym*r}W zRh?4wi+YJy1+$fxf>cGoIJ(J|+7M<#dAA+n{@SnED*Zw>Cb=UY!6_AJeta6s{ZFA` zPemMg*Fabae?xtAZCXLmWEy{nPK0vr$N@X{A>?NC;Dz$yWAfM!U9~2_W3!@YS~wr~ z^2wpI=)_3nKu5B(wmrWA7bxAgzi+2>Z@)*;I8|9NmKjpgWE5$F(R37Vc`F@%eBLQ8 zP(KzrbBQ1wm*%5(My8hg(_RBZ@9fvxB&ddHS!Y1EDw#p1ym;PYPNU;aFO-!J(DxtZ z6nHbJtbbBF=Lo?XLAV2~zO`+8%NP1gVoY~){i)1EgCyo|Drpr62XsGDZ8Pa{3Ao@P z!K?tTrtzuJm`vfVAyw7;qzBmS`rT^2b|yiM_Y5e#zISYorE97D+pqYx-+WyaRKWcj zrcV{zL+5kJI!)UA~bnt zPE-G9n(jw|r1$vYa_xO`OR32oaKRVd^v5;%ll`i^1iE7RlD%q zYWl3%0V)zIR@pObUgp9Ju@|g`r?GtT4$gBec>e+r={M(>T5{70`aGtaYE|zauaE2E z)`Gu)g{>g&tY)4F1a5au{+f~P03ao|9kg0v^?tB|KR}=U&M=5llzI~*Mf1t~7`N4@ zTkwtH@40?bcHjY&JTW%29!sde(P{Nub^*l7|S8BHQk|LNK?%?h1tUV$V zMD`d=23PdnW+@&7XVf?Oa$#X~JIwEjhJfZzVW|j4K3{(k$Pn`lNs8vg#AfcA>O;-& zxR3x2=+D=`hmgXQY*Kg#!u{X>xh#as$jaZ%b-1=~F($9E^OSk5mfk103}_k+o_bx!-Y`+ybA>`sx3xhlw6)1NZ{T?i&dLw*p5Dl5ACUPSWyjM@i_QZ z^o2qw(h5k|F~hxB{!2jX4E7rBjx%yTeX=b~`>0Mh_^>ZgoBsJ6lZXM(YYdpRM^Rm~ur*Grbomqt zzK|^np*_2%cl(Y?GvS5Dqg9|aSf5Rt@242xGXP=Nr{kZ`yBDi!du9`eD>xcGYo3R{ z7U?+sirl~MeR2Fvvi3xf_7hGsebL6(R+6%PGD|1FeI~CqGLN`-72VIH#=26ap|s0o zYL(%$o|9e*V`;on5DN*QMOD7A|F(BxV&`jNH2OVB2+fRzL!5EuZ&8O@*<0~8@lo>>5eBWK z3A{9CuqL#e9=c*G=XTpuY8l|S(&&E)iJItMBw}Ry z^;~trPY_a{iIDt_&5`?|C4ic}68T*Cq-%N$`w_2Col^uv!+2UBsox&m-|fneZmfJ) zUq9Yy7t>3W1=#ltW8vV&Nq@s&z}KrlEGlU5`c|Y3Qv+BO;PRVZDGv45DUiTyWgEPO zYYRdJ_^vp}LsyQt8Qvb;44ZIsC=d#R1*uN*NsG~+Ft7Aln) zIhVZd9ieAG@O&AJrc&mJ!UO*qz}d-Ei9u3Du^Yg;+(~Do0h4G?pUcqynw(ovyre#o zu0rZZQJs`im&$&<*h3;}s7cPz&Q0H$Yjo^MAmXuL`9;P;9Y*JG-V!88thq!qVV*CM8^-qL&!gm z1@nMIiu2Wc0zqmshWaN zv++`)m~JuT$7Si{=IJD7hFE!^qgvBr1WZLF&rb(h#|GWxjW>Rht;3k)C&zcSvK6<7 zPc!Ayf&hy!aQT4uAmrNM%1hynu}5Op&tli84uV%nLu^h(OV&1S`fqsIc{1o*ZoRys ziN$Oi4&oG$HvbP(Ume%v_r4DUq?8mzrIeBqi;|jxB8_x|fhZ{;9b*=X3Q8(1NQ?&Q zoB|>sC8JYnvN3uLHrRe===<~i{m1Jyp7WgNocq4dUDtK68n^KFp}vugH+z`osZn8m za~@u?!Y{;PC?sB^IQ3Gg`IbSxv!i|r@5YRXJpJs^0E|RiXgj~x_tm^mC_t>Dq?!tn zr*k!hL};h8-;C=iOiDG0bQ$Q&wiPhNNj;hhbb(b4cD~IjDk2&06%@O;>W2X7ILZ2k ze|#stA}u)oSw%V7u?qN;pZew*HBJSkjant49R*q6zMwWtZrhX z#$8a8@Q@jfVbx2u-siJOxSeki)#&++wvgx8WPP(?%r{Dcj7h>KI`^H7(ix+u@YS@( zOW6w*>AnR``_-#C^K>>=)f%Bps53L`0qbp*(%l!|GMuZ|D|x&!vBRGx{H{5i61L+p zVzzs`Re1=u?@E4A5e~R9OmaRa)ovKzJIe^csh|x(IwUcTa65lrecHZZ%qoT-a+_&~ zCV~CIb51jCmg3%{P`~L=8wbX8&5R;QI=RBd(zU2+C~gt0%DnvJ{li7Wp#>Jcz|UiX zY7G;L29aPnZNOw;!QBr-PezD`u(FL|`VhXhV6*`2C^1{V6 z2%lF1i-xZcE4wH04%>b)GB~Mgi)>cuNfLdT*&aGL7Cd)EDQ8>5ezxG&!zGd!>*k`F z&%Wx;%&!Natb31&_0KmW1>3r+S=fKnvrvdV>+Dz*zBE>OB0aK6CD`vex3CO7bB>)^fMZ0Cd$Ow-ebWH&_P6X?rb5|u#%qN1#}s=X8rQaPZCqkI z11rYb(2|#y{vrqHTr<@+V-q5=qu1M|WD~1D?a&d zPmf$IjLYZPiAFxmT~ZHTzNH^iP;6|UcDE|YDu(CpmcWGAg!5-JffHI8zd~?lb1dSe zMWn(j?`eBUosc;l9!?LaAp-G(gGZHCRTks|9?5MA7kC5Ta?W=cu|H@oB_5SujnQ5& zX}14(u{L-xxZ%T|ip4CU$wDvqdi88G)_>CMaaRSX-XjFgZ=*!`kUAm!Ywv%W@*1n& zN!6jmIFCRSrVDrS{e(tib*8*uZ*0ONN`s&NxprI;D}DV>tA*1xCfsr$xZLc^22~4Q4T$*Kw=n6lghQ{Kk<~ zh-%z+3AGEks0x6m8={+OZngPbJ+TO-{IQn#=YTjRjr;QC>h)LrH4jzjC)df}hIaPz z)nJ6l{QEcTnE+kMxa)L~>rPpf*fmcJ_01EZ#0eij43X;omOuUBaF4TxZNTZ1;9Gt^ z?!|o>vUa+mG5(1)pkZ&jI`Z*IX`gV9m>R_>R^J`Imvm)fUuEQ_x@ttCYsbPEW+k-1PA9j%$*XX@D{!H4$6G={uSkL7y z^_`3jLw^G!u$&hl#>$s6?zZz)iJ1a;t}k-~G5?sLLUD2Q_3BdCM*?nu{mR+8qf9k4 zaMp81!pR=i(7Ts!RU`ezmxKY1by9GFj_2qaI}$`f^GzhXctl8g*ox9Z{iE9q%879t zOt#4JbkklWOG7XVlk7U3<+1I^8MK)!NG#w5#F}~O_u7V?Q(8HEZ0>ByURRjNu=bm7 zl&M@F9bst5!qS~-=g)m(ObX`HRj+es+H<-d0^r}1(Pi^jQ{1`N6n9X$rqQcv<5kvk zq%B;v)ctg?V-ANj-#{B6Q*7BpLIfgwmcRa^<_dhZ{z+Sy&h4Ugb&{J{h$&eA6HFnhm&}pW5{f3+B z^#&$W3lL^3fVSUUue43H6eT*eur5^Z{wgaVY$Er0oZo4pn#o*hi^5&EiWdrs#{`*!nQ;P6l$vpb50F@IuobIF|z+^8Rs zw`_e4iFs%!=6U}D{;wAsbZt0`ajS3 zl*=lmXJq?e zQrc|NDP-bX`-S}Ehdv)g!)Uss5l6y{?c!_wrI(>Vk`|aJytD${Bl^ny->8h%i!*S6t>raC>Xksf(mkDfDtbX!Mu*sB-S z&1AaJK{M}lIDv&7uJd`ZgDBfzv{WNFSrX@ssC!y{w@KGd^pOAa*tP9JMxg>VHEefp zVjVlb_3x`t7beX!Yx}EGeL=%>`O+|KQD-{$turS|1E24h*A6sc^+o)O;95d=q~)ERN0l2tge3{1 zxi8Hh3Lm2_NzYBmd7tF{_A~^x+?+IPf_P%MGfhyT2A>SR;l6NcJz@v zBM-9fjI$QWt%~o2+dWuc5M*a8HRsJGGCu6f*o)wm0ck#W$j`D{$tKNV);-QWK|O^i zaeSeE2~Ia~1AnQ<$d0lTp_6`fWcyg&r%2E!R@pAl*~6I0u9y)y`XbS!#6Gga`5UN@ z2KB&O$B397WmlW1Y|vo?1Fd(Pp2G9T7Jo7;)t$LBM>E8-U`q80%sHh_xC9Mu(*8Qka0dMOhDNwd9WZ{4=O6glvyiBF#ps)jvS@ zbQc#Zy3W!JMTTu60=)e0V6(`7v}`C=YCy7gnI`-l)bQqwLo1W8vz^I@g!XCuf&F>6Vctw0i?xCSkHQW z-5`T2%~KQTz{ZEPz1mBRQ3;sH;{i?-KY z(a)JUZrTApFy_xq)0kwP&uRiOz0(eTswb=^7ryl7V`~d^HVSA!&wN1W*5@*WTXbaS zfX~~&jXQ={Rvfk=Cs}Hr&FV&kFemngd{njR-YxdJj%URE;E#w`mbS|$sG?I>)6naM z{sp8zmkI%tO^F!O#lX6if;VTgD$!DlP1MaUsP}kpr2-+-dUwQx3=s;e1p^1GSDV(i zPP|_{d*1tAlGL})+MVNxk@Gk820`OjR=EEUmE-32FdBpf|16z#Nn;MWc`v(j#Qz;2 zFQLm!JLQHtyMVqFeS&cVdHNiq;G;%@eH)HHc?6tyLD);$prE%q8Cu<0Jr!Aye#fB% zz>=m`jvp`%b#mOqKa2f7-mkA9qoMv;UisNky}+`a*G7SyaRcS6i-To%m2jZb7=Xx- zjHdQsR;zD$*MB<&h$_>ro-M9U!|V}ms9r`QoU-=69p??BY8BXirw*;bi`gJ`;xE7ab#(IQ?Ht7v2ZW01M;{fx}LC=-rA`}Hb>Aa*IWNsrLwi>;sWcNQx;)|H31(6*T|jM6{2E>2Fet| z7c?+Uti2LzYI@d`P1?eWaxokFZ^uCQ>&U)|>*KW8_H6$ESCGjD-MwKDwYf59!^+vxvhB|h}(L52N|3~w}c{9}Iy=L9V%fg&%w($4Ql5IXXOoEdG@E|3-lCZpP{)-s5C4M#fhAFLB)qc+ODy7?CtflhC0Q?(~W$TiksM`f%+& z#Bj(`L))V{6w(m`n$Vekrk>Kycm@stfJcQg&@XluK)F7LL+2q8f&zh%6VF#KmDW^@ zW*lt8FD-dhqmvCE`(inTxpWdtIhkx7e}6;LiKzVD2}zN)9%UY#7p5F9^}sY%n_>6h z=%d91I>m4&M>a>RJJ*%&zDUanI+Sq1Z_0|YIlmH?3LxT<#{&qHv*Vwy{~ZOe0DbO2 zEE-NR#YvQsi7t)7go{~Bxj1w>yLiI+GoXd}Y1kTVVcTnv0H26`t6<)lsw|#(g;|&* zg~s9YQ!E$c{|NVfTy^}?Z7MI0kHIU2pE6Y_*T1G1u2lEk{nRQFE35 zhkU2*r!BqO_64(gmUanqsLGDxxu2XCoyZ5}n*zaw9f`5Q>z`v|bU!}hWHJbM$awPK z3-d~8VE$4QwV#hQAb-678ew@eJnh>2QZDPmbD1|la`c|FD)B+AtTLzPx>gYe_Jk0 z4c252Be}U+bjT!E$ zLVa}xR!a?pokgCz`Le2BnHTj!JyzHWml#Xc+%{k(JFM8BAA{;GO2m#A?7b#uHUf5? zcsk4YRd24tMj6h%2N(>mw3k@&O zVsBhsRaM(8SQ-l80Y$2v!#`Z^Ynd}W6P>yAwtD1DVNGY$ck!qllHTb0vjH^_+b^@G z=c655OdLK{O&IIhxMy#CbWXZA2gLlxRiv}^ypf#Q8Kau;=9jz1jlf_t5vB?@0Apg% zxy}=pwk8Qk`iWp+g*iuMMu| z$RGc&7a&`h+nI`QU(r`E1C=j8!Q`4~w-s_gGRl#KEO&}by2NpJS@llTb;m(79z<{9 zcJ^vnYq~`WS#7>HFVxA>9(T*LjedLDfq6IaCrURxunqTIO1@89{3a-TeIWWp?6r`U z=e2(V4qz|)AVep>zv+@}o|=hP$%Z9>=D`=}Y@Z&IXz>iy&4y{GUDoam4G+iL2q->V zn(-?9^S2onV8AK?eM$^2gHJ{7TEY-2{$04?d*|oIxg_IfC|QKw10JD4E&zRE8yED2 zuq`MC)URs)N)NO4N(sp91kf4p;d4rBBh;2wqQ`5syV#Ac$_81ld1Qjh$kg-SGtDjf z(Js{Pk18tngj)A@@uwDc*vK(6wbw`({Yk^j1Y)FL%aOwXqyNKZW=hFmU0NZf{R_LyvZV%y1_OS+^bkYb&>rgIfj%WCWQG3jj3eoo`% ziO?@GWq0FC(284pN7e3%5#R=NOqWm3=yItt_Jd&MG$mZ#D>IE0E;TL_IP4eKw`NQ@~%yqhQJr?}VN8ws9pdd}T#V%VtF z3IoxVFkr_r?eCH;xEK5sFi--v#nlyb{de#0h2J@&2al~~7dxkBtBgiar-D}FRf%6 zKLL2v0{C`}p{quAA!Q*UFG|I)4;E0j!vsQ3yLj*A)PA?8srM;=rGQ|K(Y~jGE(-uT zUBvlkP&Ym4ikawm;tRI4pJ@{O)|cvb^j<@f-G?3_%OIUCiRQ(X=C7Yg1J_T6X%FXV z9gbrK0%jkq&jzv=2W)ZRC4LY<9ak5&B^*EX$x&H48pR)C62E+dPww=)C?u!WS&PM( z1dTb|62Jc+UkWm&=ZDb1HG071(H#(xxlW_Eznh(S?AMXYPoSa%nP%FRNuEXXZb4}<0ZHa$wYR-ApS`U|T(G4>Sfbqxh|IDzkdBSQ6NLqP# z$76H&RV*H6Olnz{fx7wIWKWMEnw+LPLFW?i!_keouLR+<3@?tDBsbaEV#Z_h(W`Xk zX*T`U@hkmVJ7rq00W!`To~DW?Uue%uxp+MrU^(I}U5OS_N?uD!$q$mB_GjHj;bJd5Twl+J}ckn@7O z7C%%JvbuUI2#}5ik>5v)s2SZ9gvT?vHC9gDBi%Z}D2SvCRmT~wPDw3=Oac6&4$IbR zNd>@W$4@L6v6WF$H$_lx!&0?lF{U>l?5d`3@v0@bcv)?ME0={Ij%#b`u34o3lW^@1SL6VIua1|r!48~FvM z#N#`t>h!Y3Rb8?x4wj%H!~n=ZjT3A?i9{?`c^ax05gG;GeH#&K52q*MP0j;-b@Rx` zA=ml$(P6G!yPENscG`EX?KqDPX8e`q27$IfOW;=XI;-_k0T-#@TAqYSyxO+$GVF8%tjq3#LDWG}d9oKAtv;3rCyoZ{-fo7FtQ( z9aYOYnTy{3T|9ohNTPzf97!fgU4c31_}2FsPc;P>t-74@PUo_LK++1M9QshB-9<=H zxj{M#U8lF^crb0xN@cWK96D|1{UKB! zfJuLa06hGmkHS#4sdI65#N+t-RdaBTBJM>&l-58%)W#f8|E2Ai%O)b%bVCEd*GUfk zy$$FN*4-M)0D%>ta^g-msQq<`ui2K!x>y|orVE=$f_a)ewL zqJt-U7>l}ze|z*n&CYA*7t+k4J0liBy*1&75i;>)*lt&ToFH`GXnXpW!$sfi(G!01 zW}wd(T4K-g2#~osl|+21Ru6hS!><$YkQMaZ?5Uzn{z#dQuJeaJ9-Di^&RL@f(F6+Fm+g( zG-Ub_MzykK`}+?V65%`aDv6z-wzd6mz;weuhc7~X?<%hN;D&^}m~c;P}=X}&(vzVabRF^10n$b)V_N$CexSX8_@!B=nqqf=1E!YuA#&4O<=HAc;5RFobl9 z`b_`G9pa&EbR}=kn*lg7ue{!^i(27)E_SO}I}bo}(!AOvxtl4(NTscWFVcr7vZY*dcnelTwF84W=a75#Nc^0O7KATy2a8s8Y$zF^^pP z)vV~w>bvbf7!UszKmBOux6UOzzG`Z(r0L45-&bQ0%oAbCr=Bf9Dm^&F^kXbmrvSwg z6{Nj8=aBEhKP{a*<<{}lUkdaaRWz*D@yuEZiGg?&+`eEqxkhM~3hMb8qoE5+$AnU5 zUz^Fo+V9xv<_m5nxk|Q!t{l*}Z{Qm^-Eh00C|~`?kMmdIcK&9+(!Pie@-$90fV&1f znnW&r{iM-J%Sr`lcyw^Zb5XEodKmpsJ? zGgMssz;fiI@hf{Ic0cdd=$lPc)$z1VNg^Cdg(A$e+YP5BikS#sJa z3b{RAV7Ia&5@9`zPDrRy=Tg7 z;ez`&aXyRLd@hqw064ZVY+pY?&hiCeEa1rN2JI!~Lp-ARg*`@CZYs-?*vYFI+O)lm zt;d7bD}3wcWog9IMnn{350Xi7(f|zssR58F^6j#saSBSEvY5PK3)_!T{;(L!wZ_a7 zA&_5T;=pc`+$_snF50#_?Q+M`S2FP=5Yz;_a z`KzU;`*7KbTPgLI{@xcM1jNH-zMl7-UH;2m(vW-vXs!sPK#vg>g_>#@0AKAoiBWUN zArOT*A6l0{n1FjlAIj9qFrYOwPzx+qv&2TWcy9##IUV0bvPs>Ojsm2YO>wv$Bij7NUaDI%9bD06C0^*G(|nZzq^qy}ox#2F81_i=&lw4&bCvIv0t z`nzr!`H&_QIVU;pQ*-(tsWEC{fdEQ7wWf%CSG}gdUD7hQVIKcb!H`nJW|SIQS9TL8 z=b{CG)1XW3d~AK!r^-`f4ixKj&uO)CT4+r+OJQwJQZpqizK9=&fb}%5xtD7rCbj1d z=+#7-1B!~AEo`lLSlf<5x9qKC;uA$`=&Af3qvbBbW`~4qY{n-zkH?pG3 zck=EYmQ>{ZLUZeMjR-B@FX?L>Eo^7XWq%bmmi4IkH<3*}Y9>QhRnciz%+g^~(?vc8 zjrgW?f~uMCvpJqKmR=Vu(uH9YUYrd7 z_yC5%1hF3bY!bkt`?`rQ|5`W=T;gaRtfsr0d+Sx2p~|}EkUH^?gqupT%NyiX#_Y3U zFW2F%jU zMB5m?5Kpm1qiAjQnebOt&+tqfLeOwz2?Hj<$B^{4L$lWq6ZJIdNvn)u^U7U!qU%@% zYH??dyI@j$%(L)#yLTRi9dyg+H{P&gI(!qPG`{>!YAtJeaK}~*a~>8p1naWln!Ljs zaoZ3}1N(UapxYvMku}U4yy~Ng&&^G%F+R+K{X#}JH8)%~g_V@|NJ zP#XqJ19q*&q$NVoV0kdmes}{hM_Nd1_sUHj8}oTlZ;vfb-hi$6yy|hC>nmBsHUxGw z3#288O3*?=IrHd99pd7ou+XylwAIY(>qWV|JxPO!i*ZZdEQJl;Y60JvU*V6Y6%z(J zhMEoBu*c6f_nmW&*t!5dmE!{!+|IA#zDnbHwpi7b;TFptl}o$bsmYcUhkM#%p71Lr z`}@A^axirJ(J!i=v!XEjXaviZw}*MX;R)DrqS6oydCTB zVj{=9B)ESo-!4`PgI@nosG%xWlP313wbH~vHeM8nQXjKhgIA=j1(*J|;)DHwA*AE} zy9LH^a+sVwX>6VXBO7LhDax@Ks;A;m1E;=-h(aBOAif<4DdTVIW{gjOGHttX?GPDU zg;E_~Dp=ErCwnC6pu){mkJS`&B<1_Lll%li&i9mrEO7XrynY}|JuE2pm(yfFrWEnk zPZnrdLzVY?YCB(B-^%O6MHyxY7?cvGtV+^e6g7X(ve@^v+C|w~wDT)8ZErq=(C0t_ z#lx5Pb(aII93HH;yOy*03gy$bavSP3`7DYIeg15P@vx>d^X=FY2yUIVnbz5Q^h8mw zNg1#B_!qT= zt>R_hFdI7a)ez^6rr$4G>o%MlR@4NWJFbKPu466O+oy3*VTP&gs-s;Zt6m?LqdYUQ z!_e93eY;MtR?b&;!JWUGNI6sZfaUbPd!@LG{o8y)cnlZ5?wbtJn7ZpieQfZP(12-@ zv0$u2`=@EN3}xGMv?rbXa5hR=tV*>AJ}|wTirVUOFesFoAmPqY0%W^B6$jY(4ptFv z;)VMl@VZ&_=UC&9PN82L)#VKET{E+7qf^tA&{?c~Vhw)iRm(-BnUAS^tyAZIq=g8% zgc7d^3%fIDqyAc}nZJ`1Jl(YoTAXj`gwp_(s}nLZ#D>kkM^#O?+wHd3dJNXBf0rIk zvBsOOCS9&yVthn<9hPEAmt$ha zHhvnIC|Za5w9>?8S1R9Trib95PT_~I!Jz6jxC*y9MsI`)?na@wX0JXwkXkZHM&|wZCVe0vgXQiX@!eD zTZo*{Edu>8o5NvYMmAwc%P)2e{mP%L0gu0;26c3U*KI7$zTW&uq$HRz*gMUn#V#l0zS`D%o)71%wM$pSotHc*|tRywH7&lcMkMOvHN_n8XnnKiN zrk-9j@DA3H<+~kOVyFGnc-Yyf1N>rl(TM}59d`!Iq6LCC<)E`#qSqh2B^9yF%UQG0 zsTh!?!}s|>aWe!VtD&CWMG@(W?5`QAU-}foC|4TtOZ#C&&`qlZ@L!Lu9bd~B?+34F z^P!?T{dS|79#;yYiVmYw6T^&Rqwj0~m3F2rjSP05$s_}EOQ-RKz{E88rwEK=O#IH-a&dQ*bUo0R>g08|cBC0~*3uEk7pE3AswZL|M06kghr4>7!v_OZx!vLh@60&r zC`=r@4agg(vJWdvy!)f_vgKuXSl@V<5xT{L=S@Kz4P2WE1s{i+bMTiczG2d<5(Fqb zzGzkXepx-QcU%X{D#ii;EIPhR*{@<%*-O>hIpM{Gr@83{>H!oIcPe}PRj@hi^zx%3 zANNSj;(nnK1!Usam&gFpSHKO1EArF_&d6WY`7?d^u+n?l z#IG0HMRdR>_9=ki{N~Vchm;XKe<=~mC=BVsFtV#2&9!L0^3Rko9WDVpHWDO@<|IoW zf;2V+Nm`%n(~n?aHD2{^5H1r)dq+Js>4|60``o`ssc+VOJQMYl?qUQd91Nwx5e*!J z|0aav%&V1B-!4NT7B>)^G#AeC)!~f%g&HtsxZRZkW?G1bWa*fT#LmN9(JN3dUd}&3 z>VW-f>#0n&-v5T+$&=D{IhUSUzh3>GQ)k+-$5LCF4;&-SWw6 z79Ejb7eAbYUwPEL{&qBt&YzDDIgSJ5X&41(ga39}=+TeU7xHRhDeT{$YAEx&M9tUw zgc87_p8pIki+lHE8i*R*F|Z0%TTgv@^@E3sDy(AuZ47;wOzCvkE{ROy9`_dbEHLJy zIrG}#jYc z{?QXmbrI!BzR=&r?DL1eRp=25+lbXff25j$A@<^$Gr01(s8>{z_1JzU=D&2w`M%YGj%QJg zk*sZ~@11U?%X$1YxE%hvStARGK;S(LF0w%J=0B4}W)*egXFUsu_`+s z?ivf2+~1-od$Q6qRcJbXe`7konQnL)m_5cFvLcGd!V?y}p`aA}zhyzOq5?tsBi&t@ z0+zq8B~40m3w!u%OUO0 z|MxMA%qSi!%3iNT@}?TtojDxi>tk&=yHy=qr})29*|N$I<5t<7cgqXY=3R}NK+g^H zL_B9TnI3Mc>g6}xJ}|VvQVXya*123Plya`~Cg-F*B)qy}>u)zS9lR>we{+=TF(Y^A zF`XYZ*t>($Kp62#U}mgtYA(8cL9XP417!pFYX<=fbbvlbU-wmyx_Uo9v)2P`;Bo-g z%#d^MyTYt7UHV_~hTf%`rt7uyF`A59if;%2<P{%|f|=)Vc2F`c+T$i2Wd_?9m;u6;uV*W;4!&a7Jm^ zC4OwAR5gnMQA(KZ?+A{7O3%;`E?alDKb6Vr!FV8Pwhb{XktsdRx8Mgj_MFN)TD8w7ApFkaD2W zOcmxCemmkR?bYA&DmuO2}il^7O#x*0+8&TFJ+Gfn|# z9*g%Z{pu~>5PcBV)H7g?wJ5LLROLUPj9c3pkeIHY8e9oO`2Dx69V~^__TIo<)I`^I z`&pKYEGX~JYsQvQ&kV2@S@c5=>aw<8*E8utzVKlp3+AG9Yt3yh{7(^uN*}{P6CKeU1jMx{o;@Y-J{aK=AJv>Zf<{06ckl_Fw}(&q_nzED zi(l#GFKn6gcP9C2-K3u6R7X7pGLYl+yf;K_ebkwo|4;7sPsKGvjKvq#1FfR2B-E3B zYHxAgq`FY4`E9$Y5(uQLs@sk=J~XWT>v2$IOMmIWcV_5PB>w!@3&6SNP4#}&6o4J5RBub=$b*?(V4JY!^7TL3R~PV2Z&49&n= zlPT+hUa6OIWxLOs{F5yjxC09cbr^X4_J76#(WZlh1e@9V4zW6cgB|;D4PJK!8)s79 zkGQf{8LCW}Jl@-_kpAs^g~a~`I9^n)!vQp=42MoNF>7WNc!@T*S-SUkNx~s`%keKg zI;V3m(2**fwDr;EeS)Cl-LcF6BqAL7`hGXdH}`7MDqMq4KITh+*Pi`4OKK!lvY9U5 z%<%B7#}6HK$!B}h(v+I?dB0}vBCO>7c z--tQdGhjPgTFCvp1g4r^kgFozwLh`1m*eh5hK+u;+gRGw}4EFVODi_-M)q`iPt~$;|wrCH!c_ z!Pn|(0@B+;Iat_idDzzz+XVknr!zW3|9kMkYv4KOh()U=07?>6BXT!O;G#B?P+A zYBI0Hnv3^MIBntAnsJw0dig2EL3eC+8_04HLw~cTx|maQBY$_AS0f|sMrfzR-H;3? z*ZN`+3ZK^5KAS)}+6uO*iUb@vfXUVk`%lDqn2)WP&A~`MLNNO@DLke{V)U zWAGc3C^%>PVI*PmcR#*nZ-bfJ>ixmEhYbJ^Q8})kv8qbi!Pg8^jmdv{smkepb6#XE ztT65j=v95?t|Ta67mZHS-IpKB+jY-;M*qSPDf6sl_`c3W0GtQ5+ZeQ&e98f(%~P5t zzcU{j+xfloe_|$cJWqSF8DHdQ@%TB9akFsL1Ao=QD5N9(VaFriC(UhSN{JZ6lt6Gr zU6O3w&Mc}VEYQW@eK6|Zw?iGvA?P&aQN9;<0cE_}f*V#N5X+QwyfqN=P&IaNVq;%o(&^*sIE%jU3%CR zjPI+*v13rH1KUO&HA$-U`$>s$T(x)=J_KB~H`*&-Efe>K`qLHZ(s2(&$T;j)-BJ#l zLU}&bpizHd(zDDEAp5lObYvb18as*ZHB=q8cKvqGTm(8}0a-*6d7i5?>6y=f2#d|v zG0iPZ-wxDwlC3W;ocT}49+V?5^?rUFtu{7qyj|=j_sADX+t_7dKwl>X5)Sq5C5x)u zmY$9G@Kc0s>8=~jTWc+E&!v5%9_|M-xNHi9wC2%m4c3vlJ4F@77x(%if>O$snI0P> zY0Slw?XrV~jeMY?Yfn2$($q$hCz6x9o-lOFB**;U5EdC8JRut$iwR|dqT@cj^K}&R z{tvq}`noe<3Q{sfX`vi>+GnS?a@6ik%}5_{l&kSN`k&E1PY*axwzhZHn8kT>=!zaGLz-V8@>u<|0U z!=%zh)^y=kUpCrbiTlOg>z1px@~jm5Z=wFYg@-2YwQOKdvy1^-z zac;mVVkqx}-WI9<)b;t$+Kb{D7QO$}=)4g;NyG2^8~CxiGpAttSd(Y`{(J$qj)o&S zfF7UB{BDf83NCli(OD~t7A+p>U+tA2KU^p3_SYm+&mUd}mzCVnFA-&1jZ!{GW*SM8 z2OC6{9I(rBOLtUNvkS_kqE*ReEu^Ohy8?d75QoS;@@YBJQCxr?Fk5v_f7^1*X6u>7 zUq{s!9yB)7t)P|3rf3&E?lF!}`$Ybq^2~AONtFnMDZ2XZhty#}U&Z<#48OqKl7`y2 z6Fh1PGPodSL0D(y^EDmv!cJ`B>^GCpvNrz{DWTHK@T5(|q4I?7(Q}Q2`sU;@k8|ZloAD`~QhDl(o5@R|4sZqut#DVOcZ-I`T-Bj&Stsd+y z@jiG^77p?>O6mmLOuFSwaB-b*mtE*#Zx>mRxaor6FBb(#6HiBI&ehCuO|QLZ9Wy++ zw(9M9%O5g%%rX>@qJYpf)uOqyWwtGFKU?-+$^i~aVvT-NAn~ZlIFha0KpfOv*b)OX zu@*P-5l#zo$=~kNcy*Uw_Lxj3$vNfypJVT-e&wGiWEd&C*ih}G;t=^4cHK+V+`s9R z3y?B6Gc!td32~7T>|Jc zcrokp7eo4>&=AW~p@$F7zD?ir{t6k#5KVQ!Tj8mjS@ zDl?8;5QkkI^A#f8I}=aYLyN;b&44MnYy`N)jPn=;^-}(jy{xdRc+(BaQ}vtFpcA;e zEmB6LvRFd{{)9@wEi$X}plt&q|K*0zs~uDyDI)^ntNY{I^9~g4<2!7=;gew`0r{vx z@VfIf@S>{D8?g1)t%xhjod7! z$`SlGx8nX*Tx}6!1EP3%7v1i&5Fb$@d-eh?*v&kf%f+=~Y$lDALfl|cq%VVIwas-L zGY9VtT7UV^gD%Z#qdUuAI!cOB{NL3+{iMpyd1g3e!=c=ppF=Fzu`EmlYtC!Ip_l1n z9S@3UJRjs`k^#X?YtC}7XU*w_k8-X$N^Vrk%YAIby}Hn)r$BLw7+`EsF*xu@e8LD= zSLzHo@?fSIolo|Hmh&LD$2d~!6OrX`Rus&TN0Imk)rcUi`t)e;Vs-{66O4U;ftNfV z8l)rdX@AFrkn1inoU6uzPI4_o>|d)r*p?WGGA{0LTG*8`8OHLQ!K}qr;xn~n$+9B(16KD-K`uH(?3HETJ>%^wjB8~2FO*SrVOppP9_FL2C15b zUVHnH`Y6YPG-P&SjviayQ(uribA$DyEaEvu&pD5=r)j&52R;BJ?#w;R#SdZi z)!(rmjA-RZ+;W>CV!6lt99>*PF@mMNAvwMLnjp1OVGkGI?>hgd!tQu8}al<0`=R2q=2HT?%vz*&EPkP7%()f#hUj` zZFALvUAO@!wig&o)Ynl;;PpwQg17EP2H>;IGjhClzNqUq*-gT#vZsI-?KcrHH?_?T zo}n%^17>QqiZ8s*IvRwdoq$9uv_=EGM+;~qC#?k@r`1N*A3QFR`I}xM?kyd zx3XnL$LnIg6rj`H5&o%K;0`)f+_{U?*aU9R?A8?MozL%Q!v@8?_bY0b^DMnu67(hn zNY-m)9X3bIf9m6d<`WUQX*w!9GY6R{9gtPq!xwpqj(JuM{GKKkS=%1y^$lilJ+?#? z-J^Pb&@+l3_iDf4s}AA`M}Um)>e@v+&uvp=myPIxeb8(f@7-c(fIsf`ML*JKK>yWt z^5mltkNO+F8q@#;>d)X3^*M0!&J1+oNCdFqy}(V_+WH0#kjL79WXjmaV(R;CtM=x) zUzq}azp|S$i|OkmjnG=no64Mdo4HOn*#^OqD#eiPFLwKf@)W>MoD?e^vX+|1RcVFhgXoe#_SwJ=|L2Z9Y z(}(W+r-Js^U9yJz*a*ts%&cQ414uF?FJox68`G!BpviK9-|xdl@q?#eC#@WNQry5D zLR>hC*kb8}Tj2JJE@~mOLTjc?*1+x@$U%k0w7dk=zpb~om;WaD4pXK2%WcL7AO|?e zE&x*zScSndrM`f=#`!l8{OhvX;$K0P>B(+;qrEZHNv>>nON{PA=x@+tB%Z7W8mhQt z+$kq9V%xvCvjwlF1|6i29ib4o^~D!MnsttvYZ>g~EW?}uN>?*@$%x4~t-Kl8U= z?|bF#XTizL{l!MgHc_DDa%8wU`l`Y$9xt#DM zx=dpC&yQa3fgY&96&fX+@>h%Y15}6$YaNDSl%%cNK-Funm>yeX^jKhi^ROOk&2%8q z?N$v`3oQ0edd4JyquLP(!RtPub3gf{WkX4YN%X zla_~P`#pgqcKS_`x35;zxpN7DJ``Uyh9lTuCOnOyU7Rk z$94v`7wuN=&d7`ZIObQx{SDmN#sBSCD8oed9832 z1BX`_LcuqG{|b+4c?oSMf`s#W5%*8Ip&uJ8Qq=MdZ!>bD)(ZXN6c-8q|Ju9mhbFJK zjX`h~q*25V2UQuetv2oeR_j8BQb-^W5M@XJ8AgT}h_=PxD^wvMs3ca|5cUeQ1q6*O zA)$f+0U44igcw!=eD@R7_xm5d@*5;1=Q-y-_qp!ty3RTC^znCL01L8fI7SHR{><2V zWX&G;i~OWOR<-k#jemceb7WTT*6_Y!MOj|6X8QX3)vnQt%VS4%s^BoZWMW8MgP^K#`G3FG!=RO&juPQq0vN&&( z1=^r@_w>1t&<-PU=U>w`rnCRk6m_%62?a9ZT&3s{ZE-G_2G?18hW`bEY{tAxZ)H)d zUY1O3Lt+|^Wmw(TDIXbdiSaG9eV*ytFxzxuppbu+rk@kHQwu03{oIGIszq=VT+~70Wl%4`Is*CJUU$?9=L!MllM=L;p`e7=SXr&y0R3Bk?$+gvyp|r7>@r+ z4W+EQeY9t7y-!ZxU`*OPpgUpu6SPOm8AtyH_!R9`oi+zrvHMa&hZE~dC{|q3`wx;g zfgy{)keBlJw$ttXf8TqQN!ghhnI~=6NO9y|Ci$B8O_%v?w2TbJpB7KMhe_gYAk&DV z<=1^_wu2fE0ZN!Vf_0Z@ENuHQ7agR@{%f1^t3Ox5eMBYK=EXFh+3JiFmwO6Xjv0}T zovKDR-xrs3+rqV8md%3I?Ma1W`Hhq8aqN;>#zwJyhpYs$NyYSsO(gN#xjFVe2UIdn zbkplZTarWdI4}!on8X0ALc1!#|L)XrChN+4ch^0}_zw6C;ndA6R#eZ{YtZkXeq{Qd zXGNxNrUVC6y}fY^H`;iTz@BM%{5a4AB6>npVl>8EZl4j4~ToK|_7qSq?N2BxREa_PGidk1x| zpQo}FGPm~H$lC&CY!c2fq>+EJo!>EjOnOepG&Q zX*VLCvnq^~7bW33)e=69;G4MfL#i3yRRj|DLJ*(sS@S0ZRNp*Pv8d&ses9HiGQ3{f zawx4gT-r#3*)Y?=(4ML=eZdgjs?Ga!Gkh+s2-lpLv?M-!lT*u(o{A#k(!b#>J9VJL zcXKMQe?d}M1-!3tnqr*4@Hm|sU8%QaFO_R&P9sIU1y0*her5G#;oDvKNcN3km>LJh z;`78jPQjHj&|5N<_MIsY>M;~H?N~>8JNY=W;2%jNqKF6gQ>-P8>SdXc8SgsIp?p;F zk8@OQj8TjBOv{I3-xn|>jwXZXo<;8=Rn&!50G5L`7@T})YATF0JtO^dE!wzTGe3C% zu3*Yq_U@qYMcUJ<4e9#UB+L73jNtDJg(-E)=8i%9dbU;ww4E zlJCXLpmJ3q>Z{`ga#q;v{r=<)+Cj=SMRI30kld$<;}B;IP5gb12RZ z>yfj?eOVf#f)(jQ&+9xu?FKfn-{+7Pqo|g;Li_ff)-wRLPI>48?moWiO`sJK(&b1a zoM%31u}_OihYg;q+2Yq}c5`*h;5P5kpn#1ms z4$p}46si4cyCN`BpLE684e92uYv#BY#b5;D8Pj5WBfbIGL8;lJbXfB2sy>v|Gv*D1 zu-1^GQ`y_ISt?ojyT;U>M~U(8L(7h08@?|W8$0zUH1@pjD7?cSV7fZdb{%Xv7}KCo{H`WCT_%(U&qK#O?6hB2Mwrhta-)=tXBE7|(>@g*Og zSK@F7xpcZJ>AMPFrLFqBg^QwQn<(&QZpNC~%J&wBC!I;mF^{Nyw|qhDTu9-25hQ8P zhrE1%&v%g{;R%-IlJBf_ZbWWPdTC{f^`H93#QRJ#w37SqWs*FKi!Wnwe7~St`_>kn|?tb&`&r zQ))nPFS5iUh5VnWa4}BzjgHCP;E_j4M%jOSyYW867qz`-VgkXrFC47|p+owGhv(Ef z`m++zU#Uo7b}m!d_e4?p1u5$+Pi7MmB~{mF*KPD&CXcV;|L7-{N%rR`M!p@eV|A9wa&^S{B(!)MQMV%xcF+hU zeGItzP@A{;K%LDxt*>l3z?nZ#U|YplnH&&GMG#5Nv|$n!_yXVqKTP2y)vMuNxq?NJ zsTJ%6Jr5+jngy&;=hzHW+nv zDjO|mJ8JXJR9aULg=do9&Yhn-9@cc;>dx9uwVL~S$$^#wR^i>%L zz$skqF~lr3oj_0b82djWv-SSyl?|D!1#>z8%_mvUxiu5N8ZfS3b+vfbTZ3*&l%u*K z+-AvLE0vAEoAJ->_pn?&5nrmg@SdT~i{KSqlGkGbfd29r8@E%{Z-q*Q>*sP84+_nl zPghR&*6T13V?~=LJcWgqlH0aC@O!zy^|Z4y{gFD=gIj2O+?U|OQeRxLXDVio=|=R; z9RC4)wf+lIBSHEh>UrR0G2Vu$mCK*4$NLmlia!SFz|o5XQ$0GYg416;Do{2(Se|^% zsJMJ6zFtH6oiNS@9ljt!>WVr8n0ogKRfl_ z+L5GdZ6)srda|T{?K)Wg&_L}7X*#mf9V;e^Jcn63z3^da~1 zUT4oIpHgC*Iy1kH239SdnsGU7 zhrP4kk%64jsW6+}kh*}-QOtW5Aq3g&Ci-uR#CsMPU{XXD>u-lkDM#opuYQ^2AJ&6M zr&BQeV4ZRGe&HJDla<0}vU*if2Lj7)s^m-r4Cu>;i7`G^3rebDt@b>Yr4C?kZDxx)%F+h<9BBI|M1eH0{T zPzV(BC_nYDMJ?~vwU&1%bWfpxNQ^kbF>S@&UiPfk&z2GBX7B+Pwx)h_TTO=;dTAgd zO`GWd)4+uoE#8Q%>@eE|&sY3dl}klTf!<~3?O23w;cP(8<**B`{LK9{3q1Wxi;Soz zG@2V*59kOh0YFq32>)Cmb2*GgL-c#@d!H%)ecc4~tJdRCL*d#pG+O3Og@Lm}V(Rf@A~k0e$=-XeQ!< z1h-$=szQ({E1UOn67R~KEbv@M^(SH(Pe;K&K2RA#1D0f_X@|@%rm_)%V8W=`!;s9i zHLd0GQB0D&gL6IGE zd6ES|Y@vd8qsRTBO$(pp6o$S0o<`QfUH46)PqIIoRz?|S{K?Xbr%KM^tgkPK>=)W& z(1Q3H9mYS-_U~k6c#W=$oM0bD3R9T+Y-M&0CF-2KedOkQLtWK-{h!=XxA|FNN#{PQ zZ(S;N@;j30_gsrc=JXw1tK`F7{}38$h{$LKind$8n!P=5ek$Yr-4og$Yr$~q;qCt+Fq1>^ zrJBB_P6ffO*U*9&Ib1u=R6 z4Nx23Sh8!?P9RVwx#S#@DbM@}jAr4aQHAEh)McIONjeRa5vJrfWV__h@%*jUVqePU z#YtltSVn`JF|<=yM3&a z4;L=JmFd=_w-A)@Ly;G(qb{x}a#p`O>Eb+i z)x%(q3t-{;1A%CP2=iS$PFA?JVW{keaWE8$)YCKd(_66na=V_shkZNJR_->1VLWhYLd|>V;rJj+r$vmMpqPY zGB@GIxY~PKuLH0t;~y&urLhc6VsO9cg3}TDQtQo_U64>h4fRZAt&B9oV*SBJQ8b)H z!>DzE^M43exjjF*059Oy;t@O!2^tUL5YcIw_*2L1yC*T)#4BfA`Q1Scs`e~u#!t5k z90gx8Q0i%T=$}vZAXZQqTBfhCbvb2l#a%kRen$uH05-ljDlUf&G6L|m$w&z#&4JqN zkQ6;Fn`gC8mwVlt%_+6qTO%gGLSV}ge`;Wh{jWU4=nKkUkno3#T#@z9sS%bh-VGvIKfIr?SlZivQyQeBh6!oJ&+ae@v=#P zQL&5t8<(wLrN?_iBxhY{=x)LLqkbw~O|4$@pK68N;j5X1iQ~p#Sc>}8!Kn7De4koL zls`u%5&8FU@@JnUNQh@QeTJy^m`Xh(|w0b~~%o)JZ>CT$w#B5IxD&%pA%exTkfgl}-Pc-&o*S+I@#e znfSBPIreT>K!Z9HSOeMj!#L3(yCvO9BFn}Jh(49{WvThDN(NjBM$R_ZYOh^&6>DrX zfNRz<`3y*l0K@Ts>%N~N?5j);+AVCCHT!d4Eq%hjfB90}{M#*`W9;9M_%|f}O%DHW eW&xC?3mPX)x!7jk^iVnYIrWp}G1iYRH~tshrx-W@ literal 0 HcmV?d00001 diff --git a/locales/en.yml b/locales/en.yml index 39eae3e..142e4ac 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -11,13 +11,16 @@ wallets: pass: 'Password:' name_empty: Enter name of wallet pass_empty: Enter password for wallet - word_number: 'Word #%{number}' - word_empty: 'Enter word #%{number} from recovery phrase' - not_valid_word: Entered word is not valid create: Create - import: Import - mnemonic_desc: 'Safely write down and save your %{}-word recovery phrase below. If you lose your device, you will need them to restore access to your funds.' - mnemonic_conf: Select words in the same order as they are displayed in your recovery phrase. + recover: Restore + words_count: 'Words count:' + word_number: 'Word #%{number}' + word_empty: 'Enter word #%{number} from your recovery phrase' + not_valid_word: Entered word is not valid + create_phrase_desc: Safely write down and save your recovery phrase. + restore_phrase_desc: Enter words from your saved recovery phrase. + conf_phrase_desc: Select words in the same order as they are displayed in your recovery phrase. + setup_conn_desc: Choose wallet connection method. network: self: Network node: Integrated node diff --git a/locales/ru.yml b/locales/ru.yml index 8286ab3..22ca972 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -11,13 +11,16 @@ wallets: pass: 'Пароль:' name_empty: Введите название кошелька pass_empty: Введите пароль для кошелька - word_number: 'Слово #%{number}' - word_empty: 'Введите слово #%{number} из фразы восстановления' - not_valid_word: Введено недопустимое слово create: Создать - import: Импортировать - mnemonic_desc: 'Безопасно запишите и сохраните свою фразу восстановления из %{} слов ниже. Они понадобятся вам, чтобы восстановить доступ к вашим средствам, если вы потеряете устройство.' - mnemonic_conf: Выберите слова в таком же порядке, как они отображены в вашей фразе восстановления. + recover: Восстановить + words_count: 'Количество слов:' + word_number: 'Слово #%{number}' + word_empty: 'Введите слово #%{number} из вашей фразы восстановления' + not_valid_word: Введено недопустимое слово + create_phrase_desc: Безопасно запишите и сохраните свою фразу восстановления. + restore_phrase_desc: Введите слова из вашей сохранённой фразы восстановления. + conf_phrase_desc: Выберите слова в таком же порядке, как они отображены в вашей фразе восстановления. + setup_conn_desc: Выберите способ подключения кошелька network: self: Сеть node: Встроенный узел diff --git a/src/gui/app.rs b/src/gui/app.rs index f3fc8be..c2d1040 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -49,6 +49,8 @@ impl eframe::App for PlatformApp { BACK_BUTTON_PRESSED.store(false, Ordering::Relaxed); } self.root.on_back(); + // Request repaint to update previous content. + ctx.request_repaint(); } // Show main content. diff --git a/src/gui/views/wallets/creation/connection.rs b/src/gui/views/wallets/creation/connection.rs index 42d3e48..cabc67a 100644 --- a/src/gui/views/wallets/creation/connection.rs +++ b/src/gui/views/wallets/creation/connection.rs @@ -13,7 +13,6 @@ // limitations under the License. use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::wallets::creation::StepControl; #[derive(Default)] pub struct ConnectionSetup { @@ -21,7 +20,7 @@ pub struct ConnectionSetup { } impl ConnectionSetup { - pub fn ui(&mut self, ui: &mut egui::Ui, step: &dyn StepControl, cb: &dyn PlatformCallbacks) { + pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { } } \ No newline at end of file diff --git a/src/gui/views/wallets/creation/content.rs b/src/gui/views/wallets/creation/creation.rs similarity index 68% rename from src/gui/views/wallets/creation/content.rs rename to src/gui/views/wallets/creation/creation.rs index b96a832..cf0cf32 100644 --- a/src/gui/views/wallets/creation/content.rs +++ b/src/gui/views/wallets/creation/creation.rs @@ -12,29 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::{Margin, RichText, TextStyle, Widget}; -use egui_extras::{Size, StripBuilder}; +use egui::{Margin, RichText, TextStyle, vec2, Widget}; +use egui_extras::{RetainedImage, Size, StripBuilder}; +use crate::built_info; use crate::gui::Colors; -use crate::gui::icons::{EYE, EYE_SLASH, PLUS_CIRCLE}; +use crate::gui::icons::{CHECK, EYE, EYE_SLASH, PLUS_CIRCLE, SHARE_FAT}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, ModalPosition, View}; -use crate::gui::views::wallets::creation::{ConnectionSetup, MnemonicSetup, StepControl}; -use crate::gui::views::wallets::creation::mnemonic::PhraseMode; - -/// Wallet creation step. -enum Step { - /// Mnemonic phrase input. - EnterMnemonic, - /// Mnemonic phrase confirmation for [`Mnemonic`]. - ConfirmMnemonic, - /// Wallet connection setup. - SetupConnection -} +use crate::gui::views::wallets::creation::{ConnectionSetup, MnemonicSetup}; +use crate::gui::views::wallets::creation::types::{PhraseMode, Step}; /// Wallet creation content. pub struct WalletCreation { - /// Wallet creation ui step. + /// Wallet creation step. step: Option, /// Flag to check if [`Modal`] just was opened to focus on first field. @@ -50,6 +41,9 @@ pub struct WalletCreation { pub(crate) mnemonic_setup: MnemonicSetup, /// Network setup content. pub(crate) network_setup: ConnectionSetup, + + /// App logo image. + logo: RetainedImage, } impl Default for WalletCreation { @@ -62,52 +56,10 @@ impl Default for WalletCreation { hide_pass: true, mnemonic_setup: MnemonicSetup::default(), network_setup: ConnectionSetup::default(), - } - } -} - - -impl StepControl for WalletCreation { - /// Go to next wallet creation [`Step`]. - fn next_step(&mut self) { - self.step = match &self.step { - None => Some(Step::EnterMnemonic), - Some(step) => { - match step { - Step::EnterMnemonic => { - if self.mnemonic_setup.get_mnemonic_mode() == &PhraseMode::Generate { - Some(Step::SetupConnection) - } else { - Some(Step::ConfirmMnemonic) - } - } - Step::ConfirmMnemonic => Some(Step::SetupConnection), - Step::SetupConnection => { - //TODO: Confirm mnemonic - None - } - } - } - } - } - - /// Go to previous wallet creation [`Step`]. - fn prev_step(&mut self) { - match &self.step { - None => {} - Some(step) => { - match step { - Step::EnterMnemonic => { - // Clear values if it needs to go back on first step. - self.step = None; - self.name_edit = "".to_string(); - self.pass_edit = "".to_string(); - self.mnemonic_setup.reset(); - } - Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic), - Step::SetupConnection => self.step = Some(Step::ConfirmMnemonic) - } - } + logo: RetainedImage::from_image_bytes( + "logo.png", + include_bytes!("../../../../../img/logo.png"), + ).unwrap(), } } } @@ -117,10 +69,62 @@ impl WalletCreation { pub const MODAL_ID: &'static str = "create_wallet_modal"; pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { + // Show wallet creation step description and confirmation panel. + if self.can_go_back() { + egui::TopBottomPanel::bottom("wallet_creation_step_panel") + .frame(egui::Frame { + stroke: View::DEFAULT_STROKE, + inner_margin: Margin { + left: View::far_left_inset_margin(ui) + 4.0, + right: View::get_right_inset() + 4.0, + top: 4.0, + bottom: View::get_bottom_inset() + 4.0, + }, + ..Default::default() + }) + .show_inside(ui, |ui| { + ui.vertical_centered(|ui| { + if let Some(step) = &self.step { + // Setup step description text. + let step_text = match step { + Step::EnterMnemonic => { + let mode = &self.mnemonic_setup.mnemonic.mode; + let size_value = self.mnemonic_setup.mnemonic.size.value(); + if mode == &PhraseMode::Generate { + t!("wallets.create_phrase_desc", "number" => size_value) + } else { + t!("wallets.restore_phrase_desc", "number" => size_value) + } + } + Step::ConfirmMnemonic => t!("wallets.conf_phrase_desc"), + Step::SetupConnection => t!("wallets.setup_conn_desc") + }; + // Show step description. + ui.label(RichText::new(step_text).size(16.0).color(Colors::GRAY)); + ui.add_space(4.0); + + // Setup next step button text. + let (next_text, color) = if step == &Step::SetupConnection { + (format!("{} {}", CHECK, t!("complete")), Colors::GOLD) + } else { + let text = format!("{} {}", SHARE_FAT, t!("continue")); + (text, Colors::WHITE) + }; + // Show next step button. + View::button(ui, next_text.to_uppercase(), color, || { + self.forward(); + }); + ui.add_space(4.0); + } + }); + }); + } + // Show wallet creation step content. egui::CentralPanel::default() .frame(egui::Frame { stroke: View::DEFAULT_STROKE, + fill: if self.step.is_none() { Colors::FILL_DARK } else { Colors::WHITE }, inner_margin: Margin { left: View::far_left_inset_margin(ui) + 4.0, right: View::get_right_inset() + 4.0, @@ -139,11 +143,24 @@ impl WalletCreation { match &self.step { None => { // Show wallet creation message if step is empty. - View::center_content(ui, 124.0 + View::get_bottom_inset(), |ui| { + View::center_content(ui, 415.0 + View::get_bottom_inset(), |ui| { + ui.add( + egui::Image::new(self.logo.texture_id(ui.ctx()), vec2(200.0, 200.0)) + ); + ui.add_space(-15.0); + ui.label(RichText::new("GRIM") + .size(24.0) + .color(Colors::BLACK) + ); + ui.label(RichText::new(built_info::PKG_VERSION) + .size(16.0) + .color(Colors::BLACK) + ); + ui.add_space(4.0); let text = t!("wallets.create_desc"); ui.label(RichText::new(text) .size(16.0) - .color(Colors::INACTIVE_TEXT) + .color(Colors::GRAY) ); ui.add_space(8.0); let add_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add")); @@ -155,7 +172,7 @@ impl WalletCreation { Some(step) => { match step { Step::EnterMnemonic => { - self.mnemonic_setup.ui(ui, self, cb); + self.mnemonic_setup.ui(ui, cb); } Step::ConfirmMnemonic => {} Step::SetupConnection => {} @@ -169,9 +186,47 @@ impl WalletCreation { self.step.is_some() } - /// Back button key event handling. - pub fn go_back(&mut self) { - self.prev_step(); + /// Back to previous wallet creation [`Step`]. + pub fn back(&mut self) { + match &self.step { + None => {} + Some(step) => { + match step { + Step::EnterMnemonic => { + // Clear values if it needs to go back on first step. + self.step = None; + self.name_edit = "".to_string(); + self.pass_edit = "".to_string(); + self.mnemonic_setup.reset(); + } + Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic), + Step::SetupConnection => self.step = Some(Step::EnterMnemonic) + } + } + } + } + + /// Go to the next wallet creation [`Step`]. + fn forward(&mut self) { + self.step = match &self.step { + None => Some(Step::EnterMnemonic), + Some(step) => { + match step { + Step::EnterMnemonic => { + if self.mnemonic_setup.mnemonic.mode == PhraseMode::Generate { + Some(Step::ConfirmMnemonic) + } else { + Some(Step::SetupConnection) + } + } + Step::ConfirmMnemonic => Some(Step::SetupConnection), + Step::SetupConnection => { + //TODO: Confirm mnemonic + None + } + } + } + } } /// Start wallet creation from showing [`Modal`] to enter name and password. @@ -181,17 +236,6 @@ impl WalletCreation { .title(t!("wallets.add"))); } - /// Callback to go to next step for wallet creation from [`Modal`]. - fn on_modal_confirmation(&mut self, modal: &Modal, cb: &dyn PlatformCallbacks) { - // Check if input values are not empty. - if self.name_edit.is_empty() || self.pass_edit.is_empty() { - return; - } - self.step = Some(Step::EnterMnemonic); - cb.hide_keyboard(); - modal.close(); - } - /// Draw wallet creation [`Modal`] content. pub fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { ui.add_space(6.0); @@ -300,7 +344,13 @@ impl WalletCreation { }); columns[1].vertical_centered_justified(|ui| { View::button(ui, t!("continue"), Colors::WHITE, || { - self.on_modal_confirmation(modal, cb); + // Check if input values are not empty. + if self.name_edit.is_empty() || self.pass_edit.is_empty() { + return; + } + self.forward(); + cb.hide_keyboard(); + modal.close(); }); }); }); diff --git a/src/gui/views/wallets/creation/mnemonic.rs b/src/gui/views/wallets/creation/mnemonic.rs index 950718c..209665b 100644 --- a/src/gui/views/wallets/creation/mnemonic.rs +++ b/src/gui/views/wallets/creation/mnemonic.rs @@ -12,89 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use grin_keychain::mnemonic::from_entropy; -use rand::{Rng, thread_rng}; +use egui::{RichText, ScrollArea}; + +use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::wallets::creation::StepControl; - -/// Mnemonic phrase setup mode. -#[derive(PartialEq)] -pub enum PhraseMode { - /// Generate new mnemonic phrase. - Generate, - /// Import existing mnemonic phrase. - Import -} - -/// Mnemonic phrase type based on words count. -pub enum PhraseType { Words12, Words15, Words18, Words21, Words24 } - -impl PhraseType { - pub fn value(&self) -> usize { - match *self { - PhraseType::Words12 => 12, - PhraseType::Words15 => 15, - PhraseType::Words18 => 18, - PhraseType::Words21 => 21, - PhraseType::Words24 => 24 - } - } -} - -/// Mnemonic phrase container. -pub struct Mnemonic { - /// Phrase setup mode. - pub(crate) mode: PhraseMode, - /// Type of phrase based on words count. - size: PhraseType, - /// Words for phrase. - words: Vec -} - -impl Default for Mnemonic { - fn default() -> Self { - let size = PhraseType::Words12; - let size_value = size.value(); - Self { mode: PhraseMode::Generate, size, words: Vec::with_capacity(size_value) } - } -} - -impl Mnemonic { - /// Change mnemonic phrase setup [`PhraseMode`]. - fn set_mode(&mut self, mode: PhraseMode) { - self.mode = mode; - self.setup_words(); - } - - /// Change mnemonic phrase words [`PhraseType`]. - fn set_size(&mut self, size: PhraseType) { - self.size = size; - self.setup_words(); - } - - /// Setup words based on current [`PhraseMode`] and [`PhraseType`]. - fn setup_words(&mut self) { - self.words = match self.mode { - PhraseMode::Generate => { - let mut rng = thread_rng(); - let mut entropy: Vec = Vec::with_capacity(self.size.value()); - for _ in 0..self.size.value() { - entropy.push(rng.gen()); - } - from_entropy(&entropy).unwrap() - .split(" ") - .map(|s| s.to_string()) - .collect::>() - }, - PhraseMode::Import => Vec::with_capacity(self.size.value()) - }; - } -} +use crate::gui::views::{Root, View}; +use crate::gui::views::wallets::creation::types::{Mnemonic, PhraseMode, PhraseSize}; /// Mnemonic phrase setup content. pub struct MnemonicSetup { /// Current mnemonic phrase. - mnemonic: Mnemonic, + pub(crate) mnemonic: Mnemonic, /// Word value for [`Modal`]. word_edit: String, } @@ -109,12 +37,172 @@ impl Default for MnemonicSetup { } impl MnemonicSetup { - pub fn ui(&self, ui: &mut egui::Ui, step: &dyn StepControl, cb: &dyn PlatformCallbacks) { + pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { + ScrollArea::vertical() + .id_source("mnemonic_words_list") + .auto_shrink([false; 2]) + .show(ui, |ui| { + ui.add_space(10.0); + // Show mode and type setup. + self.mode_type_ui(ui); + + ui.add_space(12.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(12.0); + + // Show words setup. + self.words_ui(ui); + }); } - pub fn get_mnemonic_mode(&self) -> &PhraseMode { - &self.mnemonic.mode + /// Draw mode and size setup. + fn mode_type_ui(&mut self, ui: &mut egui::Ui) { + // Show mode setup. + let mut mode = self.mnemonic.mode.clone(); + ui.columns(2, |columns| { + columns[0].vertical_centered(|ui| { + let create_mode = PhraseMode::Generate; + let create_text = t!("wallets.create"); + View::radio_value(ui, &mut mode, create_mode, create_text); + }); + columns[1].vertical_centered(|ui| { + let import_mode = PhraseMode::Import; + let import_text = t!("wallets.recover"); + View::radio_value(ui, &mut mode, import_mode, import_text); + }); + }); + if mode != self.mnemonic.mode { + self.mnemonic.set_mode(mode) + } + + ui.add_space(10.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("wallets.words_count")) + .size(16.0) + .color(Colors::GRAY) + ); + }); + ui.add_space(6.0); + + // Show mnemonic phrase size setup. + let mut size = self.mnemonic.size.clone(); + ui.columns(5, |columns| { + columns[0].vertical_centered(|ui| { + let words12 = PhraseSize::Words12; + let text = words12.value().to_string(); + View::radio_value(ui, &mut size, words12, text); + }); + columns[1].vertical_centered(|ui| { + let words15 = PhraseSize::Words15; + let text = words15.value().to_string(); + View::radio_value(ui, &mut size, words15, text); + }); + columns[2].vertical_centered(|ui| { + let words18 = PhraseSize::Words18; + let text = words18.value().to_string(); + View::radio_value(ui, &mut size, words18, text); + }); + columns[3].vertical_centered(|ui| { + let words21 = PhraseSize::Words21; + let text = words21.value().to_string(); + View::radio_value(ui, &mut size, words21, text); + }); + columns[4].vertical_centered(|ui| { + let words24 = PhraseSize::Words24; + let text = words24.value().to_string(); + View::radio_value(ui, &mut size, words24, text); + }); + }); + if size != self.mnemonic.size { + self.mnemonic.set_size(size); + } + } + + /// Draw words setup based on selected [`PhraseMode`]. + fn words_ui(&mut self, ui: &mut egui::Ui) { + ui.vertical_centered(|ui| { + // Show word list based on setup mode. + match self.mnemonic.mode { + PhraseMode::Generate => self.word_list_generate_ui(ui), + PhraseMode::Import => self.word_list_import_ui(ui) + } + }); + } + + /// Draw word list for [`PhraseMode::Generate`] mode. + fn word_list_generate_ui(&mut self, ui: &mut egui::Ui) { + // Calculate rows count based on available ui width. + const PADDING: f32 = 24.0; + let w = ui.available_width(); + let min_panel_w = Root::SIDE_PANEL_MIN_WIDTH; + let double_min_panel_w = (min_panel_w * 2.0) - PADDING; + let cols = if w >= (min_panel_w * 1.5) - PADDING && w < double_min_panel_w { + 3 + } else if w >= double_min_panel_w { + 4 + } else { + 2 + }; + + // Show words amount. + let mut word_number = 0; + let _ = self.mnemonic.words.chunks(cols).map(|chunk| { + let size = chunk.len(); + word_number += 1; + if size > 1 { + ui.columns(cols, |columns| { + columns[0].horizontal(|ui| { + ui.add_space(PADDING); + Self::generated_word_ui(ui, word_number, chunk, 0); + }); + columns[1].horizontal(|ui| { + ui.add_space(PADDING); + word_number += 1; + Self::generated_word_ui(ui, word_number, chunk, 1); + }); + if size > 2 { + columns[2].horizontal(|ui| { + ui.add_space(PADDING); + word_number += 1; + Self::generated_word_ui(ui, word_number, chunk, 2); + }); + } + if size > 3 { + columns[3].horizontal(|ui| { + ui.add_space(PADDING); + word_number += 1; + Self::generated_word_ui(ui, word_number, chunk, 3); + }); + } + }); + } else { + ui.columns(cols, |columns| { + columns[0].horizontal(|ui| { + ui.add_space(PADDING); + Self::generated_word_ui(ui, word_number, chunk, 0); + }); + }); + ui.add_space(12.0); + } + ui.add_space(8.0); + }).collect::>(); + } + + /// Draw generated word at given index from provided chunk. + fn generated_word_ui(ui: &mut egui::Ui, + word_number: usize, + chunk: &[String], + index: usize) { + let word = chunk.get(index).unwrap(); + let text = format!("#{} {}", word_number, word); + ui.label(RichText::new(text).size(16.0).color(Colors::BLACK)); + } + + + /// Draw word list for [`PhraseMode::Import`] mode. + fn word_list_import_ui(&mut self, ui: &mut egui::Ui) { + } /// Reset mnemonic phrase to default values. diff --git a/src/gui/views/wallets/creation/mod.rs b/src/gui/views/wallets/creation/mod.rs index f9b7b2a..bc1b941 100644 --- a/src/gui/views/wallets/creation/mod.rs +++ b/src/gui/views/wallets/creation/mod.rs @@ -18,13 +18,7 @@ pub use mnemonic::MnemonicSetup; mod connection; pub use connection::ConnectionSetup; -mod content; -pub use content::WalletCreation; +mod creation; +pub use creation::WalletCreation; -/// Interface to provide moving between wallet creation steps. -pub trait StepControl { - /// Go to next wallet creation step. - fn next_step(&mut self); - /// Go to previous wallet creation Step. - fn prev_step(&mut self); -} \ No newline at end of file +pub mod types; \ No newline at end of file diff --git a/src/gui/views/wallets/creation/types.rs b/src/gui/views/wallets/creation/types.rs new file mode 100644 index 0000000..94bac21 --- /dev/null +++ b/src/gui/views/wallets/creation/types.rs @@ -0,0 +1,115 @@ +// Copyright 2023 The Grim Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use grin_keychain::mnemonic::from_entropy; +use rand::{Rng, thread_rng}; + +/// Wallet creation step. +#[derive(PartialEq)] +pub enum Step { + /// Mnemonic phrase input. + EnterMnemonic, + /// Mnemonic phrase confirmation for [`Mnemonic`]. + ConfirmMnemonic, + /// Wallet connection setup. + SetupConnection +} + +/// Mnemonic phrase setup mode. +#[derive(PartialEq, Clone)] +pub enum PhraseMode { + /// Generate new mnemonic phrase. + Generate, + /// Import existing mnemonic phrase. + Import +} + +/// Mnemonic phrase size based on words count. +#[derive(PartialEq, Clone)] +pub enum PhraseSize { Words12, Words15, Words18, Words21, Words24 } + +impl PhraseSize { + /// Gen words count number. + pub fn value(&self) -> usize { + match *self { + PhraseSize::Words12 => 12, + PhraseSize::Words15 => 15, + PhraseSize::Words18 => 18, + PhraseSize::Words21 => 21, + PhraseSize::Words24 => 24 + } + } + + /// Gen entropy size for current phrase size. + pub fn entropy_size(&self) -> usize { + match *self { + PhraseSize::Words12 => 16, + PhraseSize::Words15 => 20, + PhraseSize::Words18 => 24, + PhraseSize::Words21 => 28, + PhraseSize::Words24 => 32 + } + } +} + +/// Mnemonic phrase container. +pub struct Mnemonic { + /// Phrase setup mode. + pub(crate) mode: PhraseMode, + /// Size of phrase based on words count. + pub(crate) size: PhraseSize, + /// Words for phrase. + pub(crate) words: Vec +} + +impl Default for Mnemonic { + fn default() -> Self { + let size = PhraseSize::Words24; + let mode = PhraseMode::Generate; + let words = Self::generate_words(&mode, &size); + Self { mode, size, words } + } +} + +impl Mnemonic { + /// Change mnemonic phrase setup [`PhraseMode`]. + pub fn set_mode(&mut self, mode: PhraseMode) { + self.mode = mode; + self.words = Self::generate_words(&self.mode, &self.size); + } + + /// Change mnemonic phrase words [`PhraseSize`]. + pub fn set_size(&mut self, size: PhraseSize) { + self.size = size; + self.words = Self::generate_words(&self.mode, &self.size); + } + + /// Setup words based on provided [`PhraseMode`] and [`PhraseSize`]. + fn generate_words(mode: &PhraseMode, size: &PhraseSize) -> Vec { + match mode { + PhraseMode::Generate => { + let mut rng = thread_rng(); + let mut entropy: Vec = Vec::with_capacity(size.entropy_size()); + for _ in 0..size.entropy_size() { + entropy.push(rng.gen()); + } + from_entropy(&entropy).unwrap() + .split(" ") + .map(|s| s.to_string()) + .collect::>() + }, + PhraseMode::Import => Vec::with_capacity(size.value()) + } + } +} \ No newline at end of file diff --git a/src/gui/views/wallets/wallets.rs b/src/gui/views/wallets/wallets.rs index 3a34caf..447d9c0 100644 --- a/src/gui/views/wallets/wallets.rs +++ b/src/gui/views/wallets/wallets.rs @@ -113,18 +113,23 @@ impl Wallets { /// Draw title content. fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { // Setup title text. - let title_content = TitleType::Single(t!("wallets.title").to_uppercase()); + let title_text = if self.creation_content.can_go_back() { + t!("wallets.new") + } else { + t!("wallets.title") + }; + let title_content = TitleType::Single(title_text.to_uppercase()); // Draw title panel. TitlePanel::ui(title_content, |ui, frame| { - if !Root::is_dual_panel_mode(frame) { + if self.creation_content.can_go_back() { + View::title_button(ui, ARROW_LEFT, || { + self.creation_content.back(); + }); + } else if !Root::is_dual_panel_mode(frame) { View::title_button(ui, GLOBE, || { Root::toggle_network_panel(); }); - } else if self.creation_content.can_go_back() { - View::title_button(ui, ARROW_LEFT, || { - self.creation_content.go_back(); - }); }; }, |ui, frame| { View::title_button(ui, GEAR, || { @@ -192,7 +197,7 @@ impl Wallets { pub fn on_back(&mut self) -> bool { let can_go_back = self.creation_content.can_go_back(); if can_go_back { - self.creation_content.go_back(); + self.creation_content.back(); } !can_go_back } diff --git a/src/lib.rs b/src/lib.rs index 1c28c58..5735930 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,11 @@ use crate::node::Node; i18n!("locales"); +// Include build information. +pub mod built_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} + mod node; mod wallet; pub mod gui;