From 33209c3cd621cfbc4e7b28b00b193f3019f53d76 Mon Sep 17 00:00:00 2001 From: Oleksandr Sobol Date: Fri, 31 Jul 2020 18:29:21 +0300 Subject: [PATCH] CAKE-20 | updated exchange and exchange template pages; created exchange_view_model and applied to exchange and exchange template pages; applied new design to menu widget; changed text validator and amount validator --- assets/images/2.0x/eye_menu.png | Bin 0 -> 939 bytes assets/images/2.0x/key_menu.png | Bin 0 -> 923 bytes assets/images/2.0x/monero_menu.png | Bin 0 -> 2804 bytes assets/images/2.0x/nodes_menu.png | Bin 0 -> 1085 bytes assets/images/2.0x/open_book_menu.png | Bin 0 -> 735 bytes assets/images/2.0x/reconnect_menu.png | Bin 0 -> 875 bytes assets/images/2.0x/settings_menu.png | Bin 0 -> 1051 bytes assets/images/2.0x/wallet_menu.png | Bin 0 -> 659 bytes assets/images/3.0x/eye_menu.png | Bin 0 -> 1398 bytes assets/images/3.0x/key_menu.png | Bin 0 -> 1353 bytes assets/images/3.0x/monero_menu.png | Bin 0 -> 4077 bytes assets/images/3.0x/nodes_menu.png | Bin 0 -> 1476 bytes assets/images/3.0x/open_book_menu.png | Bin 0 -> 994 bytes assets/images/3.0x/reconnect_menu.png | Bin 0 -> 1281 bytes assets/images/3.0x/settings_menu.png | Bin 0 -> 1444 bytes assets/images/3.0x/wallet_menu.png | Bin 0 -> 920 bytes assets/images/eye_menu.png | Bin 0 -> 536 bytes assets/images/key_menu.png | Bin 0 -> 518 bytes assets/images/monero_menu.png | Bin 0 -> 1411 bytes assets/images/nodes_menu.png | Bin 0 -> 597 bytes assets/images/open_book_menu.png | Bin 0 -> 454 bytes assets/images/reconnect_menu.png | Bin 0 -> 495 bytes assets/images/settings_menu.png | Bin 0 -> 594 bytes assets/images/wallet_menu.png | Bin 0 -> 358 bytes lib/core/amount_validator.dart | 3 +- lib/core/validator.dart | 4 +- lib/di.dart | 16 + lib/generated/i18n.dart | 23 ++ lib/palette.dart | 10 +- lib/router.dart | 41 +- lib/src/screens/base_page.dart | 8 + lib/src/screens/dashboard/dashboard_page.dart | 15 +- lib/src/screens/dashboard/wallet_menu.dart | 14 +- .../dashboard/widgets/menu_widget.dart | 293 ++++++-------- lib/src/screens/exchange/exchange_page.dart | 58 +-- .../exchange/exchange_template_page.dart | 45 +-- .../widgets/base_exchange_widget.dart | 378 +++++++++--------- .../exchange/widgets/exchange_card.dart | 110 ++++- .../widgets/present_provider_picker.dart | 33 +- lib/src/screens/receive/receive_page.dart | 2 +- lib/src/widgets/base_text_form_field.dart | 4 + lib/src/widgets/nav_bar.dart | 2 + lib/src/widgets/trail_button.dart | 3 +- .../exchange/exchange_view_model.dart | 306 ++++++++++++++ res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_zh.arb | 1 + 56 files changed, 856 insertions(+), 524 deletions(-) create mode 100644 assets/images/2.0x/eye_menu.png create mode 100644 assets/images/2.0x/key_menu.png create mode 100644 assets/images/2.0x/monero_menu.png create mode 100644 assets/images/2.0x/nodes_menu.png create mode 100644 assets/images/2.0x/open_book_menu.png create mode 100644 assets/images/2.0x/reconnect_menu.png create mode 100644 assets/images/2.0x/settings_menu.png create mode 100644 assets/images/2.0x/wallet_menu.png create mode 100644 assets/images/3.0x/eye_menu.png create mode 100644 assets/images/3.0x/key_menu.png create mode 100644 assets/images/3.0x/monero_menu.png create mode 100644 assets/images/3.0x/nodes_menu.png create mode 100644 assets/images/3.0x/open_book_menu.png create mode 100644 assets/images/3.0x/reconnect_menu.png create mode 100644 assets/images/3.0x/settings_menu.png create mode 100644 assets/images/3.0x/wallet_menu.png create mode 100644 assets/images/eye_menu.png create mode 100644 assets/images/key_menu.png create mode 100644 assets/images/monero_menu.png create mode 100644 assets/images/nodes_menu.png create mode 100644 assets/images/open_book_menu.png create mode 100644 assets/images/reconnect_menu.png create mode 100644 assets/images/settings_menu.png create mode 100644 assets/images/wallet_menu.png create mode 100644 lib/view_model/exchange/exchange_view_model.dart diff --git a/assets/images/2.0x/eye_menu.png b/assets/images/2.0x/eye_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..82e097023cf02ace3a854ddab8b06989149becd7 GIT binary patch literal 939 zcmV;c162HpP)CqS&J!tn{>>?lZxv!_bZ2{8Fk?2?>7`5YkT1Ti-dH}F2vG~L2- zG^5eS zL8kmYB-jO@0D1!#OziSJ9AEX4?h-f%lU;@0x2grU2c?gs-*s84k&IX)M?cxl674{u z_#_@jP5a8V_$f?A8oh@OA}a!lJj!36MjtSz{0)Gfx5xt_hF_wahmT=0bnVwHa~LL5 zV7Kt!0W@FRXm(M=D->y^vC?^%kt) zBg$h$FQ^IdO;&i z?MCS;j3TGle-fyM|6Za>B%rFo5_qglukKqy62T_oE+dqRoSKUT2x9XR9OeI7f&p%F zj#}Zwid{RYCPGPsd)|F*0zjM=KjkyK;U4N~yO79V2eEN!hKjQg1HM3fKPU)D<{@7D zI=gb6aSvK!LS)>gz6QR53U9$T(*^7PH7lIwLhzo^+pd!w&BgeKu zq(gS2)TZp>m+e(@X3C9GW=cm!`vAx2;?vE3x}Znq@j5ZsRl;b8lVn*5K0r)Og#_>r-Vz z3OxQ^q9b(1lL=|KO=_A8|C?T!y&h54==yfcGU(#b1xpxYRCHtf>G1 N002ovPDHLkV1l;8tfl|} literal 0 HcmV?d00001 diff --git a/assets/images/2.0x/key_menu.png b/assets/images/2.0x/key_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..dc69ce578e24cf9f3a8dc41ffd3b876a2baa7182 GIT binary patch literal 923 zcmV;M17!S(P)s=XP=@0^)vH4~|(h8k^_I7Xg5Vpi8ckB`35dek%Dc<|;w*#>&*a9;#E}zx`(6-QW z(|b=YC?f`nL_DdWA~6h4S>(f03tA#ZsPBO4q!$)!SIixV&gzn;uOpY zbz6+o`RX=@`WQ7R;n*G3r%-DkQuA&=tV``7z<@L2`pkbbStUkZOmX~Izo!U$d{|GA z2#G-*M_l|6DDwhJzEO_s+paebkYE@C-XOxcE{1@-)C)Y72l=U_u;|?jL4D}{bT}RV z+!wzx(3an^m{F0KGp{)>31@!$f+&)p4IoK*@CrxFUX*1=6?8FGmUAdVTkVAV_pw>U+6e!FnK552LAfSzU!Es9<)duA0$ z3`N`^>0%^1ba*vdAj3=x#egpZ$py#NEG{%I!TbtpeYky$WKwQMDQTvI`c;tm*qTDGF8 zAg;iAb~s$u9H@YF2<($q*2b;r5DZY<7EJg!$r`zkp16zo9^3MIMSy{ct|gl6D061 zb3j^fR%Fok_Km$GL literal 0 HcmV?d00001 diff --git a/assets/images/2.0x/monero_menu.png b/assets/images/2.0x/monero_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..b92733a165a0f14ebdd26f537ac5a8a0352ea476 GIT binary patch literal 2804 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 D2WR;< literal 0 HcmV?d00001 diff --git a/assets/images/2.0x/open_book_menu.png b/assets/images/2.0x/open_book_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..9f7c2c0f674c1b478cc6b45f35df7bb05ff759c9 GIT binary patch literal 735 zcmV<50wDc~P)K_oFK{xq8o^*4O`R+tTHfS$L<4AJV6ZDG156V5NRWIs1t-bLF5~-w{|Bp z6h%vnewMZh7kt2y5*NGk?QmGU0cd0=l*@TAY8g=HfOt#*Z2+3`R6+#t40QS<>`!42 zk~kV1pQC=@!2VM#iX5WwGVEt1)Y&lpLx3IFNRBB1F(k&<;b8(bAZFlp5UB_G{^V_r zL8&71QNZvtxEt`3Tc{<@B0>ZPCRNCj;8#Z}(?tTGA>^Pf+g&*^!|UN;=py15&H~gE zc^uS}Jf9BlGJ)i0l6nTwt=iia;%p807P>S)O{quH@82lr9d9h}yu)$59eZ&m$OlVt zycFdSoN34C2aV3(rRla@-0GsDsAY`hfBq3D)#ZBf1(1q`HIx}(091)GkMW|I7vfC+ zx4_-CW3fqG41sL(7bLHai#N|=wPrE~Px(uZ$WhVvuLloMQ#+y7Ku-Dgvlw#Hl?u8V z9=(MZO2I2s17)iQ%2o}Otr{p>HBh!{plsDZ*{XrERRd+KR|zG?LEK8|NZ1J6N)N#W zomB16zO-cLk1JEz0xE@7!#O~N0oy1ByieGwBMN&KteXk0=qbQZ=vvFFlXz%8wzJ!#4#DtZc&<@&`< zE0*ke_nAF}s>+^%b}-py=MgM*5{$U-4!^y#b2&O8~e^geaR03sVxmtS#a)DV&%Jkw(9F9HMlZs#v zsb+g5)v@ym3Gmvx!tj^UZB9VERTA+d z>;S`>fgh-2NfGG;lxh#+j7wqt2`uFyd{)N@=mtcq^D;LEuh)sgDOl(>8us)p|0mOU zZ;84o2Va@%`1ix7rZ*oKns5v zt2RVGg3U0UG8k6HG?~Mi`k5@OX({j239CoZ5)(wAn1W_BYIWnwj_H|1d{qX+@=P#X z4)~HkC+IzbLEI|Pzd`<`5WT1Qru!+Pk}{YzAaiZPDinxv4<|y^!B0;LwvO%%iRzlR zt>sH`Zi30)wi$*cI^A}yFzN9&ftKKCn_m^ZuuR@&7%m63jTw01Gh@|O!Acz*l>w0h zN4peC+J>Aj+2o})pw`?88{i1TqP(Q{HC1qwm##UXOl+(6UwD>lBdBUy)wLWDRoFAt zfS855m3$O>)lR@s9(|h}kSD|wScuC3Xg)*dtN}P`Eiu}(5vJNi!C^ciZw-XG49e&6 z@*-IT!~QpOXw-5*lDah2fNM2pdzhkfUEGSQ zZzp^@m_g;tqiB*^RPGaI1;l=X^9paN{lc)aD9c)jdF4JT%nFFrjl&-QGNDIZfd-pe zh+SknN>fWgzf1r}TR@*>Sx6X!?1OG}X_Y)}^dGn%q&lwF;h_Kk002ovPDHLkV1hL= BiQ)hN literal 0 HcmV?d00001 diff --git a/assets/images/2.0x/settings_menu.png b/assets/images/2.0x/settings_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..9456a57c05abf797f818a388076c663753db4b9e GIT binary patch literal 1051 zcmV+$1mydPP){90n3~qc^KI9We;UZ4GJwr~(4&$QT07GSs7YT2DuDZ@$ZUxXa?xaD1fD z8xnwTUqPuxh`xovJ#2}s6yRI?8Mf|NKo_>dw7+ za*J|JoP|I88G0`+;q3p&qwFL9?a6oR8s3|sdj1+uzqsUVSl}cacRXwjUFK&0KP@Ob zHqE(`R!|Cv{hRRcH&j3@hX|oNQ1!@T*R0)wYwb09 zkB&`q)_&9?eYH!9okr7;(=!6cWnM`ih&5WdP7dJ}=58;b)j=SCxeocD#jL2{Z4|`3 z?w;dFVyIs}8U6RD#{6X;#bj)UJ5xiOxeI>o^3OJGBH{FlO@&=Q zeQcVW58>(up+j)_e(dAxIEc*g^KhE5&|*eBaT+em=^9c%OyRsSV&TJWgbO77@;wH) zCJcb%!LR1o4 zdsXMFs>%HJM&M+W9hkxeH}PR2bLCO=&*NzTO%>oPn46RDac|~YE^}n$J`whzsRFop z`zxpeEzC;Ufu;(Oks(#LUxVHAWDNPxRDriZoeR?9Z!@iII0=81tR}A@G*y5Ds=OIn zpP>dGW~JAZAEXqTDuAey@?jJSUvRBo&Gn088fgMe6}S$Asp$rfUrrmt5yC2S-)sq3h8bqT5f3X zFPO2S-6aBR-&fZ^3d81bFPL1m*r}kQ?|y1yTPc2 zq;6e9vrBvK?E5wa+Z&q33anAz_fbluFKwLhzbGKcvjw~(E+QA}KwZ80lU@5q!9N5K VBu)%jROSEx002ovPDHLkV1mY-P literal 0 HcmV?d00001 diff --git a/assets/images/2.0x/wallet_menu.png b/assets/images/2.0x/wallet_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..c5b622fed65e3d58b63a3f490d361252aeaebe32 GIT binary patch literal 659 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 LNkvXXu0mjflvQ(O literal 0 HcmV?d00001 diff --git a/assets/images/3.0x/monero_menu.png b/assets/images/3.0x/monero_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..7d81566f554143862b10626dfeea7139b9453295 GIT binary patch literal 4077 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 literal 0 HcmV?d00001 diff --git a/assets/images/3.0x/nodes_menu.png b/assets/images/3.0x/nodes_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..d97ca64e155a269e975506548ee0022056daeff1 GIT binary patch literal 1476 zcmV;#1v~nQP)8Wkt#Lm|MUZx z9@|OZpbAi+K$Z%~XoxR&kCaSF1uDx4y=|u3p3@ z##3J8MY15q7F0u}lJAeh-ftifKJWWk4kx{BWr7bC!jC6G_gTu@g8kr}q96v-@6uqD z_u~mV-iiN}Xy)gISHW@6)k~Z`6JKe6%Ux|b#S_;^B9&@W-XG`XV)Op3$PqGuo8y%UB{z6d}FNqM@Yz@y-T!@pO`71YR8 z$)`n8k(HmI`wtkvWgsVzV1cxxTw4;rC_DpM$fq=b_AJ-rZKg|ueixO2kktg~cy6a~ z6e=l|g%{Fza|)C(D*(@q$2Tsn^L3;d0&XfW59{PgNm4YCZrUvdTnX9YB$_m(0 z;(U&<)1ZK*;Q7csbZulB`PT(6+Z2%sM#^Pqmtft zw4@g4novTlfK+&qG)fOveY8s>M9}H#8TmVL2|C&ov?&Ie@=7cBuC#R1zl-JkiAQd>Z4s!e~> zB2sA$>PdyqAnB|aX%;P!%8+kesg9#Cnp(%Got}MMgd(GiSe| zE+q&WZYftuhXw{90=BIbw4a=t&b$#F0gX<9r)NGTsL-t!bv&CcDFMj!d7J_-)D&at z{v6Wg-GgSKOG;jo9TQcBE~N%rf-VsskX^68clYF{FJD4+#CEwLUgIt$ttB&|c-_t; zJ3Csfi0A!;76l|-N@-`YX-G@Txj;rUy^C2_lFT#wLrrvP)4RG5b{6IsqmSDobvF4V5+W4a5mzP7r4stm$-1 zvQdl-k}L;A%~vIj^!6p*40jR;JYFv&vM(>sa_q)3@PToHkx8J?!@X_HT&ljHU!S^L zKCF=s+}O6VTS%c079?Vp!}qyg-BxdYEW3=tQixiO2!>M#{Kb~b3ujJk3YLQi%|!O( z3<(a7-R-`7#5fgyLJ44)!o*^6m^FnZ)f8xlP&GL+2TCBI;Pt7y{WyTXt)NbD?j5}&LAt*gE{ zGCmJ%1gfW1C0#3}Qg)MrX>)w>>{OUlzB^;WvHE~G;JAHD2RBQGR9$xVvZvt2t6$%~ zY~6r74B$x4?YkE&G#uy+7VE%&fK%P)vFvg04_M4~-`$xcuSe;hjtx0D?rx8`kS(0q zv{Fr;Jfv5hLNi-B6Adf`m8bOWa8XPia*0b4S2~Qh=m!8#DEI;FV2RwP@1?!|#8nj|uBsSuRmF&_Dn?vYG2*I< z5m!}=xT<2rRTU$ys=R1%vVw^6 zvqR$zt;2PCj2?R4u+X}K)x9Ef<6EHhNK%*^AT_e%-ZmyutqB>7Ygy_KD0mYuS>L(# z#Erl`cvYh10%GG^J?#otJT9{?8D~=p-Y4e-T)gQij5I|NK^WRRV36jDz=yuPMk7s8 z#=H#>m7m3cgOGz2j5I}&;CaI0NvnVXCzrTIW=5LW_~t+fMDHl_$}r&k1-%K27afQ9 QcmMzZ07*qoM6N<$g7*}~m;e9( literal 0 HcmV?d00001 diff --git a/assets/images/3.0x/reconnect_menu.png b/assets/images/3.0x/reconnect_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..ebc7672cf14479f051bd368b66f4fb7c1750747f GIT binary patch literal 1281 zcmV+c1^)VpP)Sq z%!~l6cL9>2jQ7;5#Nsa+r+RCV!-5{%2X%t;KIi7G%PXr2tW~R0TQL}(|+>gI2@4I z@$|$;|1U6~B^@)L%2!wEFkc?<;6PXuB@9aRFCQK_h%n2dmW(cv*-G5rg_(#VfH|T! zssca!kes3X@TQ44G?DD6AKe?v`103Y_n46=r15p!f2WH#$;RhoQwvow=M!#LV_b+5{=WM?|33Qh8&u31 zAN$|wjJXoEf{Hs$?E8LZLHZ70(o>?NA@zO_cDk(s+y$VQ=D_ z#I-2lb^PavCX`zj#!IjVh%a?@`O4*0_?9RF{Y})xJRKA~q1sYLx zt*E3zNaVMJ8AD|X_RHb~cBD&`%+_)bTNlO#jPjyMgc0n3iKu(3PM81L7lx>-21fOz zU<$h}WK|qZpbb5;-}KTqz?_f>M|*-!Guzoz5=f3T50iYjSelCuhfEz448T0Si4g z5!HhlMy@>yB2vDem*dV=zlvIzuEiE?c@zPZ znhF8k>@m6LyAO||fFM+U22c(T@c8t=~3uE2i*vhDnJ!jw%9hPZ=7>Kri$^Z zn1BOVn0oV4Rs9PVXg6NiuB)m$aDXHa<>oalUE1hw6056CF7}(CErNHg2uwT*pU6o$ zC3w0bwcuMZuq7t#ycjQSHx+ip<3d!u^kM(#Uq%_fbk1(>r|a~^ zT#m>xJpw;!?uE5J;I(&A!m5MjS(7t92D=&naS-@ct(5yqlRu^_s2$100000NkvXXu0mjf9X3&s literal 0 HcmV?d00001 diff --git a/assets/images/3.0x/settings_menu.png b/assets/images/3.0x/settings_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..2da43a238955b8fdfcf75ea75505fd74d3f1c0f0 GIT binary patch literal 1444 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 IM6N<$f_^R9S^xk5 literal 0 HcmV?d00001 diff --git a/assets/images/monero_menu.png b/assets/images/monero_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..2bb420a807c8b33ab40cde4da0bcacccad02f5ec GIT binary patch literal 1411 zcmV-}1$_F6P)Lvpcg0N6M}L&jc*^83`r`G$w!%6qiVXfH6OlYXbx?58*Fs zI6`np=efTeWnj$-@q+==Qz+))*54@&{|;wy-?w@J3RsHvpM2}>>7uO_TMS-UlSXXC z@t*!sU(im#cmDI7d-+heSR#Y)^Cvq?Ul-vl+53L@e912Mc%TV`yHyLt8A9;cpFYB| zfbad)Lb-F0(!H?$vxoe#bCS{dEQSnT z+k)dZSo!S!pn{-{McGxGCt3mD2di%t9K|VRv%kXG#`pl2=cHKHgYICFL7$p)?stDV zcFsNE(r+z zvVfl%PGlrH1x$X(SX2d#UUQ)Y6bL96VTyIMYO9_Z&VaO*GOy)_L)(w+&RDg1zZFac zpjc18CF`8NH?1mB24|8=7xHR=`Pkx8A}E(J2c4S|P*(ZkF~xcju3MGC!FmEpe}xkz zL-KN6rC4)qEe?(die*VYVxG6kLR&>9uJvoCRkhrQv*;8^fot1^-V|%9mPq{{-TKRk z#i*BM6PAsJRnG&t4^D}vZ-~-W`4pW?i$TgQN;ZD^U$^S%kR5O$xwYiRk^E8fah@rF zZcfeJIlzIG2Lj<{B82osRgsSVG=E}a?@G*Av{NMATLW~}eAsB0u}V-6D3 zVeRUD5wh(o^pvJ0kAW+os;!j4#>F!fz-ryusx|Q;oUM6IM&<>yAG!(I3CFsc`tSYg z7ir2%h!PYx4tIWGWyM#ZJ>`FEx&Ito585rXyRD4VjSkDP0c0X$iM61b zpdOqc0y)I3ZDJ-aVpS)<1zhkP2>5m+t}-*46zoZhmP;5YT(wPjdD^LgQb;#CIhN(C znYiSDoGd#5KvWgM%|N(lpJP(Z<(*EdQ{b<=)U3O4-F{_EmjOw z#(RWh3!AcwCY)KgHxmqOQ`0>9Fz1R^tV)8n(C_FYKMB)|vB=R!9|~aS@QC;3@KZPq zntkPXP5Ma=nHjJs`#BHVc088{L^x!)C&zEyN{i!MB78h__AH** zf-~kVOSVuC8i$~Se5B`3Kf(d&1XKg*Ip?)2K|Z?3=Ney>NPw~1+ye`IB+J^Nli5o5 z0MVek$GN?P^G;f9DSJHoA$ptS!9h4-i%rylxeG=ntgekziySa(GlG98`42GBI57*` R7Bv6>002ovPDHLkV1j`^l5YS2 literal 0 HcmV?d00001 diff --git a/assets/images/nodes_menu.png b/assets/images/nodes_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..b743f91073016fce2c785f5a89804fbbae26a6b9 GIT binary patch literal 597 zcmV-b0;>IqP)=W~- z+$nHp8HJdZgl0XRgt#8kUwtOvid-qHu85t~wX1p(+b zR13*_ap^4o(1YnT@ShtaT6%79T~oh#W~6TA8~xaBptYZ_*iR#(PCwNlKr45$>F*0gRMo3 zWIw_cwgSgA@6}g0k^Mbr>9@(>G@^y31&Bj%40^K^j~ODA;Jw@6G=FLN$CmH1li&ZC=l1y8`K-r6Ug55jq?f>8WA^y z6<_lD{&eQ|<}Ky}jwtHKv%n*7Dv&_8nGjM~ypNMkwlCo}UPfSClAwz!M^>fS-X!Ve z-WKE}kp@K|U@%&&9Ndyk3G90Jk`aOSA&d{7nV5`p{}wbjc;jUmvxV3 zcA+CB;+*Z*yd?8Upo-zu5f4dEE5Up-FpOud62?_It!gG3isAI>uKlq~)))M&J^Ues z7dky_a0MI3ok#p5#u0Ufonfbdxejn`@~ literal 0 HcmV?d00001 diff --git a/assets/images/reconnect_menu.png b/assets/images/reconnect_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..d94a875e7e2f1d17f33277b979a4053a0355a41f GIT binary patch literal 495 zcmV@<(WR}m%iv)W>B4Wt@6-pf1k5mUn3BoE6#*e`n z8ZskRk+3Q+MR$RHAUoPm8UCA&002ovPDHLkV1k4L&7S}O literal 0 HcmV?d00001 diff --git a/assets/images/settings_menu.png b/assets/images/settings_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..35ef51aeeaf2f65fad0e3b86f667fa81feea3293 GIT binary patch literal 594 zcmV-Y0Iu#ZLS7K(p2Fw_h-+dJ=LM^M0S1#)ZZx*%nV#O>9;iKMXlAKXdPuLC>90^Ma>cgPK)b1xi5?KPDEk> zyAGY(?WC;+_vAiF&A`lh;2IgKqgYYl*bGH0K<7N>gzw4!(0zl>L~1T=whXALZq5)P z>&dj%Jnh1Kz8c%Zsix|KOyjR7)0;gz04(Xp5X?t5D`Psu)4C&h{}Mo{UOTkwk6i$_ z`5&TO+!)gmvkpgo259*#KoCd1w1WQk)h>?Z{T=je)TZqZ0r0e=I30FhR9zFMe630C z&iWYxqi*OgF(_`>7*u$AME1h=eJqo?x$mS1SS4=bjnr7ORNUyT%w{?h;GojprKxv? zCQqy!3%-~?2MMZK!+}#-;d+@8HW2kZOEwrY7)CAjt#fN>9#2W2;?nfbo5?2tQKYcK g{^{b9HO&>z161hx`rZsv5&!@I07*qoM6N<$f`z*SB>(^b literal 0 HcmV?d00001 diff --git a/assets/images/wallet_menu.png b/assets/images/wallet_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..47f9681935734d2f2dad279795c77671da61b135 GIT binary patch literal 358 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|&H|6fVg?3oVGw3ym^DWND9BhG zm^pF z87m(&_!zOZ*p@IJTy=b=hLCjT%vB1R!unGSgbdfWhEKgb&1FNkTXgcTy=INUrC;{{ zoYUdM*>(2ceV1jD8-HHqb+1uZiZ*_hsF(L4$7S8w4MIB)d9}Dr5Bea#A;*;N9nS=5KvM6r=gif3?;7?)XA0 zO*qbQ$;ZlO>je%{)lYfVW+Z(6aeG1Pqo;|xnK?a^`$F`_G BjG+Jk literal 0 HcmV?d00001 diff --git a/lib/core/amount_validator.dart b/lib/core/amount_validator.dart index 0aa0aba23..8ed81e0a6 100644 --- a/lib/core/amount_validator.dart +++ b/lib/core/amount_validator.dart @@ -3,10 +3,11 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/domain/common/wallet_type.dart'; class AmountValidator extends TextValidator { - AmountValidator({WalletType type}) + AmountValidator({WalletType type, bool isAutovalidate = false}) : super( errorMessage: S.current.error_text_amount, pattern: _pattern(type), + isAutovalidate: isAutovalidate, minLength: 0, maxLength: 0); diff --git a/lib/core/validator.dart b/lib/core/validator.dart index 312c7f356..fa867c1d6 100644 --- a/lib/core/validator.dart +++ b/lib/core/validator.dart @@ -16,18 +16,20 @@ class TextValidator extends Validator { this.maxLength, this.pattern, this.length, + this.isAutovalidate = false, String errorMessage}) : super(errorMessage: errorMessage); final int minLength; final int maxLength; final List length; + final bool isAutovalidate; String pattern; @override bool isValid(String value) { if (value == null || value.isEmpty) { - return false; + return isAutovalidate ? true : false; } return value.length > (minLength ?? 0) && diff --git a/lib/di.dart b/lib/di.dart index 56e5eeb3a..fff785d99 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -10,6 +10,8 @@ import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart'; import 'package:cake_wallet/src/screens/send/send_template_page.dart'; import 'package:cake_wallet/src/screens/settings/settings.dart'; import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; +import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; +import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart'; import 'package:cake_wallet/store/contact_list_store.dart'; import 'package:cake_wallet/store/node_list_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -42,6 +44,7 @@ import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; import 'package:cake_wallet/view_model/wallet_keys_view_model.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_seed_view_model.dart'; +import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; import 'package:flutter/foundation.dart'; import 'package:get_it/get_it.dart'; import 'package:hive/hive.dart'; @@ -299,4 +302,17 @@ Future setup( getIt.registerFactory( () => NodeCreateOrEditPage(getIt.get())); + + getIt.registerFactory(() => + ExchangeViewModel( + wallet: getIt.get().wallet, + exchangeTemplateStore: getIt.get(), + trades: tradesSource + )); + + getIt.registerFactory(() => + ExchangePage(getIt.get())); + + getIt.registerFactory(() => + ExchangeTemplatePage(getIt.get())); } diff --git a/lib/generated/i18n.dart b/lib/generated/i18n.dart index 261e73f6d..7dcdbd306 100644 --- a/lib/generated/i18n.dart +++ b/lib/generated/i18n.dart @@ -135,6 +135,7 @@ class S implements WidgetsLocalizations { String get reconnect => "Reconnect"; String get reconnect_alert_text => "Are you sure to reconnect?"; String get reconnection => "Reconnection"; + String get refund_address => "Refund address"; String get remove => "Remove"; String get remove_node => "Remove node"; String get remove_node_message => "Are you sure that you want to remove selected node?"; @@ -372,6 +373,8 @@ class $de extends S { @override String get trade_state_underpaid => "Unterbezahlt"; @override + String get refund_address => "Rückerstattungsadresse"; + @override String get welcome => "Willkommen zu"; @override String get share_address => "Adresse teilen "; @@ -998,6 +1001,8 @@ class $hi extends S { @override String get trade_state_underpaid => "के तहत भुगतान किया"; @override + String get refund_address => "वापसी का पता"; + @override String get welcome => "स्वागत हे सेवा मेरे"; @override String get share_address => "पता साझा करें"; @@ -1624,6 +1629,8 @@ class $ru extends S { @override String get trade_state_underpaid => "Недоплаченная"; @override + String get refund_address => "Адрес возврата"; + @override String get welcome => "Приветствуем в"; @override String get share_address => "Поделиться адресом"; @@ -2250,6 +2257,8 @@ class $ko extends S { @override String get trade_state_underpaid => "미지급"; @override + String get refund_address => "환불 주소"; + @override String get welcome => "환영 에"; @override String get share_address => "주소 공유"; @@ -2876,6 +2885,8 @@ class $pt extends S { @override String get trade_state_underpaid => "Parcialmente paga"; @override + String get refund_address => "Endereço de reembolso"; + @override String get welcome => "Bem-vindo ao"; @override String get share_address => "Compartilhar endereço"; @@ -3502,6 +3513,8 @@ class $uk extends S { @override String get trade_state_underpaid => "Недоплачена"; @override + String get refund_address => "Адреса повернення коштів"; + @override String get welcome => "Вітаємо в"; @override String get share_address => "Поділитися адресою"; @@ -4128,6 +4141,8 @@ class $ja extends S { @override String get trade_state_underpaid => "支払不足"; @override + String get refund_address => "払い戻し住所"; + @override String get welcome => "ようこそ に"; @override String get share_address => "住所を共有する"; @@ -4758,6 +4773,8 @@ class $pl extends S { @override String get trade_state_underpaid => "Niedopłacone"; @override + String get refund_address => "Adres zwrotu"; + @override String get welcome => "Witamy w"; @override String get share_address => "Udostępnij adres"; @@ -5384,6 +5401,8 @@ class $es extends S { @override String get trade_state_underpaid => "Poco pagado"; @override + String get refund_address => "Dirección de reembolso"; + @override String get welcome => "Bienvenido"; @override String get share_address => "Compartir dirección"; @@ -6010,6 +6029,8 @@ class $nl extends S { @override String get trade_state_underpaid => "Slecht betaald"; @override + String get refund_address => "Adres voor terugbetaling"; + @override String get welcome => "Welkom bij"; @override String get share_address => "Deel adres"; @@ -6636,6 +6657,8 @@ class $zh extends S { @override String get trade_state_underpaid => "支付不足"; @override + String get refund_address => "退款地址"; + @override String get welcome => "歡迎來到"; @override String get share_address => "分享地址"; diff --git a/lib/palette.dart b/lib/palette.dart index 02b135319..f5f3becc1 100644 --- a/lib/palette.dart +++ b/lib/palette.dart @@ -23,8 +23,8 @@ class PaletteDark { static const Color lightDistantBlue = Color.fromRGBO(81, 96, 147, 1.0); // borderCardColor static const Color gray = Color.fromRGBO(140, 153, 201, 1.0); // walletCardText static const Color violetBlue = Color.fromRGBO(51, 63, 104, 1.0); // walletCardAddressField - static const Color moderateBlue = Color.fromRGBO(63, 77, 122, 1.0); // walletCardSubAddressField - static const Color darkNightBlue = Color.fromRGBO(33, 43, 73, 1.0); // historyPanel + //static const Color moderateBlue = Color.fromRGBO(63, 77, 122, 1.0); // walletCardSubAddressField + //static const Color darkNightBlue = Color.fromRGBO(33, 43, 73, 1.0); // historyPanel static const Color pigeonBlue = Color.fromRGBO(91, 112, 146, 1.0); // historyPanelText static const Color moderateNightBlue = Color.fromRGBO(39, 53, 96, 1.0); // historyPanelButton static const Color headerNightBlue = Color.fromRGBO(41, 52, 84, 1.0); // menuHeader @@ -46,6 +46,12 @@ class PaletteDark { static const Color lightBlueGrey = Color.fromRGBO(125, 141, 183, 1.0); static const Color lightVioletBlue = Color.fromRGBO(56, 71, 109, 1.0); static const Color darkVioletBlue = Color.fromRGBO(49, 60, 96, 1.0); + static const Color wildVioletBlue = Color.fromRGBO(45, 60, 97, 1.0); + static const Color darkNightBlue = Color.fromRGBO(33, 45, 76, 1.0); + static const Color blueGrey = Color.fromRGBO(87, 98, 138, 1.0); + static const Color moderateBlue = Color.fromRGBO(60, 73, 118, 1.0); + static const Color deepPurpleBlue = Color.fromRGBO(19, 29, 56, 1.0); + static const Color lightOceanBlue = Color.fromRGBO(30, 42, 73, 1.0); // FIXME: Rename. static const Color eee = Color.fromRGBO(236, 239, 245, 1.0); diff --git a/lib/router.dart b/lib/router.dart index 974c6caa3..6c03dc1dd 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -411,45 +411,12 @@ class Router { walletRestorationFromSeedVM: walletRestorationFromSeedVM)); case Routes.exchange: - return MaterialPageRoute( - builder: (_) => MultiProvider(providers: [ - Provider(create: (_) { - final xmrtoprovider = XMRTOExchangeProvider(); - - return ExchangeStore( - initialProvider: xmrtoprovider, - initialDepositCurrency: CryptoCurrency.xmr, - initialReceiveCurrency: CryptoCurrency.btc, - trades: trades, - providerList: [ - xmrtoprovider, - ChangeNowExchangeProvider(), - MorphTokenExchangeProvider(trades: trades) - ], - walletStore: walletStore); - }), - ], child: ExchangePage())); + return CupertinoPageRoute( + builder: (_) => getIt.get()); case Routes.exchangeTemplate: - return MaterialPageRoute( - builder: (_) => Provider( - create: (_) { - final xmrtoprovider = XMRTOExchangeProvider(); - - return ExchangeStore( - initialProvider: xmrtoprovider, - initialDepositCurrency: CryptoCurrency.xmr, - initialReceiveCurrency: CryptoCurrency.btc, - trades: trades, - providerList: [ - xmrtoprovider, - ChangeNowExchangeProvider(), - MorphTokenExchangeProvider(trades: trades) - ], - walletStore: walletStore); - }, - child: ExchangeTemplatePage(), - )); + return CupertinoPageRoute( + builder: (_) => getIt.get()); case Routes.settings: return MaterialPageRoute( diff --git a/lib/src/screens/base_page.dart b/lib/src/screens/base_page.dart index e4eacd21c..30136815d 100644 --- a/lib/src/screens/base_page.dart +++ b/lib/src/screens/base_page.dart @@ -19,10 +19,14 @@ abstract class BasePage extends StatelessWidget { bool get resizeToAvoidBottomPadding => true; + Widget get endDrawer => null; + AppBarStyle get appBarStyle => AppBarStyle.regular; Widget Function(BuildContext, Widget) get rootWrapper => null; + final GlobalKey _scaffoldKey = GlobalKey(); + final _backArrowImage = Image.asset('assets/images/back_arrow.png', color: Colors.white); final _backArrowImageDarkTheme = @@ -32,6 +36,8 @@ abstract class BasePage extends StatelessWidget { final _closeButtonImageDarkTheme = Image.asset('assets/images/close_button_dark_theme.png'); + void onOpenEndDrawer() => _scaffoldKey.currentState.openEndDrawer(); + void onClose(BuildContext context) => Navigator.of(context).pop(); Widget leading(BuildContext context) { @@ -123,9 +129,11 @@ abstract class BasePage extends StatelessWidget { final _isDarkTheme = _themeChanger.getTheme() == Themes.darkTheme; final root = Scaffold( + key: _scaffoldKey, backgroundColor: _isDarkTheme ? backgroundDarkColor : backgroundLightColor, resizeToAvoidBottomPadding: resizeToAvoidBottomPadding, + endDrawer: endDrawer, appBar: appBar(context), body: body(context), //SafeArea(child: ), floatingActionButton: floatingActionButton(context)); diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index b32b72e64..5b7939c92 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -23,6 +23,12 @@ class DashboardPage extends BasePage { @override Color get backgroundDarkColor => PaletteDark.backgroundColor; + @override + Widget get endDrawer => MenuWidget( + name: walletViewModel.name, + subname: walletViewModel.subname, + type: walletViewModel.type); + @override Widget middle(BuildContext context) { return SyncIndicator(dashboardViewModel: walletViewModel); @@ -40,14 +46,7 @@ class DashboardPage extends BasePage { highlightColor: Colors.transparent, splashColor: Colors.transparent, padding: EdgeInsets.all(0), - onPressed: () async { - await showDialog( - builder: (_) => MenuWidget( - name: walletViewModel.name, - subname: walletViewModel.subname, - type: walletViewModel.type), - context: context); - }, + onPressed: () => onOpenEndDrawer(), child: menuButton ) ); diff --git a/lib/src/screens/dashboard/wallet_menu.dart b/lib/src/screens/dashboard/wallet_menu.dart index 2190a8d34..a3c4666f0 100644 --- a/lib/src/screens/dashboard/wallet_menu.dart +++ b/lib/src/screens/dashboard/wallet_menu.dart @@ -20,13 +20,13 @@ class WalletMenu { ]; final List images = [ - Image.asset('assets/images/reconnect.png'), - Image.asset('assets/images/wallet.png'), - Image.asset('assets/images/nodes.png'), - Image.asset('assets/images/eye.png'), - Image.asset('assets/images/key.png'), - Image.asset('assets/images/open_book.png'), - Image.asset('assets/images/settings.png'), + Image.asset('assets/images/reconnect_menu.png', height: 16, width: 16), + Image.asset('assets/images/wallet_menu.png', height: 16, width: 16), + Image.asset('assets/images/nodes_menu.png', height: 16, width: 16), + Image.asset('assets/images/eye_menu.png', height: 16, width: 16), + Image.asset('assets/images/key_menu.png', height: 16, width: 16), + Image.asset('assets/images/open_book_menu.png', height: 16, width: 16), + Image.asset('assets/images/settings_menu.png', height: 16, width: 16), ]; final BuildContext context; diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index fdd9a704c..9a461c085 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -1,4 +1,5 @@ import 'dart:ui'; +import 'package:cake_wallet/palette.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/src/domain/common/wallet_type.dart'; import 'package:cake_wallet/src/screens/dashboard/wallet_menu.dart'; @@ -15,7 +16,7 @@ class MenuWidget extends StatefulWidget { } class MenuWidgetState extends State { - final moneroIcon = Image.asset('assets/images/monero.png'); + final moneroIcon = Image.asset('assets/images/monero_menu.png'); final bitcoinIcon = Image.asset('assets/images/bitcoin.png'); final largeScreen = 731; @@ -36,10 +37,10 @@ class MenuWidgetState extends State { screenHeight = 0; opacity = 0; - headerHeight = 120; + headerHeight = 125; tileHeight = 75; fromTopEdge = 50; - fromBottomEdge = 30; + fromBottomEdge = 21;//30; super.initState(); WidgetsBinding.instance.addPostFrameCallback(afterLayout); @@ -66,189 +67,139 @@ class MenuWidgetState extends State { @override Widget build(BuildContext context) { final walletMenu = WalletMenu(context); -// final walletStore = Provider.of(context); final itemCount = walletMenu.items.length; - return Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( + return SafeArea( + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( padding: EdgeInsets.only(left: 24), child: Container( height: 60, width: 4, decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(2)), - color: Theme.of(context).hintColor), + borderRadius: BorderRadius.all(Radius.circular(2)), + color: Theme.of(context).hintColor), )), - SizedBox(width: 12), - Expanded( - child: GestureDetector( - onTap: () => null, - child: Container( - width: menuWidth, - height: double.infinity, - decoration: BoxDecoration( + SizedBox(width: 12), + Expanded( + child: ClipRRect( borderRadius: BorderRadius.only( - topLeft: Radius.circular(24), - bottomLeft: Radius.circular(24)), - color: Theme.of(context).primaryTextTheme.display1.color), - child: ClipRRect( - borderRadius: BorderRadius.only( topLeft: Radius.circular(24), bottomLeft: Radius.circular(24)), - child: ListView.separated( - itemBuilder: (_, index) { - if (index == 0) { - return Container( - height: headerHeight, - padding: EdgeInsets.only( - left: 24, - top: fromTopEdge, - right: 24, - bottom: fromBottomEdge), - decoration: BoxDecoration( - borderRadius: - BorderRadius.only(topLeft: Radius.circular(24)), - color: Theme.of(context) - .primaryTextTheme - .display2 - .color), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - _iconFor(type: widget.type), - SizedBox(width: 16), - Expanded( - child: Container( - height: 40, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: widget.subname != null - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.center, - children: [ - Text( - widget.name, - style: TextStyle( - color: Theme.of(context) - .primaryTextTheme - .title - .color, - decoration: TextDecoration.none, - fontFamily: 'Avenir Next', - fontSize: 20, - fontWeight: FontWeight.bold), - ), - if (widget.subname != null) - Text( - widget.subname, - style: TextStyle( - color: Theme.of(context) - .primaryTextTheme - .caption - .color, - decoration: TextDecoration.none, - fontFamily: 'Avenir Next', - fontSize: 12), - ) - ], - ), - )) - ], + child: Container( + width: menuWidth, + height: double.infinity, + color: PaletteDark.deepPurpleBlue, + child: SingleChildScrollView( + child: Column( + children: [ + Container( + height: headerHeight, + padding: EdgeInsets.only( + left: 24, + top: fromTopEdge, + right: 24, + bottom: fromBottomEdge), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _iconFor(type: widget.type), + SizedBox(width: 12), + Expanded( + child: Container( + height: 42, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: widget.subname != null + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.center, + children: [ + Text( + widget.name, + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold), + ), + if (widget.subname != null) + Text( + widget.subname, + style: TextStyle( + color: PaletteDark.darkCyanBlue, + fontWeight: FontWeight.w500, + fontSize: 12), + ) + ], + ), + )) + ], + ), ), - ); - } + Container( + height: 1, + color: PaletteDark.lightOceanBlue, + ), + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (_, index) { - index -= 1; - final item = walletMenu.items[index]; - final image = walletMenu.images[index] ?? Offstage(); + final item = walletMenu.items[index]; + final image = walletMenu.images[index] ?? Offstage(); + final isLastTile = index == itemCount - 1; - return GestureDetector( - onTap: () { - Navigator.of(context).pop(); - walletMenu.action(index); - }, - child: index == itemCount - 1 - ? Container( - height: headerHeight, - padding: EdgeInsets.only( - left: 24, - right: 24, - top: fromBottomEdge, - bottom: fromTopEdge), - alignment: Alignment.topLeft, - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(24)), - color: Theme.of(context) - .primaryTextTheme - .display1 - .color, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - image, - SizedBox(width: 16), - Expanded( - child: Text( - item, - style: TextStyle( - decoration: TextDecoration.none, - color: Theme.of(context) - .primaryTextTheme - .title - .color, - fontFamily: 'Avenir Next', - fontSize: 20, - fontWeight: FontWeight.bold), - )) - ], - ), - ) - : Container( - height: tileHeight, - padding: EdgeInsets.only(left: 24, right: 24), - color: Theme.of(context) - .primaryTextTheme - .display1 - .color, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - image, - SizedBox(width: 16), - Expanded( - child: Text( - item, - style: TextStyle( - decoration: TextDecoration.none, - color: Theme.of(context) - .primaryTextTheme - .title - .color, - fontFamily: 'Avenir Next', - fontSize: 20, - fontWeight: FontWeight.bold), - )) - ], - ), + return GestureDetector( + onTap: () { + Navigator.of(context).pop(); + walletMenu.action(index); + }, + child: Container( + height: isLastTile + ? headerHeight + : tileHeight, + padding: isLastTile + ? EdgeInsets.only( + left: 24, + right: 24, + top: fromBottomEdge, + bottom: fromTopEdge) + : EdgeInsets.only(left: 24, right: 24), + alignment: isLastTile ? Alignment.topLeft : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + image, + SizedBox(width: 16), + Expanded( + child: Text( + item, + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold), + )) + ], + ), + ) + ); + }, + separatorBuilder: (_, index) => Container( + height: 1, + color: PaletteDark.lightOceanBlue, ), - ); - }, - separatorBuilder: (_, index) => Container( - height: 1, - color: Theme.of(context).dividerColor, - ), - itemCount: itemCount + 1), - ), - ), - )) - ], + itemCount: itemCount) + ], + ), + ), + ), + ) + ) + ], + ) ); } diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index c5226e8f7..cc47033b4 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -1,66 +1,40 @@ import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; -import 'package:cake_wallet/src/stores/exchange/exchange_store.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/base_exchange_widget.dart'; import 'package:cake_wallet/src/widgets/trail_button.dart'; +import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; class ExchangePage extends BasePage { + ExchangePage(this.exchangeViewModel); + + final ExchangeViewModel exchangeViewModel; + @override String get title => S.current.exchange; @override - Color get backgroundLightColor => Palette.darkLavender; + Color get backgroundLightColor => PaletteDark.wildVioletBlue; @override - Color get backgroundDarkColor => PaletteDark.moderateBlue; + Color get backgroundDarkColor => PaletteDark.wildVioletBlue; @override - Widget middle(BuildContext context) { - final exchangeStore = Provider.of(context); - - return PresentProviderPicker(exchangeStore: exchangeStore); - } + Widget middle(BuildContext context) => + PresentProviderPicker(exchangeViewModel: exchangeViewModel); @override - Widget trailing(BuildContext context) { - final exchangeStore = Provider.of(context); - - return TrailButton( - caption: S.of(context).reset, - onPressed: () => exchangeStore.reset() - ); - } + Widget trailing(BuildContext context) => + TrailButton( + caption: S.of(context).reset, + onPressed: () => exchangeViewModel.reset() + ); @override - Widget body(BuildContext context) => ExchangeForm(); -} - -class ExchangeForm extends StatefulWidget { - @override - State createState() => ExchangeFormState(); -} - -class ExchangeFormState extends State { - - @override - Widget build(BuildContext context) { - final exchangeStore = Provider.of(context); - final walletStore = Provider.of(context); - final exchangeTemplateStore = Provider.of(context); - - return BaseExchangeWidget( - exchangeStore: exchangeStore, - walletStore: walletStore, - exchangeTemplateStore: exchangeTemplateStore, - isTemplate: false - ); - } + Widget body(BuildContext context) => + BaseExchangeWidget(exchangeViewModel: exchangeViewModel); } diff --git a/lib/src/screens/exchange/exchange_template_page.dart b/lib/src/screens/exchange/exchange_template_page.dart index db2a6965b..139a1e4cd 100644 --- a/lib/src/screens/exchange/exchange_template_page.dart +++ b/lib/src/screens/exchange/exchange_template_page.dart @@ -1,55 +1,32 @@ import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:cake_wallet/palette.dart'; -import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; -import 'package:cake_wallet/src/stores/exchange/exchange_store.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart'; -import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/base_exchange_widget.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; class ExchangeTemplatePage extends BasePage { + ExchangeTemplatePage(this.exchangeViewModel); + + final ExchangeViewModel exchangeViewModel; + @override String get title => S.current.exchange_new_template; @override - Color get backgroundLightColor => Palette.darkLavender; + Color get backgroundLightColor => PaletteDark.wildVioletBlue; @override - Color get backgroundDarkColor => PaletteDark.moderateBlue; + Color get backgroundDarkColor => PaletteDark.wildVioletBlue; @override - Widget trailing(BuildContext context) { - final exchangeStore = Provider.of(context); - - return PresentProviderPicker(exchangeStore: exchangeStore); - } + Widget trailing(BuildContext context) => + PresentProviderPicker(exchangeViewModel: exchangeViewModel); @override - Widget body(BuildContext context) => ExchangeTemplateForm(); -} - -class ExchangeTemplateForm extends StatefulWidget{ - @override - ExchangeTemplateFormState createState() => ExchangeTemplateFormState(); -} - -class ExchangeTemplateFormState extends State { - - @override - Widget build(BuildContext context) { - final exchangeStore = Provider.of(context); - final walletStore = Provider.of(context); - final exchangeTemplateStore = Provider.of(context); - - return BaseExchangeWidget( - exchangeStore: exchangeStore, - walletStore: walletStore, - exchangeTemplateStore: exchangeTemplateStore, - isTemplate: true - ); - } + Widget body(BuildContext context) => + BaseExchangeWidget(exchangeViewModel: exchangeViewModel, isTemplate: true); } \ No newline at end of file diff --git a/lib/src/screens/exchange/widgets/base_exchange_widget.dart b/lib/src/screens/exchange/widgets/base_exchange_widget.dart index 5500ffda7..57a24a162 100644 --- a/lib/src/screens/exchange/widgets/base_exchange_widget.dart +++ b/lib/src/screens/exchange/widgets/base_exchange_widget.dart @@ -1,5 +1,7 @@ import 'dart:ui'; +import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/src/domain/exchange/exchange_template.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/template_tile.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:flutter/cupertino.dart'; @@ -12,49 +14,37 @@ import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart'; import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart'; import 'package:cake_wallet/src/stores/exchange/limits_state.dart'; -import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; -import 'package:cake_wallet/src/stores/exchange/exchange_store.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/src/widgets/top_panel.dart'; -import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; class BaseExchangeWidget extends StatefulWidget { BaseExchangeWidget({ - @ required this.exchangeStore, - @ required this.walletStore, - @ required this.exchangeTemplateStore, - @ required this.isTemplate, + @ required this.exchangeViewModel, + this.isTemplate = false, }); - final ExchangeStore exchangeStore; - final WalletStore walletStore; - final ExchangeTemplateStore exchangeTemplateStore; + final ExchangeViewModel exchangeViewModel; final bool isTemplate; @override BaseExchangeWidgetState createState() => BaseExchangeWidgetState( - exchangeStore: exchangeStore, - walletStore: walletStore, - exchangeTemplateStore: exchangeTemplateStore, + exchangeViewModel: exchangeViewModel, isTemplate: isTemplate ); } class BaseExchangeWidgetState extends State { BaseExchangeWidgetState({ - @ required this.exchangeStore, - @ required this.walletStore, - @ required this.exchangeTemplateStore, + @ required this.exchangeViewModel, @ required this.isTemplate, }); - final ExchangeStore exchangeStore; - final WalletStore walletStore; - final ExchangeTemplateStore exchangeTemplateStore; + final ExchangeViewModel exchangeViewModel; final bool isTemplate; final depositKey = GlobalKey(); @@ -64,31 +54,31 @@ class BaseExchangeWidgetState extends State { @override Widget build(BuildContext context) { - final Image arrowBottomPurple = Image.asset( + final arrowBottomPurple = Image.asset( 'assets/images/arrow_bottom_purple_icon.png', - color: Theme.of(context).primaryTextTheme.title.color, + color: Colors.white, height: 8, ); - final Image arrowBottomCakeGreen = Image.asset( + final arrowBottomCakeGreen = Image.asset( 'assets/images/arrow_bottom_cake_green.png', - color: Theme.of(context).primaryTextTheme.title.color, + color: Colors.white, height: 8, ); final depositWalletName = - exchangeStore.depositCurrency == CryptoCurrency.xmr - ? walletStore.name + exchangeViewModel.depositCurrency == CryptoCurrency.xmr + ? exchangeViewModel.wallet.name : null; final receiveWalletName = - exchangeStore.receiveCurrency == CryptoCurrency.xmr - ? walletStore.name + exchangeViewModel.receiveCurrency == CryptoCurrency.xmr + ? exchangeViewModel.wallet.name : null; WidgetsBinding.instance.addPostFrameCallback( - (_) => _setReactions(context, exchangeStore, walletStore)); + (_) => _setReactions(context, exchangeViewModel)); return Container( - color: Theme.of(context).backgroundColor, + color: PaletteDark.backgroundColor, child: Form( key: _formKey, child: ScrollableWithBottomSection( @@ -96,73 +86,60 @@ class BaseExchangeWidgetState extends State { content: Column( children: [ TopPanel( - color: Theme.of(context).accentTextTheme.title.backgroundColor, - edgeInsets: EdgeInsets.only(bottom: 24), + color: PaletteDark.darkNightBlue, + edgeInsets: EdgeInsets.only(bottom: 32), widget: Column( children: [ TopPanel( - color: Theme.of(context).accentTextTheme.title.color, + edgeInsets: EdgeInsets.fromLTRB(24, 29, 24, 32), + color: PaletteDark.wildVioletBlue, widget: Observer( builder: (_) => ExchangeCard( key: depositKey, title: S.of(context).you_will_send, - initialCurrency: exchangeStore.depositCurrency, + initialCurrency: exchangeViewModel.depositCurrency, initialWalletName: depositWalletName, initialAddress: - exchangeStore.depositCurrency == walletStore.type - ? walletStore.address - : exchangeStore.depositAddress, + exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency + ? exchangeViewModel.wallet.address + : exchangeViewModel.depositAddress, initialIsAmountEditable: true, - initialIsAddressEditable: exchangeStore.isDepositAddressEnabled, + initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled, isAmountEstimated: false, currencies: CryptoCurrency.all, onCurrencySelected: (currency) => - exchangeStore.changeDepositCurrency(currency: currency), + exchangeViewModel.changeDepositCurrency(currency: currency), imageArrow: arrowBottomPurple, - currencyButtonColor: Theme.of(context).accentTextTheme.title.color, - addressButtonsColor: Theme.of(context).accentTextTheme.title.backgroundColor, - currencyValueValidator: (value) { - exchangeStore.validateCryptoCurrency(value); - return exchangeStore.errorMessage; - }, - addressTextFieldValidator: (value) { - exchangeStore.validateAddress(value, - cryptoCurrency: exchangeStore.depositCurrency); - return exchangeStore.errorMessage; - }, + currencyButtonColor: PaletteDark.wildVioletBlue, + addressButtonsColor: PaletteDark.moderateBlue, + currencyValueValidator: exchangeViewModel.amountValidator, + addressTextFieldValidator: exchangeViewModel.addressValidator, ), ) ), Padding( - padding: EdgeInsets.only(top: 32, left: 24, right: 24), + padding: EdgeInsets.only(top: 29, left: 24, right: 24), child: Observer( builder: (_) => ExchangeCard( key: receiveKey, title: S.of(context).you_will_get, - initialCurrency: exchangeStore.receiveCurrency, + initialCurrency: exchangeViewModel.receiveCurrency, initialWalletName: receiveWalletName, initialAddress: - exchangeStore.receiveCurrency == walletStore.type - ? walletStore.address - : exchangeStore.receiveAddress, + exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency + ? exchangeViewModel.wallet.address + : exchangeViewModel.receiveAddress, initialIsAmountEditable: false, - initialIsAddressEditable: exchangeStore.isReceiveAddressEnabled, + initialIsAddressEditable: exchangeViewModel.isReceiveAddressEnabled, isAmountEstimated: true, currencies: CryptoCurrency.all, - onCurrencySelected: (currency) => exchangeStore + onCurrencySelected: (currency) => exchangeViewModel .changeReceiveCurrency(currency: currency), imageArrow: arrowBottomCakeGreen, - currencyButtonColor: Theme.of(context).accentTextTheme.title.backgroundColor, - addressButtonsColor: Theme.of(context).accentTextTheme.title.color, - currencyValueValidator: (value) { - exchangeStore.validateCryptoCurrency(value); - return exchangeStore.errorMessage; - }, - addressTextFieldValidator: (value) { - exchangeStore.validateAddress(value, - cryptoCurrency: exchangeStore.receiveCurrency); - return exchangeStore.errorMessage; - }, + currencyButtonColor: PaletteDark.darkNightBlue, + addressButtonsColor: PaletteDark.moderateBlue, + currencyValueValidator: exchangeViewModel.amountValidator, + addressTextFieldValidator: exchangeViewModel.addressValidator, )), ) ], @@ -172,7 +149,7 @@ class BaseExchangeWidgetState extends State { ? Offstage() : Padding( padding: EdgeInsets.only( - top: 32, + top: 30, left: 24, bottom: 24 ), @@ -184,7 +161,7 @@ class BaseExchangeWidgetState extends State { style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Theme.of(context).primaryTextTheme.caption.color + color: PaletteDark.darkCyanBlue ), ) ], @@ -196,66 +173,90 @@ class BaseExchangeWidgetState extends State { height: 40, width: double.infinity, padding: EdgeInsets.only(left: 24), - child: Observer( - builder: (_) { - final itemCount = exchangeTemplateStore.templates.length + 1; - - return ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: itemCount, - itemBuilder: (context, index) { - - if (index == 0) { - return GestureDetector( - onTap: () => Navigator.of(context) - .pushNamed(Routes.exchangeTemplate), - child: Container( - padding: EdgeInsets.only(right: 10), - child: DottedBorder( - borderType: BorderType.RRect, - dashPattern: [8, 4], - color: Theme.of(context).accentTextTheme.title.backgroundColor, - strokeWidth: 2, - radius: Radius.circular(20), - child: Container( - height: 40, - width: 75, - padding: EdgeInsets.only(left: 10, right: 10), - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(20)), - color: Colors.transparent, - ), - child: Text( - S.of(context).send_new, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Theme.of(context).primaryTextTheme.caption.color - ), - ), - ) + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + GestureDetector( + onTap: () => Navigator.of(context) + .pushNamed(Routes.exchangeTemplate), + child: Container( + padding: EdgeInsets.only(left: 1, right: 10), + child: DottedBorder( + borderType: BorderType.RRect, + dashPattern: [6, 4], + color: PaletteDark.darkCyanBlue, + strokeWidth: 2, + radius: Radius.circular(20), + child: Container( + height: 34, + width: 75, + padding: EdgeInsets.only(left: 10, right: 10), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20)), + color: Colors.transparent, + ), + child: Text( + S.of(context).send_new, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: PaletteDark.darkCyanBlue ), ), - ); - } + ) + ), + ), + ), + Observer( + builder: (_) { + final templates = exchangeViewModel.templates; + final itemCount = exchangeViewModel.templates.length; - index -= 1; + return ListView.builder( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: itemCount, + itemBuilder: (context, index) { + final template = templates[index]; - final template = exchangeTemplateStore.templates[index]; - - return TemplateTile( - amount: template.amount, - from: template.depositCurrency, - to: template.receiveCurrency, - onTap: () { - applyTemplate(exchangeStore, template); + return TemplateTile( + key: UniqueKey(), + amount: template.amount, + from: template.depositCurrency, + to: template.receiveCurrency, + onTap: () { + applyTemplate(exchangeViewModel, template); + }, + onRemove: () { + showDialog( + context: context, + builder: (dialogContext) { + return AlertWithTwoActions( + alertTitle: S.of(context).template, + alertContent: S.of(context).confirm_delete_template, + leftButtonText: S.of(context).delete, + rightButtonText: S.of(context).cancel, + actionLeftButton: () { + Navigator.of(dialogContext).pop(); + exchangeViewModel.exchangeTemplateStore.remove(template: template); + exchangeViewModel.exchangeTemplateStore.update(); + }, + actionRightButton: () => Navigator.of(dialogContext).pop() + ); + } + ); + }, + ); } ); } - ); - } - ), + ), + ], + ) + ) ) ], ), @@ -265,14 +266,15 @@ class BaseExchangeWidgetState extends State { padding: EdgeInsets.only(bottom: 15), child: Observer(builder: (_) { final description = - exchangeStore.provider is XMRTOExchangeProvider + exchangeViewModel.provider is XMRTOExchangeProvider ? S.of(context).amount_is_guaranteed : S.of(context).amount_is_estimate; return Center( child: Text( description, style: TextStyle( - color: Theme.of(context).primaryTextTheme.caption.color, + color: PaletteDark.darkCyanBlue, + fontWeight: FontWeight.w500, fontSize: 12 ), ), @@ -283,15 +285,15 @@ class BaseExchangeWidgetState extends State { ? PrimaryButton( onPressed: () { if (_formKey.currentState.validate()) { - exchangeTemplateStore.addTemplate( - amount: exchangeStore.depositAmount, - depositCurrency: exchangeStore.depositCurrency.toString(), - receiveCurrency: exchangeStore.receiveCurrency.toString(), - provider: exchangeStore.provider.toString(), - depositAddress: exchangeStore.depositAddress, - receiveAddress: exchangeStore.receiveAddress + exchangeViewModel.exchangeTemplateStore.addTemplate( + amount: exchangeViewModel.depositAmount, + depositCurrency: exchangeViewModel.depositCurrency.toString(), + receiveCurrency: exchangeViewModel.receiveCurrency.toString(), + provider: exchangeViewModel.provider.toString(), + depositAddress: exchangeViewModel.depositAddress, + receiveAddress: exchangeViewModel.receiveAddress ); - exchangeTemplateStore.update(); + exchangeViewModel.exchangeTemplateStore.update(); Navigator.of(context).pop(); } }, @@ -304,41 +306,47 @@ class BaseExchangeWidgetState extends State { text: S.of(context).exchange, onPressed: () { if (_formKey.currentState.validate()) { - exchangeStore.createTrade(); + exchangeViewModel.createTrade(); } }, color: Colors.blue, textColor: Colors.white, - isLoading: exchangeStore.tradeState is TradeIsCreating, + isLoading: exchangeViewModel.tradeState is TradeIsCreating, )), ]), )), ); } - void applyTemplate(ExchangeStore store, ExchangeTemplate template) { - store.changeDepositCurrency(currency: CryptoCurrency.fromString(template.depositCurrency)); - store.changeReceiveCurrency(currency: CryptoCurrency.fromString(template.receiveCurrency)); + void applyTemplate(ExchangeViewModel exchangeViewModel, + ExchangeTemplate template) { + exchangeViewModel.changeDepositCurrency( + currency: CryptoCurrency.fromString(template.depositCurrency)); + exchangeViewModel.changeReceiveCurrency( + currency: CryptoCurrency.fromString(template.receiveCurrency)); switch (template.provider) { case 'XMR.TO': - store.changeProvider(provider: store.providerList[0]); + exchangeViewModel.changeProvider( + provider: exchangeViewModel.providerList[0]); break; case 'ChangeNOW': - store.changeProvider(provider: store.providerList[1]); + exchangeViewModel.changeProvider( + provider: exchangeViewModel.providerList[1]); break; case 'MorphToken': - store.changeProvider(provider: store.providerList[2]); + exchangeViewModel.changeProvider( + provider: exchangeViewModel.providerList[2]); break; } - store.changeDepositAmount(amount: template.amount); - store.depositAddress = template.depositAddress; - store.receiveAddress = template.receiveAddress; + exchangeViewModel.changeDepositAmount(amount: template.amount); + exchangeViewModel.depositAddress = template.depositAddress; + exchangeViewModel.receiveAddress = template.receiveAddress; } void _setReactions( - BuildContext context, ExchangeStore store, WalletStore walletStore) { + BuildContext context, ExchangeViewModel exchangeViewModel) { if (_isReactionsSet) { return; } @@ -347,7 +355,7 @@ class BaseExchangeWidgetState extends State { final depositAmountController = depositKey.currentState.amountController; final receiveAddressController = receiveKey.currentState.addressController; final receiveAmountController = receiveKey.currentState.amountController; - final limitsState = store.limitsState; + final limitsState = exchangeViewModel.limitsState; if (limitsState is LimitsLoadedSuccessfully) { final min = limitsState.limits.min != null @@ -360,63 +368,62 @@ class BaseExchangeWidgetState extends State { key.currentState.changeLimits(min: min, max: max); } - _onCurrencyChange(store.receiveCurrency, walletStore, receiveKey); - _onCurrencyChange(store.depositCurrency, walletStore, depositKey); + _onCurrencyChange(exchangeViewModel.receiveCurrency, exchangeViewModel, receiveKey); + _onCurrencyChange(exchangeViewModel.depositCurrency, exchangeViewModel, depositKey); reaction( - (_) => walletStore.name, + (_) => exchangeViewModel.wallet.name, (String _) => _onWalletNameChange( - walletStore, store.receiveCurrency, receiveKey)); + exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey)); reaction( - (_) => walletStore.name, + (_) => exchangeViewModel.wallet.name, (String _) => _onWalletNameChange( - walletStore, store.depositCurrency, depositKey)); + exchangeViewModel, exchangeViewModel.depositCurrency, depositKey)); reaction( - (_) => store.receiveCurrency, + (_) => exchangeViewModel.receiveCurrency, (CryptoCurrency currency) => - _onCurrencyChange(currency, walletStore, receiveKey)); + _onCurrencyChange(currency, exchangeViewModel, receiveKey)); reaction( - (_) => store.depositCurrency, + (_) => exchangeViewModel.depositCurrency, (CryptoCurrency currency) => - _onCurrencyChange(currency, walletStore, depositKey)); + _onCurrencyChange(currency, exchangeViewModel, depositKey)); - reaction((_) => store.depositAmount, (String amount) { + reaction((_) => exchangeViewModel.depositAmount, (String amount) { if (depositKey.currentState.amountController.text != amount) { depositKey.currentState.amountController.text = amount; } }); - reaction((_) => store.depositAddress, (String address) { + reaction((_) => exchangeViewModel.depositAddress, (String address) { if (depositKey.currentState.addressController.text != address) { depositKey.currentState.addressController.text = address; } }); - reaction((_) => store.isDepositAddressEnabled, (bool isEnabled) { + reaction((_) => exchangeViewModel.isDepositAddressEnabled, (bool isEnabled) { depositKey.currentState.isAddressEditable(isEditable: isEnabled); }); - reaction((_) => store.receiveAmount, (String amount) { - if (receiveKey.currentState.amountController.text != - store.receiveAmount) { + reaction((_) => exchangeViewModel.receiveAmount, (String amount) { + if (receiveKey.currentState.amountController.text != amount) { receiveKey.currentState.amountController.text = amount; } }); - reaction((_) => store.receiveAddress, (String address) { + reaction((_) => exchangeViewModel.receiveAddress, (String address) { if (receiveKey.currentState.addressController.text != address) { receiveKey.currentState.addressController.text = address; } }); - reaction((_) => store.isReceiveAddressEnabled, (bool isEnabled) { + reaction((_) => exchangeViewModel.isReceiveAddressEnabled, (bool isEnabled) { receiveKey.currentState.isAddressEditable(isEditable: isEnabled); }); - reaction((_) => store.tradeState, (ExchangeTradeState state) { + reaction((_) => exchangeViewModel.tradeState, (ExchangeTradeState state) { if (state is TradeIsCreatedFailure) { WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( @@ -437,7 +444,7 @@ class BaseExchangeWidgetState extends State { } }); - reaction((_) => store.limitsState, (LimitsState state) { + reaction((_) => exchangeViewModel.limitsState, (LimitsState state) { String min; String max; @@ -461,29 +468,29 @@ class BaseExchangeWidgetState extends State { }); depositAddressController.addListener( - () => store.depositAddress = depositAddressController.text); + () => exchangeViewModel.depositAddress = depositAddressController.text); depositAmountController.addListener(() { - if (depositAmountController.text != store.depositAmount) { - store.changeDepositAmount(amount: depositAmountController.text); + if (depositAmountController.text != exchangeViewModel.depositAmount) { + exchangeViewModel.changeDepositAmount(amount: depositAmountController.text); } }); receiveAddressController.addListener( - () => store.receiveAddress = receiveAddressController.text); + () => exchangeViewModel.receiveAddress = receiveAddressController.text); receiveAmountController.addListener(() { - if (receiveAmountController.text != store.receiveAmount) { - store.changeReceiveAmount(amount: receiveAmountController.text); + if (receiveAmountController.text != exchangeViewModel.receiveAmount) { + exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text); } }); - reaction((_) => walletStore.address, (String address) { - if (store.depositCurrency == CryptoCurrency.xmr) { + reaction((_) => exchangeViewModel.wallet.address, (String address) { + if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) { depositKey.currentState.changeAddress(address: address); } - if (store.receiveCurrency == CryptoCurrency.xmr) { + if (exchangeViewModel.receiveCurrency == CryptoCurrency.xmr) { receiveKey.currentState.changeAddress(address: address); } }); @@ -491,28 +498,33 @@ class BaseExchangeWidgetState extends State { _isReactionsSet = true; } - void _onCurrencyChange(CryptoCurrency currency, WalletStore walletStore, + void _onCurrencyChange(CryptoCurrency currency, + ExchangeViewModel exchangeViewModel, GlobalKey key) { - final isCurrentTypeWallet = currency == walletStore.type; + final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency; key.currentState.changeSelectedCurrency(currency); key.currentState - .changeWalletName(isCurrentTypeWallet ? walletStore.name : null); + .changeWalletName(isCurrentTypeWallet + ? exchangeViewModel.wallet.name : null); key.currentState - .changeAddress(address: isCurrentTypeWallet ? walletStore.address : ''); + .changeAddress(address: isCurrentTypeWallet + ? exchangeViewModel.wallet.address : ''); key.currentState.changeAmount(amount: ''); } - void _onWalletNameChange(WalletStore walletStore, CryptoCurrency currency, + void _onWalletNameChange(ExchangeViewModel exchangeViewModel, + CryptoCurrency currency, GlobalKey key) { - final isCurrentTypeWallet = currency == walletStore.type; + final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency; if (isCurrentTypeWallet) { - key.currentState.changeWalletName(walletStore.name); - key.currentState.addressController.text = walletStore.address; - } else if (key.currentState.addressController.text == walletStore.address) { + key.currentState.changeWalletName(exchangeViewModel.wallet.name); + key.currentState.addressController.text = exchangeViewModel.wallet.address; + } else if (key.currentState.addressController.text == + exchangeViewModel.wallet.address) { key.currentState.changeWalletName(null); key.currentState.addressController.text = null; } diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 308784f3c..548383176 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; import 'package:cake_wallet/src/widgets/address_text_field.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; +import 'package:cake_wallet/palette.dart'; class ExchangeCard extends StatefulWidget { ExchangeCard( @@ -57,6 +58,10 @@ class ExchangeCardState extends State { bool _isAddressEditable; bool _isAmountEstimated; + final copyImage = Image.asset('assets/images/copy_content.png', + height: 16, width: 16, + color: Colors.white); + @override void initState() { _title = widget.title; @@ -114,6 +119,7 @@ class ExchangeCardState extends State { width: double.infinity, color: Colors.transparent, child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.start, @@ -123,13 +129,13 @@ class ExchangeCardState extends State { style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Theme.of(context).primaryTextTheme.caption.color + color: PaletteDark.lightBlueGrey ), ) ], ), Padding( - padding: EdgeInsets.only(top: 10), + padding: EdgeInsets.only(top: 20), child: Stack( children: [ BaseTextFormField( @@ -143,7 +149,20 @@ class ExchangeCardState extends State { RegExp('[\\-|\\ |\\,]')) ], hintText: '0.0000', - validator: widget.currencyValueValidator + borderColor: PaletteDark.blueGrey, + textStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white + ), + placeholderTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: PaletteDark.lightBlueGrey + ), + validator: _isAmountEditable + ? widget.currencyValueValidator + : null ), Positioned( top: 8, @@ -163,7 +182,7 @@ class ExchangeCardState extends State { style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16, - color: Theme.of(context).primaryTextTheme.title.color)), + color: Colors.white)), Padding( padding: EdgeInsets.only(left: 5), child: widget.imageArrow, @@ -181,45 +200,96 @@ class ExchangeCardState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ _min != null - ? Text( + ? Text( S.of(context).min_value( _min, _selectedCurrency.toString()), style: TextStyle( fontSize: 10, height: 1.2, - color: Theme.of(context).primaryTextTheme.caption.color), + color: PaletteDark.lightBlueGrey), ) - : Offstage(), + : Offstage(), _min != null ? SizedBox(width: 10) : Offstage(), _max != null - ? Text( + ? Text( S.of(context).max_value( _max, _selectedCurrency.toString()), style: TextStyle( fontSize: 10, height: 1.2, - color: Theme.of(context).primaryTextTheme.caption.color)) - : Offstage(), + color: PaletteDark.lightBlueGrey)) + : Offstage(), ]), ), Padding( - padding: EdgeInsets.only(top: 10), - child: AddressTextField( + padding: EdgeInsets.only(top: 20), + child: Text( + _isAddressEditable + ? S.of(context).widgets_address + : S.of(context).refund_address, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: PaletteDark.lightBlueGrey + ), + ) + ), + _isAddressEditable + ? AddressTextField( controller: addressController, - isActive: _isAddressEditable, - options: _isAddressEditable - ? _walletName != null - ? [] - : [ + options: [ + AddressTextFieldOption.paste, AddressTextFieldOption.qrCode, AddressTextFieldOption.addressBook, - ] - : [], + ], + placeholder: '', isBorderExist: false, + textStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white), buttonColor: widget.addressButtonsColor, validator: widget.addressTextFieldValidator, - ), ) + : Padding( + padding: EdgeInsets.only(top: 10), + child: Builder( + builder: (context) => GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: addressController.text)); + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + S.of(context).copied_to_clipboard, + style: TextStyle(color: Colors.white), + ), + backgroundColor: Colors.green, + duration: Duration(milliseconds: 500), + )); + }, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Text( + addressController.text, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white), + ), + ), + Padding( + padding: EdgeInsets.only(left: 16), + child: copyImage, + ) + ], + ), + ) + ), + ), ]), ); } diff --git a/lib/src/screens/exchange/widgets/present_provider_picker.dart b/lib/src/screens/exchange/widgets/present_provider_picker.dart index 1d3a7953d..79fbb80cd 100644 --- a/lib/src/screens/exchange/widgets/present_provider_picker.dart +++ b/lib/src/screens/exchange/widgets/present_provider_picker.dart @@ -1,21 +1,22 @@ import 'package:flutter/material.dart'; -import 'package:cake_wallet/src/stores/exchange/exchange_store.dart'; import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; +import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; +import 'package:cake_wallet/palette.dart'; class PresentProviderPicker extends StatelessWidget { - PresentProviderPicker({@required this.exchangeStore}); + PresentProviderPicker({@required this.exchangeViewModel}); - final ExchangeStore exchangeStore; + final ExchangeViewModel exchangeViewModel; @override Widget build(BuildContext context) { - final Image arrowBottom = + final arrowBottom = Image.asset('assets/images/arrow_bottom_purple_icon.png', - color: Theme.of(context).primaryTextTheme.title.color, + color: Colors.white, height: 6); return FlatButton( @@ -33,19 +34,19 @@ class PresentProviderPicker extends StatelessWidget { Text(S.of(context).exchange, style: TextStyle( fontSize: 16.0, - fontWeight: FontWeight.w400, - color: Theme.of(context).primaryTextTheme.title.color)), + fontWeight: FontWeight.w600, + color: Colors.white)), Observer( - builder: (_) => Text('${exchangeStore.provider.title}', + builder: (_) => Text('${exchangeViewModel.provider.title}', style: TextStyle( fontSize: 10.0, - fontWeight: FontWeight.w400, - color: Theme.of(context).primaryTextTheme.caption.color))) + fontWeight: FontWeight.w500, + color: PaletteDark.lightBlueGrey))) ], ), SizedBox(width: 5), Padding( - padding: EdgeInsets.only(top: 8), + padding: EdgeInsets.only(top: 12), child: arrowBottom, ) ], @@ -54,11 +55,11 @@ class PresentProviderPicker extends StatelessWidget { } void _presentProviderPicker(BuildContext context) { - final items = exchangeStore.providersForCurrentPair(); - final selectedItem = items.indexOf(exchangeStore.provider); - final images = List(); + final items = exchangeViewModel.providersForCurrentPair(); + final selectedItem = items.indexOf(exchangeViewModel.provider); + final images = []; - for (ExchangeProvider provider in items) { + for (var provider in items) { switch (provider.description) { case ExchangeProviderDescription.xmrto: images.add(Image.asset('assets/images/xmr_btc.png')); @@ -79,7 +80,7 @@ class PresentProviderPicker extends StatelessWidget { selectedAtIndex: selectedItem, title: S.of(context).change_exchange_provider, onItemSelected: (ExchangeProvider provider) => - exchangeStore.changeProvider(provider: provider)), + exchangeViewModel.changeProvider(provider: provider)), context: context); } } \ No newline at end of file diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 19cc6fecc..47608b59c 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -125,7 +125,7 @@ class ReceivePage extends BasePage { .headline5 .color .withOpacity(0.4), - validator: AmountValidator(), + validator: AmountValidator(isAutovalidate: true), autovalidate: true, placeholderTextStyle: TextStyle( color: Theme.of(context) diff --git a/lib/src/widgets/base_text_form_field.dart b/lib/src/widgets/base_text_form_field.dart index e8b77c22c..b3931a92d 100644 --- a/lib/src/widgets/base_text_form_field.dart +++ b/lib/src/widgets/base_text_form_field.dart @@ -72,6 +72,10 @@ class BaseTextFormField extends StatelessWidget { borderSide: BorderSide( color: borderColor ?? Theme.of(context).dividerColor, width: 1.0)), + disabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: borderColor ?? Theme.of(context).dividerColor, + width: 1.0)), enabledBorder: UnderlineInputBorder( borderSide: BorderSide( color: borderColor ?? Theme.of(context).dividerColor, diff --git a/lib/src/widgets/nav_bar.dart b/lib/src/widgets/nav_bar.dart index b5274d9b4..0d51c8e72 100644 --- a/lib/src/widgets/nav_bar.dart +++ b/lib/src/widgets/nav_bar.dart @@ -71,6 +71,8 @@ class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget { EdgeInsetsDirectional.only(bottom: _paddingBottom, top: paddingTop), child: CupertinoNavigationBar( leading: leading, + automaticallyImplyLeading: false, + automaticallyImplyMiddle: false, middle: middle, trailing: trailing, backgroundColor: backgroundColor, diff --git a/lib/src/widgets/trail_button.dart b/lib/src/widgets/trail_button.dart index d9752e410..65cfc14dc 100644 --- a/lib/src/widgets/trail_button.dart +++ b/lib/src/widgets/trail_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; class TrailButton extends StatelessWidget { TrailButton({ @@ -21,7 +22,7 @@ class TrailButton extends StatelessWidget { child: Text( caption, style: TextStyle( - color: Theme.of(context).primaryTextTheme.caption.color, + color: PaletteDark.lightBlueGrey, fontWeight: FontWeight.w500, fontSize: 14), ), diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart new file mode 100644 index 000000000..0383338e8 --- /dev/null +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -0,0 +1,306 @@ +import 'package:cake_wallet/core/address_validator.dart'; +import 'package:cake_wallet/core/amount_validator.dart'; +import 'package:cake_wallet/core/template_validator.dart'; +import 'package:cake_wallet/core/validator.dart'; +import 'package:cake_wallet/core/wallet_base.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/limits.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:cake_wallet/src/stores/exchange/limits_state.dart'; +import 'package:intl/intl.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:hive/hive.dart'; +import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart'; +import 'package:cake_wallet/src/domain/exchange/changenow/changenow_exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/changenow/changenow_request.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_request.dart'; +import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_trade_request.dart'; +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_request.dart'; +import 'package:cake_wallet/store/templates/exchange_template_store.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_template.dart'; + +part 'exchange_view_model.g.dart'; + +class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel; + +abstract class ExchangeViewModelBase with Store { + ExchangeViewModelBase({this.wallet, this.trades, this.exchangeTemplateStore}) { + providerList = [ + XMRTOExchangeProvider(), + ChangeNowExchangeProvider(), + MorphTokenExchangeProvider(trades: trades) + ]; + + provider = providerList[ 0 ]; + + depositCurrency = CryptoCurrency.xmr; + receiveCurrency = CryptoCurrency.btc; + isDepositAddressEnabled = !(depositCurrency == wallet.currency); + isReceiveAddressEnabled = !(receiveCurrency == wallet.currency); + depositAmount = ''; + receiveAmount = ''; + depositAddress = ''; + receiveAddress = ''; + limitsState = LimitsInitialState(); + tradeState = ExchangeTradeStateInitial(); + _cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12; + loadLimits(); + } + + final WalletBase wallet; + final Box trades; + final ExchangeTemplateStore exchangeTemplateStore; + + @observable + ExchangeProvider provider; + + @observable + List providerList; + + @observable + CryptoCurrency depositCurrency; + + @observable + CryptoCurrency receiveCurrency; + + @observable + LimitsState limitsState; + + @observable + ExchangeTradeState tradeState; + + @observable + String depositAmount; + + @observable + String receiveAmount; + + @observable + String depositAddress; + + @observable + String receiveAddress; + + @observable + bool isDepositAddressEnabled; + + @observable + bool isReceiveAddressEnabled; + + @observable + bool isValid; + + @observable + String errorMessage; + + Limits limits; + + NumberFormat _cryptoNumberFormat; + + Validator get amountValidator => AmountValidator(type: wallet.type); + + Validator get addressValidator => AddressValidator(type: wallet.currency); + + Validator get templateValidator => TemplateValidator(); + + @computed + ObservableList get templates => + exchangeTemplateStore.templates; + + @action + void changeProvider({ExchangeProvider provider}) { + this.provider = provider; + depositAmount = ''; + receiveAmount = ''; + loadLimits(); + } + + @action + void changeDepositCurrency({CryptoCurrency currency}) { + depositCurrency = currency; + _onPairChange(); + isDepositAddressEnabled = !(depositCurrency == wallet.currency); + isReceiveAddressEnabled = !(receiveCurrency == wallet.currency); + } + + @action + void changeReceiveCurrency({CryptoCurrency currency}) { + receiveCurrency = currency; + _onPairChange(); + isDepositAddressEnabled = !(depositCurrency == wallet.currency); + isReceiveAddressEnabled = !(receiveCurrency == wallet.currency); + } + + @action + void changeReceiveAmount({String amount}) { + receiveAmount = amount; + + if (amount == null || amount.isEmpty) { + depositAmount = ''; + receiveAmount = ''; + return; + } + + final _amount = double.parse(amount) ?? 0; + + provider + .calculateAmount( + from: depositCurrency, to: receiveCurrency, amount: _amount) + .then((amount) => _cryptoNumberFormat.format(amount).toString().replaceAll(RegExp("\\,"), "")) + .then((amount) => depositAmount = amount); + } + + @action + void changeDepositAmount({String amount}) { + depositAmount = amount; + + if (amount == null || amount.isEmpty) { + depositAmount = ''; + receiveAmount = ''; + return; + } + + final _amount = double.parse(amount); + provider + .calculateAmount( + from: depositCurrency, to: receiveCurrency, amount: _amount) + .then((amount) => _cryptoNumberFormat.format(amount).toString().replaceAll(RegExp("\\,"), "")) + .then((amount) => receiveAmount = amount); + } + + @action + Future loadLimits() async { + limitsState = LimitsIsLoading(); + + try { + limits = await provider.fetchLimits( + from: depositCurrency, to: receiveCurrency); + limitsState = LimitsLoadedSuccessfully(limits: limits); + } catch (e) { + limitsState = LimitsLoadedFailure(error: e.toString()); + } + } + + @action + Future createTrade() async { + TradeRequest request; + String amount; + CryptoCurrency currency; + + if (provider is XMRTOExchangeProvider) { + request = XMRTOTradeRequest( + from: depositCurrency, + to: receiveCurrency, + amount: depositAmount, + address: receiveAddress, + refundAddress: depositAddress); + amount = depositAmount; + currency = depositCurrency; + } + + if (provider is ChangeNowExchangeProvider) { + request = ChangeNowRequest( + from: depositCurrency, + to: receiveCurrency, + amount: depositAmount, + refundAddress: depositAddress, + address: receiveAddress); + amount = depositAmount; + currency = depositCurrency; + } + + if (provider is MorphTokenExchangeProvider) { + request = MorphTokenRequest( + from: depositCurrency, + to: receiveCurrency, + amount: depositAmount, + refundAddress: depositAddress, + address: receiveAddress); + amount = depositAmount; + currency = depositCurrency; + } + + if (limitsState is LimitsLoadedSuccessfully && amount != null) { + if (double.parse(amount) < limits.min) { + tradeState = TradeIsCreatedFailure(error: S.current.error_text_minimal_limit('${provider.description}', + '${limits.min}', currency.toString())); + } else if (limits.max != null && double.parse(amount) > limits.max) { + tradeState = TradeIsCreatedFailure(error: S.current.error_text_maximum_limit('${provider.description}', + '${limits.max}', currency.toString())); + } else { + try { + tradeState = TradeIsCreating(); + final trade = await provider.createTrade(request: request); + trade.walletId = wallet.id; + await trades.add(trade); + tradeState = TradeIsCreatedSuccessfully(trade: trade); + } catch (e) { + tradeState = TradeIsCreatedFailure(error: e.toString()); + } + } + } else { + tradeState = TradeIsCreatedFailure(error: S.current.error_text_limits_loading_failed("${provider.description}")); + } + + } + + @action + void reset() { + depositAmount = ''; + receiveAmount = ''; + depositCurrency = CryptoCurrency.xmr; + receiveCurrency = CryptoCurrency.btc; + depositAddress = depositCurrency == wallet.currency ? wallet.address : ''; + receiveAddress = receiveCurrency == wallet.currency ? wallet.address : ''; + isDepositAddressEnabled = !(depositCurrency == wallet.currency); + isReceiveAddressEnabled = !(receiveCurrency == wallet.currency); + _onPairChange(); + } + + List providersForCurrentPair() { + return _providersForPair(from: depositCurrency, to: receiveCurrency); + } + + List _providersForPair( + {CryptoCurrency from, CryptoCurrency to}) { + final providers = providerList + .where((provider) => provider.pairList + .where((pair) => + pair.from == depositCurrency && pair.to == receiveCurrency) + .isNotEmpty) + .toList(); + + return providers; + } + + void _onPairChange() { + final isPairExist = provider.pairList + .where((pair) => + pair.from == depositCurrency && pair.to == receiveCurrency) + .isNotEmpty; + + if (!isPairExist) { + final provider = + _providerForPair(from: depositCurrency, to: receiveCurrency); + + if (provider != null) { + changeProvider(provider: provider); + } + } + + depositAmount = ''; + receiveAmount = ''; + + loadLimits(); + } + + ExchangeProvider _providerForPair({CryptoCurrency from, CryptoCurrency to}) { + final providers = _providersForPair(from: from, to: to); + return providers.isNotEmpty ? providers[0] : null; + } + +} \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 20c7988bd..0724a9577 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -61,6 +61,7 @@ "exchange" : "Austausch", "clear" : "klar", + "refund_address" : "Rückerstattungsadresse", "change_exchange_provider" : "Wechseln Sie den Exchange-Anbieter", "you_will_send" : "Du wirst senden", "you_will_get" : "Sie erhalten", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 4048513a2..ff0c481c0 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -61,6 +61,7 @@ "exchange" : "Exchange", "clear" : "Clear", + "refund_address" : "Refund address", "change_exchange_provider" : "Change Exchange Provider", "you_will_send" : "You will send", "you_will_get" : "You will get", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 625d93254..9f0ea478a 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -61,6 +61,7 @@ "exchange" : "Intercambiar", "clear" : "Claro", + "refund_address" : "Dirección de reembolso", "change_exchange_provider" : "Cambiar proveedor de intercambio", "you_will_send" : "Enviarás", "you_will_get" : "Conseguirás", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 5a5ee3551..1a915a1d0 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -61,6 +61,7 @@ "exchange" : "अदला बदली", "clear" : "स्पष्ट", + "refund_address" : "वापसी का पता", "change_exchange_provider" : "एक्सचेंज प्रदाता बदलें", "you_will_send" : "तुम भेजोगे", "you_will_get" : "आपको मिल जायेगा", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index c8fb80b86..5f45c0949 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -61,6 +61,7 @@ "exchange" : "交換する", "clear" : "クリア", + "refund_address" : "払い戻し住所", "change_exchange_provider" : "Exchangeプロバイダーの変更", "you_will_send" : "送ります", "you_will_get" : "あなたが取得します", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 061016ecd..74430e1a2 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -61,6 +61,7 @@ "exchange" : "교환", "clear" : "명확한", + "refund_address" : "환불 주소", "change_exchange_provider" : "교환 공급자 변경", "you_will_send" : "보내드립니다", "you_will_get" : "당신은 얻을 것이다", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index ca10484da..78e3b77f6 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -61,6 +61,7 @@ "exchange" : "Uitwisseling", "clear" : "Duidelijk", + "refund_address" : "Adres voor terugbetaling", "change_exchange_provider" : "Wijzig Exchange Provider", "you_will_send" : "Je zal versturen", "you_will_get" : "Je zult krijgen", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 3319d6745..0647fd32c 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -61,6 +61,7 @@ "exchange" : "Wymieniać się", "clear" : "Wyczyść", + "refund_address" : "Adres zwrotu", "change_exchange_provider" : "Zmień dostawcę programu Exchange", "you_will_send" : "Wyślesz", "you_will_get" : "Dostaniesz", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index c1f4e110a..04b12862c 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -61,6 +61,7 @@ "exchange" : "Trocar", "clear" : "Limpar", + "refund_address" : "Endereço de reembolso", "change_exchange_provider" : "Alterar o provedor de troca", "you_will_send" : "Você enviará", "you_will_get" : "Você receberá", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 22d77e0be..441ff68a0 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -61,6 +61,7 @@ "exchange" : "Обмен", "clear" : "Очистить", + "refund_address" : "Адрес возврата", "change_exchange_provider" : "Изменить провайдера обмена", "you_will_send" : "Вы отправите", "you_will_get" : "Вы получите", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 326ec25ec..648bd8e70 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -61,6 +61,7 @@ "exchange" : "Обмін", "clear" : "Очистити", + "refund_address" : "Адреса повернення коштів", "change_exchange_provider" : "Змінити провайдера обміну", "you_will_send" : "Ви відправите", "you_will_get" : "Ви отримаєте", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index bf1b410a9..bf0523f96 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -61,6 +61,7 @@ "exchange" : "交换", "clear" : "明确", + "refund_address" : "退款地址", "change_exchange_provider" : "更改交易所提供商", "you_will_send" : "您将发送", "you_will_get" : "你会得到",