From 5314d4986c07a4b794e5739b4c1ad98b897ec947 Mon Sep 17 00:00:00 2001 From: M Date: Mon, 30 May 2022 14:34:21 +0100 Subject: [PATCH 01/55] Initial ionia service --- lib/ionia/ionia.dart | 62 +++++++++++++ lib/ionia/ionia_api.dart | 125 ++++++++++++++++++++++++++ lib/ionia/ionia_user_credentials.dart | 6 ++ lib/ionia/ionia_virtual_card.dart | 43 +++++++++ 4 files changed, 236 insertions(+) create mode 100644 lib/ionia/ionia.dart create mode 100644 lib/ionia/ionia_api.dart create mode 100644 lib/ionia/ionia_user_credentials.dart create mode 100644 lib/ionia/ionia_virtual_card.dart diff --git a/lib/ionia/ionia.dart b/lib/ionia/ionia.dart new file mode 100644 index 0000000000..f55af0b17f --- /dev/null +++ b/lib/ionia/ionia.dart @@ -0,0 +1,62 @@ +import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/ionia/ionia_api.dart'; + +class IoniaService { + IoniaService(this.secureStorage, this.ioniaApi); + + static const ioniaUsernameStorageKey = 'ionia_username'; + static const ioniaPasswordStorageKey = 'ionia_password'; + + static String get clientId => secrets.ioniaClientId; + + final FlutterSecureStorage secureStorage; + final IoniaApi ioniaApi; + + // Create user + + Future createUser(String email) async { + final username = await ioniaApi.createUser(email, clientId: clientId); + await secureStorage.write(key: ioniaUsernameStorageKey, value: username); + } + + // Verify email + + Future verifyEmail(String code) async { + final username = await secureStorage.read(key: ioniaUsernameStorageKey); + final credentials = await ioniaApi.verifyEmail(username: username, code: code, clientId: clientId); + await secureStorage.write(key: ioniaPasswordStorageKey, value: credentials.password); + } + + // Check is user logined + + Future isLogined() async { + final username = await secureStorage.read(key: ioniaUsernameStorageKey) ?? ''; + final password = await secureStorage.read(key: ioniaPasswordStorageKey) ?? ''; + return username.isNotEmpty && password.isNotEmpty; + } + + // Logout + + Future logout() async { + await secureStorage.delete(key: ioniaUsernameStorageKey); + await secureStorage.delete(key: ioniaPasswordStorageKey); + } + + // Create virtual card + + Future createCard() async { + final username = await secureStorage.read(key: ioniaUsernameStorageKey); + final password = await secureStorage.read(key: ioniaPasswordStorageKey); + return ioniaApi.createCard(username: username, password: password, clientId: clientId); + } + + // Get virtual card + + Future getCard() async { + final username = await secureStorage.read(key: ioniaUsernameStorageKey); + final password = await secureStorage.read(key: ioniaPasswordStorageKey); + return ioniaApi.getCards(username: username, password: password, clientId: clientId); + } +} \ No newline at end of file diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart new file mode 100644 index 0000000000..2ef367c362 --- /dev/null +++ b/lib/ionia/ionia_api.dart @@ -0,0 +1,125 @@ +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart'; +import 'package:cake_wallet/ionia/ionia_user_credentials.dart'; +import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; + +class IoniaApi { + static const baseUri = 'apidev.dashdirect.org'; + static const pathPrefix = 'cake'; + static final createUserUri = Uri.https(baseUri, '/$pathPrefix/CreateUser'); + static final verifyEmailUri = Uri.https(baseUri, '/$pathPrefix/VerifyEmail'); + static final createCardUri = Uri.https(baseUri, '/$pathPrefix/CreateCard'); + static final getCardsUri = Uri.https(baseUri, '/$pathPrefix/GetCards'); + + // Create user + + Future createUser(String email, {@required String clientId}) async { + final headers = {'clientId': clientId}; + final query = {'emailAddress': email}; + final uri = createUserUri.replace(queryParameters: query); + final response = await put(uri, headers: headers); + + if (response.statusCode != 200) { + // throw exception + return null; + } + + final bodyJson = json.decode(response.body) as Map; + final data = bodyJson['Data'] as Map; + final isSuccessful = bodyJson['Successful'] as bool; + + if (!isSuccessful) { + throw Exception(data['ErrorMessage'] as String); + } + + return data['username'] as String; + } + + // Verify email + + Future verifyEmail({ + @required String username, + @required String code, + @required String clientId}) async { + final headers = { + 'clientId': clientId, + 'username': username}; + final query = {'verificationCode': code}; + final uri = verifyEmailUri.replace(queryParameters: query); + final response = await put(uri, headers: headers); + + if (response.statusCode != 200) { + // throw exception + return null; + } + + final bodyJson = json.decode(response.body) as Map; + final data = bodyJson['Data'] as Map; + final isSuccessful = bodyJson['Successful'] as bool; + + if (!isSuccessful) { + throw Exception(data['ErrorMessage'] as String); + } + + final password = data['password'] as String; + return IoniaUserCredentials(username, password); + } + + // Get virtual card + + Future getCards({ + @required String username, + @required String password, + @required String clientId}) async { + final headers = { + 'clientId': clientId, + 'username': username, + 'password': password}; + final response = await post(getCardsUri, headers: headers); + + if (response.statusCode != 200) { + // throw exception + return null; + } + + final bodyJson = json.decode(response.body) as Map; + final data = bodyJson['Data'] as Map; + final isSuccessful = bodyJson['Successful'] as bool; + + if (!isSuccessful) { + throw Exception(data['ErrorMessage'] as String); + } + + final virtualCard = data['VirtualCard'] as Map; + return IoniaVirtualCard.fromMap(virtualCard); + } + + // Create virtual card + + Future createCard({ + @required String username, + @required String password, + @required String clientId}) async { + final headers = { + 'clientId': clientId, + 'username': username, + 'password': password}; + final response = await post(createCardUri, headers: headers); + + if (response.statusCode != 200) { + // throw exception + return null; + } + + final bodyJson = json.decode(response.body) as Map; + final data = bodyJson['Data'] as Map; + final isSuccessful = bodyJson['Successful'] as bool; + + if (!isSuccessful) { + throw Exception(data['ErrorMessage'] as String); + } + + return IoniaVirtualCard.fromMap(data); + } +} \ No newline at end of file diff --git a/lib/ionia/ionia_user_credentials.dart b/lib/ionia/ionia_user_credentials.dart new file mode 100644 index 0000000000..c398385f5f --- /dev/null +++ b/lib/ionia/ionia_user_credentials.dart @@ -0,0 +1,6 @@ +class IoniaUserCredentials { + const IoniaUserCredentials(this.username, this.password); + + final String username; + final String password; +} \ No newline at end of file diff --git a/lib/ionia/ionia_virtual_card.dart b/lib/ionia/ionia_virtual_card.dart new file mode 100644 index 0000000000..43cb075843 --- /dev/null +++ b/lib/ionia/ionia_virtual_card.dart @@ -0,0 +1,43 @@ +import 'package:flutter/foundation.dart'; + +class IoniaVirtualCard { + IoniaVirtualCard({ + @required this.token, + @required this.createdAt, + @required this.lastFour, + @required this.state, + @required this.pan, + @required this.cvv, + @required this.expirationMonth, + @required this.expirationYear, + @required this.fundsLimit, + @required this.spendLimit}); + + factory IoniaVirtualCard.fromMap(Map source) { + final created = source['created'] as String; + final createdAt = DateTime.tryParse(created); + + return IoniaVirtualCard( + token: source['token'] as String, + createdAt: createdAt, + lastFour: source['lastFour'] as String, + state: source['state'] as String, + pan: source['pan'] as String, + cvv: source['cvv'] as String, + expirationMonth: source['expirationMonth'] as String, + expirationYear: source['expirationYear'] as String, + fundsLimit: source['FundsLimit'] as double, + spendLimit: source['spend_limit'] as double); + } + + final String token; + final String lastFour; + final String state; + final String pan; + final String cvv; + final String expirationMonth; + final String expirationYear; + final DateTime createdAt; + final double fundsLimit; + final double spendLimit; +} \ No newline at end of file From de0ca1de6e5df3a5e9025e332e20d8af1c6045c9 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Wed, 15 Jun 2022 14:26:19 +0300 Subject: [PATCH 02/55] Ionia manage card UI (#374) * design ui for cakepay * Add manage cards page ui * create auth ui for ionia * add authentication logic * implement user create card --- assets/images/badge_discount.png | Bin 0 -> 1411 bytes assets/images/card.png | Bin 0 -> 556 bytes assets/images/filter.png | Bin 0 -> 504 bytes assets/images/mastercard.png | Bin 0 -> 2114 bytes assets/images/profile.png | Bin 0 -> 1060 bytes assets/images/search_icon.png | Bin 0 -> 536 bytes assets/images/wifi.png | Bin 0 -> 1087 bytes cw_haven/pubspec.lock | 7 - lib/core/email_validator.dart | 11 + lib/di.dart | 35 ++ lib/ionia/ionia_api.dart | 4 +- lib/ionia/ionia_create_state.dart | 56 +++ lib/router.dart | 29 ++ lib/routes.dart | 12 +- lib/src/screens/dashboard/dashboard_page.dart | 10 +- .../dashboard/widgets/market_place_page.dart | 52 +++ .../ionia/auth/ionia_create_account_page.dart | 143 +++++++ .../screens/ionia/auth/ionia_login_page.dart | 112 +++++ .../ionia/auth/ionia_verify_otp_page.dart | 132 ++++++ .../ionia/auth/ionia_welcome_page.dart | 104 +++++ .../cards/ionia_activate_debit_card_page.dart | 116 ++++++ .../cards/ionia_buy_card_detail_page.dart | 322 +++++++++++++++ .../ionia/cards/ionia_buy_gift_card.dart | 157 +++++++ .../ionia/cards/ionia_debit_card_page.dart | 382 ++++++++++++++++++ .../ionia/cards/ionia_manage_cards_page.dart | 247 +++++++++++ lib/src/screens/ionia/ionia.dart | 9 + lib/src/screens/ionia/widgets/card_item.dart | 122 ++++++ lib/src/screens/ionia/widgets/card_menu.dart | 11 + .../screens/ionia/widgets/confirm_modal.dart | 146 +++++++ .../ionia/widgets/text_icon_button.dart | 35 ++ lib/src/widgets/alert_with_two_actions.dart | 16 +- lib/src/widgets/base_alert_dialog.dart | 114 +++--- lib/src/widgets/discount_badge.dart | 31 ++ lib/src/widgets/market_place_item.dart | 66 +++ lib/typography.dart | 59 +++ lib/view_model/ionia/ionia_view_model.dart | 91 +++++ res/values/strings_en.arb | 66 ++- 37 files changed, 2617 insertions(+), 80 deletions(-) create mode 100644 assets/images/badge_discount.png create mode 100644 assets/images/card.png create mode 100644 assets/images/filter.png create mode 100644 assets/images/mastercard.png create mode 100644 assets/images/profile.png create mode 100644 assets/images/search_icon.png create mode 100644 assets/images/wifi.png create mode 100644 lib/core/email_validator.dart create mode 100644 lib/ionia/ionia_create_state.dart create mode 100644 lib/src/screens/dashboard/widgets/market_place_page.dart create mode 100644 lib/src/screens/ionia/auth/ionia_create_account_page.dart create mode 100644 lib/src/screens/ionia/auth/ionia_login_page.dart create mode 100644 lib/src/screens/ionia/auth/ionia_verify_otp_page.dart create mode 100644 lib/src/screens/ionia/auth/ionia_welcome_page.dart create mode 100644 lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart create mode 100644 lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart create mode 100644 lib/src/screens/ionia/cards/ionia_buy_gift_card.dart create mode 100644 lib/src/screens/ionia/cards/ionia_debit_card_page.dart create mode 100644 lib/src/screens/ionia/cards/ionia_manage_cards_page.dart create mode 100644 lib/src/screens/ionia/ionia.dart create mode 100644 lib/src/screens/ionia/widgets/card_item.dart create mode 100644 lib/src/screens/ionia/widgets/card_menu.dart create mode 100644 lib/src/screens/ionia/widgets/confirm_modal.dart create mode 100644 lib/src/screens/ionia/widgets/text_icon_button.dart create mode 100644 lib/src/widgets/discount_badge.dart create mode 100644 lib/src/widgets/market_place_item.dart create mode 100644 lib/typography.dart create mode 100644 lib/view_model/ionia/ionia_view_model.dart diff --git a/assets/images/badge_discount.png b/assets/images/badge_discount.png new file mode 100644 index 0000000000000000000000000000000000000000..64c8789c53a37ea9e1c5621a08b531c1441b1301 GIT binary patch literal 1411 zcmV-}1$_F6P)Q{Nhzs%n4Xya)KgVSnJKB> zQ&}&(`}4Kf^+ABYL{%kTSpPac={>{zeXI zn{!>RxBW1mGuOh{5W|UWck|hHy|rb;{DKr#1TL0gOq*UZJS=bCj`369(fLK5vP`kGLgLSOg7wqvkX#~wrs$mIjq^{FD7(v6_| zdu))%)bG7~NNd|z(Bjh&k^txrq-7Y+4N3ZSn{wwo^F>i>WYFQq%!e4ayha9GkfylWc*#;-~ zjSsl|x2kp`KvG$WX5}v)AHJ@E|K#OYq9M(QE22|9hvz4LCsE3u@OPdkjO&`!HAJpv zo)~@3{TT?z4DS5kzE&ybqgMs;)4q%V@p8~w27$Vv9Un5Si5WCn5qFX;;8H7kIE~2p z=XRMUnLbtXMoRcRYk8MyL`L9$Tp!o_lT;oHx|nrUng}C{NClcsTB5VrDJP;pM@r~j ze`5xuajBvm)t%>ay_R*@xtu)>x~hlgEVg@7B}yH*et23z<6{~(L=1kg00xo%(;+h( zjN>S7!uphs7}CM8aF6yn+i}b5wJq)nSTmi09w~D&o}uoY_a8%R#yyVfOR=EIx`mXF z4bEyS&5xG!nt9BUr!gn-SwK+XBMV7d>yAQ+!xd&fO4@sN6=x^Bo|6*7;#tTPd@f$cfEQ+`z% zvU7M|Youae>J>(2J9}73@of$N&GI)788QR8XKLi3pO)Bu$WRuSR)mad))g69Q_%bC z33LH+6xmc()g+Z_jiZ4DBxGfdtI7Z%ea{2p#iLY! z8Y>sWEL08P%*l`$UxnsvBiR*_$5t!Z$;{*BvK1>V3?($_ z1v#(eTYV7pNeiBQtIA#mk=5R zFOZGI#WEEbBC023s*H7K9he$sGUb?diIMTiD(wcy^6KSF=|eW`Dn3ICZndYes$VyI z^qUry%9NHnrrL|K%s{^*lOfYeVRII=z)G1}tECS$6Bt*K#J!}>>y%*?D~-!BnBT_@ z@_dv4ex)2!>W2U6kL3-&R{eB1N;i9PP3NSfiz2n=FrnkPqd z&6vXxcRuNyEQaFyfkkFUJ~W{}>W7$H#O)Wy6ONGRc6C%&tya=cN91`=<{=>-4)8Y0 zjzV+*HgcF0B5p>QN80>JwXBx8ub~(TuB-$H@2MA$=Ss#O*N^YB=la=LDA%|Z2}X4m z9NNT@v{J*0sagYI2NI@|&6a@LL#>KeR^d8}D7uz3h#f8mGr^Bw6Y#9|zW`BSjzW_V R`&R$}002ovPDHLkV1hR^mL32A literal 0 HcmV?d00001 diff --git a/assets/images/card.png b/assets/images/card.png new file mode 100644 index 0000000000000000000000000000000000000000..58935bdaca99aca87b6ee1888f90f0cf01df986f GIT binary patch literal 556 zcmV+{0@MA8P)8^9)%Nq|XcCx8uNCLl}@?!ZBp#C8&@#!lrYooYEh(w%)y zsvjVcNX(k7Pf%5rqMr@Lib7!;w23myvUiMyl3r0fP<&S5DXQOGlr7iHk~|AvG2Lnp zMN{l)N3@)56yQj{q&S-24@|?_JU5@*KrDuLrIIG~(mSHYp2`r+uoY%^0*8t9r0> zh(sFxZoDb+mo&WPV~AyAR^3}%-i#`Z=qB`Xi@>8 zNd<%^6%d+KKxmd@#Ox5D>lYRDzv-n%1K&O8Ft`Ad*puTLI3b_Lbq~(us#-|8UF)`x zR_mEp4W1VAXyCeM1PYd1T?O300w;(bhG_nYT-2g-mCd3V8C#nnq8yQ6{Lw$V-6uTA zU5LRSHNL3R+-=+IY;L`wdtlgV)R@*@=Uj4h?e)etg4uuI=)4DvU{CRgI6PAB0M2FG uPM#U^k&jQj!t@{cep=U-bRvvt&t0000NZ&QsDO$c2g?g z*KyhMXpH-C@<-6NTs8rlSJ=FeQZ^Qs27Wu)b~i$5^)^Py&J|aC8hU*pr4Z`_kK5k> zg2ITL@OV*AstKt9SO*&BhMmz*lt?N7%-%B|A1I-GfDZM&_r6B`ykO{E%qD1o+1g{a z?(wb?Dti6XlzPp3aZa)`F}^;;jv{5Dg?~kJBzC=C z`~AIL&-#_LL;|9nf-u8D)@tozAl^mL&>68=CAkRhhV>Y*siq*8Lb#K%4zxSKk|Gpy z7Pz}X9%y&H?r0@|RdfTm0i*1^@s6d7R)b00009a7bBm000&x z000&x0ZCFM@Bjb+0drDELIAGL9O(c600d`2O+f$vv5yPi%^;?wPrF z6h^=%ip3^!Wf#ts0Jfxn5@3r8s%U{?XFu9rv#>RKRi*8Z9jG+}|M4YkRFE)sxmc`Y zyyU>W?igD#3Lb6sEVWS`Y9qC)rrK>(*46@Qr_rWTKx0+S)oMr>g@ky9sWPBv=n(Xb zGKQx@ja_jNJetHt6*0gf?V<1?b^He;MCq_FgFv5OHNYdInk$95XRAnnSOsHCGSmSE z_5;KtQZGXBF5<~O48ZpCRN1x80+MZGl^%=ZYUe?mmKTd)ER>J@4RUce?U4Va5vkH2 zu-LC~^}ExvjsgqiVRTfX>ntXqjhJH{<_Vq6%bOkr86&_zq3iv8f-L9J~d!Pu%OPThe_ zs`O{gjW7EUU{$?Qb}={i)e<5SNidF80rIyg)t!Z4^4GVKC7bfEoP%Vn!krv;mZBsj zeG9gUDbI&-%J#A2L{UAUiZK{%co zbMA9yd)iK?3g%;i2brQ{7K@cHeShYD?s^^0`VJ(+<*|mDxP96_Jq7y?Z?IT5AsAio z`x2`|=Cc}9y#>)I%nXYY>de8Wey36iy+C^f^23p!wU^U|kKV@L3D( z!TGiU=_sY&@4OBLqs)-286Jcar*(DE-|Qjm-#2+8yCb0IZcA&3AIL>79 zg6Vn_(ov+9wy-br0?@wD+2q}oN`Zx0WlNkTdJv8-B;T28U%p^G+lK`cZ(+^|1VhV0 zIy}236@rZd<)^?Uwgvl{6!EiQDT4(|e46L51a${VW8LJB5d*bU2sRQjiOn944H|+~ zS+FXEV@6?NWAZJG()t>erpy-*j;Xsy8dJzZI=r%irgBFjv)$vQwVAwNe1WNWtf`V< zO1xmf^DJyL*%)IB8AvBE&Z>6dvS8v95C3^`F3Vhy?+%%BAFo&RB&sZye7CrfT&D&X zb4URd{t*tGDVRNga9ZDtAt_8Tx$lk(im)sE914~}W(C4&ZHy(7-gB~%iuko3JmTk2 zu(n&QKsb$0^N1V&%6X)KN@ptVp<1Gfyk^9x+54mrd$yP+cgP$Fb_wehE`2~SwyClI zWqb(&Pga*d{<3S?L(3fK|=SE}_C)5}Wuq(5H5sMnNQUg*;IWM_qj zsu>gWPv*|rukYMAt)l&u#A5osF7Y++7s`%lyQ}9>|KE=*khb0`N*Bw9Zm>_Wn8>5g zXSlpb$NB3ZpB)wMrRG}hFADa2Msmf*l3IVxfAl0$ju83EUrt1P1aAr*r}2!2hmB{> zd-j5Uk6*X93B#@hK7JEbEM|yx>%F26e}N8u12U{_la#qduKP$Q?A@K7g<_8&qCUzl z@NYR4Z6)o)4BgRRW6E>k{1Y#3IPfxr3p-&4J4;v5y%YA9r#xew1#&06mexyYBlrYy zpl+Qu#_EUI8c(sU+>UK!jHXjI=^+1ZA-MH=pLaVtCLEJ=J7W{7L+lM^i*vwt4ze?J zn#POy7}NoLl2!k!j{2Qm7IhXZ9E?+LyGpOUIj(lH#dEvvsX#r5OQ;Ts{)e&EQF8Kr zd*PN+#N&37h4y85>Ob^cc~LTpkNV%IOKwKZP|C*ZIh8S38~FuaD)=Y~kr1&_v9N|> zv48?ks=n^SOp^8eQk|Gzt-?F>(c?>cze*ccDTx^AHIpo$(%ORxN-njL@6cz}j}!Nm sRpA}_0);JO@z^n{I_}m-a(6!F2gMud)jP@6n*aa+07*qoM6N<$f&sS!@c;k- literal 0 HcmV?d00001 diff --git a/assets/images/profile.png b/assets/images/profile.png new file mode 100644 index 0000000000000000000000000000000000000000..d7dfe2508e091dda852740aca5a2169de485aa56 GIT binary patch literal 1060 zcmV+<1l#+GP)+{`t?<4`4 z;hWh(Nc-N^y<~uQye=j%AP{j%e-fgU=#1!-ezYTcB-#^gCzHt@214%u$Z6&51yQC+ zf|BTgXidS?@ERb;Gok|fqagYQkqEAU&iF03vt2`z;U{QXMnSdKIdtu1!UZFjj;ZEdJ9I5!XlSE_#aJ$J+M_pd1aj9$ugyqh$#?<-LE9({8&z)`I`gLU zIgA8dQ8S46O6XE@%wc4ZjfGilhye*M4Mhu~oElzNM&&AfFB9sR$JpiylF+u%?PgZY zh84<2E3=(akb;;u7*ImAzbig$gh3P`ezZbn`4WN5+LGmrJx?t>cLKd;CuATHD!3y8 z{>(74@bE4|9m2y_Do!ycJ+r?Wfq!cFl=_(S5an8CCsN+=^NQ_20HZ5%DdUAeH}KSe z5TP>ItesjEE+(^7q8ado0&x2Ff${P3hwPg6%#fsoQk$#~+LxN$O4(isFjyN1Ice}f#nTGketI+(qn@|JK|1vN~r zc&lJDk*QtWPtK=hrLYF@5r)AP(FVFI&J}sy(VFN61^};sH9`sWOEv>pOO!-kBKE%4 z4siu=27yox5zV0q0Eh>0pz{3@CW@Ct&$X)_I-=1G$ zRaSaXLU-QtmFN!y0Gq{M2F=UNOWvLlZHZK?Z6OR9yDptAh#^bYPNv;L?of`Mmbnz# z;49eN$C$cGW#PTXFsEIp&_DTkm-rKlfXaNP|GlAd<)%XVqjpoab&o1-3C&VO4Fpg6 zetS~=S<59B*&3Qrsy6Jcp_{!``^>-xJl{)8by82(WUIs#7x5cb1y7&xkW?yy) zjF!!`R<^S^-fOhNn=n!_*1drZdI>0000tajG#exvv>VV3U<23yHc+i1H>!#;54-*EeBr>7KFjhW z0ROB6RtgbQ^OJF-=gzbtNsEC#eg}u0QV{;>;DWPh z-!$?GpSh@s19)hAZ^vyASMOAC1(V((uy9D&)}(oY&Sy)5If3R1#CGx1O{ z-RXP>1ETOD2!RyQ2hJNa8aSAF>3=T#_3W?hY&J{jT;P@8@F|?X44nhKq6?1WKrP}E z4Ht7j&keJoslzKd_gBUkmZlD`!&R%DI<~ zztQ8c2iH+ri zxTYb1i%`uy<+zx)SAb9Q=1#ajlFMia2#UXj1-u8r2uNn3mFWzOL)&pAtSgj}5@G^m z`b~x`&+QG6k*2}|$SeVWzl9MONy-TdSs;TF+g2DSt2;)gU^xmBAhXtbl<_p!Qr}=X ztZjsUuHg?d=uNTJhRNGOS6qa6p1oHBpA@1f3KGD-1N>5D*nS@1a(|91jIh=`AmbOF zOc=uf8B1+LP{sm&DGF|t{Ae|JsC$2eT0KFQLn;~tzI}c4KRJIOKi;`U22@1Nv7$KLWa;n$UEGZ-fD(^}aXU9?0b3ZX)zO#qk-?Q3N1^ zl~CINVMpd3{CzEmYQkEp5OPTERY1EhfQk&{(Hwt~OoXNiXnb8lL}zXx<}@l}Gx0I~OgJE0u4gs03C;E&-bBv=#yh`%{P!(tnn7LJ9jC@$Jn z4m2xJi|awj-)&1w13?+)cvDnT<{FH#1pM#U6s-f64>O>K=mzjh{XledW=nm6t2ahW zM5f7fQMb`M0n3{rNSTesU_l0MjZvRqyxHj(dtYU+AeU%mIs_s4U7n42GxlIjaXV}W3RvpFV*4oVt9W*Xjq z0iPa~O)8XMPR9FMuv{sQ)B74!g#MbQ46opKLHR#4Ww4y}TMPXme>7!;|L!LL{UGBP zt7(V+Yj|aM{`&V?d2TuDkLA AddressResolver(yatService: getIt.get())); + getIt.registerFactory(() => IoniaApi()); + + getIt.registerFactory( + () => IoniaService(getIt.get(), getIt.get())); + + getIt.registerFactory(() => IoniaViewModel(ioniaService: getIt.get())); + + getIt.registerFactory(() => IoniaCreateAccountPage(getIt.get())); + + getIt.registerFactory(() => IoniaLoginPage(getIt.get())); + + getIt.registerFactoryParam((List args, _) { + final email = args.first as String; + final ioniaViewModel = args[1] as IoniaViewModel; + + return IoniaVerifyIoniaOtp(ioniaViewModel, email); + }); + + getIt.registerFactory(() => IoniaWelcomePage(getIt.get())); + + getIt.registerFactory(() => IoniaBuyGiftCardPage()); + + getIt.registerFactory(() => IoniaBuyGiftCardDetailPage()); + + getIt.registerFactory(() => IoniaManageCardsPage(getIt.get())); + + getIt.registerFactory(() => IoniaDebitCardPage(getIt.get())); + + getIt.registerFactory(() => IoniaActivateDebitCardPage(getIt.get())); + + _isSetupFinished = true; } diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart index 2ef367c362..10169c7797 100644 --- a/lib/ionia/ionia_api.dart +++ b/lib/ionia/ionia_api.dart @@ -88,7 +88,7 @@ class IoniaApi { final isSuccessful = bodyJson['Successful'] as bool; if (!isSuccessful) { - throw Exception(data['ErrorMessage'] as String); + throw Exception(data['message'] as String); } final virtualCard = data['VirtualCard'] as Map; @@ -117,7 +117,7 @@ class IoniaApi { final isSuccessful = bodyJson['Successful'] as bool; if (!isSuccessful) { - throw Exception(data['ErrorMessage'] as String); + throw Exception(data['message'] as String); } return IoniaVirtualCard.fromMap(data); diff --git a/lib/ionia/ionia_create_state.dart b/lib/ionia/ionia_create_state.dart new file mode 100644 index 0000000000..199d72c735 --- /dev/null +++ b/lib/ionia/ionia_create_state.dart @@ -0,0 +1,56 @@ +import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; +import 'package:flutter/material.dart'; + +abstract class IoniaCreateAccountState {} + +class IoniaCreateStateSuccess extends IoniaCreateAccountState {} + +class IoniaCreateStateLoading extends IoniaCreateAccountState {} + +class IoniaCreateStateFailure extends IoniaCreateAccountState { + IoniaCreateStateFailure({@required this.error}); + + final String error; +} + +abstract class IoniaOtpState {} + +class IoniaOtpValidating extends IoniaOtpState {} + +class IoniaOtpSuccess extends IoniaOtpState {} + +class IoniaOtpSendDisabled extends IoniaOtpState {} + +class IoniaOtpSendEnabled extends IoniaOtpState {} + +class IoniaOtpFailure extends IoniaOtpState { + IoniaOtpFailure({@required this.error}); + + final String error; +} + +class IoniaCreateCardState {} + +class IoniaCreateCardSuccess extends IoniaCreateCardState {} + +class IoniaCreateCardLoading extends IoniaCreateCardState {} + +class IoniaCreateCardFailure extends IoniaCreateCardState { + IoniaCreateCardFailure({@required this.error}); + + final String error; +} + +class IoniaFetchCardState {} + +class IoniaNoCardState extends IoniaFetchCardState {} + +class IoniaFetchingCard extends IoniaFetchCardState {} + +class IoniaFetchCardFailure extends IoniaFetchCardState {} + +class IoniaCardSuccess extends IoniaFetchCardState { + IoniaCardSuccess({@required this.card}); + + final IoniaVirtualCard card; +} diff --git a/lib/router.dart b/lib/router.dart index acfb18684c..930424019e 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -70,6 +70,7 @@ import 'package:hive/hive.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; +import 'package:cake_wallet/src/screens/ionia/ionia.dart'; RouteSettings currentRouteSettings; @@ -402,6 +403,34 @@ Route createRoute(RouteSettings settings) { getIt.get( param1: args)); + case Routes.ioniaWelcomePage: + return CupertinoPageRoute(builder: (_) => getIt.get()); + + case Routes.ioniaLoginPage: + return CupertinoPageRoute( builder: (_) => getIt.get()); + + case Routes.ioniaCreateAccountPage: + return CupertinoPageRoute( builder: (_) => getIt.get()); + + case Routes.ioniaManageCardsPage: + return CupertinoPageRoute(builder: (_) => getIt.get()); + + case Routes.ioniaBuyGiftCardPage: + return CupertinoPageRoute(builder: (_) => getIt.get()); + + case Routes.ioniaBuyGiftCardDetailPage: + return CupertinoPageRoute(builder: (_) => getIt.get()); + + case Routes.ioniaVerifyIoniaOtpPage: + final args = settings.arguments as List; + return CupertinoPageRoute(builder: (_) =>getIt.get(param1: args)); + + case Routes.ioniaDebitCardPage: + return CupertinoPageRoute(builder: (_) => getIt.get()); + + case Routes.ioniaActivateDebitCardPage: + return CupertinoPageRoute(builder: (_) => getIt.get()); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index 23e2360235..4ab4c0b26f 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -60,4 +60,14 @@ class Routes { static const moneroRestoreWalletFromWelcome = '/monero_restore_wallet'; static const moneroNewWalletFromWelcome = '/monero_new_wallet'; static const addressPage = '/address_page'; -} \ No newline at end of file + static const ioniaWelcomePage = '/cake_pay_welcome_page'; + static const ioniaCreateAccountPage = '/cake_pay_create_account_page'; + static const ioniaLoginPage = '/cake_pay_login_page'; + static const ioniaManageCardsPage = '/manage_cards_page'; + static const ioniaBuyGiftCardPage = '/buy_gift_card_page'; + static const ioniaBuyGiftCardDetailPage = '/buy_gift_card_detail_page'; + static const ioniaVerifyIoniaOtpPage = '/cake_pay_verify_otp_page'; + static const ioniaDebitCardPage = '/debit_card_page'; + static const ioniaActivateDebitCardPage = '/activate_debit_card_page'; + +} diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index eb8c9ab177..f11398564b 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -1,8 +1,8 @@ import 'dart:async'; +import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/src/screens/yat/yat_popup.dart'; import 'package:cake_wallet/src/screens/yat_emoji_id.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/themes/theme_base.dart'; @@ -14,19 +14,15 @@ import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/action_button.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart'; -import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; -import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:cake_wallet/main.dart'; -import 'package:cake_wallet/router.dart'; import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:cake_wallet/wallet_type_utils.dart'; class DashboardPage extends BasePage { DashboardPage({ @@ -85,7 +81,7 @@ class DashboardPage extends BasePage { final DashboardViewModel walletViewModel; final WalletAddressListViewModel addressListViewModel; - final controller = PageController(initialPage: 0); + final controller = PageController(initialPage: 1); var pages = []; bool _isEffectsInstalled = false; @@ -221,7 +217,7 @@ class DashboardPage extends BasePage { if (_isEffectsInstalled) { return; } - + pages.add(MarketPlacePage()); pages.add(balancePage); pages.add(TransactionsPage(dashboardViewModel: walletViewModel)); _isEffectsInstalled = true; diff --git a/lib/src/screens/dashboard/widgets/market_place_page.dart b/lib/src/screens/dashboard/widgets/market_place_page.dart new file mode 100644 index 0000000000..b616e955f1 --- /dev/null +++ b/lib/src/screens/dashboard/widgets/market_place_page.dart @@ -0,0 +1,52 @@ +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/market_place_item.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class MarketPlacePage extends StatelessWidget { + final _scrollController = ScrollController(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: RawScrollbar( + thumbColor: Colors.white.withOpacity(0.15), + radius: Radius.circular(20), + isAlwaysShown: true, + thickness: 2, + controller: _scrollController, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 50), + Text( + S.of(context).market_place, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme.display3.backgroundColor, + ), + ), + Expanded( + child: ListView( + controller: _scrollController, + children: [ + SizedBox(height: 20), + MarketPlaceItem( + onTap: () => Navigator.of(context).pushNamed(Routes.ioniaWelcomePage), + title: S.of(context).cake_pay_title, + subTitle: S.of(context).cake_pay_subtitle, + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/screens/ionia/auth/ionia_create_account_page.dart b/lib/src/screens/ionia/auth/ionia_create_account_page.dart new file mode 100644 index 0000000000..3fd9124040 --- /dev/null +++ b/lib/src/screens/ionia/auth/ionia_create_account_page.dart @@ -0,0 +1,143 @@ +import 'package:cake_wallet/core/email_validator.dart'; +import 'package:cake_wallet/ionia/ionia_create_state.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.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/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; + +class IoniaCreateAccountPage extends BasePage { + IoniaCreateAccountPage(this._ioniaViewModel) + : _emailFocus = FocusNode(), + _emailController = TextEditingController(), + _formKey = GlobalKey() { + _emailController.text = _ioniaViewModel.email; + _emailController.addListener(() => _ioniaViewModel.email = _emailController.text); + } + + final IoniaViewModel _ioniaViewModel; + + final GlobalKey _formKey; + + final FocusNode _emailFocus; + final TextEditingController _emailController; + + @override + Widget middle(BuildContext context) { + return Text( + S.current.sign_up, + style: TextStyle( + fontSize: 22, + fontFamily: 'Lato', + color: Theme.of(context).accentTextTheme.display4.backgroundColor, + fontWeight: FontWeight.w900, + ), + ); + } + + @override + Widget body(BuildContext context) { + reaction((_) => _ioniaViewModel.createUserState, (IoniaCreateAccountState state) { + if (state is IoniaCreateStateFailure) { + _onCreateUserFailure(context, state.error); + } + if (state is IoniaCreateStateSuccess) { + _onCreateSuccessful(context, _ioniaViewModel); + } + }); + + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.all(24), + content: Form( + key: _formKey, + child: BaseTextFormField( + hintText: S.of(context).email_address, + focusNode: _emailFocus, + validator: EmailValidator(), + controller: _emailController, + ), + ), + bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24), + bottomSection: Column( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Observer( + builder: (_) => LoadingPrimaryButton( + text: S.of(context).create_account, + onPressed: () async { + if (!_formKey.currentState.validate()) { + return; + } + await _ioniaViewModel.createUser(_emailController.text); + }, + isLoading: _ioniaViewModel.createUserState is IoniaCreateStateLoading, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ), + ), + SizedBox( + height: 20, + ), + RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: S.of(context).agree_to, + style: TextStyle( + color: Color(0xff7A93BA), + fontSize: 12, + fontFamily: 'Lato', + ), + children: [ + TextSpan( + text: S.of(context).settings_terms_and_conditions, + style: TextStyle( + color: Theme.of(context).accentTextTheme.body2.color, + fontWeight: FontWeight.w700, + ), + ), + TextSpan(text: ' ${S.of(context).and} '), + TextSpan( + text: S.of(context).privacy_policy, + style: TextStyle( + color: Theme.of(context).accentTextTheme.body2.color, + fontWeight: FontWeight.w700, + ), + ), + TextSpan(text: ' ${S.of(context).by_cake_pay}'), + ], + ), + ), + ], + ), + ], + ), + ); + } + + void _onCreateUserFailure(BuildContext context, String error) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.current.create_account, + alertContent: error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + + void _onCreateSuccessful(BuildContext context, IoniaViewModel ioniaViewModel) => Navigator.pushNamed( + context, + Routes.ioniaVerifyIoniaOtpPage, + arguments: [ioniaViewModel.email, ioniaViewModel], + ); +} diff --git a/lib/src/screens/ionia/auth/ionia_login_page.dart b/lib/src/screens/ionia/auth/ionia_login_page.dart new file mode 100644 index 0000000000..584682341a --- /dev/null +++ b/lib/src/screens/ionia/auth/ionia_login_page.dart @@ -0,0 +1,112 @@ +import 'package:cake_wallet/core/email_validator.dart'; +import 'package:cake_wallet/ionia/ionia_create_state.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.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/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; + +class IoniaLoginPage extends BasePage { + IoniaLoginPage(this._ioniaViewModel) + : _formKey = GlobalKey(), + _emailController = TextEditingController() { + _emailController.text = _ioniaViewModel.email; + _emailController.addListener(() => _ioniaViewModel.email = _emailController.text); + } + + final GlobalKey _formKey; + + final IoniaViewModel _ioniaViewModel; + + @override + Color get titleColor => Colors.black; + + final TextEditingController _emailController; + + @override + Widget middle(BuildContext context) { + return Text( + S.current.login, + style: TextStyle( + fontSize: 22, + fontFamily: 'Lato', + fontWeight: FontWeight.w900, + ), + ); + } + + @override + Widget body(BuildContext context) { + reaction((_) => _ioniaViewModel.createUserState, (IoniaCreateAccountState state) { + if (state is IoniaCreateStateFailure) { + _onLoginUserFailure(context, state.error); + } + if (state is IoniaCreateStateSuccess) { + _onLoginSuccessful(context, _ioniaViewModel); + } + }); + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.all(24), + content: Form( + key: _formKey, + child: BaseTextFormField( + hintText: S.of(context).email_address, + validator: EmailValidator(), + controller: _emailController, + ), + ), + bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24), + bottomSection: Column( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Observer( + builder: (_) => LoadingPrimaryButton( + text: S.of(context).login, + onPressed: () async { + if (!_formKey.currentState.validate()) { + return; + } + await _ioniaViewModel.createUser(_emailController.text); + }, + isLoading: _ioniaViewModel.createUserState is IoniaCreateStateLoading, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ), + ), + SizedBox( + height: 20, + ), + ], + ), + ], + ), + ); + } + + void _onLoginUserFailure(BuildContext context, String error) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.current.login, + alertContent: error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + + void _onLoginSuccessful(BuildContext context, IoniaViewModel ioniaViewModel) => Navigator.pushNamed( + context, + Routes.ioniaVerifyIoniaOtpPage, + arguments: [ioniaViewModel.email, ioniaViewModel], + ); +} diff --git a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart new file mode 100644 index 0000000000..2af82350a6 --- /dev/null +++ b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart @@ -0,0 +1,132 @@ +import 'package:cake_wallet/ionia/ionia_create_state.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.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/typography.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; + +class IoniaVerifyIoniaOtp extends BasePage { + + IoniaVerifyIoniaOtp(this._ioniaViewModel, this._email) + : _codeController = TextEditingController(), + _codeFocus = FocusNode() { + _codeController.addListener(() { + final otp = _codeController.text; + _ioniaViewModel.otp = otp; + if (otp.length > 3) { + _ioniaViewModel.otpState = IoniaOtpSendEnabled(); + } else { + _ioniaViewModel.otpState = IoniaOtpSendDisabled(); + } + }); + } + + final IoniaViewModel _ioniaViewModel; + + final String _email; + + @override + Widget middle(BuildContext context) { + return Text( + S.current.verification, + style: TextStyle( + fontSize: 22, + fontFamily: 'Lato', + color: Theme.of(context).accentTextTheme.display4.backgroundColor, + fontWeight: FontWeight.w900, + ), + ); + } + + final TextEditingController _codeController; + final FocusNode _codeFocus; + + @override + Widget body(BuildContext context) { + reaction((_) => _ioniaViewModel.otpState, (IoniaOtpState state) { + if (state is IoniaOtpFailure) { + _onOtpFailure(context, state.error); + } + if (state is IoniaOtpSuccess) { + _onOtpSuccessful(context); + } + }); + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.all(24), + content: Column( + children: [ + BaseTextFormField( + hintText: S.of(context).enter_code, + focusNode: _codeFocus, + controller: _codeController, + ), + SizedBox(height: 14), + Text( + S.of(context).fill_code, + style: TextStyle(color: Color(0xff7A93BA), fontSize: 12), + ), + SizedBox(height: 34), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(S.of(context).dont_get_code), + SizedBox(width: 20), + InkWell( + onTap: () => _ioniaViewModel.createUser(_email), + child: Text( + S.of(context).resend_code, + style: textSmallSemiBold(color: Palette.blueCraiola), + ), + ), + ], + ), + ], + ), + bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24), + bottomSection: Column( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Observer( + builder: (_) => LoadingPrimaryButton( + text: S.of(context).continue_text, + onPressed: () async => await _ioniaViewModel.verifyEmail(_codeController.text), + isDisabled: _ioniaViewModel.otpState is IoniaOtpSendDisabled, + isLoading: _ioniaViewModel.otpState is IoniaOtpValidating, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ), + ), + SizedBox(height: 20), + ], + ), + ], + ), + ); + } + + void _onOtpFailure(BuildContext context, String error) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.current.verification, + alertContent: error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + + void _onOtpSuccessful(BuildContext context) => + Navigator.pushNamedAndRemoveUntil(context, Routes.ioniaManageCardsPage, ModalRoute.withName(Routes.dashboard)); +} diff --git a/lib/src/screens/ionia/auth/ionia_welcome_page.dart b/lib/src/screens/ionia/auth/ionia_welcome_page.dart new file mode 100644 index 0000000000..0c4abecabb --- /dev/null +++ b/lib/src/screens/ionia/auth/ionia_welcome_page.dart @@ -0,0 +1,104 @@ +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:mobx/mobx.dart'; + +class IoniaWelcomePage extends BasePage { + IoniaWelcomePage(this._ioniaViewModel); + + @override + Widget middle(BuildContext context) { + return Text( + S.current.welcome_to_cakepay, + style: TextStyle( + fontSize: 22, + fontFamily: 'Lato', + color: Theme.of(context).accentTextTheme.display4.backgroundColor, + fontWeight: FontWeight.w900, + ), + ); + } + + final IoniaViewModel _ioniaViewModel; + + @override + Widget body(BuildContext context) { + reaction((_) => _ioniaViewModel.isLoggedIn, (bool state) { + if (state) { + Navigator.pushReplacementNamed(context, Routes.ioniaDebitCardPage); + } + }); + return Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + SizedBox(height: 100), + Text( + S.of(context).about_cake_pay, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w400, + fontFamily: 'Lato', + color: Theme.of(context).primaryTextTheme.title.color, + ), + ), + SizedBox(height: 20), + Text( + S.of(context).cake_pay_account_note, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w400, + fontFamily: 'Lato', + color: Theme.of(context).primaryTextTheme.title.color, + ), + ), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + PrimaryButton( + text: S.of(context).create_account, + onPressed: () => Navigator.of(context).pushNamed(Routes.ioniaCreateAccountPage), + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ), + SizedBox( + height: 16, + ), + Text( + S.of(context).already_have_account, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + fontFamily: 'Lato', + color: Theme.of(context).primaryTextTheme.title.color, + ), + ), + SizedBox(height: 8), + InkWell( + onTap: () => Navigator.of(context).pushNamed(Routes.ioniaLoginPage), + child: Text( + S.of(context).login, + style: TextStyle( + color: Palette.blueCraiola, + fontSize: 16, + fontWeight: FontWeight.w900, + ), + ), + ) + ], + ) + ], + ), + ); + } +} diff --git a/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart b/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart new file mode 100644 index 0000000000..91fd8615cc --- /dev/null +++ b/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart @@ -0,0 +1,116 @@ +import 'package:cake_wallet/ionia/ionia_create_state.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.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/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:mobx/mobx.dart'; + +class IoniaActivateDebitCardPage extends BasePage { + + IoniaActivateDebitCardPage(this._ioniaViewModel); + + final IoniaViewModel _ioniaViewModel; + + @override + Widget middle(BuildContext context) { + return Text( + S.current.debit_card, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).accentTextTheme.display4.backgroundColor, + fontFamily: 'Lato', + fontWeight: FontWeight.w900, + ), + ); + } + + @override + Widget body(BuildContext context) { + reaction((_) => _ioniaViewModel.createCardState, (IoniaCreateCardState state) { + if (state is IoniaCreateCardFailure) { + _onCreateCardFailure(context, state.error); + } + if (state is IoniaCreateCardSuccess) { + _onCreateCardSuccess(context); + } + }); + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.zero, + content: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + SizedBox(height: 16), + Text(S.of(context).debit_card_terms), + SizedBox(height: 24), + Text(S.of(context).please_reference_document), + SizedBox(height: 40), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + children: [ + TextIconButton( + label: S.current.cardholder_agreement, + onTap: () {}, + ), + SizedBox( + height: 24, + ), + TextIconButton( + label: S.current.e_sign_consent, + onTap: () {}, + ), + ], + ), + ), + ], + ), + ), + bottomSection: LoadingPrimaryButton( + onPressed: () { + _ioniaViewModel.createCard(); + }, + isLoading: _ioniaViewModel.createCardState is IoniaCreateCardLoading, + text: S.of(context).agree_and_continue, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ), + ); + } + + void _onCreateCardFailure(BuildContext context, String errorMessage) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.current.error, + alertContent: errorMessage, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + + void _onCreateCardSuccess(BuildContext context) { + Navigator.pushNamed( + context, + Routes.ioniaDebitCardPage, + ); + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).congratulations, + alertContent: S.of(context).you_now_have_debit_card, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } +} diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart new file mode 100644 index 0000000000..9f0fa17927 --- /dev/null +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -0,0 +1,322 @@ +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/confirm_modal.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/src/widgets/discount_badge.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/store/settings_store.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/typography.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class IoniaBuyGiftCardDetailPage extends StatelessWidget { + ThemeBase get currentTheme => getIt.get().currentTheme; + + Color get backgroundLightColor => Colors.white; + + Color get backgroundDarkColor => PaletteDark.backgroundColor; + + void onClose(BuildContext context) => Navigator.of(context).pop(); + + Widget leading(BuildContext context) { + if (ModalRoute.of(context).isFirst) { + return null; + } + + final _backButton = Icon( + Icons.arrow_back_ios, + color: Theme.of(context).primaryTextTheme.title.color, + size: 16, + ); + return Padding( + padding: const EdgeInsets.only(left: 10.0), + child: SizedBox( + height: 37, + width: 37, + child: ButtonTheme( + minWidth: double.minPositive, + child: FlatButton( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + padding: EdgeInsets.all(0), + onPressed: () => onClose(context), + child: _backButton), + ), + ), + ); + } + + Widget middle(BuildContext context) { + return Text( + 'AppleBees', + style: TextStyle( + fontSize: 22, + fontFamily: 'Lato', + fontWeight: FontWeight.w900, + ), + ); + } + + @override + Widget build(BuildContext context) { + final _backgroundColor = currentTheme.type == ThemeType.dark ? backgroundDarkColor : backgroundLightColor; + + return Scaffold( + backgroundColor: _backgroundColor, + body: ScrollableWithBottomSection( + contentPadding: EdgeInsets.zero, + content: Column( + children: [ + SizedBox(height: 60), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [leading(context), middle(context), DiscountBadge()], + ), + SizedBox(height: 36), + Container( + padding: EdgeInsets.symmetric(vertical: 24), + margin: EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient( + colors: [ + Theme.of(context).primaryTextTheme.subhead.color, + Theme.of(context).primaryTextTheme.subhead.decorationColor, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Column( + children: [ + Text( + S.of(context).gift_card_amount, + style: textSmall(), + ), + SizedBox(height: 4), + Text( + '\$1000.12', + style: textXLargeSemiBold(), + ), + SizedBox(height: 24), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).bill_amount, + style: textSmall(), + ), + SizedBox(height: 4), + Text( + '\$1000.00', + style: textLargeSemiBold(), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + S.of(context).tip, + style: textSmall(), + ), + SizedBox(height: 4), + Text( + '\$1000.00', + style: textLargeSemiBold(), + ), + ], + ), + ], + ), + ), + SizedBox(height: 16), + Divider(), + SizedBox(height: 16), + Text( + S.of(context).you_pay, + style: textSmall(), + ), + SizedBox(height: 4), + Text( + '22.3435345000 XMR', + style: textLargeSemiBold(), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).tip, + style: TextStyle( + color: Theme.of(context).primaryTextTheme.title.color, + fontWeight: FontWeight.w700, + fontSize: 14, + ), + ), + SizedBox(height: 4), + TipButtonGroup() + ], + ), + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: TextIconButton( + label: S.of(context).how_to_use_card, + onTap: () {}, + ), + ), + ], + ), + bottomSection: Column( + children: [ + Padding( + padding: EdgeInsets.only(bottom: 12), + child: PrimaryButton( + onPressed: () => purchaseCard(context), + text: S.of(context).purchase_gift_card, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ), + ), + SizedBox(height: 8), + Text(S.of(context).settings_terms_and_conditions, + style: textMediumSemiBold( + color: Theme.of(context).primaryTextTheme.body1.color, + ).copyWith(fontSize: 12)), + SizedBox(height: 16) + ], + ), + ), + ); + } + + void purchaseCard(BuildContext context) { + showPopUp( + context: context, + builder: (_) { + return IoniaConfirmModal( + alertTitle: S.of(context).confirm_sending, + alertContent: SizedBox( + //Todo:: substitute this widget with modal content + height: 200, + ), + rightButtonText: S.of(context).ok, + leftButtonText: S.of(context).cancel, + leftActionColor: Color(0xffFF6600), + rightActionColor: Theme.of(context).accentTextTheme.body2.color, + actionRightButton: () async { + Navigator.of(context).pop(); + }, + actionLeftButton: () => Navigator.of(context).pop()); + }); + } +} + +class TipButtonGroup extends StatefulWidget { + const TipButtonGroup({ + Key key, + }) : super(key: key); + + @override + _TipButtonGroupState createState() => _TipButtonGroupState(); +} + +class _TipButtonGroupState extends State { + String selectedTip; + bool _isSelected(String value) { + return selectedTip == value; + } + + @override + Widget build(BuildContext context) { + return Row( + children: [ + TipButton( + isSelected: _isSelected('299'), + caption: '\$10', + subTitle: '%299', + ), + SizedBox(width: 4), + TipButton( + caption: '\$10', + subTitle: '%299', + ), + SizedBox(width: 4), + TipButton( + isSelected: _isSelected('299'), + caption: '\$10', + subTitle: '%299', + ), + SizedBox(width: 4), + TipButton( + isSelected: _isSelected('299'), + caption: S.of(context).custom, + ), + ], + ); + } +} + +class TipButton extends StatelessWidget { + final String caption; + final String subTitle; + final bool isSelected; + final void Function(int, bool) onTap; + + const TipButton({ + @required this.caption, + this.subTitle, + this.onTap, + this.isSelected = false, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: 49, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(caption, style: textSmallSemiBold(color: Theme.of(context).primaryTextTheme.title.color)), + if (subTitle != null) ...[ + SizedBox(height: 4), + Text( + subTitle, + style: textXxSmallSemiBold(color: Palette.gray), + ), + ] + ], + ), + padding: EdgeInsets.symmetric(horizontal: 18, vertical: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Color.fromRGBO(242, 240, 250, 1), + gradient: isSelected + ? LinearGradient( + colors: [ + Theme.of(context).primaryTextTheme.subhead.color, + Theme.of(context).primaryTextTheme.subhead.decorationColor, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ) + : null, + ), + ); + } +} diff --git a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart new file mode 100644 index 0000000000..1677a9e77f --- /dev/null +++ b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart @@ -0,0 +1,157 @@ +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; +import 'package:cake_wallet/src/widgets/keyboard_done_button.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/themes/theme_base.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:keyboard_actions/keyboard_actions.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class IoniaBuyGiftCardPage extends BasePage { + IoniaBuyGiftCardPage() + : _amountFieldFocus = FocusNode(), + _amountController = TextEditingController(); + @override + String get title => S.current.enter_amount; + + @override + Color get titleColor => Colors.white; + + @override + bool get extendBodyBehindAppBar => true; + + @override + AppBarStyle get appBarStyle => AppBarStyle.transparent; + + Color get textColor => currentTheme.type == ThemeType.dark ? Colors.white : Color(0xff393939); + + final TextEditingController _amountController; + final FocusNode _amountFieldFocus; + + @override + Widget body(BuildContext context) { + final _width = MediaQuery.of(context).size.width; + return KeyboardActions( + disableScroll: true, + config: KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: _amountFieldFocus, + toolbarButtons: [(_) => KeyboardDoneButton()], + ), + ]), + child: Container( + color: Theme.of(context).backgroundColor, + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.zero, + content: Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 25), + decoration: BoxDecoration( + borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), + gradient: LinearGradient(colors: [ + Theme.of(context).primaryTextTheme.subhead.color, + Theme.of(context).primaryTextTheme.subhead.decorationColor, + ], begin: Alignment.topLeft, end: Alignment.bottomRight), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 150), + BaseTextFormField( + controller: _amountController, + focusNode: _amountFieldFocus, + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\-|\ ]'))], + hintText: '1000', + placeholderTextStyle: TextStyle( + color: Theme.of(context).primaryTextTheme.headline.color, + fontWeight: FontWeight.w500, + fontSize: 36, + ), + borderColor: Theme.of(context).primaryTextTheme.headline.color, + textColor: Colors.white, + textStyle: TextStyle( + color: Colors.white, + fontSize: 36, + ), + suffixIcon: SizedBox( + width: _width / 6, + ), + prefixIcon: Padding( + padding: EdgeInsets.only( + top: 5.0, + left: _width / 4, + ), + child: Text( + 'USD: ', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w900, + fontSize: 36, + ), + ), + ), + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + S.of(context).min_amount('5'), + style: TextStyle( + color: Theme.of(context).primaryTextTheme.headline.color, + ), + ), + Text( + S.of(context).max_amount('20000'), + style: TextStyle( + color: Theme.of(context).primaryTextTheme.headline.color, + ), + ), + ], + ), + SizedBox(height: 24), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(24.0), + child: CardItem( + onTap: () {}, + title: 'Applebee’s', + hasDiscount: true, + subTitle: 'subTitle', + logoUrl: '', + ), + ) + ], + ), + bottomSection: Column( + children: [ + Padding( + padding: EdgeInsets.only(bottom: 12), + child: PrimaryButton( + onPressed: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardDetailPage), + text: S.of(context).continue_text, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ), + ), + SizedBox(height: 30), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/screens/ionia/cards/ionia_debit_card_page.dart b/lib/src/screens/ionia/cards/ionia_debit_card_page.dart new file mode 100644 index 0000000000..0d2a6294fd --- /dev/null +++ b/lib/src/screens/ionia/cards/ionia_debit_card_page.dart @@ -0,0 +1,382 @@ +import 'package:cake_wallet/ionia/ionia_create_state.dart'; +import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; +import 'package:cake_wallet/src/widgets/alert_background.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/typography.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class IoniaDebitCardPage extends BasePage { + final IoniaViewModel _ioniaViewModel; + + IoniaDebitCardPage(this._ioniaViewModel); + + @override + Widget middle(BuildContext context) { + return Text( + S.current.debit_card, + style: TextStyle( + fontSize: 22, + fontFamily: 'Lato', + color: Theme.of(context).accentTextTheme.display4.backgroundColor, + fontWeight: FontWeight.w900, + ), + ); + } + + @override + Widget body(BuildContext context) { + return Observer( + builder: (_) { + final cardState = _ioniaViewModel.cardState; + if (cardState is IoniaFetchingCard) { + return Center(child: CircularProgressIndicator()); + } + if (cardState is IoniaCardSuccess) { + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.zero, + content: Padding( + padding: const EdgeInsets.all(16.0), + child: _IoniaDebitCard( + cardInfo: cardState.card, + ), + ), + bottomSection: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Text( + S.of(context).billing_address_info, + style: textSmall(color: Theme.of(context).textTheme.display1.color), + textAlign: TextAlign.center, + ), + ), + SizedBox(height: 24), + PrimaryButton( + text: S.of(context).order_physical_card, + onPressed: () {}, + color: Color(0xffE9F2FC), + textColor: Theme.of(context).textTheme.display2.color, + ), + SizedBox(height: 8), + PrimaryButton( + text: S.of(context).add_value, + onPressed: () {}, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ), + SizedBox(height: 16) + ], + ), + ); + } + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.zero, + content: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + _IoniaDebitCard(isCardSample: true), + SizedBox(height: 40), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + children: [ + TextIconButton( + label: S.current.how_to_use_card, + onTap: () => _showHowToUseCard(context), + ), + SizedBox( + height: 24, + ), + TextIconButton( + label: S.current.frequently_asked_questions, + onTap: () {}, + ), + ], + ), + ), + SizedBox(height: 50), + Container( + padding: EdgeInsets.all(20), + margin: EdgeInsets.all(8), + width: double.infinity, + decoration: BoxDecoration( + color: Color.fromRGBO(233, 242, 252, 1), + borderRadius: BorderRadius.circular(20), + ), + child: RichText( + text: TextSpan( + text: S.of(context).get_a, + style: textMedium(color: Theme.of(context).textTheme.display2.color), + children: [ + TextSpan( + text: S.of(context).digital_and_physical_card, + style: textMediumBold(color: Theme.of(context).textTheme.display2.color), + ), + TextSpan( + text: S.of(context).get_card_note, + ) + ], + )), + ), + ], + ), + ), + bottomSectionPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 32, + ), + bottomSection: PrimaryButton( + text: S.of(context).activate, + onPressed: () => _showHowToUseCard(context, activate: true), + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ), + ); + }, + ); + } + + void _showHowToUseCard(BuildContext context, {bool activate = false}) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertBackground( + child: Material( + color: Colors.transparent, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(height: 10), + Container( + padding: EdgeInsets.only(top: 24, left: 24, right: 24), + margin: EdgeInsets.all(24), + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + borderRadius: BorderRadius.circular(30), + ), + child: Column( + children: [ + Text( + S.of(context).how_to_use_card, + style: textLargeSemiBold( + color: Theme.of(context).textTheme.body1.color, + ), + ), + SizedBox(height: 24), + Align( + alignment: Alignment.bottomLeft, + child: Text( + S.of(context).signup_for_card_accept_terms, + style: textSmallSemiBold( + color: Theme.of(context).textTheme.display2.color, + ), + ), + ), + SizedBox(height: 24), + _TitleSubtitleTile( + title: S.of(context).add_fund_to_card('1000'), + subtitle: S.of(context).use_card_info_two, + ), + SizedBox(height: 21), + _TitleSubtitleTile( + title: S.of(context).use_card_info_three, + subtitle: S.of(context).optionally_order_card, + ), + SizedBox(height: 35), + PrimaryButton( + onPressed: () => activate + ? Navigator.pushNamed(context, Routes.ioniaActivateDebitCardPage) + : Navigator.pop(context), + text: S.of(context).send_got_it, + color: Color.fromRGBO(233, 242, 252, 1), + textColor: Theme.of(context).textTheme.display2.color, + ), + SizedBox(height: 21), + ], + ), + ), + InkWell( + onTap: () => Navigator.pop(context), + child: Container( + margin: EdgeInsets.only(bottom: 40), + child: CircleAvatar( + child: Icon( + Icons.close, + color: Colors.black, + ), + backgroundColor: Colors.white, + ), + ), + ) + ], + ), + ), + ); + }); + } +} + +class _IoniaDebitCard extends StatefulWidget { + final bool isCardSample; + final IoniaVirtualCard cardInfo; + const _IoniaDebitCard({ + Key key, + this.isCardSample = false, + this.cardInfo, + }) : super(key: key); + + @override + _IoniaDebitCardState createState() => _IoniaDebitCardState(); +} + +class _IoniaDebitCardState extends State<_IoniaDebitCard> { + bool _showDetails = false; + void _toggleVisibility() { + setState(() => _showDetails = !_showDetails); + } + + String _formatPan(String pan) { + if (pan == null) return ''; + return pan.replaceAllMapped(RegExp(r'.{4}'), (match) => '${match.group(0)} '); + } + + String get _getLast4 => widget.isCardSample ? '0000' : widget.cardInfo.pan.substring(widget.cardInfo.pan.length - 5); + + String get _getSpendLimit => widget.isCardSample ? '10000' : widget.cardInfo.spendLimit.toStringAsFixed(2); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 19), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + gradient: LinearGradient( + colors: [ + Theme.of(context).primaryTextTheme.subhead.color, + Theme.of(context).primaryTextTheme.subhead.decorationColor, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + S.current.cakepay_prepaid_card, + style: textSmall(), + ), + Image.asset( + 'assets/images/mastercard.png', + width: 54, + ), + ], + ), + Text( + widget.isCardSample ? S.of(context).upto(_getSpendLimit) : '\$$_getSpendLimit', + style: textXLargeSemiBold(), + ), + SizedBox(height: 16), + Text( + _showDetails ? _formatPan(widget.cardInfo.pan) : '**** **** **** $_getLast4', + style: textMediumSemiBold(), + ), + SizedBox(height: 32), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (widget.isCardSample) + Text( + S.current.no_id_needed, + style: textMediumBold(), + ) + else ...[ + Column( + children: [ + Text( + 'CVV', + style: textXSmallSemiBold(), + ), + SizedBox(height: 4), + Text( + _showDetails ? widget.cardInfo.cvv : '***', + style: textMediumSemiBold(), + ) + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).expires, + style: textXSmallSemiBold(), + ), + SizedBox(height: 4), + Text( + '${widget.cardInfo.expirationMonth ?? S.of(context).mm}/${widget.cardInfo.expirationYear ?? S.of(context).yy}', + style: textMediumSemiBold(), + ) + ], + ), + ] + ], + ), + if (!widget.isCardSample) ...[ + SizedBox(height: 8), + Center( + child: InkWell( + onTap: () => _toggleVisibility(), + child: Text( + _showDetails ? S.of(context).hide_details : S.of(context).show_details, + style: textSmall(), + ), + ), + ), + ], + ], + ), + ); + } +} + +class _TitleSubtitleTile extends StatelessWidget { + final String title; + final String subtitle; + const _TitleSubtitleTile({ + Key key, + @required this.title, + @required this.subtitle, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: textSmallSemiBold(color: Theme.of(context).textTheme.display2.color), + ), + SizedBox(height: 4), + Text( + subtitle, + style: textSmall(color: Theme.of(context).textTheme.display2.color), + ), + ], + ); + } +} diff --git a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart new file mode 100644 index 0000000000..49276307d3 --- /dev/null +++ b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart @@ -0,0 +1,247 @@ +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/card_menu.dart'; +import 'package:cake_wallet/src/widgets/market_place_item.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class IoniaManageCardsPage extends BasePage { + IoniaManageCardsPage(this._ioniaViewModel); + + final IoniaViewModel _ioniaViewModel; + + @override + Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white; + + @override + Color get backgroundDarkColor => Colors.transparent; + + @override + Color get titleColor => currentTheme.type == ThemeType.bright ? Colors.white : Colors.black; + + @override + Widget Function(BuildContext, Widget) get rootWrapper => (BuildContext context, Widget scaffold) => Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).accentColor, + Theme.of(context).scaffoldBackgroundColor, + Theme.of(context).primaryColor, + ], + begin: Alignment.topRight, + end: Alignment.bottomLeft, + ), + ), + child: scaffold, + ); + + @override + bool get resizeToAvoidBottomInset => false; + + @override + Widget get endDrawer => CardMenu(); + + @override + Widget leading(BuildContext context) { + final _backButton = Icon( + Icons.arrow_back_ios, + color: titleColor ?? Theme.of(context).primaryTextTheme.title.color, + size: 16, + ); + + return SizedBox( + height: 37, + width: 37, + child: ButtonTheme( + minWidth: double.minPositive, + child: FlatButton( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + padding: EdgeInsets.all(0), + onPressed: () => Navigator.pushReplacementNamed(context, Routes.dashboard), + child: _backButton), + ), + ); + } + + @override + Widget middle(BuildContext context) { + return Text( + S.of(context).manage_cards, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme.display3.backgroundColor, + ), + ); + } + + final ScrollController _scrollController = ScrollController(); + + @override + Widget trailing(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + _TrailingIcon( + asset: 'assets/images/card.png', + onPressed: () => Navigator.pushNamed(context, Routes.ioniaDebitCardPage), + ), + SizedBox(width: 16), + _TrailingIcon( + asset: 'assets/images/profile.png', + onPressed: () {}, + ), + ], + ); + } + + @override + Widget body(BuildContext context) { + final filterIcon = Image.asset( + 'assets/images/filter.png', + color: Theme.of(context).textTheme.caption.decorationColor, + ); + + return Padding( + padding: const EdgeInsets.all(14.0), + child: Column( + children: [ + MarketPlaceItem( + onTap: () {}, + title: S.of(context).setup_your_debit_card, + subTitle: S.of(context).no_id_required, + ), + SizedBox(height: 48), + Container( + padding: EdgeInsets.only(left: 2, right: 22), + height: 32, + child: Row( + children: [ + Expanded(child: _SearchWidget()), + SizedBox(width: 10), + Container( + width: 32, + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.15), + border: Border.all( + color: Colors.white.withOpacity(0.2), + ), + borderRadius: BorderRadius.circular(10), + ), + child: filterIcon, + ) + ], + ), + ), + SizedBox(height: 8), + Expanded( + child: RawScrollbar( + thumbColor: Colors.white.withOpacity(0.15), + radius: Radius.circular(20), + isAlwaysShown: true, + thickness: 2, + controller: _scrollController, + child: ListView.separated( + padding: EdgeInsets.only(left: 2, right: 22), + controller: _scrollController, + itemCount: 20, + separatorBuilder: (_, __) => SizedBox(height: 4), + itemBuilder: (_, index) { + return CardItem( + logoUrl: '', + onTap: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage), + title: 'Amazon', + subTitle: 'Onlin', + hasDiscount: true, + ); + }, + ), + ), + ), + ], + ), + ); + } +} + +class _SearchWidget extends StatelessWidget { + const _SearchWidget({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final searchIcon = Padding( + padding: EdgeInsets.all(8), + child: Image.asset( + 'assets/images/search_icon.png', + color: Theme.of(context).textTheme.caption.decorationColor, + ), + ); + + return TextField( + style: TextStyle(color: Colors.white), + decoration: InputDecoration( + filled: true, + contentPadding: EdgeInsets.only( + top: 10, + left: 10, + ), + fillColor: Colors.white.withOpacity(0.15), + hintText: 'Search', + hintStyle: TextStyle( + color: Colors.white.withOpacity(0.6), + ), + alignLabelWithHint: true, + floatingLabelBehavior: FloatingLabelBehavior.never, + suffixIcon: searchIcon, + border: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white.withOpacity(0.2), + ), + borderRadius: BorderRadius.circular(10), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white.withOpacity(0.2), + ), + borderRadius: BorderRadius.circular(10), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white.withOpacity(0.2)), + borderRadius: BorderRadius.circular(10), + )), + ); + } +} + +class _TrailingIcon extends StatelessWidget { + final String asset; + final VoidCallback onPressed; + + const _TrailingIcon({this.asset, this.onPressed}); + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.centerRight, + width: 25, + child: FlatButton( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + padding: EdgeInsets.all(0), + onPressed: onPressed, + child: Image.asset( + asset, + color: Theme.of(context).accentTextTheme.display3.backgroundColor, + ), + ), + ); + } +} diff --git a/lib/src/screens/ionia/ionia.dart b/lib/src/screens/ionia/ionia.dart new file mode 100644 index 0000000000..bdc2065a9b --- /dev/null +++ b/lib/src/screens/ionia/ionia.dart @@ -0,0 +1,9 @@ +export 'auth/ionia_welcome_page.dart'; +export 'auth/ionia_create_account_page.dart'; +export 'auth/ionia_login_page.dart'; +export 'auth/ionia_verify_otp_page.dart'; +export 'cards/ionia_activate_debit_card_page.dart'; +export 'cards/ionia_buy_card_detail_page.dart'; +export 'cards/ionia_manage_cards_page.dart'; +export 'cards/ionia_debit_card_page.dart'; +export 'cards/ionia_buy_gift_card.dart'; diff --git a/lib/src/screens/ionia/widgets/card_item.dart b/lib/src/screens/ionia/widgets/card_item.dart new file mode 100644 index 0000000000..95bf9ee1ec --- /dev/null +++ b/lib/src/screens/ionia/widgets/card_item.dart @@ -0,0 +1,122 @@ +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/widgets/discount_badge.dart'; +import 'package:flutter/material.dart'; + +class CardItem extends StatelessWidget { + + CardItem({ + @required this.onTap, + @required this.title, + @required this.subTitle, + this.logoUrl, + this.hasDiscount = false, + }); + + final VoidCallback onTap; + final String title; + final String subTitle; + final String logoUrl; + final bool hasDiscount; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Stack( + children: [ + Container( + padding: EdgeInsets.all(12), + width: double.infinity, + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.white.withOpacity(0.20), + ), + ), + child: Row( + children: [ + if (logoUrl != null) ...[ + ClipOval( + child: Image.network( + logoUrl, + width: 42.0, + height: 42.0, + loadingBuilder: (BuildContext _, Widget child, ImageChunkEvent loadingProgress) { + if (loadingProgress == null) { + return child; + } else { + return _PlaceholderContainer(text: 'Logo'); + } + }, + errorBuilder: (_, __, ___) => _PlaceholderContainer(text: '!'), + ), + ), + SizedBox(width: 5), + ], + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + color: Palette.stateGray, + fontSize: 24, + fontWeight: FontWeight.w900, + ), + ), + SizedBox(height: 5), + Text( + subTitle, + style: TextStyle( + color: Palette.niagara , + fontWeight: FontWeight.w500, + fontFamily: 'Lato'), + ) + ], + ), + ], + ), + ), + if (hasDiscount) + Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.only(top: 20.0), + child: DiscountBadge(), + ), + ), + ], + ), + ); + } +} + +class _PlaceholderContainer extends StatelessWidget { + + const _PlaceholderContainer({@required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Container( + height: 42, + width: 42, + child: Center( + child: Text( + text, + style: TextStyle( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w900, + ), + ), + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(100), + ), + ); + } +} diff --git a/lib/src/screens/ionia/widgets/card_menu.dart b/lib/src/screens/ionia/widgets/card_menu.dart new file mode 100644 index 0000000000..9212c0448b --- /dev/null +++ b/lib/src/screens/ionia/widgets/card_menu.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +class CardMenu extends StatelessWidget { + + @override + Widget build(BuildContext context) { + return Container( + + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/ionia/widgets/confirm_modal.dart b/lib/src/screens/ionia/widgets/confirm_modal.dart new file mode 100644 index 0000000000..dd513ffa73 --- /dev/null +++ b/lib/src/screens/ionia/widgets/confirm_modal.dart @@ -0,0 +1,146 @@ +import 'dart:ui'; + +import 'package:cake_wallet/palette.dart'; +import 'package:flutter/material.dart'; + +class IoniaConfirmModal extends StatelessWidget { + IoniaConfirmModal({ + @required this.alertTitle, + @required this.alertContent, + @required this.leftButtonText, + @required this.rightButtonText, + @required this.actionLeftButton, + @required this.actionRightButton, + this.leftActionColor, + this.rightActionColor, + }); + + final String alertTitle; + final Widget alertContent; + final String leftButtonText; + final String rightButtonText; + final VoidCallback actionLeftButton; + final VoidCallback actionRightButton; + final Color leftActionColor; + final Color rightActionColor; + + Widget actionButtons(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.max, + children: [ + IoniaActionButton( + buttonText: leftButtonText, + action: actionLeftButton, + backgoundColor: leftActionColor, + ), + Container( + width: 1, + height: 52, + color: Theme.of(context).dividerColor, + ), + IoniaActionButton( + buttonText: rightButtonText, + action: actionRightButton, + backgoundColor: rightActionColor, + ), + ], + ); + } + + Widget title(BuildContext context) { + return Text( + alertTitle, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).primaryTextTheme.title.color, + decoration: TextDecoration.none, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.transparent, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), + child: Container( + decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)), + child: Center( + child: GestureDetector( + onTap: () => null, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(30)), + child: Container( + width: 327, + color: Theme.of(context).accentTextTheme.title.decorationColor, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(24, 20, 24, 0), + child: title(context), + ), + Padding( + padding: EdgeInsets.only(top: 16, bottom: 8), + child: Container( + height: 1, + color: Theme.of(context).dividerColor, + ), + ), + alertContent, + actionButtons(context), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } +} + +class IoniaActionButton extends StatelessWidget { + const IoniaActionButton({ + @required this.buttonText, + @required this.action, + this.backgoundColor, + }); + + final String buttonText; + final VoidCallback action; + final Color backgoundColor; + + @override + Widget build(BuildContext context) { + return Flexible( + child: Container( + height: 52, + padding: EdgeInsets.only(left: 6, right: 6), + color: backgoundColor, + child: ButtonTheme( + minWidth: double.infinity, + child: FlatButton( + onPressed: action, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + child: Text( + buttonText, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: backgoundColor != null ? Colors.white : Theme.of(context).primaryTextTheme.body1.backgroundColor, + decoration: TextDecoration.none, + ), + )), + ), + )); + } +} diff --git a/lib/src/screens/ionia/widgets/text_icon_button.dart b/lib/src/screens/ionia/widgets/text_icon_button.dart new file mode 100644 index 0000000000..16606e65d3 --- /dev/null +++ b/lib/src/screens/ionia/widgets/text_icon_button.dart @@ -0,0 +1,35 @@ +import 'package:cake_wallet/typography.dart'; +import 'package:flutter/material.dart'; + +class TextIconButton extends StatelessWidget { + final String label; + final VoidCallback onTap; + const TextIconButton({ + Key key, + this.label, + this.onTap, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return + InkWell( + onTap: onTap, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: textMediumSemiBold( + color: Theme.of(context).primaryTextTheme.title.color, + ), + ), + Icon( + Icons.chevron_right_rounded, + color: Theme.of(context).primaryTextTheme.title.color, + ), + ], + ), + ); + } +} diff --git a/lib/src/widgets/alert_with_two_actions.dart b/lib/src/widgets/alert_with_two_actions.dart index c2831675ee..f3c66f275e 100644 --- a/lib/src/widgets/alert_with_two_actions.dart +++ b/lib/src/widgets/alert_with_two_actions.dart @@ -10,7 +10,10 @@ class AlertWithTwoActions extends BaseAlertDialog { @required this.rightButtonText, @required this.actionLeftButton, @required this.actionRightButton, - this.alertBarrierDismissible = true + this.alertBarrierDismissible = true, + this.isDividerExist = false, + this.leftActionColor, + this.rightActionColor, }); final String alertTitle; @@ -20,6 +23,9 @@ class AlertWithTwoActions extends BaseAlertDialog { final VoidCallback actionLeftButton; final VoidCallback actionRightButton; final bool alertBarrierDismissible; + final Color leftActionColor; + final Color rightActionColor; + final bool isDividerExist; @override String get titleText => alertTitle; @@ -35,4 +41,10 @@ class AlertWithTwoActions extends BaseAlertDialog { VoidCallback get actionRight => actionRightButton; @override bool get barrierDismissible => alertBarrierDismissible; -} \ No newline at end of file + @override + Color get leftButtonColor => leftActionColor; + @override + Color get rightButtonColor => rightActionColor; + @override + bool get isDividerExists => isDividerExist; +} diff --git a/lib/src/widgets/base_alert_dialog.dart b/lib/src/widgets/base_alert_dialog.dart index 19ff62ce2b..d0aaace2f4 100644 --- a/lib/src/widgets/base_alert_dialog.dart +++ b/lib/src/widgets/base_alert_dialog.dart @@ -46,30 +46,28 @@ class BaseAlertDialog extends StatelessWidget { children: [ Flexible( child: Container( - height: 52, - padding: EdgeInsets.only(left: 6, right: 6), - color: Theme.of(context).accentTextTheme.body2.decorationColor, - child: ButtonTheme( - minWidth: double.infinity, - child: FlatButton( - onPressed: actionLeft, - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - child: Text( - leftActionButtonText, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context).primaryTextTheme.body2 - .backgroundColor, - decoration: TextDecoration.none, - ), - )), - ), - ) - ), + height: 52, + padding: EdgeInsets.only(left: 6, right: 6), + color: Theme.of(context).accentTextTheme.body2.decorationColor, + child: ButtonTheme( + minWidth: double.infinity, + child: FlatButton( + onPressed: actionLeft, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + child: Text( + leftActionButtonText, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).primaryTextTheme.body2.backgroundColor, + decoration: TextDecoration.none, + ), + )), + ), + )), Container( width: 1, height: 52, @@ -77,30 +75,28 @@ class BaseAlertDialog extends StatelessWidget { ), Flexible( child: Container( - height: 52, - padding: EdgeInsets.only(left: 6, right: 6), - color: Theme.of(context).accentTextTheme.body1.backgroundColor, - child: ButtonTheme( - minWidth: double.infinity, - child: FlatButton( - onPressed: actionRight, - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - child: Text( - rightActionButtonText, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context).primaryTextTheme.body1 - .backgroundColor, - decoration: TextDecoration.none, - ), - )), - ), - ) - ), + height: 52, + padding: EdgeInsets.only(left: 6, right: 6), + color: Theme.of(context).accentTextTheme.body1.backgroundColor, + child: ButtonTheme( + minWidth: double.infinity, + child: FlatButton( + onPressed: actionRight, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + child: Text( + rightActionButtonText, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).primaryTextTheme.body1.backgroundColor, + decoration: TextDecoration.none, + ), + )), + ), + )), ], ); } @@ -108,9 +104,7 @@ class BaseAlertDialog extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onTap: () => barrierDismissible - ? Navigator.of(context).pop() - : null, + onTap: () => barrierDismissible ? Navigator.of(context).pop() : null, child: Container( color: Colors.transparent, child: BackdropFilter( @@ -136,14 +130,14 @@ class BaseAlertDialog extends StatelessWidget { child: title(context), ), isDividerExists - ? Padding( - padding: EdgeInsets.only(top: 16, bottom: 8), - child: Container( - height: 1, - color: Theme.of(context).dividerColor, - ), - ) - : Offstage(), + ? Padding( + padding: EdgeInsets.only(top: 16, bottom: 8), + child: Container( + height: 1, + color: Theme.of(context).dividerColor, + ), + ) + : Offstage(), Padding( padding: EdgeInsets.fromLTRB(24, 8, 24, 32), child: content(context), @@ -166,4 +160,4 @@ class BaseAlertDialog extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/src/widgets/discount_badge.dart b/lib/src/widgets/discount_badge.dart new file mode 100644 index 0000000000..ad9c4e2ceb --- /dev/null +++ b/lib/src/widgets/discount_badge.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + + +class DiscountBadge extends StatelessWidget { + const DiscountBadge({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.centerRight, + children: [ + Image.asset('assets/images/badge_discount.png'), + Padding( + padding: const EdgeInsets.only(right: 10.0), + child: Text( + S.of(context).discount('20'), + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500, + fontFamily: 'Lato', + ), + ), + ) + ], + ); + } +} diff --git a/lib/src/widgets/market_place_item.dart b/lib/src/widgets/market_place_item.dart new file mode 100644 index 0000000000..0112ea00db --- /dev/null +++ b/lib/src/widgets/market_place_item.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; + +class MarketPlaceItem extends StatelessWidget { + + + MarketPlaceItem({ + @required this.onTap, + @required this.title, + @required this.subTitle, + }); + + final VoidCallback onTap; + final String title; + final String subTitle; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Stack( + children: [ + Container( + padding: EdgeInsets.all(20), + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).textTheme.title.backgroundColor, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.white.withOpacity(0.20), + ), + ), + child: + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + color: Theme.of(context) + .accentTextTheme + .display3 + .backgroundColor, + fontSize: 24, + fontWeight: FontWeight.w900, + ), + ), + SizedBox(height: 5), + Text( + subTitle, + style: TextStyle( + color: Theme.of(context) + .accentTextTheme + .display3 + .backgroundColor, + fontWeight: FontWeight.w500, + fontFamily: 'Lato'), + ) + ], + ), + ), + ], + ), + ); + } +} + diff --git a/lib/typography.dart b/lib/typography.dart new file mode 100644 index 0000000000..08491e2cbb --- /dev/null +++ b/lib/typography.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +const latoFont = "Lato"; + +TextStyle textXxSmall({Color color}) => _cakeRegular(10, color); + +TextStyle textXxSmallSemiBold({Color color}) => _cakeSemiBold(10, color); + +TextStyle textXSmall({Color color}) => _cakeRegular(12, color); + +TextStyle textXSmallSemiBold({Color color}) => _cakeSemiBold(12, color); + +TextStyle textSmall({Color color}) => _cakeRegular(14, color); + +TextStyle textSmallSemiBold({Color color}) => _cakeSemiBold(14, color); + +TextStyle textMedium({Color color}) => _cakeRegular(16, color); + +TextStyle textMediumBold({Color color}) => _cakeBold(16, color); + +TextStyle textMediumSemiBold({Color color}) => _cakeSemiBold(22, color); + +TextStyle textLarge({Color color}) => _cakeRegular(18, color); + +TextStyle textLargeSemiBold({Color color}) => _cakeSemiBold(24, color); + +TextStyle textXLarge({Color color}) => _cakeRegular(32, color); + +TextStyle textXLargeSemiBold({Color color}) => _cakeSemiBold(32, color); + +TextStyle _cakeRegular(double size, Color color) => _textStyle( + size: size, + fontWeight: FontWeight.normal, + color: color, + ); + +TextStyle _cakeBold(double size, Color color) => _textStyle( + size: size, + fontWeight: FontWeight.w900, + color: color, + ); + +TextStyle _cakeSemiBold(double size, Color color) => _textStyle( + size: size, + fontWeight: FontWeight.w700, + color: color, + ); + +TextStyle _textStyle({ + @required double size, + @required FontWeight fontWeight, + Color color, +}) => + TextStyle( + fontFamily: latoFont, + fontSize: size, + fontWeight: fontWeight, + color: color ?? Colors.white, + ); diff --git a/lib/view_model/ionia/ionia_view_model.dart b/lib/view_model/ionia/ionia_view_model.dart new file mode 100644 index 0000000000..6aa324db3e --- /dev/null +++ b/lib/view_model/ionia/ionia_view_model.dart @@ -0,0 +1,91 @@ +import 'package:cake_wallet/ionia/ionia.dart'; +import 'package:cake_wallet/ionia/ionia_create_state.dart'; +import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; +import 'package:mobx/mobx.dart'; +part 'ionia_view_model.g.dart'; + +class IoniaViewModel = IoniaViewModelBase with _$IoniaViewModel; + +abstract class IoniaViewModelBase with Store { + IoniaViewModelBase({this.ioniaService}) + : createUserState = IoniaCreateStateSuccess(), + otpState = IoniaOtpSendDisabled(), + cardState = IoniaNoCardState() { + _getCard(); + _getAuthStatus().then((value) => isLoggedIn = value); + } + + final IoniaService ioniaService; + + @observable + IoniaCreateAccountState createUserState; + + @observable + IoniaOtpState otpState; + + @observable + IoniaCreateCardState createCardState; + + @observable + IoniaFetchCardState cardState; + + @observable + String email; + + @observable + String otp; + + @observable + bool isLoggedIn; + + @action + Future createUser(String email) async { + createUserState = IoniaCreateStateLoading(); + try { + await ioniaService.createUser(email); + + createUserState = IoniaCreateStateSuccess(); + } on Exception catch (e) { + createUserState = IoniaCreateStateFailure(error: e.toString()); + } + } + + @action + Future verifyEmail(String code) async { + try { + otpState = IoniaOtpValidating(); + await ioniaService.verifyEmail(code); + otpState = IoniaOtpSuccess(); + } catch (_) { + otpState = IoniaOtpFailure(error: 'Invalid OTP. Try again'); + } + } + + Future _getAuthStatus() async { + return await ioniaService.isLogined(); + } + + @action + Future createCard() async { + createCardState = IoniaCreateCardLoading(); + try { + final card = await ioniaService.createCard(); + createCardState = IoniaCreateCardSuccess(); + return card; + } on Exception catch (e) { + createCardState = IoniaCreateCardFailure(error: e.toString()); + } + return null; + } + + Future _getCard() async { + cardState = IoniaFetchingCard(); + try { + final card = await ioniaService.getCard(); + + cardState = IoniaCardSuccess(card: card); + } catch (_) { + cardState = IoniaFetchCardFailure(); + } + } +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index dfe3b84f37..7f85e3eb49 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -530,5 +530,69 @@ "learn_more" : "Learn More", "search": "Search", "new_template" : "New Template", - "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work" + "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work", + "market_place": "Market place", + "cake_pay_title": "Gift cards and debit cards", + "cake_pay_subtitle": "Buy gift cards and top up no-KYC debit cards", + "about_cake_pay": "CakePay allows you to easily buy gift cards and load up prepaid debit cards with cryptocurrencies, spendable at millions of merchants in the United States.", + "cake_pay_account_note": "Make an account to see the available cards. Some are even available at a discount!", + "already_have_account": "Already have an account?", + "create_account": "Create Account", + "privacy_policy": "Privacy policy", + "welcome_to_cakepay": "Welcome to CakePay!", + "sign_up": "Sign Up", + "forgot_password": "Forgot Password", + "reset_password": "Reset Password", + "manage_cards": "Manage Cards", + "setup_your_debit_card": "Set up your debit card", + "no_id_required": "No ID required. Top up and spend anywhere", + "how_to_use_card": "How to use this card", + "purchase_gift_card": "Purchase Gift Card", + "verification": "Verification", + "fill_code": "Please fill in the verification code provided to your email", + "dont_get_code": "Don't get code", + "resend_code": "Please resend it", + "debit_card": "Debit Card", + "cakepay_prepaid_card": "CakePay Prepaid Debit Card", + "no_id_needed": "No ID needed!", + "frequently_asked_questions": "Frequently asked questions", + "debit_card_terms": "The storage and usage of your payment card number (and credentials corresponding to your payment card number) in this digital wallet are subject to the Terms and Conditions of the applicable cardholder agreement with the payment card issuer, as in effect from time to time.", + "please_reference_document": "Please reference the documents below for more information.", + "cardholder_agreement": "Cardholder Agreement", + "e_sign_consent": "E-Sign Consent", + "agree_and_continue": "Agree & Continue", + "email_address": "Email Address", + "agree_to": "By creating account you agree to the ", + "and": "and", + "enter_code": "Enter code", + "congratulations": "Congratulations!", + "you_now_have_debit_card": "You now have a debit card", + "min_amount" : "Min: ${value}", + "max_amount" : "Max: ${value}", + "enter_amount": "Enter Amount", + "billing_address_info": "If asked for a billing address, provide your shipping address", + "order_physical_card": "Order Physical Card", + "add_value": "Add value", + "activate": "Activate", + "get_a": "Get a ", + "digital_and_physical_card": " digital and physical prepaid debit card", + "get_card_note": " that you can reload with digital currencies. No additional information needed!", + "signup_for_card_accept_terms": "Sign up for the card and accept the terms.", + "add_fund_to_card": "Add prepaid funds to the cards (up to ${value})", + "use_card_info_two": "Funds are converted to USD when the held in the prepaid account, not in digital currencies.", + "use_card_info_three": "Use the digital card online or with contactless payment methods.", + "optionally_order_card": "Optionally order a physical card.", + "hide_details" : "Hide Details", + "show_details" : "Show Details", + "upto": "up to ${value}", + "discount": "Save ${value}%", + "gift_card_amount": "Gift Card Amount", + "bill_amount": "Bill amount", + "you_pay": "You pay", + "tip": "Tip:", + "custom": "custom", + "by_cake_pay": "by CakePay", + "expires": "Expires", + "mm": "MM", + "yy": "YY" } From 3753b16750c9d110cb2fd300e69492915ed4d0e1 Mon Sep 17 00:00:00 2001 From: M Date: Thu, 16 Jun 2022 11:08:39 +0100 Subject: [PATCH 03/55] Add ionia merchant sevic --- lib/di.dart | 6 + lib/ionia/ionia_merchant.dart | 170 ++++++++++++++++++++++++++ lib/ionia/ionia_merchant_service.dart | 51 ++++++++ lib/ionia/ionia_token_data.dart | 43 +++++++ lib/ionia/ionia_token_service.dart | 72 +++++++++++ 5 files changed, 342 insertions(+) create mode 100644 lib/ionia/ionia_merchant.dart create mode 100644 lib/ionia/ionia_merchant_service.dart create mode 100644 lib/ionia/ionia_token_data.dart create mode 100644 lib/ionia/ionia_token_service.dart diff --git a/lib/di.dart b/lib/di.dart index 45760f1dbe..5f4363c334 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -125,6 +125,8 @@ import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; +import 'package:cake_wallet/ionia/ionia_merchant_service.dart'; +import 'package:cake_wallet/ionia/ionia_token_service.dart'; final getIt = GetIt.instance; @@ -218,6 +220,10 @@ Future setup( secureStorage: getIt.get(), sharedPreferences: getIt.get())); + getIt.registerFactory(() => IoniaTokenService(getIt.get())); + + getIt.registerFactory(() => IoniaMerchantService(getIt.get(), isDevEnv: true)); + getIt.registerFactoryParam((type, _) => WalletNewVM(getIt.get(), getIt.get(param1: type), _walletInfoSource, diff --git a/lib/ionia/ionia_merchant.dart b/lib/ionia/ionia_merchant.dart new file mode 100644 index 0000000000..38758dc0e6 --- /dev/null +++ b/lib/ionia/ionia_merchant.dart @@ -0,0 +1,170 @@ +import 'package:flutter/foundation.dart'; + +class IoniaMerchant { + IoniaMerchant({ + @required this.id, + @required this.legalName, + @required this.systemName, + @required this.description, + @required this.website, + @required this.termsAndConditions, + @required this.logoUrl, + @required this.cardImageUrl, + @required this.cardholderAgreement, + @required this.purchaseFee, + @required this.revenueShare, + @required this.marketingFee, + @required this.minimumDiscount, + @required this.level1, + @required this.level2, + @required this.level3, + @required this.level4, + @required this.level5, + @required this.level6, + @required this.level7, + @required this.isActive, + @required this.isDeleted, + @required this.isOnline, + @required this.isPhysical, + @required this.isVariablePurchase, + @required this.minimumCardPurchase, + @required this.maximumCardPurchase, + @required this.acceptsTips, + @required this.createdDateFormatted, + @required this.createdBy, + @required this.isRegional, + @required this.modifiedDateFormatted, + @required this.modifiedBy, + @required this.usageInstructions, + @required this.usageInstructionsBak, + @required this.paymentGatewayId, + @required this.giftCardGatewayId, + @required this.isHtmlDescription, + @required this.purchaseInstructions, + @required this.balanceInstructions, + @required this.amountPerCard, + @required this.processingMessage, + @required this.hasBarcode, + @required this.hasInventory, + @required this.isVoidable, + @required this.receiptMessage, + @required this.cssBorderCode, + @required this.paymentInstructions, + @required this.alderSku, + @required this.ngcSku, + @required this.acceptedCurrency, + @required this.deepLink, + @required this.isPayLater + }); + + factory IoniaMerchant.fromJsonMap(Map element) { + return IoniaMerchant( + id: element["Id"] as int, + legalName: element["LegalName"] as String, + systemName: element["SystemName"] as String, + description: element["Description"] as String, + website: element["Website"] as String, + termsAndConditions: element["TermsAndConditions"] as String, + logoUrl: element["LogoUrl"] as String, + cardImageUrl: element["CardImageUrl"] as String, + cardholderAgreement: element["CardholderAgreement"] as String, + purchaseFee: element["PurchaseFee"] as double, + revenueShare: element["RevenueShare"] as double, + marketingFee: element["MarketingFee"] as double, + minimumDiscount: element["MinimumDiscount"] as double, + level1: element["Level1"] as double, + level2: element["Level2"] as double, + level3: element["Level3"] as double, + level4: element["Level4"] as double, + level5: element["Level5"] as double, + level6: element["Level6"] as double, + level7: element["Level7"] as double, + isActive: element["IsActive"] as bool, + isDeleted: element["IsDeleted"] as bool, + isOnline: element["IsOnline"] as bool, + isPhysical: element["IsPhysical"] as bool, + isVariablePurchase: element["IsVariablePurchase"] as bool, + minimumCardPurchase: element["MinimumCardPurchase"] as double, + maximumCardPurchase: element["MaximumCardPurchase"] as double, + acceptsTips: element["AcceptsTips"] as bool, + createdDateFormatted: element["CreatedDate"] as String, + createdBy: element["CreatedBy"] as int, + isRegional: element["IsRegional"] as bool, + modifiedDateFormatted: element["ModifiedDate"] as String, + modifiedBy: element["ModifiedBy"] as int, + usageInstructions: element["UsageInstructions"] as String, + usageInstructionsBak: element["UsageInstructionsBak"] as String, + paymentGatewayId: element["PaymentGatewayId"] as int, + giftCardGatewayId: element["GiftCardGatewayId"] as int , + isHtmlDescription: element["IsHtmlDescription"] as bool, + purchaseInstructions: element["PurchaseInstructions"] as String, + balanceInstructions: element["BalanceInstructions"] as String, + amountPerCard: element["AmountPerCard"] as double, + processingMessage: element["ProcessingMessage"] as String, + hasBarcode: element["HasBarcode"] as bool, + hasInventory: element["HasInventory"] as bool, + isVoidable: element["IsVoidable"] as bool, + receiptMessage: element["ReceiptMessage"] as String, + cssBorderCode: element["CssBorderCode"] as String, + paymentInstructions: element["PaymentInstructions"] as String, + alderSku: element["AlderSku"] as String, + ngcSku: element["NgcSku"] as String, + acceptedCurrency: element["AcceptedCurrency"] as String, + deepLink: element["DeepLink"] as String, + isPayLater: element["IsPayLater"] as bool); + } + + final int id; + final String legalName; + final String systemName; + final String description; + final String website; + final String termsAndConditions; + final String logoUrl; + final String cardImageUrl; + final String cardholderAgreement; + final double purchaseFee; + final double revenueShare; + final double marketingFee; + final double minimumDiscount; + final double level1; + final double level2; + final double level3; + final double level4; + final double level5; + final double level6; + final double level7; + final bool isActive; + final bool isDeleted; + final bool isOnline; + final bool isPhysical; + final bool isVariablePurchase; + final double minimumCardPurchase; + final double maximumCardPurchase; + final bool acceptsTips; + final String createdDateFormatted; + final int createdBy; + final bool isRegional; + final String modifiedDateFormatted; + final int modifiedBy; + final String usageInstructions; + final String usageInstructionsBak; + final int paymentGatewayId; + final int giftCardGatewayId; + final bool isHtmlDescription; + final String purchaseInstructions; + final String balanceInstructions; + final double amountPerCard; + final String processingMessage; + final bool hasBarcode; + final bool hasInventory; + final bool isVoidable; + final String receiptMessage; + final String cssBorderCode; + final String paymentInstructions; + final String alderSku; + final String ngcSku; + final String acceptedCurrency; + final String deepLink; + final bool isPayLater; +} diff --git a/lib/ionia/ionia_merchant_service.dart b/lib/ionia/ionia_merchant_service.dart new file mode 100644 index 0000000000..75bb4633d2 --- /dev/null +++ b/lib/ionia/ionia_merchant_service.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; +import 'package:http/http.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/ionia/ionia_merchant.dart'; +import 'package:cake_wallet/ionia/ionia_token_service.dart'; + +class IoniaMerchantService { + IoniaMerchantService(this._tokenService, {@required this.isDevEnv}); + + static String devApiUrl = "https://apidev.dashdirect.org/partner"; + + final bool isDevEnv; + + final TokenService _tokenService; + + String get apiUrl => isDevEnv ? devApiUrl : ''; + + String get getMerchantsUrl => '$apiUrl/GetMerchants'; + + Future> getMerchants() async { + final token = await _tokenService.getToken(); + // FIX ME: remove hardcoded values + final headers = { + 'Authorization': token.toString(), + 'firstName': 'cake', + 'lastName': 'cake', + 'email': 'cake@test'}; + final response = await post(getMerchantsUrl, headers: headers); + + if (response.statusCode != 200) { + return []; + } + + final decodedBody = json.decode(response.body) as Map; + final isSuccessful = decodedBody['Successful'] as bool ?? false; + + if (!isSuccessful) { + return []; + } + + final data = decodedBody['Data'] as List; + return data.map((dynamic e) { + final element = e as Map; + return IoniaMerchant.fromJsonMap(element); + }).toList(); + } + + Future purchaseGiftCard() async { + + } +} \ No newline at end of file diff --git a/lib/ionia/ionia_token_data.dart b/lib/ionia/ionia_token_data.dart new file mode 100644 index 0000000000..1baa4c63da --- /dev/null +++ b/lib/ionia/ionia_token_data.dart @@ -0,0 +1,43 @@ +import 'package:flutter/foundation.dart'; +import 'dart:convert'; + +class IoniaTokenData { + IoniaTokenData({@required this.accessToken, @required this.tokenType, @required this.expiredAt}); + + factory IoniaTokenData.fromJson(String source) { + final decoded = json.decode(source) as Map; + final accessToken = decoded['access_token'] as String; + final expiresIn = decoded['expires_in'] as int; + final tokenType = decoded['token_type'] as String; + final expiredAtInMilliseconds = decoded['expired_at'] as int; + DateTime expiredAt; + + if (expiredAtInMilliseconds != null) { + expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtInMilliseconds); + } else { + expiredAt = DateTime.now().add(Duration(seconds: expiresIn)); + } + + return IoniaTokenData( + accessToken: accessToken, + tokenType: tokenType, + expiredAt: expiredAt); + } + + final String accessToken; + final String tokenType; + final DateTime expiredAt; + + bool get isExpired => DateTime.now().isAfter(expiredAt); + + @override + String toString() => '$tokenType $accessToken'; + + String toJson() { + return json.encode({ + 'access_token': accessToken, + 'token_type': tokenType, + 'expired_at': expiredAt.millisecondsSinceEpoch + }); + } +} \ No newline at end of file diff --git a/lib/ionia/ionia_token_service.dart b/lib/ionia/ionia_token_service.dart new file mode 100644 index 0000000000..1915959956 --- /dev/null +++ b/lib/ionia/ionia_token_service.dart @@ -0,0 +1,72 @@ +import 'dart:convert'; +import 'package:http/http.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:cake_wallet/ionia/ionia_token_data.dart'; +import 'package:cake_wallet/.secrets.g.dart'; + +String basicAuth(String username, String password) => + 'Basic ' + base64Encode(utf8.encode('$username:$password')); + +abstract class TokenService { + TokenService(this.flutterSecureStorage); + + String get serviceName; + String get oauthUrl; + final FlutterSecureStorage flutterSecureStorage; + + String get _storeKey => '${serviceName}_oauth_token'; + + Future getToken() async { + final storedTokenJson = await flutterSecureStorage.read(key: _storeKey); + IoniaTokenData token; + + if (storedTokenJson != null) { + token = IoniaTokenData.fromJson(storedTokenJson); + } else { + token = await _fetchNewToken(); + await _storeToken(token); + } + + if (token.isExpired) { + token = await _fetchNewToken(); + await _storeToken(token); + } + + return token; + } + + Future _fetchNewToken() async { + final basic = basicAuth(ioniaClientId, ioniaClientSecret); + final body = {'grant_type': 'client_credentials', 'scope': 'cake_dev'}; + final response = await post( + oauthUrl, + headers: { + 'Authorization': basic, + 'Content-Type': 'application/x-www-form-urlencoded'}, + encoding: Encoding.getByName('utf-8'), + body: body); + + if (response.statusCode != 200) { + // throw exception + return null; + } + + return IoniaTokenData.fromJson(response.body); + } + + Future _storeToken(IoniaTokenData token) async { + await flutterSecureStorage.write(key: _storeKey, value: token.toJson()); + } + +} + +class IoniaTokenService extends TokenService { + IoniaTokenService(FlutterSecureStorage flutterSecureStorage) + : super(flutterSecureStorage); + + @override + String get serviceName => 'Ionia'; + + @override + String get oauthUrl => 'https://auth.craypay.com/connect/token'; +} \ No newline at end of file From 6c947cdb63cac08c78acb1c5658764680ef90076 Mon Sep 17 00:00:00 2001 From: M Date: Mon, 20 Jun 2022 20:27:28 +0100 Subject: [PATCH 04/55] Add anypay. Add purschase gift card. --- lib/anypay/any_pay_payment.dart | 38 ++++++++++ lib/anypay/any_pay_payment_instruction.dart | 30 ++++++++ .../any_pay_payment_instruction_output.dart | 10 +++ lib/anypay/anypay_api.dart | 48 +++++++++++++ lib/di.dart | 5 +- lib/ionia/ionia_api.dart | 69 +++++++++++++++++++ lib/ionia/ionia_merchant_service.dart | 51 -------------- lib/ionia/ionia_order.dart | 23 +++++++ lib/ionia/{ionia.dart => ionia_service.dart} | 28 ++++++++ lib/view_model/ionia/ionia_view_model.dart | 2 +- 10 files changed, 248 insertions(+), 56 deletions(-) create mode 100644 lib/anypay/any_pay_payment.dart create mode 100644 lib/anypay/any_pay_payment_instruction.dart create mode 100644 lib/anypay/any_pay_payment_instruction_output.dart create mode 100644 lib/anypay/anypay_api.dart delete mode 100644 lib/ionia/ionia_merchant_service.dart create mode 100644 lib/ionia/ionia_order.dart rename lib/ionia/{ionia.dart => ionia_service.dart} (69%) diff --git a/lib/anypay/any_pay_payment.dart b/lib/anypay/any_pay_payment.dart new file mode 100644 index 0000000000..4c8dff2d6d --- /dev/null +++ b/lib/anypay/any_pay_payment.dart @@ -0,0 +1,38 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/anypay/any_pay_payment_instruction.dart'; + +class AnyPayPayment { + AnyPayPayment({ + @required this.time, + @required this.expires, + @required this.memo, + @required this.paymentUrl, + @required this.paymentId, + @required this.chain, + @required this.network, + @required this.instructions}); + + factory AnyPayPayment.fromMap(Map obj) { + final instructions = (obj['instructions'] as List) + .map((dynamic instruction) => AnyPayPaymentInstruction.fromMap(instruction as Map)) + .toList(); + return AnyPayPayment( + time: DateTime.parse(obj['time'] as String), + expires: DateTime.parse(obj['expires'] as String), + memo: obj['memo'] as String, + paymentUrl: obj['paymentUrl'] as String, + paymentId: obj['paymentId'] as String, + chain: obj['chain'] as String, + network: obj['network'] as String, + instructions: instructions); + } + + final DateTime time; + final DateTime expires; + final String memo; + final String paymentUrl; + final String paymentId; + final String chain; + final String network; + final List instructions; +} \ No newline at end of file diff --git a/lib/anypay/any_pay_payment_instruction.dart b/lib/anypay/any_pay_payment_instruction.dart new file mode 100644 index 0000000000..d6ed855273 --- /dev/null +++ b/lib/anypay/any_pay_payment_instruction.dart @@ -0,0 +1,30 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/anypay/any_pay_payment_instruction_output.dart'; + +class AnyPayPaymentInstruction { + AnyPayPaymentInstruction({ + @required this.type, + @required this.requiredFeeRate, + @required this.txKey, + @required this.txHash, + @required this.outputs}); + + factory AnyPayPaymentInstruction.fromMap(Map obj) { + final outputs = (obj['outputs'] as List) + .map((dynamic out) => + AnyPayPaymentInstructionOutput.fromMap(out as Map)) + .toList(); + return AnyPayPaymentInstruction( + type: obj['type'] as String, + requiredFeeRate: obj['requiredFeeRate'] as int, + txKey: obj['tx_key'] as bool, + txHash: obj['tx_hash'] as bool, + outputs: outputs); + } + + final String type; + final int requiredFeeRate; + final bool txKey; + final bool txHash; + final List outputs; +} \ No newline at end of file diff --git a/lib/anypay/any_pay_payment_instruction_output.dart b/lib/anypay/any_pay_payment_instruction_output.dart new file mode 100644 index 0000000000..7fabea966b --- /dev/null +++ b/lib/anypay/any_pay_payment_instruction_output.dart @@ -0,0 +1,10 @@ +class AnyPayPaymentInstructionOutput { + const AnyPayPaymentInstructionOutput(this.address, this.amount); + + factory AnyPayPaymentInstructionOutput.fromMap(Map obj) { + return AnyPayPaymentInstructionOutput(obj['address'] as String, obj['amount'] as int); + } + + final String address; + final int amount; +} \ No newline at end of file diff --git a/lib/anypay/anypay_api.dart b/lib/anypay/anypay_api.dart new file mode 100644 index 0000000000..e7583b0833 --- /dev/null +++ b/lib/anypay/anypay_api.dart @@ -0,0 +1,48 @@ +import 'dart:convert'; +import 'package:http/http.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/anypay/any_pay_payment.dart'; + +class AnyPayApi { + static const contentTypePaymentRequest = 'application/payment-request'; + static const xPayproVersion = '2'; + + static String chainByScheme(String scheme) { + switch (scheme.toLowerCase()) { + case 'monero': + return CryptoCurrency.xmr.title; + default: + return ''; + } + } + + static CryptoCurrency currencyByScheme(String scheme) { + switch (scheme.toLowerCase()) { + case 'monero': + return CryptoCurrency.xmr; + default: + return null; + } + } + + Future pay(String uri) async { + final fragments = uri.split(':?r='); + final scheme = fragments.first; + final url = fragments[1]; + final headers = { + 'Content-Type': contentTypePaymentRequest, + 'X-Paypro-Version': xPayproVersion, + 'Accept': '*/*',}; + final body = { + 'chain': chainByScheme(scheme), + 'currency': currencyByScheme(scheme).title}; + final response = await post(url, headers: headers, body: utf8.encode(json.encode(body))); + + if (response.statusCode != 200) { + return null; + } + + final decodedBody = json.decode(response.body) as Map; + return AnyPayPayment.fromMap(decodedBody); + } +} \ No newline at end of file diff --git a/lib/di.dart b/lib/di.dart index f6afdeb3a6..8805a0162e 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,7 +1,7 @@ import 'package:cake_wallet/core/yat_service.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/wake_lock.dart'; -import 'package:cake_wallet/ionia/ionia.dart'; +import 'package:cake_wallet/ionia/ionia_service.dart'; import 'package:cake_wallet/ionia/ionia_api.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; @@ -129,7 +129,6 @@ import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; -import 'package:cake_wallet/ionia/ionia_merchant_service.dart'; import 'package:cake_wallet/ionia/ionia_token_service.dart'; final getIt = GetIt.instance; @@ -225,8 +224,6 @@ Future setup( sharedPreferences: getIt.get())); getIt.registerFactory(() => IoniaTokenService(getIt.get())); - - getIt.registerFactory(() => IoniaMerchantService(getIt.get(), isDevEnv: true)); getIt.registerFactoryParam((type, _) => WalletNewVM(getIt.get(), diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart index 10169c7797..c1a90e6caf 100644 --- a/lib/ionia/ionia_api.dart +++ b/lib/ionia/ionia_api.dart @@ -1,4 +1,6 @@ import 'dart:convert'; +import 'package:cake_wallet/ionia/ionia_merchant.dart'; +import 'package:cake_wallet/ionia/ionia_order.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; import 'package:cake_wallet/ionia/ionia_user_credentials.dart'; @@ -11,6 +13,8 @@ class IoniaApi { static final verifyEmailUri = Uri.https(baseUri, '/$pathPrefix/VerifyEmail'); static final createCardUri = Uri.https(baseUri, '/$pathPrefix/CreateCard'); static final getCardsUri = Uri.https(baseUri, '/$pathPrefix/GetCards'); + static final getMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/GetMerchants'); + static final getPurchaseMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/PurchaseGiftCard'); // Create user @@ -122,4 +126,69 @@ class IoniaApi { return IoniaVirtualCard.fromMap(data); } + + // Get Merchants + + Future> getMerchants({ + @required String username, + @required String password, + @required String clientId}) async { + final headers = { + 'clientId': clientId, + 'username': username, + 'password': password}; + final response = await post(getMerchantsUrl, headers: headers); + + if (response.statusCode != 200) { + return []; + } + + final decodedBody = json.decode(response.body) as Map; + final isSuccessful = decodedBody['Successful'] as bool ?? false; + + if (!isSuccessful) { + return []; + } + + final data = decodedBody['Data'] as List; + return data.map((dynamic e) { + final element = e as Map; + return IoniaMerchant.fromJsonMap(element); + }).toList(); + } + + // Purchase Gift Card + + Future purchaseGiftCard({ + @required String merchId, + @required double amount, + @required String currency, + @required String username, + @required String password, + @required String clientId}) async { + final headers = { + 'clientId': clientId, + 'username': username, + 'password': password, + 'Content-Type': 'application/json'}; + final body = { + 'Amount': amount, + 'Currency': currency, + 'MerchantId': merchId}; + final response = await post(getPurchaseMerchantsUrl, headers: headers, body: json.encode(body)); + + if (response.statusCode != 200) { + return null; + } + + final decodedBody = json.decode(response.body) as Map; + final isSuccessful = decodedBody['Successful'] as bool ?? false; + + if (!isSuccessful) { + return null; + } + + final data = decodedBody['Data'] as Map; + return IoniaOrder.fromMap(data); + } } \ No newline at end of file diff --git a/lib/ionia/ionia_merchant_service.dart b/lib/ionia/ionia_merchant_service.dart deleted file mode 100644 index 75bb4633d2..0000000000 --- a/lib/ionia/ionia_merchant_service.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'dart:convert'; -import 'package:http/http.dart'; -import 'package:flutter/foundation.dart'; -import 'package:cake_wallet/ionia/ionia_merchant.dart'; -import 'package:cake_wallet/ionia/ionia_token_service.dart'; - -class IoniaMerchantService { - IoniaMerchantService(this._tokenService, {@required this.isDevEnv}); - - static String devApiUrl = "https://apidev.dashdirect.org/partner"; - - final bool isDevEnv; - - final TokenService _tokenService; - - String get apiUrl => isDevEnv ? devApiUrl : ''; - - String get getMerchantsUrl => '$apiUrl/GetMerchants'; - - Future> getMerchants() async { - final token = await _tokenService.getToken(); - // FIX ME: remove hardcoded values - final headers = { - 'Authorization': token.toString(), - 'firstName': 'cake', - 'lastName': 'cake', - 'email': 'cake@test'}; - final response = await post(getMerchantsUrl, headers: headers); - - if (response.statusCode != 200) { - return []; - } - - final decodedBody = json.decode(response.body) as Map; - final isSuccessful = decodedBody['Successful'] as bool ?? false; - - if (!isSuccessful) { - return []; - } - - final data = decodedBody['Data'] as List; - return data.map((dynamic e) { - final element = e as Map; - return IoniaMerchant.fromJsonMap(element); - }).toList(); - } - - Future purchaseGiftCard() async { - - } -} \ No newline at end of file diff --git a/lib/ionia/ionia_order.dart b/lib/ionia/ionia_order.dart new file mode 100644 index 0000000000..691327e43b --- /dev/null +++ b/lib/ionia/ionia_order.dart @@ -0,0 +1,23 @@ +import 'package:flutter/foundation.dart'; + +class IoniaOrder { + IoniaOrder({@required this.id, + @required this.uri, + @required this.currency, + @required this.amount, + @required this.paymentId}); + factory IoniaOrder.fromMap(Map obj) { + return IoniaOrder( + id: obj['order_id'] as String, + uri: obj['uri'] as String, + currency: obj['currency'] as String, + amount: obj['amount'] as double, + paymentId: obj['payment_id'] as String); + } + + final String id; + final String uri; + final String currency; + final double amount; + final String paymentId; +} \ No newline at end of file diff --git a/lib/ionia/ionia.dart b/lib/ionia/ionia_service.dart similarity index 69% rename from lib/ionia/ionia.dart rename to lib/ionia/ionia_service.dart index f55af0b17f..7f84ff8c81 100644 --- a/lib/ionia/ionia.dart +++ b/lib/ionia/ionia_service.dart @@ -1,4 +1,7 @@ +import 'package:cake_wallet/ionia/ionia_merchant.dart'; +import 'package:cake_wallet/ionia/ionia_order.dart'; import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/ionia/ionia_api.dart'; @@ -59,4 +62,29 @@ class IoniaService { final password = await secureStorage.read(key: ioniaPasswordStorageKey); return ioniaApi.getCards(username: username, password: password, clientId: clientId); } + + // Get Merchants + + Future> getMerchants() async { + final username = await secureStorage.read(key: ioniaUsernameStorageKey); + final password = await secureStorage.read(key: ioniaPasswordStorageKey); + return ioniaApi.getMerchants(username: username, password: password, clientId: clientId); + } + + // Purchase Gift Card + + Future purchaseGiftCard({ + @required String merchId, + @required double amount, + @required String currency}) async { + final username = await secureStorage.read(key: ioniaUsernameStorageKey); + final password = await secureStorage.read(key: ioniaPasswordStorageKey); + return ioniaApi.purchaseGiftCard( + merchId: merchId, + amount: amount, + currency: currency, + username: username, + password: password, + clientId: clientId); + } } \ No newline at end of file diff --git a/lib/view_model/ionia/ionia_view_model.dart b/lib/view_model/ionia/ionia_view_model.dart index 6aa324db3e..b07c424402 100644 --- a/lib/view_model/ionia/ionia_view_model.dart +++ b/lib/view_model/ionia/ionia_view_model.dart @@ -1,4 +1,4 @@ -import 'package:cake_wallet/ionia/ionia.dart'; +import 'package:cake_wallet/ionia/ionia_service.dart'; import 'package:cake_wallet/ionia/ionia_create_state.dart'; import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; import 'package:mobx/mobx.dart'; From 44efd56acb141452e810bb57a5a997a8d341b09c Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Tue, 21 Jun 2022 11:46:09 +0300 Subject: [PATCH 05/55] display virtual card (#385) * display virtual card * fix formatting --- lib/di.dart | 18 ++++-- lib/router.dart | 8 ++- .../ionia/auth/ionia_welcome_page.dart | 2 +- .../cards/ionia_buy_card_detail_page.dart | 11 +++- .../ionia/cards/ionia_buy_gift_card.dart | 28 +++++---- .../ionia/cards/ionia_manage_cards_page.dart | 62 ++++++++++++------- lib/src/screens/ionia/widgets/card_item.dart | 38 +++++++----- lib/src/widgets/discount_badge.dart | 5 +- lib/view_model/ionia/ionia_view_model.dart | 20 +++++- res/values/strings_en.arb | 4 +- 10 files changed, 133 insertions(+), 63 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index f6afdeb3a6..156fa61319 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/wake_lock.dart'; import 'package:cake_wallet/ionia/ionia.dart'; import 'package:cake_wallet/ionia/ionia_api.dart'; +import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/haven/haven.dart'; @@ -655,8 +656,9 @@ Future setup( getIt.registerFactory( () => IoniaService(getIt.get(), getIt.get())); - - getIt.registerFactory(() => IoniaViewModel(ioniaService: getIt.get())); + + getIt.registerFactory( + () => IoniaViewModel(ioniaService: getIt.get(), ioniaMerchantService: getIt.get())); getIt.registerFactory(() => IoniaCreateAccountPage(getIt.get())); @@ -671,9 +673,17 @@ Future setup( getIt.registerFactory(() => IoniaWelcomePage(getIt.get())); - getIt.registerFactory(() => IoniaBuyGiftCardPage()); + getIt.registerFactoryParam((List args, _) { + final merchant = args.first as IoniaMerchant; - getIt.registerFactory(() => IoniaBuyGiftCardDetailPage()); + return IoniaBuyGiftCardPage(merchant); + }); + + getIt.registerFactoryParam((List args, _) { + final merchant = args.first as IoniaMerchant; + + return IoniaBuyGiftCardDetailPage(merchant); + }); getIt.registerFactory(() => IoniaManageCardsPage(getIt.get())); diff --git a/lib/router.dart b/lib/router.dart index 930424019e..27ee246038 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -416,13 +416,15 @@ Route createRoute(RouteSettings settings) { return CupertinoPageRoute(builder: (_) => getIt.get()); case Routes.ioniaBuyGiftCardPage: - return CupertinoPageRoute(builder: (_) => getIt.get()); + final args = settings.arguments as List; + return CupertinoPageRoute(builder: (_) => getIt.get(param1: args)); case Routes.ioniaBuyGiftCardDetailPage: - return CupertinoPageRoute(builder: (_) => getIt.get()); + final args = settings.arguments as List; + return CupertinoPageRoute(builder: (_) => getIt.get(param1: args)); case Routes.ioniaVerifyIoniaOtpPage: - final args = settings.arguments as List; + final args = settings.arguments as List; return CupertinoPageRoute(builder: (_) =>getIt.get(param1: args)); case Routes.ioniaDebitCardPage: diff --git a/lib/src/screens/ionia/auth/ionia_welcome_page.dart b/lib/src/screens/ionia/auth/ionia_welcome_page.dart index 0c4abecabb..66e89899b0 100644 --- a/lib/src/screens/ionia/auth/ionia_welcome_page.dart +++ b/lib/src/screens/ionia/auth/ionia_welcome_page.dart @@ -30,7 +30,7 @@ class IoniaWelcomePage extends BasePage { Widget body(BuildContext context) { reaction((_) => _ioniaViewModel.isLoggedIn, (bool state) { if (state) { - Navigator.pushReplacementNamed(context, Routes.ioniaDebitCardPage); + Navigator.pushReplacementNamed(context, Routes.ioniaManageCardsPage); } }); return Padding( diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index 9f0fa17927..6d56ad362d 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -1,8 +1,8 @@ import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/confirm_modal.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; -import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/discount_badge.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; @@ -14,6 +14,11 @@ import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; class IoniaBuyGiftCardDetailPage extends StatelessWidget { + + const IoniaBuyGiftCardDetailPage(this.merchant); + + final IoniaMerchant merchant; + ThemeBase get currentTheme => getIt.get().currentTheme; Color get backgroundLightColor => Colors.white; @@ -52,7 +57,7 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { Widget middle(BuildContext context) { return Text( - 'AppleBees', + merchant.legalName, style: TextStyle( fontSize: 22, fontFamily: 'Lato', @@ -74,7 +79,7 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { SizedBox(height: 60), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [leading(context), middle(context), DiscountBadge()], + children: [leading(context), middle(context), DiscountBadge(percentage: merchant.minimumDiscount,)], ), SizedBox(height: 36), Container( diff --git a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart index 1677a9e77f..e46b9ea0a8 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; @@ -12,9 +13,12 @@ import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:cake_wallet/generated/i18n.dart'; class IoniaBuyGiftCardPage extends BasePage { - IoniaBuyGiftCardPage() + IoniaBuyGiftCardPage(this.merchant) : _amountFieldFocus = FocusNode(), _amountController = TextEditingController(); + + final IoniaMerchant merchant; + @override String get title => S.current.enter_amount; @@ -93,7 +97,7 @@ class IoniaBuyGiftCardPage extends BasePage { left: _width / 4, ), child: Text( - 'USD: ', + '${merchant.acceptedCurrency}: ', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w900, @@ -107,13 +111,13 @@ class IoniaBuyGiftCardPage extends BasePage { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - S.of(context).min_amount('5'), + S.of(context).min_amount(merchant.minimumCardPurchase.toString()), style: TextStyle( color: Theme.of(context).primaryTextTheme.headline.color, ), ), Text( - S.of(context).max_amount('20000'), + S.of(context).max_amount(merchant.maximumCardPurchase.toString()), style: TextStyle( color: Theme.of(context).primaryTextTheme.headline.color, ), @@ -127,11 +131,13 @@ class IoniaBuyGiftCardPage extends BasePage { Padding( padding: const EdgeInsets.all(24.0), child: CardItem( - onTap: () {}, - title: 'Applebee’s', - hasDiscount: true, - subTitle: 'subTitle', - logoUrl: '', + title: merchant.legalName, + backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), + discount: 0.0, + titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor, + subtitleColor: Theme.of(context).hintColor, + subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline, + logoUrl: merchant.logoUrl, ), ) ], @@ -141,10 +147,10 @@ class IoniaBuyGiftCardPage extends BasePage { Padding( padding: EdgeInsets.only(bottom: 12), child: PrimaryButton( - onPressed: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardDetailPage), + onPressed: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardDetailPage, arguments: [merchant] ), text: S.of(context).continue_text, color: Theme.of(context).accentTextTheme.body2.color, - textColor: Colors.white, + textColor: Theme.of(context).primaryTextTheme.body1.color, ), ), SizedBox(height: 30), diff --git a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart index 49276307d3..a2f8857749 100644 --- a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart @@ -8,6 +8,8 @@ import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; class IoniaManageCardsPage extends BasePage { IoniaManageCardsPage(this._ioniaViewModel); @@ -49,7 +51,7 @@ class IoniaManageCardsPage extends BasePage { Widget leading(BuildContext context) { final _backButton = Icon( Icons.arrow_back_ios, - color: titleColor ?? Theme.of(context).primaryTextTheme.title.color, + color: Theme.of(context).accentTextTheme.display3.backgroundColor, size: 16, ); @@ -141,27 +143,43 @@ class IoniaManageCardsPage extends BasePage { ), SizedBox(height: 8), Expanded( - child: RawScrollbar( - thumbColor: Colors.white.withOpacity(0.15), - radius: Radius.circular(20), - isAlwaysShown: true, - thickness: 2, - controller: _scrollController, - child: ListView.separated( - padding: EdgeInsets.only(left: 2, right: 22), - controller: _scrollController, - itemCount: 20, - separatorBuilder: (_, __) => SizedBox(height: 4), - itemBuilder: (_, index) { - return CardItem( - logoUrl: '', - onTap: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage), - title: 'Amazon', - subTitle: 'Onlin', - hasDiscount: true, - ); - }, - ), + child: Observer( + builder: (_) { + final merchantsList = _ioniaViewModel.ioniaMerchants; + return RawScrollbar( + thumbColor: Colors.white.withOpacity(0.15), + radius: Radius.circular(20), + isAlwaysShown: true, + thickness: 2, + controller: _scrollController, + child: ListView.separated( + padding: EdgeInsets.only(left: 2, right: 22), + controller: _scrollController, + itemCount: merchantsList.length, + separatorBuilder: (_, __) => SizedBox(height: 4), + itemBuilder: (_, index) { + final merchant = merchantsList[index]; + return CardItem( + logoUrl: merchant.logoUrl, + onTap: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, + arguments: [merchant]), + title: merchant.legalName, + subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline, + backgroundColor: Theme.of(context).textTheme.title.backgroundColor, + titleColor: Theme.of(context) + .accentTextTheme + .display3 + .backgroundColor, + subtitleColor: Theme.of(context) + .accentTextTheme + .display2 + .backgroundColor, + discount: merchant.minimumDiscount, + ); + }, + ), + ); + } ), ), ], diff --git a/lib/src/screens/ionia/widgets/card_item.dart b/lib/src/screens/ionia/widgets/card_item.dart index 95bf9ee1ec..38946da5a7 100644 --- a/lib/src/screens/ionia/widgets/card_item.dart +++ b/lib/src/screens/ionia/widgets/card_item.dart @@ -1,22 +1,28 @@ -import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/src/widgets/discount_badge.dart'; import 'package:flutter/material.dart'; class CardItem extends StatelessWidget { CardItem({ - @required this.onTap, @required this.title, @required this.subTitle, + @required this.backgroundColor, + @required this.titleColor, + @required this.subtitleColor, + this.onTap, this.logoUrl, - this.hasDiscount = false, + this.discount, + }); final VoidCallback onTap; final String title; final String subTitle; final String logoUrl; - final bool hasDiscount; + final double discount; + final Color backgroundColor; + final Color titleColor; + final Color subtitleColor; @override Widget build(BuildContext context) { @@ -28,7 +34,7 @@ class CardItem extends StatelessWidget { padding: EdgeInsets.all(12), width: double.infinity, decoration: BoxDecoration( - color: Colors.black.withOpacity(0.1), + color: backgroundColor, borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withOpacity(0.20), @@ -57,19 +63,23 @@ class CardItem extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - title, - style: TextStyle( - color: Palette.stateGray, - fontSize: 24, - fontWeight: FontWeight.w900, + SizedBox( + width: 200, + child: Text( + title, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: titleColor, + fontSize: 20, + fontWeight: FontWeight.w900, + ), ), ), SizedBox(height: 5), Text( subTitle, style: TextStyle( - color: Palette.niagara , + color: subtitleColor, fontWeight: FontWeight.w500, fontFamily: 'Lato'), ) @@ -78,12 +88,12 @@ class CardItem extends StatelessWidget { ], ), ), - if (hasDiscount) + if (discount != 0.0) Align( alignment: Alignment.topRight, child: Padding( padding: const EdgeInsets.only(top: 20.0), - child: DiscountBadge(), + child: DiscountBadge(percentage: discount), ), ), ], diff --git a/lib/src/widgets/discount_badge.dart b/lib/src/widgets/discount_badge.dart index ad9c4e2ceb..c3f9e21481 100644 --- a/lib/src/widgets/discount_badge.dart +++ b/lib/src/widgets/discount_badge.dart @@ -5,8 +5,11 @@ import 'package:cake_wallet/generated/i18n.dart'; class DiscountBadge extends StatelessWidget { const DiscountBadge({ Key key, + @required this.percentage, }) : super(key: key); + final double percentage; + @override Widget build(BuildContext context) { return Stack( @@ -16,7 +19,7 @@ class DiscountBadge extends StatelessWidget { Padding( padding: const EdgeInsets.only(right: 10.0), child: Text( - S.of(context).discount('20'), + S.of(context).discount(percentage.toString()), style: TextStyle( color: Colors.white, fontSize: 12, diff --git a/lib/view_model/ionia/ionia_view_model.dart b/lib/view_model/ionia/ionia_view_model.dart index 6aa324db3e..076bc594a5 100644 --- a/lib/view_model/ionia/ionia_view_model.dart +++ b/lib/view_model/ionia/ionia_view_model.dart @@ -1,5 +1,7 @@ import 'package:cake_wallet/ionia/ionia.dart'; import 'package:cake_wallet/ionia/ionia_create_state.dart'; +import 'package:cake_wallet/ionia/ionia_merchant.dart'; +import 'package:cake_wallet/ionia/ionia_merchant_service.dart'; import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; import 'package:mobx/mobx.dart'; part 'ionia_view_model.g.dart'; @@ -7,14 +9,19 @@ part 'ionia_view_model.g.dart'; class IoniaViewModel = IoniaViewModelBase with _$IoniaViewModel; abstract class IoniaViewModelBase with Store { - IoniaViewModelBase({this.ioniaService}) + + IoniaViewModelBase({this.ioniaService, this.ioniaMerchantService}) : createUserState = IoniaCreateStateSuccess(), otpState = IoniaOtpSendDisabled(), - cardState = IoniaNoCardState() { - _getCard(); + cardState = IoniaNoCardState(), ioniaMerchants = [] { + _getMerchants().then((value){ + ioniaMerchants = value; + }); _getAuthStatus().then((value) => isLoggedIn = value); } + final IoniaMerchantService ioniaMerchantService; + final IoniaService ioniaService; @observable @@ -29,6 +36,9 @@ abstract class IoniaViewModelBase with Store { @observable IoniaFetchCardState cardState; + @observable + List ioniaMerchants; + @observable String email; @@ -88,4 +98,8 @@ abstract class IoniaViewModelBase with Store { cardState = IoniaFetchCardFailure(); } } + + Future> _getMerchants()async{ + return await ioniaMerchantService.getMerchants(); + } } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 7f85e3eb49..c5cf068771 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -594,5 +594,7 @@ "by_cake_pay": "by CakePay", "expires": "Expires", "mm": "MM", - "yy": "YY" + "yy": "YY", + "online": "Online", + "offline": "Offline" } From 06a6d8d8bba3179b47ee3b413b7b2630dcd7679b Mon Sep 17 00:00:00 2001 From: M Date: Tue, 21 Jun 2022 10:31:38 +0100 Subject: [PATCH 06/55] Remove IoniaMerchantService from IoniaViewModel --- lib/di.dart | 2 +- lib/view_model/ionia/ionia_view_model.dart | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index 5a4fe165fc..e695d6e572 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -655,7 +655,7 @@ Future setup( () => IoniaService(getIt.get(), getIt.get())); getIt.registerFactory( - () => IoniaViewModel(ioniaService: getIt.get(), ioniaMerchantService: getIt.get())); + () => IoniaViewModel(ioniaService: getIt.get())); getIt.registerFactory(() => IoniaCreateAccountPage(getIt.get())); diff --git a/lib/view_model/ionia/ionia_view_model.dart b/lib/view_model/ionia/ionia_view_model.dart index 2f8c54e21a..cc24838901 100644 --- a/lib/view_model/ionia/ionia_view_model.dart +++ b/lib/view_model/ionia/ionia_view_model.dart @@ -1,7 +1,6 @@ import 'package:cake_wallet/ionia/ionia_service.dart'; import 'package:cake_wallet/ionia/ionia_create_state.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; -import 'package:cake_wallet/ionia/ionia_merchant_service.dart'; import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; import 'package:mobx/mobx.dart'; part 'ionia_view_model.g.dart'; @@ -10,7 +9,7 @@ class IoniaViewModel = IoniaViewModelBase with _$IoniaViewModel; abstract class IoniaViewModelBase with Store { - IoniaViewModelBase({this.ioniaService, this.ioniaMerchantService}) + IoniaViewModelBase({this.ioniaService}) : createUserState = IoniaCreateStateSuccess(), otpState = IoniaOtpSendDisabled(), cardState = IoniaNoCardState(), ioniaMerchants = [] { @@ -20,8 +19,6 @@ abstract class IoniaViewModelBase with Store { _getAuthStatus().then((value) => isLoggedIn = value); } - final IoniaMerchantService ioniaMerchantService; - final IoniaService ioniaService; @observable @@ -100,6 +97,6 @@ abstract class IoniaViewModelBase with Store { } Future> _getMerchants()async{ - return await ioniaMerchantService.getMerchants(); + return await ioniaService.getMerchants(); } } From 76fe14c9a567fdf0accf3d7fcc701ae04608e9a3 Mon Sep 17 00:00:00 2001 From: M Date: Wed, 22 Jun 2022 12:39:48 +0100 Subject: [PATCH 07/55] Add hex and txKey for monero pending transaction. --- .../lib/pending_bitcoin_transaction.dart | 3 +++ cw_core/lib/pending_transaction.dart | 1 + cw_haven/lib/pending_haven_transaction.dart | 3 +++ cw_monero/ios/Classes/monero_api.cpp | 6 ++++-- .../lib/api/structs/pending_transaction.dart | 18 +++++++++++++++++- cw_monero/lib/api/transaction_history.dart | 4 ++++ cw_monero/lib/pending_monero_transaction.dart | 5 +++++ 7 files changed, 37 insertions(+), 3 deletions(-) diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index 1793db3547..2371c0b5a3 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -24,6 +24,9 @@ class PendingBitcoinTransaction with PendingTransaction { @override String get id => _tx.getId(); + @override + String get hex => ''; + @override String get amountFormatted => bitcoinAmountToString(amount: amount); diff --git a/cw_core/lib/pending_transaction.dart b/cw_core/lib/pending_transaction.dart index c7f9b77d5c..cc5686fc9d 100644 --- a/cw_core/lib/pending_transaction.dart +++ b/cw_core/lib/pending_transaction.dart @@ -2,6 +2,7 @@ mixin PendingTransaction { String get id; String get amountFormatted; String get feeFormatted; + String get hex; Future commit(); } \ No newline at end of file diff --git a/cw_haven/lib/pending_haven_transaction.dart b/cw_haven/lib/pending_haven_transaction.dart index 7a8c6acc58..d56b5096c8 100644 --- a/cw_haven/lib/pending_haven_transaction.dart +++ b/cw_haven/lib/pending_haven_transaction.dart @@ -22,6 +22,9 @@ class PendingHavenTransaction with PendingTransaction { @override String get id => pendingTransactionDescription.hash; + @override + String get hex => ''; + @override String get amountFormatted => AmountConverter.amountIntToString( cryptoCurrency, pendingTransactionDescription.amount); diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index f81f63d16a..693cc76ebe 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -166,6 +166,8 @@ extern "C" uint64_t amount; uint64_t fee; char *hash; + char *hex; + char *txKey; Monero::PendingTransaction *transaction; PendingTransactionRaw(Monero::PendingTransaction *_transaction) @@ -174,6 +176,8 @@ extern "C" amount = _transaction->amount(); fee = _transaction->fee(); hash = strdup(_transaction->txid()[0].c_str()); + hex = strdup(_transaction->hex()[0].c_str()); + txKey = strdup(_transaction->txKey()[0].c_str()); } }; @@ -228,8 +232,6 @@ extern "C" bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error) { - Monero::WalletManagerFactory::setLogLevel(4); - Monero::NetworkType _networkType = static_cast(networkType); Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager(); Monero::Wallet *wallet = walletManager->createWallet(path, password, language, _networkType); diff --git a/cw_monero/lib/api/structs/pending_transaction.dart b/cw_monero/lib/api/structs/pending_transaction.dart index b492f28a0c..edbd2d0ffd 100644 --- a/cw_monero/lib/api/structs/pending_transaction.dart +++ b/cw_monero/lib/api/structs/pending_transaction.dart @@ -10,14 +10,30 @@ class PendingTransactionRaw extends Struct { Pointer hash; + Pointer hex; + + Pointer txKey; + String getHash() => Utf8.fromUtf8(hash); + + String getHex() => Utf8.fromUtf8(hex); + + String getKey() => Utf8.fromUtf8(txKey); } class PendingTransactionDescription { - PendingTransactionDescription({this.amount, this.fee, this.hash, this.pointerAddress}); + PendingTransactionDescription({ + this.amount, + this.fee, + this.hash, + this.hex, + this.txKey, + this.pointerAddress}); final int amount; final int fee; final String hash; + final String hex; + final String txKey; final int pointerAddress; } \ No newline at end of file diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index d693e16b9c..9546a93d33 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -104,6 +104,8 @@ PendingTransactionDescription createTransactionSync( amount: pendingTransactionRawPointer.ref.amount, fee: pendingTransactionRawPointer.ref.fee, hash: pendingTransactionRawPointer.ref.getHash(), + hex: pendingTransactionRawPointer.ref.getHex(), + txKey: pendingTransactionRawPointer.ref.getKey(), pointerAddress: pendingTransactionRawPointer.address); } @@ -157,6 +159,8 @@ PendingTransactionDescription createTransactionMultDestSync( amount: pendingTransactionRawPointer.ref.amount, fee: pendingTransactionRawPointer.ref.fee, hash: pendingTransactionRawPointer.ref.getHash(), + hex: pendingTransactionRawPointer.ref.getHex(), + txKey: pendingTransactionRawPointer.ref.getKey(), pointerAddress: pendingTransactionRawPointer.address); } diff --git a/cw_monero/lib/pending_monero_transaction.dart b/cw_monero/lib/pending_monero_transaction.dart index d927dd0d7d..d32bab2ce2 100644 --- a/cw_monero/lib/pending_monero_transaction.dart +++ b/cw_monero/lib/pending_monero_transaction.dart @@ -22,6 +22,11 @@ class PendingMoneroTransaction with PendingTransaction { @override String get id => pendingTransactionDescription.hash; + @override + String get hex => pendingTransactionDescription.hex; + + String get txKey => pendingTransactionDescription.txKey; + @override String get amountFormatted => AmountConverter.amountIntToString( CryptoCurrency.xmr, pendingTransactionDescription.amount); From 1447ea32ba607a6694665729119035c6d28bcf9c Mon Sep 17 00:00:00 2001 From: M Date: Wed, 22 Jun 2022 12:42:18 +0100 Subject: [PATCH 08/55] Changed monero version and monero repo to cake tech. --- scripts/android/build_monero.sh | 2 +- scripts/ios/build_monero.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/android/build_monero.sh b/scripts/android/build_monero.sh index 0a9ce08fff..5634aa20a2 100755 --- a/scripts/android/build_monero.sh +++ b/scripts/android/build_monero.sh @@ -1,7 +1,7 @@ #!/bin/sh . ./config.sh -MONERO_BRANCH=v0.17.3.0-android +MONERO_BRANCH=release-v0.17.3.2-android MONERO_SRC_DIR=${WORKDIR}/monero git clone https://github.com/cake-tech/monero.git ${MONERO_SRC_DIR} --branch ${MONERO_BRANCH} diff --git a/scripts/ios/build_monero.sh b/scripts/ios/build_monero.sh index 2d9d32fb07..ea29c7131b 100755 --- a/scripts/ios/build_monero.sh +++ b/scripts/ios/build_monero.sh @@ -2,9 +2,9 @@ . ./config.sh -MONERO_URL="https://github.com/monero-project/monero.git" +MONERO_URL="https://github.com/cake-tech/monero.git" MONERO_DIR_PATH="${EXTERNAL_IOS_SOURCE_DIR}/monero" -MONERO_VERSION=tags/v0.17.3.0 +MONERO_VERSION=release-v0.17.3.2 BUILD_TYPE=release PREFIX=${EXTERNAL_IOS_DIR} DEST_LIB_DIR=${EXTERNAL_IOS_LIB_DIR}/monero From 657265a10add44e1408d5e9d8eed9d2db88e50b2 Mon Sep 17 00:00:00 2001 From: M Date: Tue, 28 Jun 2022 14:31:51 +0100 Subject: [PATCH 09/55] Add anypay payment. Add filter by search for ionia, add get purchased items for ionia. --- .../lib/bitcoin_transaction_credentials.dart | 5 +- cw_bitcoin/lib/electrum_wallet.dart | 88 ++++++++++++------ .../lib/pending_bitcoin_transaction.dart | 2 +- lib/anypay/any_pay_chain.dart | 5 ++ lib/anypay/any_pay_payment_instruction.dart | 2 + lib/anypay/any_pay_trasnaction.dart | 9 ++ lib/anypay/anypay_api.dart | 33 ++++++- lib/bitcoin/cw_bitcoin.dart | 12 ++- lib/di.dart | 3 + lib/ionia/ionia_anypay.dart | 89 +++++++++++++++++++ lib/ionia/ionia_api.dart | 81 ++++++++++++++++- lib/ionia/ionia_service.dart | 25 ++++++ lib/monero/cw_monero.dart | 58 +++++++++--- lib/view_model/send/send_view_model.dart | 4 +- tool/configure.dart | 6 +- 15 files changed, 371 insertions(+), 51 deletions(-) create mode 100644 lib/anypay/any_pay_chain.dart create mode 100644 lib/anypay/any_pay_trasnaction.dart create mode 100644 lib/ionia/ionia_anypay.dart diff --git a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart index bf32f61860..7df93400ae 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart @@ -2,8 +2,9 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_core/output_info.dart'; class BitcoinTransactionCredentials { - BitcoinTransactionCredentials(this.outputs, this.priority); + BitcoinTransactionCredentials(this.outputs, {this.priority, this.feeRate}); final List outputs; - BitcoinTransactionPriority priority; + final BitcoinTransactionPriority priority; + final int feeRate; } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 2902a21caa..b6fa605aaf 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -208,8 +208,14 @@ abstract class ElectrumWalletBase extends WalletBase minAmount) { @@ -346,43 +364,55 @@ abstract class ElectrumWalletBase extends WalletBase feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount); + int feeAmountWithFeeRate(int feeRate, int inputsCount, + int outputsCount) => + feeRate * estimatedTransactionSize(inputsCount, outputsCount); + @override int calculateEstimatedFee(TransactionPriority priority, int amount, {int outputsCount}) { if (priority is BitcoinTransactionPriority) { - int inputsCount = 0; + return calculateEstimatedFeeWithFeeRate( + feeRate(priority), + amount, + outputsCount: outputsCount); + } - if (amount != null) { - int totalValue = 0; + return 0; + } - for (final input in unspentCoins) { - if (totalValue >= amount) { - break; - } + int calculateEstimatedFeeWithFeeRate(int feeRate, int amount, + {int outputsCount}) { + int inputsCount = 0; - if (input.isSending) { - totalValue += input.value; - inputsCount += 1; - } + if (amount != null) { + int totalValue = 0; + + for (final input in unspentCoins) { + if (totalValue >= amount) { + break; } - if (totalValue < amount) return 0; - } else { - for (final input in unspentCoins) { - if (input.isSending) { - inputsCount += 1; - } + if (input.isSending) { + totalValue += input.value; + inputsCount += 1; } } - // If send all, then we have no change value - final _outputsCount = outputsCount ?? (amount != null ? 2 : 1); - - return feeAmountForPriority( - priority, inputsCount, _outputsCount); + if (totalValue < amount) return 0; + } else { + for (final input in unspentCoins) { + if (input.isSending) { + inputsCount += 1; + } + } } - return 0; + // If send all, then we have no change value + final _outputsCount = outputsCount ?? (amount != null ? 2 : 1); + + return feeAmountWithFeeRate( + feeRate, inputsCount, _outputsCount); } @override diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index 2371c0b5a3..b9f754c72e 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -25,7 +25,7 @@ class PendingBitcoinTransaction with PendingTransaction { String get id => _tx.getId(); @override - String get hex => ''; + String get hex => _tx.toHex(); @override String get amountFormatted => bitcoinAmountToString(amount: amount); diff --git a/lib/anypay/any_pay_chain.dart b/lib/anypay/any_pay_chain.dart new file mode 100644 index 0000000000..3f6d9b1355 --- /dev/null +++ b/lib/anypay/any_pay_chain.dart @@ -0,0 +1,5 @@ +class AnyPayChain { + static const xmr = 'XMR'; + static const btc = 'BTC'; + static const ltc = 'LTC'; +} \ No newline at end of file diff --git a/lib/anypay/any_pay_payment_instruction.dart b/lib/anypay/any_pay_payment_instruction.dart index d6ed855273..41d2ee82d2 100644 --- a/lib/anypay/any_pay_payment_instruction.dart +++ b/lib/anypay/any_pay_payment_instruction.dart @@ -22,6 +22,8 @@ class AnyPayPaymentInstruction { outputs: outputs); } + static const transactionType = 'transaction'; + final String type; final int requiredFeeRate; final bool txKey; diff --git a/lib/anypay/any_pay_trasnaction.dart b/lib/anypay/any_pay_trasnaction.dart new file mode 100644 index 0000000000..29f8a01526 --- /dev/null +++ b/lib/anypay/any_pay_trasnaction.dart @@ -0,0 +1,9 @@ +import 'package:flutter/foundation.dart'; + +class AnyPayTransaction { + const AnyPayTransaction(this.tx, {@required this.id, @required this.key}); + + final String tx; + final String id; + final String key; +} \ No newline at end of file diff --git a/lib/anypay/anypay_api.dart b/lib/anypay/anypay_api.dart index e7583b0833..2804d0616a 100644 --- a/lib/anypay/anypay_api.dart +++ b/lib/anypay/anypay_api.dart @@ -1,16 +1,23 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/anypay/any_pay_payment.dart'; +import 'package:cake_wallet/anypay/any_pay_trasnaction.dart'; class AnyPayApi { static const contentTypePaymentRequest = 'application/payment-request'; + static const contentTypePayment = 'application/payment'; static const xPayproVersion = '2'; static String chainByScheme(String scheme) { switch (scheme.toLowerCase()) { case 'monero': return CryptoCurrency.xmr.title; + case 'bitcoin': + return CryptoCurrency.btc.title; + case 'litecoin': + return CryptoCurrency.ltc.title; default: return ''; } @@ -20,12 +27,16 @@ class AnyPayApi { switch (scheme.toLowerCase()) { case 'monero': return CryptoCurrency.xmr; + case 'bitcoin': + return CryptoCurrency.btc; + case 'litecoin': + return CryptoCurrency.ltc; default: return null; } } - Future pay(String uri) async { + Future paymentRequest(String uri) async { final fragments = uri.split(':?r='); final scheme = fragments.first; final url = fragments[1]; @@ -45,4 +56,24 @@ class AnyPayApi { final decodedBody = json.decode(response.body) as Map; return AnyPayPayment.fromMap(decodedBody); } + + Future payment( + String uri, + {@required String chain, + @required String currency, + @required List transactions}) async { + final headers = { + 'Content-Type': contentTypePayment, + 'X-Paypro-Version': xPayproVersion, + 'Accept': '*/*',}; + final body = { + 'chain': chain, + 'currency': currency, + 'transactions': transactions.map((tx) => {'tx': tx.tx, 'tx_hash': tx.id, 'tx_key': tx.key}).toList()}; + final response = await post(uri, headers: headers, body: utf8.encode(json.encode(body))); + + if (response.statusCode != 200) { + return null; + } + } } \ No newline at end of file diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index e73861594f..46ef891721 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -55,7 +55,7 @@ class CWBitcoin extends Bitcoin { } @override - Object createBitcoinTransactionCredentials(List outputs, TransactionPriority priority) + Object createBitcoinTransactionCredentials(List outputs, {TransactionPriority priority, int feeRate}) => BitcoinTransactionCredentials( outputs.map((out) => OutputInfo( fiatAmount: out.fiatAmount, @@ -67,7 +67,15 @@ class CWBitcoin extends Bitcoin { isParsedAddress: out.isParsedAddress, formattedCryptoAmount: out.formattedCryptoAmount)) .toList(), - priority as BitcoinTransactionPriority); + priority: priority != null ? priority as BitcoinTransactionPriority : null, + feeRate: feeRate); + + @override + Object createBitcoinTransactionCredentialsRaw(List outputs, {TransactionPriority priority, int feeRate}) + => BitcoinTransactionCredentials( + outputs, + priority: priority != null ? priority as BitcoinTransactionPriority : null, + feeRate: feeRate); @override List getAddresses(Object wallet) { diff --git a/lib/di.dart b/lib/di.dart index e695d6e572..93eedfe99c 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -131,6 +131,7 @@ import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; import 'package:cake_wallet/ionia/ionia_token_service.dart'; +import 'package:cake_wallet/anypay/anypay_api.dart'; final getIt = GetIt.instance; @@ -651,6 +652,8 @@ Future setup( getIt.registerFactory(() => IoniaApi()); + getIt.registerFactory(() => AnyPayApi()); + getIt.registerFactory( () => IoniaService(getIt.get(), getIt.get())); diff --git a/lib/ionia/ionia_anypay.dart b/lib/ionia/ionia_anypay.dart new file mode 100644 index 0000000000..b0b92e4d84 --- /dev/null +++ b/lib/ionia/ionia_anypay.dart @@ -0,0 +1,89 @@ +import 'package:flutter/foundation.dart'; +import 'package:cw_core/monero_amount_format.dart'; +import 'package:cw_core/monero_transaction_priority.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cake_wallet/anypay/any_pay_payment.dart'; +import 'package:cake_wallet/anypay/any_pay_payment_instruction.dart'; +import 'package:cake_wallet/ionia/ionia_service.dart'; +import 'package:cake_wallet/anypay/anypay_api.dart'; +import 'package:cake_wallet/anypay/any_pay_chain.dart'; +import 'package:cake_wallet/anypay/any_pay_trasnaction.dart'; +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/monero/monero.dart'; + +class IoniaAnyPay { + IoniaAnyPay(this.ioniaService, this.anyPayApi, this.wallet); + + final IoniaService ioniaService; + final AnyPayApi anyPayApi; + final WalletBase wallet; + + Future purchase({ + @required String merchId, + @required double amount, + @required String currency}) async { + final invoice = await ioniaService.purchaseGiftCard( + merchId: merchId, + amount: amount, + currency: currency); + return anyPayApi.paymentRequest(invoice.uri); + } + + Future commitInvoice(AnyPayPayment payment) async { + final transactionCredentials = payment.instructions + .where((instruction) => instruction.type == AnyPayPaymentInstruction.transactionType) + .map((AnyPayPaymentInstruction instruction) { + switch(payment.chain.toUpperCase()) { + case AnyPayChain.xmr: + return monero.createMoneroTransactionCreationCredentialsRaw( + outputs: instruction.outputs.map((out) => + OutputInfo( + isParsedAddress: false, + address: out.address, + cryptoAmount: moneroAmountToString(amount: out.amount), + sendAll: false)).toList(), + priority: MoneroTransactionPriority.medium); // FIXME: HARDCODED PRIORITY + case AnyPayChain.btc: + return bitcoin.createBitcoinTransactionCredentialsRaw( + instruction.outputs.map((out) => + OutputInfo( + isParsedAddress: false, + address: out.address, + formattedCryptoAmount: out.amount, + sendAll: false)).toList(), + feeRate: instruction.requiredFeeRate); + case AnyPayChain.ltc: + return bitcoin.createBitcoinTransactionCredentialsRaw( + instruction.outputs.map((out) => + OutputInfo( + isParsedAddress: false, + address: out.address, + formattedCryptoAmount: out.amount, + sendAll: false)).toList(), + feeRate: instruction.requiredFeeRate); + default: + throw Exception('Incorrect transaction chain: ${payment.chain.toUpperCase()}'); + } + }); + final transactions = (await Future.wait(transactionCredentials + .map((Object credentials) async => await wallet.createTransaction(credentials)))) + .map((PendingTransaction pendingTransaction) { + switch (payment.chain.toUpperCase()){ + case AnyPayChain.xmr: + final ptx = monero.pendingTransactionInfo(pendingTransaction); + return AnyPayTransaction(ptx['hex'], id: ptx['id'], key: ptx['key']); + default: + return AnyPayTransaction(pendingTransaction.hex, id: pendingTransaction.id, key: null); + } + }) + .toList(); + + await anyPayApi.payment( + payment.paymentUrl, + chain: payment.chain, + currency: payment.chain, + transactions: transactions); + } +} \ No newline at end of file diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart index c1a90e6caf..7fd80f2aae 100644 --- a/lib/ionia/ionia_api.dart +++ b/lib/ionia/ionia_api.dart @@ -14,6 +14,7 @@ class IoniaApi { static final createCardUri = Uri.https(baseUri, '/$pathPrefix/CreateCard'); static final getCardsUri = Uri.https(baseUri, '/$pathPrefix/GetCards'); static final getMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/GetMerchants'); + static final getMerchantsByFilterUrl = Uri.https(baseUri, '/$pathPrefix/GetMerchantsByFilter'); static final getPurchaseMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/PurchaseGiftCard'); // Create user @@ -157,6 +158,52 @@ class IoniaApi { }).toList(); } + // Get Merchants By Filter + + Future> getMerchantsByFilter({ + @required String username, + @required String password, + @required String clientId, + String search, + List categories, + int merchantFilterType = 0}) async { + // MerchantFilterType: {All = 0, Nearby = 1, Popular = 2, Online = 3, MyFaves = 4, Search = 5} + + final headers = { + 'clientId': clientId, + 'username': username, + 'password': password, + 'Content-Type': 'application/json'}; + final body = {'MerchantFilterType': merchantFilterType}; + + if (search != null) { + body['SearchCriteria'] = search; + } + + if (categories != null) { + body['Categories'] = categories; + } + + final response = await post(getMerchantsByFilterUrl, headers: headers, body: json.encode(body)); + + if (response.statusCode != 200) { + return []; + } + + final decodedBody = json.decode(response.body) as Map; + final isSuccessful = decodedBody['Successful'] as bool ?? false; + + if (!isSuccessful) { + return []; + } + + final data = decodedBody['Data'] as List; + return data.map((dynamic e) { + final element = e as Map; + return IoniaMerchant.fromJsonMap(element); + }).toList(); + } + // Purchase Gift Card Future purchaseGiftCard({ @@ -173,8 +220,8 @@ class IoniaApi { 'Content-Type': 'application/json'}; final body = { 'Amount': amount, - 'Currency': currency, - 'MerchantId': merchId}; + 'Currency': currency, + 'MerchantId': merchId}; final response = await post(getPurchaseMerchantsUrl, headers: headers, body: json.encode(body)); if (response.statusCode != 200) { @@ -191,4 +238,34 @@ class IoniaApi { final data = decodedBody['Data'] as Map; return IoniaOrder.fromMap(data); } + + // Get Current User Gift Card Summaries + + Future> getCurrentUserGiftCardSummaries({ + @required String username, + @required String password, + @required String clientId}) async { + final headers = { + 'clientId': clientId, + 'username': username, + 'password': password}; + final response = await post(getMerchantsUrl, headers: headers); + + if (response.statusCode != 200) { + return []; + } + + final decodedBody = json.decode(response.body) as Map; + final isSuccessful = decodedBody['Successful'] as bool ?? false; + + if (!isSuccessful) { + return []; + } + + final data = decodedBody['Data'] as List; + return data.map((dynamic e) { + final element = e as Map; + return IoniaMerchant.fromJsonMap(element); + }).toList(); + } } \ No newline at end of file diff --git a/lib/ionia/ionia_service.dart b/lib/ionia/ionia_service.dart index 7f84ff8c81..ab8d167e7c 100644 --- a/lib/ionia/ionia_service.dart +++ b/lib/ionia/ionia_service.dart @@ -71,6 +71,23 @@ class IoniaService { return ioniaApi.getMerchants(username: username, password: password, clientId: clientId); } + // Get Merchants By Filter + + Future> getMerchantsByFilter({ + String search, + List categories, + int merchantFilterType = 0}) async { + final username = await secureStorage.read(key: ioniaUsernameStorageKey); + final password = await secureStorage.read(key: ioniaPasswordStorageKey); + return ioniaApi.getMerchantsByFilter( + username: username, + password: password, + clientId: clientId, + search: search, + categories: categories, + merchantFilterType: merchantFilterType); + } + // Purchase Gift Card Future purchaseGiftCard({ @@ -87,4 +104,12 @@ class IoniaService { password: password, clientId: clientId); } + + // Get Current User Gift Card Summaries + + Future> getCurrentUserGiftCardSummaries() async { + final username = await secureStorage.read(key: ioniaUsernameStorageKey); + final password = await secureStorage.read(key: ioniaPasswordStorageKey); + return ioniaApi.getCurrentUserGiftCardSummaries(username: username, password: password, clientId: clientId); + } } \ No newline at end of file diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 98ba264469..dfa308b0d0 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -2,7 +2,7 @@ part of 'monero.dart'; class CWMoneroAccountList extends MoneroAccountList { CWMoneroAccountList(this._wallet); - Object _wallet; + final Object _wallet; @override @computed @@ -39,13 +39,13 @@ class CWMoneroAccountList extends MoneroAccountList { @override Future addAccount(Object wallet, {String label}) async { final moneroWallet = wallet as MoneroWallet; - moneroWallet.walletAddresses.accountList.addAccount(label: label); + await moneroWallet.walletAddresses.accountList.addAccount(label: label); } @override Future setLabelAccount(Object wallet, {int accountIndex, String label}) async { final moneroWallet = wallet as MoneroWallet; - moneroWallet.walletAddresses.accountList + await moneroWallet.walletAddresses.accountList .setLabelAccount( accountIndex: accountIndex, label: label); @@ -95,7 +95,7 @@ class CWMoneroSubaddressList extends MoneroSubaddressList { @override Future addSubaddress(Object wallet, {int accountIndex, String label}) async { final moneroWallet = wallet as MoneroWallet; - moneroWallet.walletAddresses.subaddressList + await moneroWallet.walletAddresses.subaddressList .addSubaddress( accountIndex: accountIndex, label: label); @@ -105,7 +105,7 @@ class CWMoneroSubaddressList extends MoneroSubaddressList { Future setLabelSubaddress(Object wallet, {int accountIndex, int addressIndex, String label}) async { final moneroWallet = wallet as MoneroWallet; - moneroWallet.walletAddresses.subaddressList + await moneroWallet.walletAddresses.subaddressList .setLabelSubaddress( accountIndex: accountIndex, addressIndex: addressIndex, @@ -140,35 +140,43 @@ class CWMonero extends Monero { return CWMoneroAccountList(wallet); } + @override MoneroSubaddressList getSubaddressList(Object wallet) { return CWMoneroSubaddressList(wallet); } + @override TransactionHistoryBase getTransactionHistory(Object wallet) { final moneroWallet = wallet as MoneroWallet; return moneroWallet.transactionHistory; } + @override MoneroWalletDetails getMoneroWalletDetails(Object wallet) { return CWMoneroWalletDetails(wallet); } + @override int getHeigthByDate({DateTime date}) { return getMoneroHeigthByDate(date: date); } + @override TransactionPriority getDefaultTransactionPriority() { return MoneroTransactionPriority.slow; } + @override TransactionPriority deserializeMoneroTransactionPriority({int raw}) { return MoneroTransactionPriority.deserialize(raw: raw); } + @override List getTransactionPriorities() { return MoneroTransactionPriority.all; } + @override List getMoneroWordList(String language) { switch (language.toLowerCase()) { case 'english': @@ -196,14 +204,15 @@ class CWMonero extends Monero { } } + @override WalletCredentials createMoneroRestoreWalletFromKeysCredentials({ String name, - String spendKey, - String viewKey, - String address, - String password, - String language, - int height}) { + String spendKey, + String viewKey, + String address, + String password, + String language, + int height}) { return MoneroRestoreWalletFromKeysCredentials( name: name, spendKey: spendKey, @@ -214,6 +223,7 @@ class CWMonero extends Monero { height: height); } + @override WalletCredentials createMoneroRestoreWalletFromSeedCredentials({String name, String password, int height, String mnemonic}) { return MoneroRestoreWalletFromSeedCredentials( name: name, @@ -222,6 +232,7 @@ class CWMonero extends Monero { mnemonic: mnemonic); } + @override WalletCredentials createMoneroNewWalletCredentials({String name, String password, String language}) { return MoneroNewWalletCredentials( name: name, @@ -229,6 +240,7 @@ class CWMonero extends Monero { language: language); } + @override Map getKeys(Object wallet) { final moneroWallet = wallet as MoneroWallet; final keys = moneroWallet.keys; @@ -239,6 +251,7 @@ class CWMonero extends Monero { 'publicViewKey': keys.publicViewKey}; } + @override Object createMoneroTransactionCreationCredentials({List outputs, TransactionPriority priority}) { return MoneroTransactionCreationCredentials( outputs: outputs.map((out) => OutputInfo( @@ -254,49 +267,72 @@ class CWMonero extends Monero { priority: priority as MoneroTransactionPriority); } + @override + Object createMoneroTransactionCreationCredentialsRaw({List outputs, TransactionPriority priority}) { + return MoneroTransactionCreationCredentials( + outputs: outputs, + priority: priority as MoneroTransactionPriority); + } + + @override String formatterMoneroAmountToString({int amount}) { return moneroAmountToString(amount: amount); } + @override double formatterMoneroAmountToDouble({int amount}) { return moneroAmountToDouble(amount: amount); } + @override int formatterMoneroParseAmount({String amount}) { return moneroParseAmount(amount: amount); } + @override Account getCurrentAccount(Object wallet) { final moneroWallet = wallet as MoneroWallet; final acc = moneroWallet.walletAddresses.account; return Account(id: acc.id, label: acc.label); } + @override void setCurrentAccount(Object wallet, int id, String label) { final moneroWallet = wallet as MoneroWallet; moneroWallet.walletAddresses.account = monero_account.Account(id: id, label: label); } + @override void onStartup() { monero_wallet_api.onStartup(); } + @override int getTransactionInfoAccountId(TransactionInfo tx) { final moneroTransactionInfo = tx as MoneroTransactionInfo; return moneroTransactionInfo.accountIndex; } + @override WalletService createMoneroWalletService(Box walletInfoSource) { return MoneroWalletService(walletInfoSource); } + @override String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) { final moneroWallet = wallet as MoneroWallet; return moneroWallet.getTransactionAddress(accountIndex, addressIndex); } + @override String getSubaddressLabel(Object wallet, int accountIndex, int addressIndex) { final moneroWallet = wallet as MoneroWallet; return moneroWallet.getSubaddressLabel(accountIndex, addressIndex); } + + @override + Map pendingTransactionInfo(Object transaction) { + final ptx = transaction as PendingMoneroTransaction; + return {'id': ptx.id, 'hex': ptx.hex, 'key': ptx.txKey}; + } } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index d82dd979ff..b615c00c4b 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -222,11 +222,11 @@ abstract class SendViewModelBase with Store { case WalletType.bitcoin: final priority = _settingsStore.priority[_wallet.type]; - return bitcoin.createBitcoinTransactionCredentials(outputs, priority); + return bitcoin.createBitcoinTransactionCredentials(outputs, priority: priority); case WalletType.litecoin: final priority = _settingsStore.priority[_wallet.type]; - return bitcoin.createBitcoinTransactionCredentials(outputs, priority); + return bitcoin.createBitcoinTransactionCredentials(outputs, priority: priority); case WalletType.monero: final priority = _settingsStore.priority[_wallet.type]; diff --git a/tool/configure.dart b/tool/configure.dart index dcbac59c5c..cfa675bd63 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -77,7 +77,8 @@ abstract class Bitcoin { TransactionPriority deserializeBitcoinTransactionPriority(int raw); int getFeeRate(Object wallet, TransactionPriority priority); Future generateNewAddress(Object wallet); - Object createBitcoinTransactionCredentials(List outputs, TransactionPriority priority); + Object createBitcoinTransactionCredentials(List outputs, {TransactionPriority priority, int feeRate}); + Object createBitcoinTransactionCredentialsRaw(List outputs, {TransactionPriority priority, int feeRate}); List getAddresses(Object wallet); String getAddress(Object wallet); @@ -146,6 +147,7 @@ import 'package:cw_monero/mnemonics/spanish.dart'; import 'package:cw_monero/mnemonics/portuguese.dart'; import 'package:cw_monero/mnemonics/french.dart'; import 'package:cw_monero/mnemonics/italian.dart'; +import 'package:cw_monero/pending_monero_transaction.dart'; """; const moneroCwPart = "part 'cw_monero.dart';"; const moneroContent = """ @@ -229,6 +231,7 @@ abstract class Monero { WalletCredentials createMoneroNewWalletCredentials({String name, String password, String language}); Map getKeys(Object wallet); Object createMoneroTransactionCreationCredentials({List outputs, TransactionPriority priority}); + Object createMoneroTransactionCreationCredentialsRaw({List outputs, TransactionPriority priority}); String formatterMoneroAmountToString({int amount}); double formatterMoneroAmountToDouble({int amount}); int formatterMoneroParseAmount({String amount}); @@ -237,6 +240,7 @@ abstract class Monero { void onStartup(); int getTransactionInfoAccountId(TransactionInfo tx); WalletService createMoneroWalletService(Box walletInfoSource); + Map pendingTransactionInfo(Object transaction); } abstract class MoneroSubaddressList { From 1d1bffa62b78d7c1659904833ace9501cd2fc802 Mon Sep 17 00:00:00 2001 From: M Date: Tue, 28 Jun 2022 15:34:12 +0100 Subject: [PATCH 10/55] Fix for get transactions for hidden addresses for electrum wallet --- cw_bitcoin/lib/electrum_wallet.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index b6fa605aaf..89848a5026 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -548,10 +548,6 @@ abstract class ElectrumWalletBase extends WalletBase{}; final normalizedHistories = >[]; walletAddresses.addresses.forEach((addressRecord) { - if (addressRecord.isHidden) { - return; - } - final sh = scriptHash(addressRecord.address, networkType: networkType); addressHashes[sh] = addressRecord; }); From e9b27f93dba6aecedcbc79efd3b963b4f99a60b8 Mon Sep 17 00:00:00 2001 From: M Date: Wed, 29 Jun 2022 14:00:57 +0100 Subject: [PATCH 11/55] Add ionia categories. --- lib/ionia/ionia_api.dart | 10 +++++++--- lib/ionia/ionia_category.dart | 20 ++++++++++++++++++++ lib/ionia/ionia_service.dart | 3 ++- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 lib/ionia/ionia_category.dart diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart index 7fd80f2aae..a3667cca80 100644 --- a/lib/ionia/ionia_api.dart +++ b/lib/ionia/ionia_api.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; import 'package:cake_wallet/ionia/ionia_user_credentials.dart'; import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; +import 'package:cake_wallet/ionia/ionia_category.dart'; class IoniaApi { static const baseUri = 'apidev.dashdirect.org'; @@ -165,7 +166,7 @@ class IoniaApi { @required String password, @required String clientId, String search, - List categories, + List categories, int merchantFilterType = 0}) async { // MerchantFilterType: {All = 0, Nearby = 1, Popular = 2, Online = 3, MyFaves = 4, Search = 5} @@ -181,7 +182,10 @@ class IoniaApi { } if (categories != null) { - body['Categories'] = categories; + body['Categories'] = categories + .map((e) => e.ids) + .expand((e) => e) + .toList(); } final response = await post(getMerchantsByFilterUrl, headers: headers, body: json.encode(body)); @@ -199,7 +203,7 @@ class IoniaApi { final data = decodedBody['Data'] as List; return data.map((dynamic e) { - final element = e as Map; + final element = e['Merchant'] as Map; return IoniaMerchant.fromJsonMap(element); }).toList(); } diff --git a/lib/ionia/ionia_category.dart b/lib/ionia/ionia_category.dart new file mode 100644 index 0000000000..28d48d2b85 --- /dev/null +++ b/lib/ionia/ionia_category.dart @@ -0,0 +1,20 @@ +class IoniaCategory { + const IoniaCategory(this.title, this.ids); + + static const allCategories = [ + apparel, + onlineOnly, + food, + entertainment, + delivery, + travel]; + static const apparel = IoniaCategory('Apparel', [1]); + static const onlineOnly = IoniaCategory('Online Only', [13, 43]); + static const food = IoniaCategory('Food', [4]); + static const entertainment = IoniaCategory('Entertainment', [5]); + static const delivery = IoniaCategory('Delivery', [114, 109]); + static const travel = IoniaCategory('Travel', [12]); + + final String title; + final List ids; +} diff --git a/lib/ionia/ionia_service.dart b/lib/ionia/ionia_service.dart index ab8d167e7c..d0500d66dd 100644 --- a/lib/ionia/ionia_service.dart +++ b/lib/ionia/ionia_service.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/ionia/ionia_api.dart'; +import 'package:cake_wallet/ionia/ionia_category.dart'; class IoniaService { IoniaService(this.secureStorage, this.ioniaApi); @@ -75,7 +76,7 @@ class IoniaService { Future> getMerchantsByFilter({ String search, - List categories, + List categories, int merchantFilterType = 0}) async { final username = await secureStorage.read(key: ioniaUsernameStorageKey); final password = await secureStorage.read(key: ioniaPasswordStorageKey); From 0a7f8bf286bf8f8744f799f6397b67b2e080b71d Mon Sep 17 00:00:00 2001 From: M Date: Wed, 29 Jun 2022 17:18:41 +0100 Subject: [PATCH 12/55] Add anypay commited info for payments. --- .../any_pay_payment_committed_info.dart | 17 +++++++++++++++++ lib/anypay/anypay_api.dart | 19 ++++++++++++++++--- lib/ionia/ionia_anypay.dart | 13 +++++++------ 3 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 lib/anypay/any_pay_payment_committed_info.dart diff --git a/lib/anypay/any_pay_payment_committed_info.dart b/lib/anypay/any_pay_payment_committed_info.dart new file mode 100644 index 0000000000..126b3d92e7 --- /dev/null +++ b/lib/anypay/any_pay_payment_committed_info.dart @@ -0,0 +1,17 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/anypay/any_pay_trasnaction.dart'; + +class AnyPayPaymentCommittedInfo { + const AnyPayPaymentCommittedInfo({ + @required this.uri, + @required this.currency, + @required this.chain, + @required this.transactions, + @required this.memo}); + + final String uri; + final String currency; + final String chain; + final List transactions; + final String memo; +} \ No newline at end of file diff --git a/lib/anypay/anypay_api.dart b/lib/anypay/anypay_api.dart index 2804d0616a..c0727bc29c 100644 --- a/lib/anypay/anypay_api.dart +++ b/lib/anypay/anypay_api.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -57,7 +58,7 @@ class AnyPayApi { return AnyPayPayment.fromMap(decodedBody); } - Future payment( + Future payment( String uri, {@required String chain, @required String currency, @@ -71,9 +72,21 @@ class AnyPayApi { 'currency': currency, 'transactions': transactions.map((tx) => {'tx': tx.tx, 'tx_hash': tx.id, 'tx_key': tx.key}).toList()}; final response = await post(uri, headers: headers, body: utf8.encode(json.encode(body))); + if (response.statusCode == 400) { + final decodedBody = json.decode(response.body) as Map; + throw Exception(decodedBody['message'] as String); + } - if (response.statusCode != 200) { - return null; + if (response.statusCode != 200) { + throw Exception('Unexpected response'); } + + final decodedBody = json.decode(response.body) as Map; + return AnyPayPaymentCommittedInfo( + uri: uri, + currency: currency, + chain: chain, + transactions: transactions, + memo: decodedBody['memo'] as String); } } \ No newline at end of file diff --git a/lib/ionia/ionia_anypay.dart b/lib/ionia/ionia_anypay.dart index b0b92e4d84..b84dbf64df 100644 --- a/lib/ionia/ionia_anypay.dart +++ b/lib/ionia/ionia_anypay.dart @@ -12,6 +12,7 @@ import 'package:cake_wallet/anypay/any_pay_chain.dart'; import 'package:cake_wallet/anypay/any_pay_trasnaction.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; class IoniaAnyPay { IoniaAnyPay(this.ioniaService, this.anyPayApi, this.wallet); @@ -31,7 +32,7 @@ class IoniaAnyPay { return anyPayApi.paymentRequest(invoice.uri); } - Future commitInvoice(AnyPayPayment payment) async { + Future commitInvoice(AnyPayPayment payment) async { final transactionCredentials = payment.instructions .where((instruction) => instruction.type == AnyPayPaymentInstruction.transactionType) .map((AnyPayPaymentInstruction instruction) { @@ -80,10 +81,10 @@ class IoniaAnyPay { }) .toList(); - await anyPayApi.payment( - payment.paymentUrl, - chain: payment.chain, - currency: payment.chain, - transactions: transactions); + return await anyPayApi.payment( + payment.paymentUrl, + chain: payment.chain, + currency: payment.chain, + transactions: transactions); } } \ No newline at end of file From 03800e090c3241bf878e35576dc52ace9a5c18b9 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Wed, 29 Jun 2022 20:18:02 +0300 Subject: [PATCH 13/55] Update UI with new fixes (#400) --- .../ionia/auth/ionia_create_account_page.dart | 6 +- .../screens/ionia/auth/ionia_login_page.dart | 7 +- .../ionia/auth/ionia_verify_otp_page.dart | 5 +- .../ionia/auth/ionia_welcome_page.dart | 12 +- .../cards/ionia_activate_debit_card_page.dart | 6 +- .../cards/ionia_buy_card_detail_page.dart | 6 +- .../ionia/cards/ionia_buy_gift_card.dart | 17 +- .../ionia/cards/ionia_debit_card_page.dart | 5 +- .../ionia/cards/ionia_manage_cards_page.dart | 179 +++++++++--------- lib/src/screens/ionia/widgets/card_item.dart | 5 +- lib/src/widgets/cake_scrollbar.dart | 27 +-- lib/src/widgets/discount_badge.dart | 35 ++-- lib/view_model/ionia/ionia_view_model.dart | 16 +- res/values/strings_en.arb | 2 +- 14 files changed, 169 insertions(+), 159 deletions(-) diff --git a/lib/src/screens/ionia/auth/ionia_create_account_page.dart b/lib/src/screens/ionia/auth/ionia_create_account_page.dart index 3fd9124040..e0c0ecb35d 100644 --- a/lib/src/screens/ionia/auth/ionia_create_account_page.dart +++ b/lib/src/screens/ionia/auth/ionia_create_account_page.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.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/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; import 'package:flutter/material.dart'; @@ -33,11 +34,8 @@ class IoniaCreateAccountPage extends BasePage { Widget middle(BuildContext context) { return Text( S.current.sign_up, - style: TextStyle( - fontSize: 22, - fontFamily: 'Lato', + style: textLargeSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, - fontWeight: FontWeight.w900, ), ); } diff --git a/lib/src/screens/ionia/auth/ionia_login_page.dart b/lib/src/screens/ionia/auth/ionia_login_page.dart index 584682341a..b47944f4d5 100644 --- a/lib/src/screens/ionia/auth/ionia_login_page.dart +++ b/lib/src/screens/ionia/auth/ionia_login_page.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.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/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; import 'package:flutter/material.dart'; @@ -34,10 +35,8 @@ class IoniaLoginPage extends BasePage { Widget middle(BuildContext context) { return Text( S.current.login, - style: TextStyle( - fontSize: 22, - fontFamily: 'Lato', - fontWeight: FontWeight.w900, + style: textLargeSemiBold( + color: Theme.of(context).accentTextTheme.display4.backgroundColor, ), ); } diff --git a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart index 2af82350a6..fb254f71d0 100644 --- a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart +++ b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart @@ -38,11 +38,8 @@ class IoniaVerifyIoniaOtp extends BasePage { Widget middle(BuildContext context) { return Text( S.current.verification, - style: TextStyle( - fontSize: 22, - fontFamily: 'Lato', + style: textLargeSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, - fontWeight: FontWeight.w900, ), ); } diff --git a/lib/src/screens/ionia/auth/ionia_welcome_page.dart b/lib/src/screens/ionia/auth/ionia_welcome_page.dart index 66e89899b0..85e06faae9 100644 --- a/lib/src/screens/ionia/auth/ionia_welcome_page.dart +++ b/lib/src/screens/ionia/auth/ionia_welcome_page.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/framework.dart'; @@ -15,11 +16,8 @@ class IoniaWelcomePage extends BasePage { Widget middle(BuildContext context) { return Text( S.current.welcome_to_cakepay, - style: TextStyle( - fontSize: 22, - fontFamily: 'Lato', + style: textLargeSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, - fontWeight: FontWeight.w900, ), ); } @@ -90,11 +88,13 @@ class IoniaWelcomePage extends BasePage { S.of(context).login, style: TextStyle( color: Palette.blueCraiola, - fontSize: 16, + fontSize: 18, + letterSpacing: 1.5, fontWeight: FontWeight.w900, ), ), - ) + ), + SizedBox(height: 20) ], ) ], diff --git a/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart b/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart index 91fd8615cc..f35ddd8bb9 100644 --- a/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart +++ b/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.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/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; import 'package:flutter/material.dart'; @@ -21,11 +22,8 @@ class IoniaActivateDebitCardPage extends BasePage { Widget middle(BuildContext context) { return Text( S.current.debit_card, - style: TextStyle( - fontSize: 22, + style: textLargeSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, - fontFamily: 'Lato', - fontWeight: FontWeight.w900, ), ); } diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index 6d56ad362d..9975e095d3 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -58,11 +58,7 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { Widget middle(BuildContext context) { return Text( merchant.legalName, - style: TextStyle( - fontSize: 22, - fontFamily: 'Lato', - fontWeight: FontWeight.w900, - ), + style: textLargeSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor), ); } diff --git a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart index e46b9ea0a8..fc16db938b 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart @@ -69,6 +69,7 @@ class IoniaBuyGiftCardPage extends BasePage { child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox(height: 150), BaseTextFormField( @@ -79,7 +80,7 @@ class IoniaBuyGiftCardPage extends BasePage { hintText: '1000', placeholderTextStyle: TextStyle( color: Theme.of(context).primaryTextTheme.headline.color, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, fontSize: 36, ), borderColor: Theme.of(context).primaryTextTheme.headline.color, @@ -100,7 +101,7 @@ class IoniaBuyGiftCardPage extends BasePage { '${merchant.acceptedCurrency}: ', style: TextStyle( color: Colors.white, - fontWeight: FontWeight.w900, + fontWeight: FontWeight.w600, fontSize: 36, ), ), @@ -109,6 +110,7 @@ class IoniaBuyGiftCardPage extends BasePage { SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( S.of(context).min_amount(merchant.minimumCardPurchase.toString()), @@ -133,7 +135,7 @@ class IoniaBuyGiftCardPage extends BasePage { child: CardItem( title: merchant.legalName, backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), - discount: 0.0, + discount: merchant.minimumDiscount, titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor, subtitleColor: Theme.of(context).hintColor, subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline, @@ -147,10 +149,15 @@ class IoniaBuyGiftCardPage extends BasePage { Padding( padding: EdgeInsets.only(bottom: 12), child: PrimaryButton( - onPressed: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardDetailPage, arguments: [merchant] ), + onPressed: () { + Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardDetailPage, arguments: [merchant] ); + }, text: S.of(context).continue_text, color: Theme.of(context).accentTextTheme.body2.color, - textColor: Theme.of(context).primaryTextTheme.body1.color, + textColor: Theme.of(context) + .accentTextTheme + .headline + .decorationColor, ), ), SizedBox(height: 30), diff --git a/lib/src/screens/ionia/cards/ionia_debit_card_page.dart b/lib/src/screens/ionia/cards/ionia_debit_card_page.dart index 0d2a6294fd..42684e8ffb 100644 --- a/lib/src/screens/ionia/cards/ionia_debit_card_page.dart +++ b/lib/src/screens/ionia/cards/ionia_debit_card_page.dart @@ -22,11 +22,8 @@ class IoniaDebitCardPage extends BasePage { Widget middle(BuildContext context) { return Text( S.current.debit_card, - style: TextStyle( - fontSize: 22, - fontFamily: 'Lato', + style: textLargeSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, - fontWeight: FontWeight.w900, ), ); } diff --git a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart index a2f8857749..15ce9e644e 100644 --- a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart @@ -1,19 +1,19 @@ +import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/card_menu.dart'; -import 'package:cake_wallet/src/widgets/market_place_item.dart'; +import 'package:cake_wallet/src/widgets/cake_scrollbar.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:mobx/mobx.dart'; class IoniaManageCardsPage extends BasePage { - IoniaManageCardsPage(this._ioniaViewModel); - + IoniaManageCardsPage(this._ioniaViewModel); final IoniaViewModel _ioniaViewModel; @override @@ -64,7 +64,7 @@ class IoniaManageCardsPage extends BasePage { highlightColor: Colors.transparent, splashColor: Colors.transparent, padding: EdgeInsets.all(0), - onPressed: () => Navigator.pushReplacementNamed(context, Routes.dashboard), + onPressed: () => Navigator.pop(context), child: _backButton), ), ); @@ -74,113 +74,43 @@ class IoniaManageCardsPage extends BasePage { Widget middle(BuildContext context) { return Text( S.of(context).manage_cards, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w500, + style: textLargeSemiBold( color: Theme.of(context).accentTextTheme.display3.backgroundColor, ), ); } - final ScrollController _scrollController = ScrollController(); @override Widget trailing(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - _TrailingIcon( - asset: 'assets/images/card.png', - onPressed: () => Navigator.pushNamed(context, Routes.ioniaDebitCardPage), - ), - SizedBox(width: 16), + return _TrailingIcon( asset: 'assets/images/profile.png', onPressed: () {}, - ), - ], ); } @override Widget body(BuildContext context) { - final filterIcon = Image.asset( - 'assets/images/filter.png', - color: Theme.of(context).textTheme.caption.decorationColor, - ); return Padding( padding: const EdgeInsets.all(14.0), child: Column( children: [ - MarketPlaceItem( - onTap: () {}, - title: S.of(context).setup_your_debit_card, - subTitle: S.of(context).no_id_required, - ), - SizedBox(height: 48), Container( padding: EdgeInsets.only(left: 2, right: 22), height: 32, - child: Row( - children: [ - Expanded(child: _SearchWidget()), - SizedBox(width: 10), - Container( - width: 32, - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.15), - border: Border.all( - color: Colors.white.withOpacity(0.2), - ), - borderRadius: BorderRadius.circular(10), - ), - child: filterIcon, - ) - ], - ), + child: _SearchWidget() + ), SizedBox(height: 8), Expanded( - child: Observer( - builder: (_) { - final merchantsList = _ioniaViewModel.ioniaMerchants; - return RawScrollbar( - thumbColor: Colors.white.withOpacity(0.15), - radius: Radius.circular(20), - isAlwaysShown: true, - thickness: 2, - controller: _scrollController, - child: ListView.separated( - padding: EdgeInsets.only(left: 2, right: 22), - controller: _scrollController, - itemCount: merchantsList.length, - separatorBuilder: (_, __) => SizedBox(height: 4), - itemBuilder: (_, index) { - final merchant = merchantsList[index]; - return CardItem( - logoUrl: merchant.logoUrl, - onTap: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, - arguments: [merchant]), - title: merchant.legalName, - subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline, - backgroundColor: Theme.of(context).textTheme.title.backgroundColor, - titleColor: Theme.of(context) - .accentTextTheme - .display3 - .backgroundColor, - subtitleColor: Theme.of(context) - .accentTextTheme - .display2 - .backgroundColor, - discount: merchant.minimumDiscount, - ); - }, - ), - ); - } - ), + child: Observer(builder: (_) { + return IoniaManageCardsPageBody(scrollOffsetFromTop: _ioniaViewModel.scrollOffsetFromTop, + ioniaMerchants: _ioniaViewModel.ioniaMerchants, + onSetScrollOffset: (offset) => _ioniaViewModel.setScrollOffsetFromTop(offset), + ); + }), ), ], ), @@ -188,6 +118,81 @@ class IoniaManageCardsPage extends BasePage { } } +class IoniaManageCardsPageBody extends StatefulWidget { + const IoniaManageCardsPageBody({ + Key key, + @required this.scrollOffsetFromTop, + @required this.ioniaMerchants, + @required this.onSetScrollOffset, + }) : super(key: key); + + + final List ioniaMerchants; + final double scrollOffsetFromTop; + final Function(double) onSetScrollOffset; + + @override + _IoniaManageCardsPageBodyState createState() => _IoniaManageCardsPageBodyState(); +} + +class _IoniaManageCardsPageBodyState extends State { + + double get backgroundHeight => MediaQuery.of(context).size.height * 0.75; + double thumbHeight = 72; + bool get isAlwaysShowScrollThumb => merchantsList == null ? false : merchantsList.length > 3; + + + List get merchantsList => widget.ioniaMerchants; + + final _scrollController = ScrollController(); + +@override + void initState() { + _scrollController.addListener(() { + final scrollOffsetFromTop = _scrollController.hasClients + ? (_scrollController.offset / _scrollController.position.maxScrollExtent * (backgroundHeight - thumbHeight)) + : 0.0; + widget.onSetScrollOffset(scrollOffsetFromTop); + }); + super.initState(); + } + @override + Widget build(BuildContext context) { + return Stack(children: [ + ListView.separated( + padding: EdgeInsets.only(left: 2, right: 22), + controller: _scrollController, + itemCount: merchantsList.length, + separatorBuilder: (_, __) => SizedBox(height: 4), + itemBuilder: (_, index) { + final merchant = merchantsList[index]; + return CardItem( + logoUrl: merchant.logoUrl, + onTap: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]), + title: merchant.legalName, + subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline, + backgroundColor: Theme.of(context).textTheme.title.backgroundColor, + titleColor: Theme.of(context).accentTextTheme.display3.backgroundColor, + subtitleColor: Theme.of(context).accentTextTheme.display2.backgroundColor, + discount: merchant.minimumDiscount, + ); + }, + ), + isAlwaysShowScrollThumb + ? CakeScrollbar( + backgroundHeight: backgroundHeight, + thumbHeight: thumbHeight, + rightOffset: 1, + width: 3, + backgroundColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.05), + thumbColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.5), + fromTop: widget.scrollOffsetFromTop, + ) + : Offstage() + ]); + } +} + class _SearchWidget extends StatelessWidget { const _SearchWidget({ Key key, @@ -212,7 +217,7 @@ class _SearchWidget extends StatelessWidget { left: 10, ), fillColor: Colors.white.withOpacity(0.15), - hintText: 'Search', + hintText: S.of(context).search, hintStyle: TextStyle( color: Colors.white.withOpacity(0.6), ), @@ -263,3 +268,5 @@ class _TrailingIcon extends StatelessWidget { ); } } + + diff --git a/lib/src/screens/ionia/widgets/card_item.dart b/lib/src/screens/ionia/widgets/card_item.dart index 38946da5a7..9a46072fde 100644 --- a/lib/src/screens/ionia/widgets/card_item.dart +++ b/lib/src/screens/ionia/widgets/card_item.dart @@ -46,8 +46,9 @@ class CardItem extends StatelessWidget { ClipOval( child: Image.network( logoUrl, - width: 42.0, - height: 42.0, + width: 40.0, + height: 40.0, + fit: BoxFit.cover, loadingBuilder: (BuildContext _, Widget child, ImageChunkEvent loadingProgress) { if (loadingProgress == null) { return child; diff --git a/lib/src/widgets/cake_scrollbar.dart b/lib/src/widgets/cake_scrollbar.dart index 6ccf391c6f..6a0cb2e145 100644 --- a/lib/src/widgets/cake_scrollbar.dart +++ b/lib/src/widgets/cake_scrollbar.dart @@ -5,13 +5,19 @@ class CakeScrollbar extends StatelessWidget { @required this.backgroundHeight, @required this.thumbHeight, @required this.fromTop, - this.rightOffset = 6 + this.rightOffset = 6, + this.backgroundColor, + this.thumbColor, + this.width = 6, }); final double backgroundHeight; final double thumbHeight; final double fromTop; + final double width; final double rightOffset; + final Color backgroundColor; + final Color thumbColor; @override Widget build(BuildContext context) { @@ -19,11 +25,10 @@ class CakeScrollbar extends StatelessWidget { right: rightOffset, child: Container( height: backgroundHeight, - width: 6, + width: width, decoration: BoxDecoration( - color: Theme.of(context).textTheme.body1.decorationColor, - borderRadius: BorderRadius.all(Radius.circular(3)) - ), + color: backgroundColor ?? Theme.of(context).textTheme.body1.decorationColor, + borderRadius: BorderRadius.all(Radius.circular(3))), child: Stack( children: [ AnimatedPositioned( @@ -31,16 +36,14 @@ class CakeScrollbar extends StatelessWidget { top: fromTop, child: Container( height: thumbHeight, - width: 6.0, + width: width, decoration: BoxDecoration( - color: Theme.of(context).textTheme.body1.color, - borderRadius: BorderRadius.all(Radius.circular(3)) - ), + color: thumbColor ?? Theme.of(context).textTheme.body1.color, + borderRadius: BorderRadius.all(Radius.circular(3))), ), ) ], ), - ) - ); + )); } -} \ No newline at end of file +} diff --git a/lib/src/widgets/discount_badge.dart b/lib/src/widgets/discount_badge.dart index c3f9e21481..d4e9836b2a 100644 --- a/lib/src/widgets/discount_badge.dart +++ b/lib/src/widgets/discount_badge.dart @@ -12,23 +12,22 @@ class DiscountBadge extends StatelessWidget { @override Widget build(BuildContext context) { - return Stack( - alignment: Alignment.centerRight, - children: [ - Image.asset('assets/images/badge_discount.png'), - Padding( - padding: const EdgeInsets.only(right: 10.0), - child: Text( - S.of(context).discount(percentage.toString()), - style: TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.w500, - fontFamily: 'Lato', - ), + return Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Text( S.of(context).discount(percentage.toString()), + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500, + fontFamily: 'Lato', ), - ) - ], - ); - } + ), + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.fill, + image: AssetImage('assets/images/badge_discount.png'), + ), + ), + ); + } } diff --git a/lib/view_model/ionia/ionia_view_model.dart b/lib/view_model/ionia/ionia_view_model.dart index cc24838901..34cdcf31a3 100644 --- a/lib/view_model/ionia/ionia_view_model.dart +++ b/lib/view_model/ionia/ionia_view_model.dart @@ -8,12 +8,13 @@ part 'ionia_view_model.g.dart'; class IoniaViewModel = IoniaViewModelBase with _$IoniaViewModel; abstract class IoniaViewModelBase with Store { - IoniaViewModelBase({this.ioniaService}) : createUserState = IoniaCreateStateSuccess(), otpState = IoniaOtpSendDisabled(), - cardState = IoniaNoCardState(), ioniaMerchants = [] { - _getMerchants().then((value){ + cardState = IoniaNoCardState(), + ioniaMerchants = [], + scrollOffsetFromTop = 0.0 { + _getMerchants().then((value) { ioniaMerchants = value; }); _getAuthStatus().then((value) => isLoggedIn = value); @@ -21,6 +22,9 @@ abstract class IoniaViewModelBase with Store { final IoniaService ioniaService; + @observable + double scrollOffsetFromTop; + @observable IoniaCreateAccountState createUserState; @@ -96,7 +100,11 @@ abstract class IoniaViewModelBase with Store { } } - Future> _getMerchants()async{ + Future> _getMerchants() async { return await ioniaService.getMerchants(); } + + void setScrollOffsetFromTop(double scrollOffset) { + scrollOffsetFromTop = scrollOffset; + } } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index c5cf068771..d90b758750 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -550,7 +550,7 @@ "purchase_gift_card": "Purchase Gift Card", "verification": "Verification", "fill_code": "Please fill in the verification code provided to your email", - "dont_get_code": "Don't get code", + "dont_get_code": "Don't get code?", "resend_code": "Please resend it", "debit_card": "Debit Card", "cakepay_prepaid_card": "CakePay Prepaid Debit Card", From def28ba5c8ad093dca229e1bdc12ca538f819929 Mon Sep 17 00:00:00 2001 From: M Date: Thu, 30 Jun 2022 13:23:11 +0100 Subject: [PATCH 14/55] Change ionia base url. Add exception throwing for error messaging for some of ionia calls. --- lib/ionia/ionia_api.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart index a3667cca80..b00c52217f 100644 --- a/lib/ionia/ionia_api.dart +++ b/lib/ionia/ionia_api.dart @@ -8,7 +8,7 @@ import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; import 'package:cake_wallet/ionia/ionia_category.dart'; class IoniaApi { - static const baseUri = 'apidev.dashdirect.org'; + static const baseUri = 'apidev.ionia.io'; static const pathPrefix = 'cake'; static final createUserUri = Uri.https(baseUri, '/$pathPrefix/CreateUser'); static final verifyEmailUri = Uri.https(baseUri, '/$pathPrefix/VerifyEmail'); @@ -229,14 +229,14 @@ class IoniaApi { final response = await post(getPurchaseMerchantsUrl, headers: headers, body: json.encode(body)); if (response.statusCode != 200) { - return null; + throw Exception('Unexpected response'); } final decodedBody = json.decode(response.body) as Map; final isSuccessful = decodedBody['Successful'] as bool ?? false; - + if (!isSuccessful) { - return null; + throw Exception(decodedBody['ErrorMessage'] as String); } final data = decodedBody['Data'] as Map; From 73cbdc4546981a209deb708fcd13726b6921a0da Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Wed, 6 Jul 2022 20:13:19 +0300 Subject: [PATCH 15/55] CW-102 fix logic for ionia issues (#403) --- assets/images/airplane.png | Bin 0 -> 960 bytes assets/images/category.png | Bin 0 -> 454 bytes assets/images/copy.png | Bin 0 -> 556 bytes assets/images/delivery.png | Bin 0 -> 728 bytes assets/images/food.png | Bin 0 -> 710 bytes assets/images/gaming.png | Bin 0 -> 705 bytes assets/images/global.png | Bin 0 -> 997 bytes assets/images/tshirt.png | Bin 0 -> 583 bytes lib/anypay/any_pay_payment.dart | 26 + lib/di.dart | 74 +- lib/ionia/ionia_anypay.dart | 5 +- lib/ionia/ionia_category.dart | 30 +- lib/ionia/ionia_merchant.dart | 101 ++- lib/ionia/ionia_service.dart | 6 + lib/ionia/ionia_tip.dart | 12 + lib/main.dart | 5 +- lib/router.dart | 13 + lib/routes.dart | 3 + .../ionia/auth/ionia_create_account_page.dart | 23 +- .../screens/ionia/auth/ionia_login_page.dart | 23 +- .../ionia/auth/ionia_verify_otp_page.dart | 122 +-- .../ionia/auth/ionia_welcome_page.dart | 8 +- .../ionia/cards/ionia_account_cards_page.dart | 118 +++ .../ionia/cards/ionia_account_page.dart | 180 +++++ .../cards/ionia_activate_debit_card_page.dart | 12 +- .../cards/ionia_buy_card_detail_page.dart | 703 +++++++++++++----- .../ionia/cards/ionia_buy_gift_card.dart | 48 +- .../ionia/cards/ionia_custom_tip_page.dart | 181 +++++ .../ionia/cards/ionia_debit_card_page.dart | 8 +- .../ionia/cards/ionia_manage_cards_page.dart | 184 +++-- .../screens/ionia/widgets/confirm_modal.dart | 2 + .../ionia/widgets/ionia_filter_modal.dart | 134 ++++ lib/src/screens/ionia/widgets/ionia_tile.dart | 58 ++ .../ionia/widgets/rounded_checkbox.dart | 27 + .../ionia/ionia_account_view_model.dart | 36 + .../ionia/ionia_auth_view_model.dart | 55 ++ .../ionia/ionia_filter_view_model.dart | 58 ++ ... => ionia_gift_cards_list_view_model.dart} | 85 +-- .../ionia_purchase_merch_view_model.dart | 91 +++ res/values/strings_en.arb | 25 +- 40 files changed, 1947 insertions(+), 509 deletions(-) create mode 100644 assets/images/airplane.png create mode 100644 assets/images/category.png create mode 100644 assets/images/copy.png create mode 100644 assets/images/delivery.png create mode 100644 assets/images/food.png create mode 100644 assets/images/gaming.png create mode 100644 assets/images/global.png create mode 100644 assets/images/tshirt.png create mode 100644 lib/ionia/ionia_tip.dart create mode 100644 lib/src/screens/ionia/cards/ionia_account_cards_page.dart create mode 100644 lib/src/screens/ionia/cards/ionia_account_page.dart create mode 100644 lib/src/screens/ionia/cards/ionia_custom_tip_page.dart create mode 100644 lib/src/screens/ionia/widgets/ionia_filter_modal.dart create mode 100644 lib/src/screens/ionia/widgets/ionia_tile.dart create mode 100644 lib/src/screens/ionia/widgets/rounded_checkbox.dart create mode 100644 lib/view_model/ionia/ionia_account_view_model.dart create mode 100644 lib/view_model/ionia/ionia_auth_view_model.dart create mode 100644 lib/view_model/ionia/ionia_filter_view_model.dart rename lib/view_model/ionia/{ionia_view_model.dart => ionia_gift_cards_list_view_model.dart} (56%) create mode 100644 lib/view_model/ionia/ionia_purchase_merch_view_model.dart diff --git a/assets/images/airplane.png b/assets/images/airplane.png new file mode 100644 index 0000000000000000000000000000000000000000..6be73be94371c0f3790538bcc513affe9c231ddb GIT binary patch literal 960 zcmV;x13&zUP)*f zDF=)<%Z2F4HC_s3-#GKST}as$N%SS{dpmDtzIij>n+Ncp2Sy1;rt&HDDT5g6C!$1G z2Z$b#f2a}Mz2d7c+dhs>#G@l9q zK#}>VtiSveW%!Vz7b8CKf={%-5hh0`-zB4TgVcD4lnDX&qo4n)aLLf5Jr!#fote{( zT}%+=$igz8gse>*J5ekc}zm@C><8yR|r6a zhhosKXxp~KlcNBq3H1{xr1|k$`zY*jg2J{TAX8h{LI5O^{+(zwsMcsS8lvQBpaM#v zxv+94r0Cq5rL%hNc@G+=sgcYv`8)cuRqGpk{G6SnZry#f{{5?47rD%R_s4$l@*bdC zs%*^Xjt;imJA-4K1-Q+!m7{~t+vihi+1923F3!GarYjf{Ju1WRng)KABA&l)quEwq zS<=H)AKwXHHEcg#>s2xWe%)Bb>+0FG3y`8p4Qou>?D@1sKKceo`6+&Y`r(pCpCcB^ i9x_o%SAAyx$@l{r{hQSsZ4&SR0000?~HMP zlqnVy7j61h|G}I8UjD!bSauLu>>XX5BC;NTV3t&fQIdcg1>LcrR@>h(QgY2t+a)PL zOab~&gN`RS&EK%TviWxmV(A;ykyK}JqH6w=*yJW>l2ce1%v@ZX2d!c7H>?e681PTA zT1a0rX-X@#9AjDNvd}beQ>6Qd>2^uZAbcKnLP9;^($0JQ0I!*x`CZU6uP07*qoM6N<$f~E4i6#xJL literal 0 HcmV?d00001 diff --git a/assets/images/copy.png b/assets/images/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..41d18c74a0b99de8c18439415788a8445abda013 GIT binary patch literal 556 zcmV+{0@MA8P)wIWn%g(fD}TAL6*D#8O`?GbBv0O+|yK9n72 zK!^|pyc~oOf63lt=l1S@A2YkN2+VdOYiPhrLI{8dAfo+zmp=OGoKFsI00TS{6Nt$8 z?VNA0i)A2P1eYK|fE1=ijTsWuW&0s>11>Q3uYdr8*lLaxR4`)}U(g3rLjEbUsaj35 z=OabV)C5^bjLCs?(syd4S$|TT+8gTS6)`q78;2bwR4D|qrY2arJr+}yW+ux4YittTT$&%OA~tqv#>+ zH-l^wktkKzG^o@f3WH#B_DpuzgEg(B!Ph_@?`XvJ@E$h1{$;=8#|hFX%0rDcBD}|H z3C3KHdGWEuhqf1-`_Vrs%0o?>6$`Y-5AF!J+A)H4C^FI{2$#pD;I&3KAi&KyL6V|X z)Yy3>`sZEp0rThCh}#N!a_Hof{f2EGo0g~` zl!a)1xNCXN@+Ym3mf+jx_u<_KkH0b+harCfO;5Sc4D$MK)cyc(BT55Ktj1aZ0000< KMNUMnLSTY+8cK%% literal 0 HcmV?d00001 diff --git a/assets/images/food.png b/assets/images/food.png new file mode 100644 index 0000000000000000000000000000000000000000..64d7a76a7797f7966eb0f80457d0c6665df6e788 GIT binary patch literal 710 zcmV;%0y+JOP)jvkLddj;!YL62E)qnCL88A1g3{qUf#U*V)8x{`5e?=ENV;&2#L4EJYEvK_pKpiR#Wri>Q;dZoMan0w?soomXLjdXfXfa%&tP%2nn$wkf|NO)X0;Py zOM*NB0e-Mw@uyl~X{B1=Pm!bLdZLgB!r#A?K|_v!&XK#ZH_-w)+sVw#&b=J_|aNod!$+Lg0hP(3A~jAY&}4n?lNXiowBs zlClIB34KlPJ=wNNh$8$~h|+QWWmA`kL~81?Bn_0^EL;S}c5*sB$K}#g;Ehs%=B8L* zXNV%&YI>D$oNuS|b4_(MnvNjVIZ&R38BS9vRcD5rv(HzUxrh`sGgSUgwD73<_*$qxUa9_I3AHt669R92(^?p?syJtgz^V+Ej4xhr$Pqrmj@C zd)5S%zKm@%z~O!+ObXuj6_(UKy%_LU27|MBy1kA1V5dZ)H07*qoM6N<$g5!TbA^-pY literal 0 HcmV?d00001 diff --git a/assets/images/gaming.png b/assets/images/gaming.png new file mode 100644 index 0000000000000000000000000000000000000000..63be2322f58c3486142964d54de2637c3fbe8252 GIT binary patch literal 705 zcmV;y0zUnTP)xK~#7F?NmW> z5>XKC{=)#4S>Or65wMDsF2f1LDudZ+P7vG}VFNqRP4EQj7Gen}$hb7b%H#%fgxLv% z`EB=SW-`DCaixpAD&YTN{(GTWZZ=`> zpz|6KPkHIz%qEZBVcQr}(F>Vu{+NG?NUHL;1zFHID@JbrVO&%bvtD5N9HsU2CNZ%z2Nx5t@^7WK2 zH_%)Q1YUFin4I|X%>JNzt`J8*`X{|K0Se|AD9fyc!{VFGLrw&IBQTfTOE{r`8~{|N z0w^R2Qd}z{6w%AGW-N?a`Ct(Qwsjl9D#8Q3GCLQM@!(x$lj|guntK2ikiuQwx<)_; zP?WGT^F;)=EP6yKBncC1uq@@9bnE$}I0A*LAjz1()V~YKCQK$%pZh7~j8miw(sNOB zB(WG9rtFFBSyZld+FYA0 z6FQMEjVJ4eJXf5Q)I}mPzd+K*9n5US#aeUfn)M|ClM)_SgevS@eZ4cA_Wne^Bx2{P nW;`JJob%yD|KtnYK+Fpgfut6H(-Dmv9ogmoP`5oLvjMb25dH%1+rlSIRVa+7zyVDnQY?#&I#h2 zfQb+yA9oj3-JXeUk`N)FWXUr<^{-d20sgVz?-YvF*AXZvfwd987Mu}cLWuG4p;_;D z>w`ZEFnhmQMuX2;qdYnq5W&DdQpiCf{|GUFdfe+g>0dZNapBb)wp=EFgcU;8YWLpP zmL_$YZ8m=>fu%CmYizC6UQGK^zc|0wdMD0->TQR zJtx5ILhCtuTSPYKb)GGwc72E8;rGpHLt}o#-h+y^*Xj)k!j&%i>Q5wVQSku}b7;Sa z^DQ>;0h=EYR6#2{?*gSmHagMTB*x+f66|bsY85%ZRc*ah2#>g`x$O`2cp|`TwY7l= z1yZzxq^~1U*#ec_49*vx6UKrnOT#?{>K%8X-|N)ovH;QjW+7*Bcgk=8k};#i0Ij-i zj8H_RLQ*!=GGs;>5%n`vr9$e4AS;mPz^aOjl7DS$as4Xy_aJ{M<%E2+r=iwmvrZ1P3wiQ7aA(dIh2VhK;I6p4yX$Hk&^<@E2^9+NpZ~=+CjfDRM zS>577tHdq7?yC0XV90eR6BTOZdvAvOXE{~cw`>^r;b*oqYA>9!2Y*{!07Tm3VHL(d z1b)b1r_RTPj(wGgdZKz1DgoFZ`H|VGJ|8}mO|)p&&CyW|7Z5>`xYNfbi7Jgy&&-q+ zsk?j>S-{mS0Mam)fyyQ_ju+KTo;Q{H0%C!ppoObUP@&7z8ef(6Tm)tYH*%0KjMDcV zII`Wb*KE-u%lxAPyvEq(C38?achQ6p*-gxQ5R)-GoP;deAYHWJL{qo~Hy9bE-uowy z`DDdY1xEUa=1fk9M8*XRhJHRK2OkyS-d5+SqY}qu4n1l<0T`klsLJag%J4)a;B#ko+nuDL$;JOzU|{kk*=ZdVh%RwBDIlCR0Vk7tI?? zuS_O1R!>G&1sH8A$(70TOX>N-0aCfcOlwHr6;!dKaAY)KlInwn&9m;}|0X{HyC#$D Tc$VBx00000NkvXXu0mjftyIGc literal 0 HcmV?d00001 diff --git a/assets/images/tshirt.png b/assets/images/tshirt.png new file mode 100644 index 0000000000000000000000000000000000000000..cb20f60a49c01d7ba5e1f9a13bb3b4d385e02732 GIT binary patch literal 583 zcmV-N0=WH&P)*?Ih<8VlB=f&=Z zmpinaeXWz11mN)Jv!f@Ztn%Ngg>34CGg4URn9g88Wq`hK=$)e6o{96V%5^!FyAeGQ z((5!>Xy5acp||ga94PR12fc%K#pXchPwv@%wi}#@D>jD4PeUia#^mG*`D{+&yQL`0 z`f_M*B<~a-_E_f+hXbea;`k7Q$ngMBUCBoQWm_zZvu!@v-M5OJmlA`L9t7a=!P0tL z8W!s`apXS_RzNye%1fEH`sAvx1O!|Lyp(>xZvew7d~9-~$5TY)vaqi8&O_?7^uY}> zfjGsrNWo-ZC-jYOFJdJ>l0`t^aWgHRf+CEK)`dCt7CkO0pGQR V%fPaw->v`v002ovPDHLkV1k2F1m^$% literal 0 HcmV?d00001 diff --git a/lib/anypay/any_pay_payment.dart b/lib/anypay/any_pay_payment.dart index 4c8dff2d6d..295ee2dc48 100644 --- a/lib/anypay/any_pay_payment.dart +++ b/lib/anypay/any_pay_payment.dart @@ -1,3 +1,6 @@ +import 'package:cake_wallet/anypay/any_pay_chain.dart'; +import 'package:cw_bitcoin/bitcoin_amount_format.dart'; +import 'package:cw_core/monero_amount_format.dart'; import 'package:flutter/foundation.dart'; import 'package:cake_wallet/anypay/any_pay_payment_instruction.dart'; @@ -35,4 +38,27 @@ class AnyPayPayment { final String chain; final String network; final List instructions; + + String get totalAmount { + final total = instructions + .fold(0, (int acc, instruction) => acc + instruction.outputs + .fold(0, (int outAcc, out) => outAcc + out.amount)); + switch (chain) { + case AnyPayChain.xmr: + return moneroAmountToString(amount: total); + case AnyPayChain.btc: + return bitcoinAmountToString(amount: total); + case AnyPayChain.ltc: + return bitcoinAmountToString(amount: total); + default: + return null; + } + } + + List get outAddresses { + return instructions + .map((instuction) => instuction.outputs.map((out) => out.address)) + .expand((e) => e) + .toList(); + } } \ No newline at end of file diff --git a/lib/di.dart b/lib/di.dart index 93eedfe99c..a478be3280 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,6 +1,10 @@ import 'package:cake_wallet/core/yat_service.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/wake_lock.dart'; +import 'package:cake_wallet/ionia/ionia_anypay.dart'; +import 'package:cake_wallet/ionia/ionia_category.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart'; import 'package:cake_wallet/ionia/ionia_service.dart'; import 'package:cake_wallet/ionia/ionia_api.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; @@ -8,9 +12,14 @@ import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart'; +import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart'; +import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart'; import 'package:cake_wallet/src/screens/ionia/ionia.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cake_wallet/core/backup_service.dart'; import 'package:cw_core/wallet_service.dart'; @@ -107,6 +116,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_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:flutter/widgets.dart'; import 'package:get_it/get_it.dart'; import 'package:hive/hive.dart'; @@ -263,7 +273,6 @@ Future setup( fiatConvertationStore: getIt.get())); getIt.registerFactory(() => DashboardViewModel( - balanceViewModel: getIt.get(), appStore: getIt.get(), tradesStore: getIt.get(), @@ -564,10 +573,6 @@ Future setup( getIt.registerFactory(() => BackupPage(getIt.get())); - getIt.registerFactory(() => EditBackupPasswordViewModel( - getIt.get(), getIt.get()) - ..init()); - getIt.registerFactory( () => EditBackupPasswordPage(getIt.get())); @@ -600,10 +605,7 @@ Future setup( final url = args.first as String; final buyViewModel = args[1] as BuyViewModel; - return BuyWebViewPage( - buyViewModel: buyViewModel, - ordersStore: getIt.get(), - url: url); + return BuyWebViewPage(buyViewModel: buyViewModel, ordersStore: getIt.get(), url: url); }); getIt.registerFactoryParam((order, _) { @@ -656,41 +658,65 @@ Future setup( getIt.registerFactory( () => IoniaService(getIt.get(), getIt.get())); + + getIt.registerFactory( + () => IoniaAnyPay( + getIt.get(), + getIt.get(), + getIt.get().wallet)); - getIt.registerFactory( - () => IoniaViewModel(ioniaService: getIt.get())); + getIt.registerFactory(() => IoniaFilterViewModel()); + + getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get())); + + getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get())); + + getIt.registerFactory(() => IoniaMerchPurchaseViewModel(getIt.get())); - getIt.registerFactory(() => IoniaCreateAccountPage(getIt.get())); + getIt.registerFactory(() => IoniaAccountViewModel(ioniaService: getIt.get())); - getIt.registerFactory(() => IoniaLoginPage(getIt.get())); + getIt.registerFactory(() => IoniaCreateAccountPage(getIt.get())); + + getIt.registerFactory(() => IoniaLoginPage(getIt.get())); getIt.registerFactoryParam((List args, _) { final email = args.first as String; - final ioniaViewModel = args[1] as IoniaViewModel; + final ioniaAuthViewModel = args[1] as IoniaAuthViewModel; - return IoniaVerifyIoniaOtp(ioniaViewModel, email); + return IoniaVerifyIoniaOtp(ioniaAuthViewModel, email); }); - getIt.registerFactory(() => IoniaWelcomePage(getIt.get())); + getIt.registerFactory(() => IoniaWelcomePage(getIt.get())); getIt.registerFactoryParam((List args, _) { final merchant = args.first as IoniaMerchant; - return IoniaBuyGiftCardPage(merchant); - }); + return IoniaBuyGiftCardPage(getIt.get(), merchant); + }); getIt.registerFactoryParam((List args, _) { - final merchant = args.first as IoniaMerchant; + final amount = args.first as String; + final merchant = args.last as IoniaMerchant; - return IoniaBuyGiftCardDetailPage(merchant); + return IoniaBuyGiftCardDetailPage(amount, getIt.get(), merchant); }); - getIt.registerFactory(() => IoniaManageCardsPage(getIt.get())); + getIt.registerFactoryParam((List args, _) { + final amount = args.first as String; + final merchant = args.last as IoniaMerchant; + + return IoniaCustomTipPage(getIt.get(), amount, merchant); + }); + + getIt.registerFactory(() => IoniaManageCardsPage(getIt.get())); + + getIt.registerFactory(() => IoniaDebitCardPage(getIt.get())); - getIt.registerFactory(() => IoniaDebitCardPage(getIt.get())); + getIt.registerFactory(() => IoniaActivateDebitCardPage(getIt.get())); - getIt.registerFactory(() => IoniaActivateDebitCardPage(getIt.get())); + getIt.registerFactory(() => IoniaAccountPage(getIt.get())); + getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get())); _isSetupFinished = true; } diff --git a/lib/ionia/ionia_anypay.dart b/lib/ionia/ionia_anypay.dart index b84dbf64df..d4b6d4d59d 100644 --- a/lib/ionia/ionia_anypay.dart +++ b/lib/ionia/ionia_anypay.dart @@ -23,12 +23,11 @@ class IoniaAnyPay { Future purchase({ @required String merchId, - @required double amount, - @required String currency}) async { + @required double amount}) async { final invoice = await ioniaService.purchaseGiftCard( merchId: merchId, amount: amount, - currency: currency); + currency: wallet.currency.title.toUpperCase()); return anyPayApi.paymentRequest(invoice.uri); } diff --git a/lib/ionia/ionia_category.dart b/lib/ionia/ionia_category.dart index 28d48d2b85..4b94d70cfe 100644 --- a/lib/ionia/ionia_category.dart +++ b/lib/ionia/ionia_category.dart @@ -1,20 +1,18 @@ class IoniaCategory { - const IoniaCategory(this.title, this.ids); + const IoniaCategory({this.index, this.title, this.ids, this.iconPath}); - static const allCategories = [ - apparel, - onlineOnly, - food, - entertainment, - delivery, - travel]; - static const apparel = IoniaCategory('Apparel', [1]); - static const onlineOnly = IoniaCategory('Online Only', [13, 43]); - static const food = IoniaCategory('Food', [4]); - static const entertainment = IoniaCategory('Entertainment', [5]); - static const delivery = IoniaCategory('Delivery', [114, 109]); - static const travel = IoniaCategory('Travel', [12]); + static const allCategories = [all, apparel, onlineOnly, food, entertainment, delivery, travel]; + static const all = IoniaCategory(index: 0, title: 'All', ids: [], iconPath: 'assets/images/category.png'); + static const apparel = IoniaCategory(index: 1, title: 'Apparel', ids: [1], iconPath: 'assets/images/tshirt.png'); + static const onlineOnly = IoniaCategory(index: 2, title: 'Online Only', ids: [13, 43], iconPath: 'assets/images/global.png'); + static const food = IoniaCategory(index: 3, title: 'Food', ids: [4], iconPath: 'assets/images/food.png'); + static const entertainment = IoniaCategory(index: 4, title: 'Entertainment', ids: [5], iconPath: 'assets/images/gaming.png'); + static const delivery = IoniaCategory(index: 5, title: 'Delivery', ids: [114, 109], iconPath: 'assets/images/delivery.png'); + static const travel = IoniaCategory(index: 6, title: 'Travel', ids: [12], iconPath: 'assets/images/airplane.png'); - final String title; - final List ids; + + final int index; + final String title; + final List ids; + final String iconPath; } diff --git a/lib/ionia/ionia_merchant.dart b/lib/ionia/ionia_merchant.dart index 38758dc0e6..92ce0c03f6 100644 --- a/lib/ionia/ionia_merchant.dart +++ b/lib/ionia/ionia_merchant.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart'; - -class IoniaMerchant { +class IoniaMerchant { IoniaMerchant({ @required this.id, @required this.legalName, @@ -114,57 +113,49 @@ class IoniaMerchant { isPayLater: element["IsPayLater"] as bool); } - final int id; - final String legalName; - final String systemName; - final String description; - final String website; - final String termsAndConditions; - final String logoUrl; - final String cardImageUrl; - final String cardholderAgreement; - final double purchaseFee; - final double revenueShare; - final double marketingFee; - final double minimumDiscount; - final double level1; - final double level2; - final double level3; - final double level4; - final double level5; - final double level6; - final double level7; - final bool isActive; - final bool isDeleted; - final bool isOnline; - final bool isPhysical; - final bool isVariablePurchase; - final double minimumCardPurchase; - final double maximumCardPurchase; - final bool acceptsTips; - final String createdDateFormatted; - final int createdBy; - final bool isRegional; - final String modifiedDateFormatted; - final int modifiedBy; - final String usageInstructions; - final String usageInstructionsBak; - final int paymentGatewayId; - final int giftCardGatewayId; - final bool isHtmlDescription; - final String purchaseInstructions; - final String balanceInstructions; - final double amountPerCard; - final String processingMessage; - final bool hasBarcode; - final bool hasInventory; - final bool isVoidable; - final String receiptMessage; - final String cssBorderCode; - final String paymentInstructions; - final String alderSku; - final String ngcSku; - final String acceptedCurrency; - final String deepLink; - final bool isPayLater; + final int id; final String legalName; final String systemName; final String description; final String website; final String termsAndConditions; final String logoUrl; final String cardImageUrl; final String cardholderAgreement; final double purchaseFee; + final double revenueShare; + final double marketingFee; + final double minimumDiscount; + final double level1; + final double level2; + final double level3; + final double level4; + final double level5; + final double level6; + final double level7; + final bool isActive; + final bool isDeleted; + final bool isOnline; + final bool isPhysical; + final bool isVariablePurchase; + final double minimumCardPurchase; + final double maximumCardPurchase; + final bool acceptsTips; + final String createdDateFormatted; + final int createdBy; + final bool isRegional; + final String modifiedDateFormatted; + final int modifiedBy; + final String usageInstructions; + final String usageInstructionsBak; + final int paymentGatewayId; + final int giftCardGatewayId; + final bool isHtmlDescription; + final String purchaseInstructions; + final String balanceInstructions; + final double amountPerCard; + final String processingMessage; + final bool hasBarcode; + final bool hasInventory; + final bool isVoidable; + final String receiptMessage; + final String cssBorderCode; + final String paymentInstructions; + final String alderSku; + final String ngcSku; + final String acceptedCurrency; + final String deepLink; + final bool isPayLater; + } diff --git a/lib/ionia/ionia_service.dart b/lib/ionia/ionia_service.dart index d0500d66dd..19574e997d 100644 --- a/lib/ionia/ionia_service.dart +++ b/lib/ionia/ionia_service.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/ionia/ionia_category.dart'; class IoniaService { IoniaService(this.secureStorage, this.ioniaApi); + static const ioniaEmailStorageKey = 'ionia_email'; static const ioniaUsernameStorageKey = 'ionia_username'; static const ioniaPasswordStorageKey = 'ionia_password'; @@ -22,6 +23,7 @@ class IoniaService { Future createUser(String email) async { final username = await ioniaApi.createUser(email, clientId: clientId); + await secureStorage.write(key: ioniaEmailStorageKey, value: email); await secureStorage.write(key: ioniaUsernameStorageKey, value: username); } @@ -33,6 +35,10 @@ class IoniaService { await secureStorage.write(key: ioniaPasswordStorageKey, value: credentials.password); } + Future getUserEmail() async { + return secureStorage.read(key: ioniaEmailStorageKey); + } + // Check is user logined Future isLogined() async { diff --git a/lib/ionia/ionia_tip.dart b/lib/ionia/ionia_tip.dart new file mode 100644 index 0000000000..fbd6e4e4c3 --- /dev/null +++ b/lib/ionia/ionia_tip.dart @@ -0,0 +1,12 @@ +class IoniaTip { + const IoniaTip({this.originalAmount, this.percentage}); + final double originalAmount; + final double percentage; + double get additionalAmount => originalAmount * percentage / 100; + + static const tipList = [ + IoniaTip(originalAmount: 0, percentage: 0), + IoniaTip(originalAmount: 10, percentage: 10), + IoniaTip(originalAmount: 20, percentage: 20) + ]; +} diff --git a/lib/main.dart b/lib/main.dart index b9f8074e78..4d0ea62083 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/language_service.dart'; import 'package:cake_wallet/buy/order.dart'; +import 'package:cake_wallet/ionia/ionia_category.dart'; +import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -174,7 +176,8 @@ Future initialSetup( exchangeTemplates: exchangeTemplates, transactionDescriptionBox: transactionDescriptions, ordersSource: ordersSource, - unspentCoinsInfoSource: unspentCoinsInfoSource); + unspentCoinsInfoSource: unspentCoinsInfoSource, + ); await bootstrap(navigatorKey); monero?.onStartup(); } diff --git a/lib/router.dart b/lib/router.dart index 27ee246038..2f336a6c5b 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -5,6 +5,9 @@ import 'package:cake_wallet/src/screens/backup/backup_page.dart'; import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart'; import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart'; import 'package:cake_wallet/src/screens/buy/pre_order_page.dart'; +import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart'; +import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart'; +import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart'; import 'package:cake_wallet/src/screens/order_details/order_details_page.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart'; @@ -433,6 +436,16 @@ Route createRoute(RouteSettings settings) { case Routes.ioniaActivateDebitCardPage: return CupertinoPageRoute(builder: (_) => getIt.get()); + case Routes.ioniaAccountPage: + return CupertinoPageRoute(builder: (_) => getIt.get()); + + case Routes.ioniaAccountCardsPage: + return CupertinoPageRoute(builder: (_) => getIt.get()); + + case Routes.ioniaCustomTipPage: + final args = settings.arguments as List; + return CupertinoPageRoute(builder: (_) =>getIt.get(param1: args)); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index 4ab4c0b26f..cc4ca71143 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -69,5 +69,8 @@ class Routes { static const ioniaVerifyIoniaOtpPage = '/cake_pay_verify_otp_page'; static const ioniaDebitCardPage = '/debit_card_page'; static const ioniaActivateDebitCardPage = '/activate_debit_card_page'; + static const ioniaAccountPage = 'ionia_account_page'; + static const ioniaAccountCardsPage = 'ionia_account_cards_page'; + static const ioniaCustomTipPage = 'ionia_custom_tip_page'; } diff --git a/lib/src/screens/ionia/auth/ionia_create_account_page.dart b/lib/src/screens/ionia/auth/ionia_create_account_page.dart index e0c0ecb35d..3466df5d59 100644 --- a/lib/src/screens/ionia/auth/ionia_create_account_page.dart +++ b/lib/src/screens/ionia/auth/ionia_create_account_page.dart @@ -8,22 +8,22 @@ import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; class IoniaCreateAccountPage extends BasePage { - IoniaCreateAccountPage(this._ioniaViewModel) + IoniaCreateAccountPage(this._authViewModel) : _emailFocus = FocusNode(), _emailController = TextEditingController(), _formKey = GlobalKey() { - _emailController.text = _ioniaViewModel.email; - _emailController.addListener(() => _ioniaViewModel.email = _emailController.text); + _emailController.text = _authViewModel.email; + _emailController.addListener(() => _authViewModel.email = _emailController.text); } - final IoniaViewModel _ioniaViewModel; + final IoniaAuthViewModel _authViewModel; final GlobalKey _formKey; @@ -42,12 +42,12 @@ class IoniaCreateAccountPage extends BasePage { @override Widget body(BuildContext context) { - reaction((_) => _ioniaViewModel.createUserState, (IoniaCreateAccountState state) { + reaction((_) => _authViewModel.createUserState, (IoniaCreateAccountState state) { if (state is IoniaCreateStateFailure) { _onCreateUserFailure(context, state.error); } if (state is IoniaCreateStateSuccess) { - _onCreateSuccessful(context, _ioniaViewModel); + _onCreateSuccessful(context, _authViewModel); } }); @@ -59,6 +59,7 @@ class IoniaCreateAccountPage extends BasePage { hintText: S.of(context).email_address, focusNode: _emailFocus, validator: EmailValidator(), + keyboardType: TextInputType.emailAddress, controller: _emailController, ), ), @@ -75,9 +76,9 @@ class IoniaCreateAccountPage extends BasePage { if (!_formKey.currentState.validate()) { return; } - await _ioniaViewModel.createUser(_emailController.text); + await _authViewModel.createUser(_emailController.text); }, - isLoading: _ioniaViewModel.createUserState is IoniaCreateStateLoading, + isLoading: _authViewModel.createUserState is IoniaCreateStateLoading, color: Theme.of(context).accentTextTheme.body2.color, textColor: Colors.white, ), @@ -133,9 +134,9 @@ class IoniaCreateAccountPage extends BasePage { }); } - void _onCreateSuccessful(BuildContext context, IoniaViewModel ioniaViewModel) => Navigator.pushNamed( + void _onCreateSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed( context, Routes.ioniaVerifyIoniaOtpPage, - arguments: [ioniaViewModel.email, ioniaViewModel], + arguments: [authViewModel.email, authViewModel], ); } diff --git a/lib/src/screens/ionia/auth/ionia_login_page.dart b/lib/src/screens/ionia/auth/ionia_login_page.dart index b47944f4d5..f7f0023572 100644 --- a/lib/src/screens/ionia/auth/ionia_login_page.dart +++ b/lib/src/screens/ionia/auth/ionia_login_page.dart @@ -8,23 +8,23 @@ import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; class IoniaLoginPage extends BasePage { - IoniaLoginPage(this._ioniaViewModel) + IoniaLoginPage(this._authViewModel) : _formKey = GlobalKey(), _emailController = TextEditingController() { - _emailController.text = _ioniaViewModel.email; - _emailController.addListener(() => _ioniaViewModel.email = _emailController.text); + _emailController.text = _authViewModel.email; + _emailController.addListener(() => _authViewModel.email = _emailController.text); } final GlobalKey _formKey; - final IoniaViewModel _ioniaViewModel; + final IoniaAuthViewModel _authViewModel; @override Color get titleColor => Colors.black; @@ -43,12 +43,12 @@ class IoniaLoginPage extends BasePage { @override Widget body(BuildContext context) { - reaction((_) => _ioniaViewModel.createUserState, (IoniaCreateAccountState state) { + reaction((_) => _authViewModel.createUserState, (IoniaCreateAccountState state) { if (state is IoniaCreateStateFailure) { _onLoginUserFailure(context, state.error); } if (state is IoniaCreateStateSuccess) { - _onLoginSuccessful(context, _ioniaViewModel); + _onLoginSuccessful(context, _authViewModel); } }); return ScrollableWithBottomSection( @@ -57,6 +57,7 @@ class IoniaLoginPage extends BasePage { key: _formKey, child: BaseTextFormField( hintText: S.of(context).email_address, + keyboardType: TextInputType.emailAddress, validator: EmailValidator(), controller: _emailController, ), @@ -74,9 +75,9 @@ class IoniaLoginPage extends BasePage { if (!_formKey.currentState.validate()) { return; } - await _ioniaViewModel.createUser(_emailController.text); + await _authViewModel.createUser(_emailController.text); }, - isLoading: _ioniaViewModel.createUserState is IoniaCreateStateLoading, + isLoading: _authViewModel.createUserState is IoniaCreateStateLoading, color: Theme.of(context).accentTextTheme.body2.color, textColor: Colors.white, ), @@ -103,9 +104,9 @@ class IoniaLoginPage extends BasePage { }); } - void _onLoginSuccessful(BuildContext context, IoniaViewModel ioniaViewModel) => Navigator.pushNamed( + void _onLoginSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed( context, Routes.ioniaVerifyIoniaOtpPage, - arguments: [ioniaViewModel.email, ioniaViewModel], + arguments: [authViewModel.email, authViewModel], ); } diff --git a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart index fb254f71d0..909d8b89a3 100644 --- a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart +++ b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart @@ -4,33 +4,34 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; +import 'package:cake_wallet/src/widgets/keyboard_done_button.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/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:mobx/mobx.dart'; class IoniaVerifyIoniaOtp extends BasePage { - - IoniaVerifyIoniaOtp(this._ioniaViewModel, this._email) + IoniaVerifyIoniaOtp(this._authViewModel, this._email) : _codeController = TextEditingController(), _codeFocus = FocusNode() { _codeController.addListener(() { final otp = _codeController.text; - _ioniaViewModel.otp = otp; + _authViewModel.otp = otp; if (otp.length > 3) { - _ioniaViewModel.otpState = IoniaOtpSendEnabled(); + _authViewModel.otpState = IoniaOtpSendEnabled(); } else { - _ioniaViewModel.otpState = IoniaOtpSendDisabled(); + _authViewModel.otpState = IoniaOtpSendDisabled(); } }); } - final IoniaViewModel _ioniaViewModel; + final IoniaAuthViewModel _authViewModel; final String _email; @@ -49,7 +50,7 @@ class IoniaVerifyIoniaOtp extends BasePage { @override Widget body(BuildContext context) { - reaction((_) => _ioniaViewModel.otpState, (IoniaOtpState state) { + reaction((_) => _authViewModel.otpState, (IoniaOtpState state) { if (state is IoniaOtpFailure) { _onOtpFailure(context, state.error); } @@ -57,57 +58,74 @@ class IoniaVerifyIoniaOtp extends BasePage { _onOtpSuccessful(context); } }); - return ScrollableWithBottomSection( - contentPadding: EdgeInsets.all(24), - content: Column( - children: [ - BaseTextFormField( - hintText: S.of(context).enter_code, - focusNode: _codeFocus, - controller: _codeController, - ), - SizedBox(height: 14), - Text( - S.of(context).fill_code, - style: TextStyle(color: Color(0xff7A93BA), fontSize: 12), - ), - SizedBox(height: 34), - Row( - mainAxisAlignment: MainAxisAlignment.center, + return KeyboardActions( + config: KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: _codeFocus, + toolbarButtons: [(_) => KeyboardDoneButton()], + ), + ]), + child: Container( + height: 0, + color: Theme.of(context).backgroundColor, + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.all(24), + content: Column( children: [ - Text(S.of(context).dont_get_code), - SizedBox(width: 20), - InkWell( - onTap: () => _ioniaViewModel.createUser(_email), - child: Text( - S.of(context).resend_code, - style: textSmallSemiBold(color: Palette.blueCraiola), - ), + BaseTextFormField( + hintText: S.of(context).enter_code, + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + focusNode: _codeFocus, + controller: _codeController, + ), + SizedBox(height: 14), + Text( + S.of(context).fill_code, + style: TextStyle(color: Color(0xff7A93BA), fontSize: 12), + ), + SizedBox(height: 34), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(S.of(context).dont_get_code), + SizedBox(width: 20), + InkWell( + onTap: () => _authViewModel.createUser(_email), + child: Text( + S.of(context).resend_code, + style: textSmallSemiBold(color: Palette.blueCraiola), + ), + ), + ], ), ], ), - ], - ), - bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24), - bottomSection: Column( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Observer( - builder: (_) => LoadingPrimaryButton( - text: S.of(context).continue_text, - onPressed: () async => await _ioniaViewModel.verifyEmail(_codeController.text), - isDisabled: _ioniaViewModel.otpState is IoniaOtpSendDisabled, - isLoading: _ioniaViewModel.otpState is IoniaOtpValidating, - color: Theme.of(context).accentTextTheme.body2.color, - textColor: Colors.white, - ), + bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24), + bottomSection: Column( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Observer( + builder: (_) => LoadingPrimaryButton( + text: S.of(context).continue_text, + onPressed: () async => await _authViewModel.verifyEmail(_codeController.text), + isDisabled: _authViewModel.otpState is IoniaOtpSendDisabled, + isLoading: _authViewModel.otpState is IoniaOtpValidating, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ), + ), + SizedBox(height: 20), + ], ), - SizedBox(height: 20), ], ), - ], + ), ), ); } diff --git a/lib/src/screens/ionia/auth/ionia_welcome_page.dart b/lib/src/screens/ionia/auth/ionia_welcome_page.dart index 85e06faae9..95ee129470 100644 --- a/lib/src/screens/ionia/auth/ionia_welcome_page.dart +++ b/lib/src/screens/ionia/auth/ionia_welcome_page.dart @@ -3,14 +3,14 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/typography.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/framework.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:mobx/mobx.dart'; class IoniaWelcomePage extends BasePage { - IoniaWelcomePage(this._ioniaViewModel); + IoniaWelcomePage(this._cardsListViewModel); @override Widget middle(BuildContext context) { @@ -22,11 +22,11 @@ class IoniaWelcomePage extends BasePage { ); } - final IoniaViewModel _ioniaViewModel; + final IoniaGiftCardsListViewModel _cardsListViewModel; @override Widget body(BuildContext context) { - reaction((_) => _ioniaViewModel.isLoggedIn, (bool state) { + reaction((_) => _cardsListViewModel.isLoggedIn, (bool state) { if (state) { Navigator.pushReplacementNamed(context, Routes.ioniaManageCardsPage); } diff --git a/lib/src/screens/ionia/cards/ionia_account_cards_page.dart b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart new file mode 100644 index 0000000000..a0d02e4f4b --- /dev/null +++ b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart @@ -0,0 +1,118 @@ +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/typography.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class IoniaAccountCardsPage extends BasePage { + IoniaAccountCardsPage(this.ioniaAccountViewModel); + + final IoniaAccountViewModel ioniaAccountViewModel; + + @override + Widget middle(BuildContext context) { + return Text( + S.of(context).cards, + style: textLargeSemiBold( + color: Theme.of(context).accentTextTheme.display4.backgroundColor, + ), + ); + } + + @override + Widget body(BuildContext context) { + return _IoniaCardTabs(); + } +} + +class _IoniaCardTabs extends StatefulWidget { + @override + _IoniaCardTabsState createState() => _IoniaCardTabsState(); +} + +class _IoniaCardTabsState extends State<_IoniaCardTabs> with SingleTickerProviderStateMixin { + TabController _tabController; + + @override + void initState() { + _tabController = TabController(length: 2, vsync: this); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + _tabController.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 45, + width: 230, + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + color: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), + borderRadius: BorderRadius.circular( + 25.0, + ), + ), + child: Theme( + data: ThemeData( + primaryTextTheme: TextTheme( + body2: TextStyle(backgroundColor: Colors.transparent) + ) + ), + child: TabBar( + controller: _tabController, + indicator: BoxDecoration( + borderRadius: BorderRadius.circular( + 25.0, + ), + color: Theme.of(context).accentTextTheme.body2.color, + ), + labelColor: Theme.of(context).primaryTextTheme.display4.backgroundColor, + unselectedLabelColor: Theme.of(context).primaryTextTheme.title.color, + tabs: [ + Tab( + text: S.of(context).active, + ), + Tab( + text: S.of(context).redeemed, + ), + ], + ), + ), + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + Center( + child: Text( + S.of(context).gift_card_balance_note, + textAlign: TextAlign.center, + style: textSmall(color: Theme.of(context).primaryTextTheme.overline.color,), + ), + ), + + Center( + child: Text( + S.of(context).gift_card_redeemed_note, + textAlign: TextAlign.center, + style: textSmall(color: Theme.of(context).primaryTextTheme.overline.color,), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/ionia/cards/ionia_account_page.dart b/lib/src/screens/ionia/cards/ionia_account_page.dart new file mode 100644 index 0000000000..2612b2c666 --- /dev/null +++ b/lib/src/screens/ionia/cards/ionia_account_page.dart @@ -0,0 +1,180 @@ +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.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/typography.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class IoniaAccountPage extends BasePage { + IoniaAccountPage(this.ioniaAccountViewModel); + + final IoniaAccountViewModel ioniaAccountViewModel; + + @override + Widget middle(BuildContext context) { + return Text( + S.current.account, + style: textLargeSemiBold( + color: Theme.of(context).accentTextTheme.display4.backgroundColor, + ), + ); + } + + @override + Widget body(BuildContext context) { + final deviceWidth = MediaQuery.of(context).size.width; + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.all(24), + content: Column( + children: [ + _GradiantContainer( + content: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Observer(builder: (_) => + RichText( + text: TextSpan( + text: '${ioniaAccountViewModel.countOfMerch}', + style: textLargeSemiBold(), + children: [ + TextSpan( + text: ' ${S.of(context).active_cards}', + style: textSmall(color: Colors.white.withOpacity(0.7))), + ], + ), + )), + InkWell( + onTap: () => Navigator.pushNamed(context, Routes.ioniaAccountCardsPage), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + S.of(context).view_all, + style: textSmallSemiBold(), + ), + ), + ) + ], + ), + ), + SizedBox(height: 8), + //Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // _GradiantContainer( + // padding: EdgeInsets.all(16), + // width: deviceWidth * 0.28, + // content: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // S.of(context).total_saving, + // style: textSmall(), + // ), + // SizedBox(height: 8), + // Text( + // '\$100', + // style: textMediumSemiBold(), + // ), + // ], + // ), + // ), + // _GradiantContainer( + // padding: EdgeInsets.all(16), + // width: deviceWidth * 0.28, + // content: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // S.of(context).last_30_days, + // style: textSmall(), + // ), + // SizedBox(height: 8), + // Text( + // '\$100', + // style: textMediumSemiBold(), + // ), + // ], + // ), + // ), + // _GradiantContainer( + // padding: EdgeInsets.all(16), + // width: deviceWidth * 0.28, + // content: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // S.of(context).avg_savings, + // style: textSmall(), + // ), + // SizedBox(height: 8), + // Text( + // '10%', + // style: textMediumSemiBold(), + // ), + // ], + // ), + // ), + // ], + //), + SizedBox(height: 40), + Observer(builder: (_) => + IoniaTile( + title: S.of(context).email_address, + subTitle: ioniaAccountViewModel.email)), + Divider() + ], + ), + bottomSectionPadding: EdgeInsets.all(30), + bottomSection: Column( + children: [ + PrimaryButton( + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + text: S.of(context).logout, + onPressed: () { + ioniaAccountViewModel.logout(); + Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (route) => false); + }, + ), + ], + ), + ); + } +} + +class _GradiantContainer extends StatelessWidget { + const _GradiantContainer({ + Key key, + @required this.content, + this.padding, + this.width, + }) : super(key: key); + + final Widget content; + final EdgeInsets padding; + final double width; + + @override + Widget build(BuildContext context) { + return Container( + child: content, + width: width, + padding: padding ?? EdgeInsets.all(24), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: LinearGradient( + colors: [ + Theme.of(context).scaffoldBackgroundColor, + Theme.of(context).accentColor, + ], + begin: Alignment.topRight, + end: Alignment.bottomLeft, + ), + ), + ); + } +} diff --git a/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart b/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart index f35ddd8bb9..421b2a4fe0 100644 --- a/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart +++ b/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart @@ -7,16 +7,16 @@ import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:mobx/mobx.dart'; class IoniaActivateDebitCardPage extends BasePage { - IoniaActivateDebitCardPage(this._ioniaViewModel); + IoniaActivateDebitCardPage(this._cardsListViewModel); - final IoniaViewModel _ioniaViewModel; + final IoniaGiftCardsListViewModel _cardsListViewModel; @override Widget middle(BuildContext context) { @@ -30,7 +30,7 @@ class IoniaActivateDebitCardPage extends BasePage { @override Widget body(BuildContext context) { - reaction((_) => _ioniaViewModel.createCardState, (IoniaCreateCardState state) { + reaction((_) => _cardsListViewModel.createCardState, (IoniaCreateCardState state) { if (state is IoniaCreateCardFailure) { _onCreateCardFailure(context, state.error); } @@ -72,9 +72,9 @@ class IoniaActivateDebitCardPage extends BasePage { ), bottomSection: LoadingPrimaryButton( onPressed: () { - _ioniaViewModel.createCard(); + _cardsListViewModel.createCard(); }, - isLoading: _ioniaViewModel.createCardState is IoniaCreateCardLoading, + isLoading: _cardsListViewModel.createCardState is IoniaCreateCardLoading, text: S.of(context).agree_and_continue, color: Theme.of(context).accentTextTheme.body2.color, textColor: Colors.white, diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index 9975e095d3..0e590d5534 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -1,24 +1,40 @@ +import 'dart:ui'; + +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; +import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; +import 'package:cake_wallet/ionia/ionia_tip.dart'; import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/confirm_modal.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; +import 'package:cake_wallet/src/widgets/alert_background.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/discount_badge.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/standart_list_row.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; class IoniaBuyGiftCardDetailPage extends StatelessWidget { + IoniaBuyGiftCardDetailPage(this.amount, this.ioniaPurchaseViewModel, this.merchant) { + ioniaPurchaseViewModel.setSelectedMerchant(merchant); + } - const IoniaBuyGiftCardDetailPage(this.merchant); + final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel; final IoniaMerchant merchant; + final String amount; + ThemeBase get currentTheme => getIt.get().currentTheme; Color get backgroundLightColor => Colors.white; @@ -57,148 +73,211 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { Widget middle(BuildContext context) { return Text( - merchant.legalName, + ioniaPurchaseViewModel.ioniaMerchant.legalName, style: textLargeSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor), ); } @override Widget build(BuildContext context) { + final merchant = ioniaPurchaseViewModel.ioniaMerchant; final _backgroundColor = currentTheme.type == ThemeType.dark ? backgroundDarkColor : backgroundLightColor; + reaction((_) => ioniaPurchaseViewModel.invoiceCreationState, (ExecutionState state) { + if (state is FailureState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: state.error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + }); + } + }); + + reaction((_) => ioniaPurchaseViewModel.invoiceCommittingState, (ExecutionState state) { + if (state is FailureState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: state.error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + }); + } + + if (state is ExecutedSuccessfullyState) { + final transactionInfo = state.payload as AnyPayPaymentCommittedInfo; + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + barrierDismissible: true, + barrierColor: PaletteDark.darkNightBlue.withOpacity(0.75), + builder: (BuildContext context) { + return Center( + child: _IoniaTransactionCommitedAlert(transactionInfo: transactionInfo), + ); + }, + ); + }); + } + }); + + ioniaPurchaseViewModel.onAmountChanged(amount); return Scaffold( backgroundColor: _backgroundColor, body: ScrollableWithBottomSection( contentPadding: EdgeInsets.zero, - content: Column( - children: [ - SizedBox(height: 60), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [leading(context), middle(context), DiscountBadge(percentage: merchant.minimumDiscount,)], - ), - SizedBox(height: 36), - Container( - padding: EdgeInsets.symmetric(vertical: 24), - margin: EdgeInsets.symmetric(horizontal: 16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - gradient: LinearGradient( - colors: [ - Theme.of(context).primaryTextTheme.subhead.color, - Theme.of(context).primaryTextTheme.subhead.decorationColor, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - ), - child: Column( + content: Observer(builder: (_) { + final tipAmount = ioniaPurchaseViewModel.tipAmount; + return Column( + children: [ + SizedBox(height: 60), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - S.of(context).gift_card_amount, - style: textSmall(), - ), - SizedBox(height: 4), - Text( - '\$1000.12', - style: textXLargeSemiBold(), + leading(context), + middle(context), + DiscountBadge( + percentage: merchant.minimumDiscount, + ) + ], + ), + SizedBox(height: 36), + Container( + padding: EdgeInsets.symmetric(vertical: 24), + margin: EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient( + colors: [ + Theme.of(context).primaryTextTheme.subhead.color, + Theme.of(context).primaryTextTheme.subhead.decorationColor, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, ), - SizedBox(height: 24), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - S.of(context).bill_amount, - style: textSmall(), - ), - SizedBox(height: 4), - Text( - '\$1000.00', - style: textLargeSemiBold(), - ), - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - S.of(context).tip, - style: textSmall(), - ), - SizedBox(height: 4), - Text( - '\$1000.00', - style: textLargeSemiBold(), - ), - ], - ), - ], + ), + child: Column( + children: [ + Text( + S.of(context).gift_card_amount, + style: textSmall(), ), - ), - SizedBox(height: 16), - Divider(), - SizedBox(height: 16), - Text( - S.of(context).you_pay, - style: textSmall(), - ), - SizedBox(height: 4), - Text( - '22.3435345000 XMR', - style: textLargeSemiBold(), - ), - ], + SizedBox(height: 4), + Text( + '\$${ioniaPurchaseViewModel.giftCardAmount}', + style: textXLargeSemiBold(), + ), + SizedBox(height: 24), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).bill_amount, + style: textSmall(), + ), + SizedBox(height: 4), + Text( + '\$$amount', + style: textLargeSemiBold(), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + S.of(context).tip, + style: textSmall(), + ), + SizedBox(height: 4), + Text( + '\$$tipAmount', + style: textLargeSemiBold(), + ), + ], + ), + ], + ), + ), + ], + ), ), - ), - Padding( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - S.of(context).tip, - style: TextStyle( - color: Theme.of(context).primaryTextTheme.title.color, - fontWeight: FontWeight.w700, - fontSize: 14, + Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).tip, + style: TextStyle( + color: Theme.of(context).primaryTextTheme.title.color, + fontWeight: FontWeight.w700, + fontSize: 14, + ), ), - ), - SizedBox(height: 4), - TipButtonGroup() - ], + SizedBox(height: 4), + TipButtonGroup( + selectedTip: tipAmount, + tipsList: [ + IoniaTip(percentage: 0, originalAmount: double.parse(amount)), + IoniaTip(percentage: 10, originalAmount: double.parse(amount)), + IoniaTip(percentage: 20, originalAmount: double.parse(amount)), + ], + onSelect: (value) => ioniaPurchaseViewModel.addTip(value), + ) + ], + ), ), - ), - SizedBox(height: 20), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: TextIconButton( - label: S.of(context).how_to_use_card, - onTap: () {}, + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: TextIconButton( + label: S.of(context).how_to_use_card, + onTap: () => _showHowToUseCard(context, merchant), + ), ), - ), - ], - ), + ], + ); + }), bottomSection: Column( children: [ Padding( padding: EdgeInsets.only(bottom: 12), - child: PrimaryButton( - onPressed: () => purchaseCard(context), - text: S.of(context).purchase_gift_card, - color: Theme.of(context).accentTextTheme.body2.color, - textColor: Colors.white, - ), + child: Observer(builder: (_) { + return LoadingPrimaryButton( + isLoading: ioniaPurchaseViewModel.invoiceCreationState is IsExecutingState || + ioniaPurchaseViewModel.invoiceCommittingState is IsExecutingState, + isDisabled: !ioniaPurchaseViewModel.enableCardPurchase, + onPressed: () => purchaseCard(context), + text: S.of(context).purchase_gift_card, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ); + }), ), SizedBox(height: 8), - Text(S.of(context).settings_terms_and_conditions, - style: textMediumSemiBold( - color: Theme.of(context).primaryTextTheme.body1.color, - ).copyWith(fontSize: 12)), + InkWell( + onTap: () => _showTermsAndCondition(context), + child: Text(S.of(context).settings_terms_and_conditions, + style: textMediumSemiBold( + color: Theme.of(context).primaryTextTheme.body1.color, + ).copyWith(fontSize: 12)), + ), SizedBox(height: 16) ], ), @@ -206,117 +285,351 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { ); } - void purchaseCard(BuildContext context) { + void _showTermsAndCondition(BuildContext context) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: '', + alertContent: merchant.termsAndConditions, + buttonText: S.of(context).agree, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } + + Future purchaseCard(BuildContext context) async { + await ioniaPurchaseViewModel.createInvoice(); + + if (ioniaPurchaseViewModel.invoiceCreationState is ExecutedSuccessfullyState) { + await _presentSuccessfulInvoiceCreationPopup(context); + } + } + + void _showHowToUseCard( + BuildContext context, + IoniaMerchant merchant, + ) { showPopUp( context: context, - builder: (_) { - return IoniaConfirmModal( - alertTitle: S.of(context).confirm_sending, - alertContent: SizedBox( - //Todo:: substitute this widget with modal content - height: 200, + builder: (BuildContext context) { + return AlertBackground( + child: Material( + color: Colors.transparent, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(height: 10), + Container( + padding: EdgeInsets.only(top: 24, left: 24, right: 24), + margin: EdgeInsets.all(24), + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + borderRadius: BorderRadius.circular(30), + ), + child: Column( + children: [ + Text( + S.of(context).how_to_use_card, + style: textLargeSemiBold( + color: Theme.of(context).textTheme.body1.color, + ), + ), + SizedBox(height: 24), + Align( + alignment: Alignment.bottomLeft, + child: Text( + merchant.usageInstructionsBak, + style: textMedium( + color: Theme.of(context).textTheme.display2.color, + ), + ), + ), + SizedBox(height: 35), + PrimaryButton( + onPressed: () => Navigator.pop(context), + text: S.of(context).send_got_it, + color: Color.fromRGBO(233, 242, 252, 1), + textColor: Theme.of(context).textTheme.display2.color, + ), + SizedBox(height: 21), + ], + ), + ), + InkWell( + onTap: () => Navigator.pop(context), + child: Container( + margin: EdgeInsets.only(bottom: 40), + child: CircleAvatar( + child: Icon( + Icons.close, + color: Colors.black, + ), + backgroundColor: Colors.white, + ), + ), + ) + ], ), - rightButtonText: S.of(context).ok, - leftButtonText: S.of(context).cancel, - leftActionColor: Color(0xffFF6600), - rightActionColor: Theme.of(context).accentTextTheme.body2.color, - actionRightButton: () async { - Navigator.of(context).pop(); - }, - actionLeftButton: () => Navigator.of(context).pop()); + ), + ); }); } + + Future _presentSuccessfulInvoiceCreationPopup(BuildContext context) async { + final amount = ioniaPurchaseViewModel.invoice.totalAmount; + final addresses = ioniaPurchaseViewModel.invoice.outAddresses; + + await showPopUp( + context: context, + builder: (_) { + return IoniaConfirmModal( + alertTitle: S.of(context).confirm_sending, + alertContent: Container( + height: 200, + padding: EdgeInsets.all(15), + child: Column(children: [ + Row(children: [ + Text(S.of(context).payment_id, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: PaletteDark.pigeonBlue, + decoration: TextDecoration.none)), + Text(ioniaPurchaseViewModel.invoice.paymentId, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: PaletteDark.pigeonBlue, + decoration: TextDecoration.none)) + ], mainAxisAlignment: MainAxisAlignment.spaceBetween), + SizedBox(height: 10), + Row(children: [ + Text(S.of(context).amount, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: PaletteDark.pigeonBlue, + decoration: TextDecoration.none)), + Text('$amount ${ioniaPurchaseViewModel.invoice.chain}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: PaletteDark.pigeonBlue, + decoration: TextDecoration.none)) + ], mainAxisAlignment: MainAxisAlignment.spaceBetween), + SizedBox(height: 25), + Row(children: [ + Text(S.of(context).recipient_address, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: PaletteDark.pigeonBlue, + decoration: TextDecoration.none)) + ], mainAxisAlignment: MainAxisAlignment.center), + Expanded( + child: ListView.builder( + itemBuilder: (_, int index) { + return Text(addresses[index], + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: PaletteDark.pigeonBlue, + decoration: TextDecoration.none)); + }, + itemCount: addresses.length, + physics: NeverScrollableScrollPhysics())) + ])), + rightButtonText: S.of(context).ok, + leftButtonText: S.of(context).cancel, + leftActionColor: Color(0xffFF6600), + rightActionColor: Theme.of(context).accentTextTheme.body2.color, + actionRightButton: () async { + Navigator.of(context).pop(); + await ioniaPurchaseViewModel.commitPaymentInvoice(); + }, + actionLeftButton: () => Navigator.of(context).pop()); + }, + ); + } } -class TipButtonGroup extends StatefulWidget { - const TipButtonGroup({ +class _IoniaTransactionCommitedAlert extends StatelessWidget { + const _IoniaTransactionCommitedAlert({ Key key, + @required this.transactionInfo, }) : super(key: key); + final AnyPayPaymentCommittedInfo transactionInfo; + @override - _TipButtonGroupState createState() => _TipButtonGroupState(); + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(30)), + child: Container( + width: 327, + height: 340, + color: Theme.of(context).accentTextTheme.title.decorationColor, + child: Material( + color: Colors.transparent, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(40, 20, 40, 0), + child: Text( + S.of(context).awaiting_payment_confirmation, + textAlign: TextAlign.center, + style: textMediumSemiBold( + color: Theme.of(context).accentTextTheme.display4.backgroundColor, + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 16, bottom: 8), + child: Container( + height: 1, + color: Theme.of(context).dividerColor, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).transaction_sent, + style: textMedium( + color: Theme.of(context).primaryTextTheme.title.color, + ).copyWith(fontWeight: FontWeight.w500), + ), + SizedBox(height: 20), + Text( + S.of(context).transaction_sent_notice, + style: textMedium( + color: Theme.of(context).primaryTextTheme.title.color, + ).copyWith(fontWeight: FontWeight.w500), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.only(top: 16, bottom: 8), + child: Container( + height: 1, + color: Theme.of(context).dividerColor, + ), + ), + StandartListRow( + title: '${S.current.transaction_details_transaction_id}:', + value: transactionInfo.chain, + ), + StandartListRow( + title: '${S.current.view_in_block_explorer}:', + value: '${S.current.view_transaction_on} XMRChain.net'), + ], + ), + ), + ), + ); + } } -class _TipButtonGroupState extends State { - String selectedTip; +class TipButtonGroup extends StatelessWidget { + const TipButtonGroup({ + Key key, + @required this.selectedTip, + @required this.onSelect, + @required this.tipsList, + }) : super(key: key); + + final Function(IoniaTip) onSelect; + final double selectedTip; + final List tipsList; + bool _isSelected(String value) { - return selectedTip == value; + final tip = selectedTip.round().toString(); + return tip == value; } @override Widget build(BuildContext context) { return Row( children: [ - TipButton( - isSelected: _isSelected('299'), - caption: '\$10', - subTitle: '%299', - ), - SizedBox(width: 4), - TipButton( - caption: '\$10', - subTitle: '%299', - ), - SizedBox(width: 4), - TipButton( - isSelected: _isSelected('299'), - caption: '\$10', - subTitle: '%299', - ), - SizedBox(width: 4), - TipButton( - isSelected: _isSelected('299'), - caption: S.of(context).custom, - ), + ...[ + for (var i = 0; i < tipsList.length; i++) ...[ + TipButton( + isSelected: _isSelected(tipsList[i].originalAmount.toString()), + onTap: () => onSelect(tipsList[i]), + caption: '${tipsList[i].percentage}%', + subTitle: '\$${tipsList[i].additionalAmount}', + ), + SizedBox(width: 4), + ] + ], ], ); } } class TipButton extends StatelessWidget { - final String caption; - final String subTitle; - final bool isSelected; - final void Function(int, bool) onTap; - const TipButton({ @required this.caption, this.subTitle, - this.onTap, + @required this.onTap, this.isSelected = false, }); + final String caption; + final String subTitle; + final bool isSelected; + final void Function() onTap; + @override Widget build(BuildContext context) { - return Container( - height: 49, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(caption, style: textSmallSemiBold(color: Theme.of(context).primaryTextTheme.title.color)), - if (subTitle != null) ...[ - SizedBox(height: 4), - Text( - subTitle, - style: textXxSmallSemiBold(color: Palette.gray), - ), - ] - ], - ), - padding: EdgeInsets.symmetric(horizontal: 18, vertical: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Color.fromRGBO(242, 240, 250, 1), - gradient: isSelected - ? LinearGradient( - colors: [ - Theme.of(context).primaryTextTheme.subhead.color, - Theme.of(context).primaryTextTheme.subhead.decorationColor, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ) - : null, + return InkWell( + onTap: onTap, + child: Container( + height: 49, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(caption, + style: textSmallSemiBold( + color: isSelected + ? Theme.of(context).accentTextTheme.title.color + : Theme.of(context).primaryTextTheme.title.color)), + if (subTitle != null) ...[ + SizedBox(height: 4), + Text( + subTitle, + style: textXxSmallSemiBold( + color: isSelected + ? Theme.of(context).accentTextTheme.title.color + : Theme.of(context).primaryTextTheme.overline.color, + ), + ), + ] + ], + ), + padding: EdgeInsets.symmetric(horizontal: 18, vertical: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Color.fromRGBO(242, 240, 250, 1), + gradient: isSelected + ? LinearGradient( + colors: [ + Theme.of(context).primaryTextTheme.subhead.color, + Theme.of(context).primaryTextTheme.subhead.decorationColor, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ) + : null, + ), ), ); } diff --git a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart index fc16db938b..7a5bccbbcf 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart @@ -7,18 +7,28 @@ import 'package:cake_wallet/src/widgets/keyboard_done_button.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/themes/theme_base.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:cake_wallet/generated/i18n.dart'; class IoniaBuyGiftCardPage extends BasePage { - IoniaBuyGiftCardPage(this.merchant) - : _amountFieldFocus = FocusNode(), - _amountController = TextEditingController(); + IoniaBuyGiftCardPage( + this.ioniaPurchaseViewModel, this.merchant, + ) : _amountFieldFocus = FocusNode(), + _amountController = TextEditingController() { + ioniaPurchaseViewModel.setSelectedMerchant(merchant); + _amountController.addListener(() { + ioniaPurchaseViewModel.onAmountChanged(_amountController.text); + }); + } + final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel; final IoniaMerchant merchant; + @override String get title => S.current.enter_amount; @@ -39,6 +49,7 @@ class IoniaBuyGiftCardPage extends BasePage { @override Widget body(BuildContext context) { final _width = MediaQuery.of(context).size.width; + final merchant = ioniaPurchaseViewModel.ioniaMerchant; return KeyboardActions( disableScroll: true, config: KeyboardActionsConfig( @@ -98,7 +109,7 @@ class IoniaBuyGiftCardPage extends BasePage { left: _width / 4, ), child: Text( - '${merchant.acceptedCurrency}: ', + 'USD: ', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w600, @@ -146,20 +157,27 @@ class IoniaBuyGiftCardPage extends BasePage { ), bottomSection: Column( children: [ - Padding( - padding: EdgeInsets.only(bottom: 12), - child: PrimaryButton( - onPressed: () { - Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardDetailPage, arguments: [merchant] ); - }, - text: S.of(context).continue_text, - color: Theme.of(context).accentTextTheme.body2.color, - textColor: Theme.of(context) + Observer(builder: (_) { + return Padding( + padding: EdgeInsets.only(bottom: 12), + child: PrimaryButton( + onPressed: () => Navigator.of(context).pushNamed( + Routes.ioniaBuyGiftCardDetailPage, + arguments: [ + ioniaPurchaseViewModel.amount, + ioniaPurchaseViewModel.ioniaMerchant, + ], + ), + text: S.of(context).continue_text, + isDisabled: !ioniaPurchaseViewModel.enableCardPurchase, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Theme.of(context) .accentTextTheme .headline .decorationColor, - ), - ), + ), + ); + }), SizedBox(height: 30), ], ), diff --git a/lib/src/screens/ionia/cards/ionia_custom_tip_page.dart b/lib/src/screens/ionia/cards/ionia_custom_tip_page.dart new file mode 100644 index 0000000000..3395538d26 --- /dev/null +++ b/lib/src/screens/ionia/cards/ionia_custom_tip_page.dart @@ -0,0 +1,181 @@ +import 'package:cake_wallet/ionia/ionia_merchant.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; +import 'package:cake_wallet/src/widgets/keyboard_done_button.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/themes/theme_base.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:keyboard_actions/keyboard_actions.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class IoniaCustomTipPage extends BasePage { + IoniaCustomTipPage( + this.ioniaPurchaseViewModel, + this.billAmount, + this.merchant, + ) : _amountFieldFocus = FocusNode(), + _amountController = TextEditingController() { + ioniaPurchaseViewModel.setSelectedMerchant(merchant); + ioniaPurchaseViewModel.onAmountChanged(billAmount); + _amountController.addListener(() { + // ioniaPurchaseViewModel.onTipChanged(_amountController.text); + }); + } + + final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel; + final String billAmount; + final IoniaMerchant merchant; + + @override + String get title => S.current.enter_amount; + + @override + Color get titleColor => Colors.white; + + @override + bool get extendBodyBehindAppBar => true; + + @override + AppBarStyle get appBarStyle => AppBarStyle.transparent; + + Color get textColor => currentTheme.type == ThemeType.dark ? Colors.white : Color(0xff393939); + + final TextEditingController _amountController; + final FocusNode _amountFieldFocus; + + @override + Widget body(BuildContext context) { + final _width = MediaQuery.of(context).size.width; + final merchant = ioniaPurchaseViewModel.ioniaMerchant; + return KeyboardActions( + disableScroll: true, + config: KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: _amountFieldFocus, + toolbarButtons: [(_) => KeyboardDoneButton()], + ), + ]), + child: Container( + color: Theme.of(context).backgroundColor, + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.zero, + content: Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 25), + decoration: BoxDecoration( + borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), + gradient: LinearGradient(colors: [ + Theme.of(context).primaryTextTheme.subhead.color, + Theme.of(context).primaryTextTheme.subhead.decorationColor, + ], begin: Alignment.topLeft, end: Alignment.bottomRight), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 150), + BaseTextFormField( + controller: _amountController, + focusNode: _amountFieldFocus, + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\-|\ ]'))], + hintText: '1000', + placeholderTextStyle: TextStyle( + color: Theme.of(context).primaryTextTheme.headline.color, + fontWeight: FontWeight.w500, + fontSize: 36, + ), + borderColor: Theme.of(context).primaryTextTheme.headline.color, + textColor: Colors.white, + textStyle: TextStyle( + color: Colors.white, + fontSize: 36, + ), + suffixIcon: SizedBox( + width: _width / 6, + ), + prefixIcon: Padding( + padding: EdgeInsets.only( + top: 5.0, + left: _width / 4, + ), + child: Text( + 'USD: ', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w900, + fontSize: 36, + ), + ), + ), + ), + SizedBox(height: 8), + Observer(builder: (_) { + if (ioniaPurchaseViewModel.percentage == 0.0) { + return SizedBox.shrink(); + } + + return RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: '\$${_amountController.text}', + style: TextStyle( + color: Theme.of(context).primaryTextTheme.headline.color, + ), + children: [ + TextSpan(text: ' ${S.of(context).is_percentage} '), + TextSpan(text: '${ioniaPurchaseViewModel.percentage}%'), + TextSpan(text: ' ${S.of(context).percentageOf(billAmount)} '), + ], + ), + ); + }), + SizedBox(height: 24), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(24.0), + child: CardItem( + title: merchant.legalName, + backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), + discount: 0.0, + titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor, + subtitleColor: Theme.of(context).hintColor, + subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline, + logoUrl: merchant.logoUrl, + ), + ) + ], + ), + bottomSection: Column( + children: [ + Padding( + padding: EdgeInsets.only(bottom: 12), + child: PrimaryButton( + onPressed: () { + Navigator.of(context).pop(_amountController.text); + }, + text: S.of(context).add_tip, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + ), + ), + SizedBox(height: 30), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/screens/ionia/cards/ionia_debit_card_page.dart b/lib/src/screens/ionia/cards/ionia_debit_card_page.dart index 42684e8ffb..33ec9c9aff 100644 --- a/lib/src/screens/ionia/cards/ionia_debit_card_page.dart +++ b/lib/src/screens/ionia/cards/ionia_debit_card_page.dart @@ -8,15 +8,15 @@ import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; class IoniaDebitCardPage extends BasePage { - final IoniaViewModel _ioniaViewModel; + final IoniaGiftCardsListViewModel _cardsListViewModel; - IoniaDebitCardPage(this._ioniaViewModel); + IoniaDebitCardPage(this._cardsListViewModel); @override Widget middle(BuildContext context) { @@ -32,7 +32,7 @@ class IoniaDebitCardPage extends BasePage { Widget body(BuildContext context) { return Observer( builder: (_) { - final cardState = _ioniaViewModel.cardState; + final cardState = _cardsListViewModel.cardState; if (cardState is IoniaFetchingCard) { return Center(child: CircularProgressIndicator()); } diff --git a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart index 15ce9e644e..2d0d0db1c2 100644 --- a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart @@ -1,20 +1,37 @@ +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/ionia/ionia_category.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/card_menu.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/ionia_filter_modal.dart'; import 'package:cake_wallet/src/widgets/cake_scrollbar.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/debounce.dart'; import 'package:cake_wallet/typography.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; class IoniaManageCardsPage extends BasePage { - IoniaManageCardsPage(this._ioniaViewModel); - final IoniaViewModel _ioniaViewModel; + IoniaManageCardsPage(this._cardsListViewModel) { + _searchController.addListener(() { + if (_searchController.text != _cardsListViewModel.searchString) { + _searchDebounce.run(() { + _cardsListViewModel.searchMerchant(_searchController.text); + }); + } + }); + } + final IoniaGiftCardsListViewModel _cardsListViewModel; + + final _searchDebounce = Debounce(Duration(milliseconds: 500)); + final _searchController = TextEditingController(); @override Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white; @@ -80,18 +97,25 @@ class IoniaManageCardsPage extends BasePage { ); } - @override Widget trailing(BuildContext context) { - return - _TrailingIcon( - asset: 'assets/images/profile.png', - onPressed: () {}, + return _TrailingIcon( + asset: 'assets/images/profile.png', + onPressed: () => Navigator.pushNamed(context, Routes.ioniaAccountPage), ); } @override Widget body(BuildContext context) { + final filterIcon = InkWell( + onTap: () async { + final selectedFilters = await showCategoryFilter(context, _cardsListViewModel); + _cardsListViewModel.setSelectedFilter(selectedFilters); + }, + child: Image.asset( + 'assets/images/filter.png', + color: Theme.of(context).textTheme.caption.decorationColor, + )); return Padding( padding: const EdgeInsets.all(14.0), @@ -100,103 +124,134 @@ class IoniaManageCardsPage extends BasePage { Container( padding: EdgeInsets.only(left: 2, right: 22), height: 32, - child: _SearchWidget() - + child: Row( + children: [ + Expanded( + child: _SearchWidget( + controller: _searchController, + )), + SizedBox(width: 10), + Container( + width: 32, + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.15), + border: Border.all( + color: Colors.white.withOpacity(0.2), + ), + borderRadius: BorderRadius.circular(10), + ), + child: filterIcon, + ) + ], + ), ), SizedBox(height: 8), Expanded( - child: Observer(builder: (_) { - return IoniaManageCardsPageBody(scrollOffsetFromTop: _ioniaViewModel.scrollOffsetFromTop, - ioniaMerchants: _ioniaViewModel.ioniaMerchants, - onSetScrollOffset: (offset) => _ioniaViewModel.setScrollOffsetFromTop(offset), - ); - }), + child: IoniaManageCardsPageBody( + cardsListViewModel: _cardsListViewModel, + ), ), ], ), ); } + + Future> showCategoryFilter( + BuildContext context, + IoniaGiftCardsListViewModel viewModel, + ) async { + return await showPopUp>( + context: context, + builder: (BuildContext context) { + return IoniaFilterModal( + filterViewModel: getIt.get(), + selectedCategories: viewModel.selectedFilters, + ); + }, + ); + } } class IoniaManageCardsPageBody extends StatefulWidget { const IoniaManageCardsPageBody({ Key key, - @required this.scrollOffsetFromTop, - @required this.ioniaMerchants, - @required this.onSetScrollOffset, + @required this.cardsListViewModel, }) : super(key: key); - - final List ioniaMerchants; - final double scrollOffsetFromTop; - final Function(double) onSetScrollOffset; + final IoniaGiftCardsListViewModel cardsListViewModel; @override _IoniaManageCardsPageBodyState createState() => _IoniaManageCardsPageBodyState(); } class _IoniaManageCardsPageBodyState extends State { + double get backgroundHeight => MediaQuery.of(context).size.height * 0.75; + double thumbHeight = 72; + bool get isAlwaysShowScrollThumb => merchantsList == null ? false : merchantsList.length > 3; - double get backgroundHeight => MediaQuery.of(context).size.height * 0.75; - double thumbHeight = 72; - bool get isAlwaysShowScrollThumb => merchantsList == null ? false : merchantsList.length > 3; - - - List get merchantsList => widget.ioniaMerchants; + List get merchantsList => widget.cardsListViewModel.ioniaMerchants; final _scrollController = ScrollController(); -@override + @override void initState() { _scrollController.addListener(() { final scrollOffsetFromTop = _scrollController.hasClients ? (_scrollController.offset / _scrollController.position.maxScrollExtent * (backgroundHeight - thumbHeight)) : 0.0; - widget.onSetScrollOffset(scrollOffsetFromTop); + widget.cardsListViewModel.setScrollOffsetFromTop(scrollOffsetFromTop); }); super.initState(); } + @override Widget build(BuildContext context) { - return Stack(children: [ - ListView.separated( - padding: EdgeInsets.only(left: 2, right: 22), - controller: _scrollController, - itemCount: merchantsList.length, - separatorBuilder: (_, __) => SizedBox(height: 4), - itemBuilder: (_, index) { - final merchant = merchantsList[index]; - return CardItem( - logoUrl: merchant.logoUrl, - onTap: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]), - title: merchant.legalName, - subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline, - backgroundColor: Theme.of(context).textTheme.title.backgroundColor, - titleColor: Theme.of(context).accentTextTheme.display3.backgroundColor, - subtitleColor: Theme.of(context).accentTextTheme.display2.backgroundColor, - discount: merchant.minimumDiscount, - ); - }, - ), - isAlwaysShowScrollThumb - ? CakeScrollbar( - backgroundHeight: backgroundHeight, - thumbHeight: thumbHeight, - rightOffset: 1, - width: 3, - backgroundColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.05), - thumbColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.5), - fromTop: widget.scrollOffsetFromTop, - ) - : Offstage() - ]); + return Observer( + builder: (_) => Stack(children: [ + ListView.separated( + padding: EdgeInsets.only(left: 2, right: 22), + controller: _scrollController, + itemCount: merchantsList.length, + separatorBuilder: (_, __) => SizedBox(height: 4), + itemBuilder: (_, index) { + final merchant = merchantsList[index]; + return CardItem( + logoUrl: merchant.logoUrl, + onTap: () { + Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]); + }, + title: merchant.legalName, + subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline, + backgroundColor: Theme.of(context).textTheme.title.backgroundColor, + titleColor: Theme.of(context).accentTextTheme.display3.backgroundColor, + subtitleColor: Theme.of(context).accentTextTheme.display2.backgroundColor, + discount: merchant.minimumDiscount, + ); + }, + ), + isAlwaysShowScrollThumb + ? CakeScrollbar( + backgroundHeight: backgroundHeight, + thumbHeight: thumbHeight, + rightOffset: 1, + width: 3, + backgroundColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.05), + thumbColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.5), + fromTop: widget.cardsListViewModel.scrollOffsetFromTop, + ) + : Offstage() + ]), + ); } } class _SearchWidget extends StatelessWidget { const _SearchWidget({ Key key, + @required this.controller, }) : super(key: key); + final TextEditingController controller; @override Widget build(BuildContext context) { @@ -210,6 +265,7 @@ class _SearchWidget extends StatelessWidget { return TextField( style: TextStyle(color: Colors.white), + controller: controller, decoration: InputDecoration( filled: true, contentPadding: EdgeInsets.only( @@ -268,5 +324,3 @@ class _TrailingIcon extends StatelessWidget { ); } } - - diff --git a/lib/src/screens/ionia/widgets/confirm_modal.dart b/lib/src/screens/ionia/widgets/confirm_modal.dart index dd513ffa73..33a89a9b0e 100644 --- a/lib/src/screens/ionia/widgets/confirm_modal.dart +++ b/lib/src/screens/ionia/widgets/confirm_modal.dart @@ -13,6 +13,7 @@ class IoniaConfirmModal extends StatelessWidget { @required this.actionRightButton, this.leftActionColor, this.rightActionColor, + this.hideActions = false, }); final String alertTitle; @@ -23,6 +24,7 @@ class IoniaConfirmModal extends StatelessWidget { final VoidCallback actionRightButton; final Color leftActionColor; final Color rightActionColor; + final bool hideActions; Widget actionButtons(BuildContext context) { return Row( diff --git a/lib/src/screens/ionia/widgets/ionia_filter_modal.dart b/lib/src/screens/ionia/widgets/ionia_filter_modal.dart new file mode 100644 index 0000000000..11409a3c07 --- /dev/null +++ b/lib/src/screens/ionia/widgets/ionia_filter_modal.dart @@ -0,0 +1,134 @@ +import 'package:cake_wallet/ionia/ionia_category.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart'; +import 'package:cake_wallet/src/widgets/alert_background.dart'; +import 'package:cake_wallet/typography.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class IoniaFilterModal extends StatelessWidget { + IoniaFilterModal({ + @required this.filterViewModel, + @required this.selectedCategories, + }) { + filterViewModel.setSelectedCategories(this.selectedCategories); + } + + final IoniaFilterViewModel filterViewModel; + final List selectedCategories; + + @override + Widget build(BuildContext context) { + final searchIcon = Padding( + padding: EdgeInsets.all(10), + child: Image.asset( + 'assets/images/search_icon.png', + color: Theme.of(context).accentColor, + ), + ); + return Scaffold( + resizeToAvoidBottomInset: false, + body: AlertBackground( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(height: 10), + Container( + padding: EdgeInsets.only(top: 24, bottom: 20), + margin: EdgeInsets.all(24), + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + borderRadius: BorderRadius.circular(30), + ), + child: Column( + children: [ + SizedBox( + height: 40, + child: Padding( + padding: const EdgeInsets.only(left: 24, right: 24), + child: TextField( + onChanged: filterViewModel.onSearchFilter, + style: textMedium( + color: Theme.of(context).primaryTextTheme.title.color, + ), + decoration: InputDecoration( + filled: true, + prefixIcon: searchIcon, + hintText: S.of(context).search_category, + contentPadding: EdgeInsets.only(bottom: 5), + fillColor: Theme.of(context).textTheme.subhead.backgroundColor, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), + ), + SizedBox(height: 10), + Divider(thickness: 2), + SizedBox(height: 24), + Observer(builder: (_) { + return ListView.builder( + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: filterViewModel.ioniaCategories.length, + itemBuilder: (_, index) { + final category = filterViewModel.ioniaCategories[index]; + return Padding( + padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24), + child: InkWell( + onTap: () => filterViewModel.selectFilter(category), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + category.iconPath, + color: Theme.of(context).primaryTextTheme.title.color, + ), + SizedBox(width: 10), + Text(category.title, + style: textSmall( + color: Theme.of(context).primaryTextTheme.title.color, + ).copyWith(fontWeight: FontWeight.w500)), + ], + ), + Observer(builder: (_) { + final value = filterViewModel.selectedIndices; + return RoundedCheckbox( + value: value.contains(category.index), + ); + }), + ], + ), + ), + ); + }, + ); + }), + ], + ), + ), + InkWell( + onTap: () => Navigator.pop(context, filterViewModel.selectedCategories), + child: Container( + margin: EdgeInsets.only(bottom: 40), + child: CircleAvatar( + child: Icon( + Icons.close, + color: Colors.black, + ), + backgroundColor: Colors.white, + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/src/screens/ionia/widgets/ionia_tile.dart b/lib/src/screens/ionia/widgets/ionia_tile.dart new file mode 100644 index 0000000000..9b34202626 --- /dev/null +++ b/lib/src/screens/ionia/widgets/ionia_tile.dart @@ -0,0 +1,58 @@ +import 'package:cake_wallet/typography.dart'; +import 'package:flutter/material.dart'; + +class IoniaTile extends StatelessWidget { + const IoniaTile({ + Key key, + @required this.title, + @required this.subTitle, + this.trailing, + this.onTapTrailing, + }) : super(key: key); + + final Widget trailing; + final VoidCallback onTapTrailing; + final String title; + final String subTitle; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: textXSmall( + color: Theme.of(context).primaryTextTheme.overline.color, + ), + ), + SizedBox(height: 8), + Text( + subTitle, + style: textMediumBold( + color: Theme.of(context).primaryTextTheme.title.color, + ), + ), + ], + ), + trailing != null + ? InkWell( + onTap: () => onTapTrailing, + child: Center( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 6, vertical: 6), + decoration: BoxDecoration( + color: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(4)), + child: trailing, + ), + ), + ) + : Offstage(), + ], + ); + } +} diff --git a/lib/src/screens/ionia/widgets/rounded_checkbox.dart b/lib/src/screens/ionia/widgets/rounded_checkbox.dart new file mode 100644 index 0000000000..095ad08b59 --- /dev/null +++ b/lib/src/screens/ionia/widgets/rounded_checkbox.dart @@ -0,0 +1,27 @@ +import 'dart:ui'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class RoundedCheckbox extends StatelessWidget { + RoundedCheckbox({Key key, @required this.value}) : super(key: key); + + final bool value; + + @override + Widget build(BuildContext context) { + return value + ? Container( + height: 20.0, + width: 20.0, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(50.0)), + color: Theme.of(context).accentTextTheme.body2.color, + ), + child: Icon( + Icons.check, + color: Theme.of(context).backgroundColor, + size: 14.0, + )) + : Offstage(); + } +} diff --git a/lib/view_model/ionia/ionia_account_view_model.dart b/lib/view_model/ionia/ionia_account_view_model.dart new file mode 100644 index 0000000000..2332d57313 --- /dev/null +++ b/lib/view_model/ionia/ionia_account_view_model.dart @@ -0,0 +1,36 @@ +import 'package:cake_wallet/ionia/ionia_merchant.dart'; +import 'package:cake_wallet/ionia/ionia_service.dart'; +import 'package:mobx/mobx.dart'; + +part 'ionia_account_view_model.g.dart'; + +class IoniaAccountViewModel = IoniaAccountViewModelBase with _$IoniaAccountViewModel; + +abstract class IoniaAccountViewModelBase with Store { + + IoniaAccountViewModelBase({this.ioniaService}) { + email = ''; + merchs = []; + ioniaService.getUserEmail() + .then((email) => this.email = email); + ioniaService.getCurrentUserGiftCardSummaries() + .then((merchs) => this.merchs = merchs); + } + + final IoniaService ioniaService; + + @observable + String email; + + @observable + List merchs; + + @computed + int get countOfMerch => merchs.where((merch) => merch.isActive).length; + + @action + void logout(){ + ioniaService.logout(); + } + +} \ No newline at end of file diff --git a/lib/view_model/ionia/ionia_auth_view_model.dart b/lib/view_model/ionia/ionia_auth_view_model.dart new file mode 100644 index 0000000000..f610168669 --- /dev/null +++ b/lib/view_model/ionia/ionia_auth_view_model.dart @@ -0,0 +1,55 @@ +import 'package:cake_wallet/ionia/ionia_create_state.dart'; +import 'package:cake_wallet/ionia/ionia_service.dart'; +import 'package:mobx/mobx.dart'; + +part 'ionia_auth_view_model.g.dart'; + +class IoniaAuthViewModel = IoniaAuthViewModelBase with _$IoniaAuthViewModel; + +abstract class IoniaAuthViewModelBase with Store { + + IoniaAuthViewModelBase({this.ioniaService}): + createUserState = IoniaCreateStateSuccess(), + otpState = IoniaOtpSendDisabled(){ + + + } + + final IoniaService ioniaService; + + @observable + IoniaCreateAccountState createUserState; + + @observable + IoniaOtpState otpState; + + @observable + String email; + + @observable + String otp; + + @action + Future verifyEmail(String code) async { + try { + otpState = IoniaOtpValidating(); + await ioniaService.verifyEmail(code); + otpState = IoniaOtpSuccess(); + } catch (_) { + otpState = IoniaOtpFailure(error: 'Invalid OTP. Try again'); + } + } + + @action + Future createUser(String email) async { + createUserState = IoniaCreateStateLoading(); + try { + await ioniaService.createUser(email); + + createUserState = IoniaCreateStateSuccess(); + } on Exception catch (e) { + createUserState = IoniaCreateStateFailure(error: e.toString()); + } + } + +} \ No newline at end of file diff --git a/lib/view_model/ionia/ionia_filter_view_model.dart b/lib/view_model/ionia/ionia_filter_view_model.dart new file mode 100644 index 0000000000..43d2790e07 --- /dev/null +++ b/lib/view_model/ionia/ionia_filter_view_model.dart @@ -0,0 +1,58 @@ +import 'package:cake_wallet/ionia/ionia_category.dart'; +import 'package:mobx/mobx.dart'; + +part 'ionia_filter_view_model.g.dart'; + +class IoniaFilterViewModel = IoniaFilterViewModelBase with _$IoniaFilterViewModel; + +abstract class IoniaFilterViewModelBase with Store { + IoniaFilterViewModelBase() { + selectedIndices = ObservableList(); + ioniaCategories = IoniaCategory.allCategories; + } + + List get selectedCategories => ioniaCategories.where(_isSelected).toList(); + + @observable + ObservableList selectedIndices; + + @observable + List ioniaCategories; + + @action + void selectFilter(IoniaCategory ioniaCategory) { + if (ioniaCategory == IoniaCategory.all && !selectedIndices.contains(0)) { + selectedIndices.clear(); + selectedIndices.add(0); + return; + } + if (selectedIndices.contains(ioniaCategory.index) && ioniaCategory.index != 0) { + selectedIndices.remove(ioniaCategory.index); + return; + } + selectedIndices.add(ioniaCategory.index); + selectedIndices.remove(0); + } + + @action + void onSearchFilter(String text) { + if (text.isEmpty) { + ioniaCategories = IoniaCategory.allCategories; + } else { + ioniaCategories = IoniaCategory.allCategories + .where( + (e) => e.title.toLowerCase().contains(text.toLowerCase()), + ) + .toList(); + } + } + + @action + void setSelectedCategories(List selectedCategories) { + selectedIndices = ObservableList.of(selectedCategories.map((e) => e.index)); + } + + bool _isSelected(IoniaCategory ioniaCategory) { + return selectedIndices.contains(ioniaCategory.index); + } +} diff --git a/lib/view_model/ionia/ionia_view_model.dart b/lib/view_model/ionia/ionia_gift_cards_list_view_model.dart similarity index 56% rename from lib/view_model/ionia/ionia_view_model.dart rename to lib/view_model/ionia/ionia_gift_cards_list_view_model.dart index 34cdcf31a3..f20000a6dd 100644 --- a/lib/view_model/ionia/ionia_view_model.dart +++ b/lib/view_model/ionia/ionia_gift_cards_list_view_model.dart @@ -1,35 +1,37 @@ +import 'package:cake_wallet/ionia/ionia_category.dart'; import 'package:cake_wallet/ionia/ionia_service.dart'; import 'package:cake_wallet/ionia/ionia_create_state.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; +import 'package:flutter/material.dart'; import 'package:mobx/mobx.dart'; -part 'ionia_view_model.g.dart'; +part 'ionia_gift_cards_list_view_model.g.dart'; -class IoniaViewModel = IoniaViewModelBase with _$IoniaViewModel; +class IoniaGiftCardsListViewModel = IoniaGiftCardsListViewModelBase with _$IoniaGiftCardsListViewModel; -abstract class IoniaViewModelBase with Store { - IoniaViewModelBase({this.ioniaService}) - : createUserState = IoniaCreateStateSuccess(), - otpState = IoniaOtpSendDisabled(), +abstract class IoniaGiftCardsListViewModelBase with Store { + IoniaGiftCardsListViewModelBase({ + @required this.ioniaService, + }) : cardState = IoniaNoCardState(), ioniaMerchants = [], scrollOffsetFromTop = 0.0 { - _getMerchants().then((value) { - ioniaMerchants = value; - }); - _getAuthStatus().then((value) => isLoggedIn = value); + selectedFilters = []; + _getAuthStatus().then((value) => isLoggedIn = value); + + _getMerchants(); } final IoniaService ioniaService; - @observable - double scrollOffsetFromTop; + List ioniaMerchantList; - @observable - IoniaCreateAccountState createUserState; + String searchString; + + List selectedFilters; @observable - IoniaOtpState otpState; + double scrollOffsetFromTop; @observable IoniaCreateCardState createCardState; @@ -40,38 +42,9 @@ abstract class IoniaViewModelBase with Store { @observable List ioniaMerchants; - @observable - String email; - - @observable - String otp; - @observable bool isLoggedIn; - @action - Future createUser(String email) async { - createUserState = IoniaCreateStateLoading(); - try { - await ioniaService.createUser(email); - - createUserState = IoniaCreateStateSuccess(); - } on Exception catch (e) { - createUserState = IoniaCreateStateFailure(error: e.toString()); - } - } - - @action - Future verifyEmail(String code) async { - try { - otpState = IoniaOtpValidating(); - await ioniaService.verifyEmail(code); - otpState = IoniaOtpSuccess(); - } catch (_) { - otpState = IoniaOtpFailure(error: 'Invalid OTP. Try again'); - } - } - Future _getAuthStatus() async { return await ioniaService.isLogined(); } @@ -89,6 +62,18 @@ abstract class IoniaViewModelBase with Store { return null; } + @action + void searchMerchant(String text) { + if (text.isEmpty) { + ioniaMerchants = ioniaMerchantList; + return; + } + searchString = text; + ioniaService.getMerchantsByFilter(search: searchString).then((value) { + ioniaMerchants = value; + }); + } + Future _getCard() async { cardState = IoniaFetchingCard(); try { @@ -100,8 +85,16 @@ abstract class IoniaViewModelBase with Store { } } - Future> _getMerchants() async { - return await ioniaService.getMerchants(); + void _getMerchants() { + ioniaService.getMerchantsByFilter(categories: selectedFilters).then((value) { + ioniaMerchants = ioniaMerchantList = value; + }); + } + + @action + void setSelectedFilter(List filters) { + selectedFilters = filters; + _getMerchants(); } void setScrollOffsetFromTop(double scrollOffset) { diff --git a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart new file mode 100644 index 0000000000..55d200c05f --- /dev/null +++ b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart @@ -0,0 +1,91 @@ +import 'package:cake_wallet/anypay/any_pay_payment.dart'; +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/ionia/ionia_anypay.dart'; +import 'package:cake_wallet/ionia/ionia_merchant.dart'; +import 'package:cake_wallet/ionia/ionia_tip.dart'; +import 'package:mobx/mobx.dart'; + +part 'ionia_purchase_merch_view_model.g.dart'; + +class IoniaMerchPurchaseViewModel = IoniaMerchPurchaseViewModelBase with _$IoniaMerchPurchaseViewModel; + +abstract class IoniaMerchPurchaseViewModelBase with Store { + IoniaMerchPurchaseViewModelBase(this.ioniaAnyPayService) { + tipAmount = 0.0; + percentage = 0.0; + amount = ''; + enableCardPurchase = false; + } + + IoniaMerchant ioniaMerchant; + + IoniaAnyPay ioniaAnyPayService; + + AnyPayPayment invoice; + + AnyPayPaymentCommittedInfo committedInfo; + + @observable + ExecutionState invoiceCreationState; + + @observable + ExecutionState invoiceCommittingState; + + @observable + String amount; + + @observable + double percentage; + + @computed + double get giftCardAmount => double.parse(amount) + tipAmount; + + @observable + double tipAmount; + + @observable + bool enableCardPurchase; + + @action + void onAmountChanged(String input) { + if (input.isEmpty) return; + amount = input; + final inputAmount = double.parse(input); + final min = ioniaMerchant.minimumCardPurchase; + final max = ioniaMerchant.maximumCardPurchase; + + enableCardPurchase = inputAmount >= min && inputAmount <= max; + } + + void setSelectedMerchant(IoniaMerchant merchant) { + ioniaMerchant = merchant; + } + + @action + void addTip(IoniaTip tip) { + tipAmount = tip.additionalAmount; + } + + @action + Future createInvoice() async { + try { + invoiceCreationState = IsExecutingState(); + invoice = await ioniaAnyPayService.purchase(merchId: ioniaMerchant.id.toString(), amount: giftCardAmount); + invoiceCreationState = ExecutedSuccessfullyState(); + } catch (e) { + invoiceCreationState = FailureState(e.toString()); + } + } + + @action + Future commitPaymentInvoice() async { + try { + invoiceCommittingState = IsExecutingState(); + committedInfo = await ioniaAnyPayService.commitInvoice(invoice); + invoiceCommittingState = ExecutedSuccessfullyState(payload: committedInfo); + } catch (e) { + invoiceCommittingState = FailureState(e.toString()); + } + } +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index d90b758750..d89edde0cd 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -596,5 +596,28 @@ "mm": "MM", "yy": "YY", "online": "Online", - "offline": "Offline" + "offline": "Offline", + "gift_card_number": "Gift card number", + "pin_number": "PIN number", + "total_saving": "Total Savings", + "last_30_days": "Last 30 days", + "avg_savings": "Avg. savings", + "view_all": "View all", + "active_cards": "Active cards", + "delete_account": "Delete Account", + "cards": "Cards", + "active": "Active", + "redeemed": "Redeemed", + "gift_card_balance_note": "Gift cards with a balance remaining will appear here", + "gift_card_redeemed_note": "Gift cards you’ve redeemed will appear here", + "logout": "Logout", + "add_tip": "Add Tip", + "percentageOf": "of ${amount}", + "is_percentage": "is", + "search_category": "Search category", + "mark_as_redeemed": "Mark As Redeemed", + "more_options": "More Options", + "awaiting_payment_confirmation": "Awaiting payment confirmation", + "transaction_sent_notice": "If the screen doesn’t proceed after 1 minute, check a block explorer and your email.", + "agree": "Agree" } From f320e749b831ad7b41f0a58eb632edc116f7d402 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Thu, 7 Jul 2022 20:46:56 +0300 Subject: [PATCH 16/55] refactor tips (#406) * refactor tips * refactor ionia tips implementation --- lib/di.dart | 22 +++++--- .../cards/ionia_buy_card_detail_page.dart | 35 ++++--------- .../ionia/cards/ionia_buy_gift_card.dart | 23 ++++----- .../ionia/cards/ionia_custom_tip_page.dart | 9 +--- .../ionia/ionia_buy_card_view_model.dart | 31 ++++++++++++ .../ionia_purchase_merch_view_model.dart | 50 +++++++++---------- 6 files changed, 92 insertions(+), 78 deletions(-) create mode 100644 lib/view_model/ionia/ionia_buy_card_view_model.dart diff --git a/lib/di.dart b/lib/di.dart index a478be3280..ba49c99143 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/entities/wake_lock.dart'; import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_category.dart'; import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart'; import 'package:cake_wallet/ionia/ionia_service.dart'; import 'package:cake_wallet/ionia/ionia_api.dart'; @@ -671,7 +672,17 @@ Future setup( getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get())); - getIt.registerFactory(() => IoniaMerchPurchaseViewModel(getIt.get())); + getIt.registerFactoryParam((double amount, merchant) { + return IoniaMerchPurchaseViewModel( + ioniaAnyPayService: getIt.get(), + amount: amount, + ioniaMerchant: merchant, + ); + }); + + getIt.registerFactoryParam((IoniaMerchant merchant, _) { + return IoniaBuyCardViewModel(ioniaMerchant: merchant); + }); getIt.registerFactory(() => IoniaAccountViewModel(ioniaService: getIt.get())); @@ -691,21 +702,20 @@ Future setup( getIt.registerFactoryParam((List args, _) { final merchant = args.first as IoniaMerchant; - return IoniaBuyGiftCardPage(getIt.get(), merchant); + return IoniaBuyGiftCardPage(getIt.get(param1: merchant)); }); getIt.registerFactoryParam((List args, _) { - final amount = args.first as String; + final amount = args.first as double; final merchant = args.last as IoniaMerchant; - - return IoniaBuyGiftCardDetailPage(amount, getIt.get(), merchant); + return IoniaBuyGiftCardDetailPage(getIt.get(param1: amount, param2: merchant)); }); getIt.registerFactoryParam((List args, _) { final amount = args.first as String; final merchant = args.last as IoniaMerchant; - return IoniaCustomTipPage(getIt.get(), amount, merchant); + return IoniaCustomTipPage(getIt.get(param1: amount, param2: merchant)); }); getIt.registerFactory(() => IoniaManageCardsPage(getIt.get())); diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index 0e590d5534..b27dc9a5a4 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -25,16 +25,10 @@ import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; class IoniaBuyGiftCardDetailPage extends StatelessWidget { - IoniaBuyGiftCardDetailPage(this.amount, this.ioniaPurchaseViewModel, this.merchant) { - ioniaPurchaseViewModel.setSelectedMerchant(merchant); - } + IoniaBuyGiftCardDetailPage(this.ioniaPurchaseViewModel); final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel; - final IoniaMerchant merchant; - - final String amount; - ThemeBase get currentTheme => getIt.get().currentTheme; Color get backgroundLightColor => Colors.white; @@ -131,7 +125,6 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { } }); - ioniaPurchaseViewModel.onAmountChanged(amount); return Scaffold( backgroundColor: _backgroundColor, body: ScrollableWithBottomSection( @@ -192,7 +185,7 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { ), SizedBox(height: 4), Text( - '\$$amount', + '\$${ioniaPurchaseViewModel.amount}', style: textLargeSemiBold(), ), ], @@ -231,14 +224,12 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { ), ), SizedBox(height: 4), - TipButtonGroup( - selectedTip: tipAmount, - tipsList: [ - IoniaTip(percentage: 0, originalAmount: double.parse(amount)), - IoniaTip(percentage: 10, originalAmount: double.parse(amount)), - IoniaTip(percentage: 20, originalAmount: double.parse(amount)), - ], - onSelect: (value) => ioniaPurchaseViewModel.addTip(value), + Observer( + builder: (_) => TipButtonGroup( + selectedTip: ioniaPurchaseViewModel.selectedTip.percentage, + tipsList: ioniaPurchaseViewModel.tips, + onSelect: (value) => ioniaPurchaseViewModel.addTip(value), + ), ) ], ), @@ -262,7 +253,6 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { return LoadingPrimaryButton( isLoading: ioniaPurchaseViewModel.invoiceCreationState is IsExecutingState || ioniaPurchaseViewModel.invoiceCommittingState is IsExecutingState, - isDisabled: !ioniaPurchaseViewModel.enableCardPurchase, onPressed: () => purchaseCard(context), text: S.of(context).purchase_gift_card, color: Theme.of(context).accentTextTheme.body2.color, @@ -291,7 +281,7 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { builder: (BuildContext context) { return AlertWithOneAction( alertTitle: '', - alertContent: merchant.termsAndConditions, + alertContent: ioniaPurchaseViewModel.ioniaMerchant.termsAndConditions, buttonText: S.of(context).agree, buttonAction: () => Navigator.of(context).pop(), ); @@ -550,10 +540,7 @@ class TipButtonGroup extends StatelessWidget { final double selectedTip; final List tipsList; - bool _isSelected(String value) { - final tip = selectedTip.round().toString(); - return tip == value; - } + bool _isSelected(double value) => selectedTip == value; @override Widget build(BuildContext context) { @@ -562,7 +549,7 @@ class TipButtonGroup extends StatelessWidget { ...[ for (var i = 0; i < tipsList.length; i++) ...[ TipButton( - isSelected: _isSelected(tipsList[i].originalAmount.toString()), + isSelected: _isSelected(tipsList[i].percentage), onTap: () => onSelect(tipsList[i]), caption: '${tipsList[i].percentage}%', subTitle: '\$${tipsList[i].additionalAmount}', diff --git a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart index 7a5bccbbcf..e7c6dabf83 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/src/widgets/keyboard_done_button.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/themes/theme_base.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -16,18 +17,15 @@ import 'package:cake_wallet/generated/i18n.dart'; class IoniaBuyGiftCardPage extends BasePage { IoniaBuyGiftCardPage( - this.ioniaPurchaseViewModel, this.merchant, + this.ioniaBuyCardViewModel, ) : _amountFieldFocus = FocusNode(), _amountController = TextEditingController() { - ioniaPurchaseViewModel.setSelectedMerchant(merchant); _amountController.addListener(() { - ioniaPurchaseViewModel.onAmountChanged(_amountController.text); + ioniaBuyCardViewModel.onAmountChanged(_amountController.text); }); } - final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel; - final IoniaMerchant merchant; - + final IoniaBuyCardViewModel ioniaBuyCardViewModel; @override String get title => S.current.enter_amount; @@ -49,7 +47,7 @@ class IoniaBuyGiftCardPage extends BasePage { @override Widget body(BuildContext context) { final _width = MediaQuery.of(context).size.width; - final merchant = ioniaPurchaseViewModel.ioniaMerchant; + final merchant = ioniaBuyCardViewModel.ioniaMerchant; return KeyboardActions( disableScroll: true, config: KeyboardActionsConfig( @@ -164,17 +162,14 @@ class IoniaBuyGiftCardPage extends BasePage { onPressed: () => Navigator.of(context).pushNamed( Routes.ioniaBuyGiftCardDetailPage, arguments: [ - ioniaPurchaseViewModel.amount, - ioniaPurchaseViewModel.ioniaMerchant, + ioniaBuyCardViewModel.amount, + ioniaBuyCardViewModel.ioniaMerchant, ], ), text: S.of(context).continue_text, - isDisabled: !ioniaPurchaseViewModel.enableCardPurchase, + isDisabled: !ioniaBuyCardViewModel.isEnablePurchase, color: Theme.of(context).accentTextTheme.body2.color, - textColor: Theme.of(context) - .accentTextTheme - .headline - .decorationColor, + textColor: Theme.of(context).accentTextTheme.headline.decorationColor, ), ); }), diff --git a/lib/src/screens/ionia/cards/ionia_custom_tip_page.dart b/lib/src/screens/ionia/cards/ionia_custom_tip_page.dart index 3395538d26..5dbce02b65 100644 --- a/lib/src/screens/ionia/cards/ionia_custom_tip_page.dart +++ b/lib/src/screens/ionia/cards/ionia_custom_tip_page.dart @@ -16,20 +16,15 @@ import 'package:cake_wallet/generated/i18n.dart'; class IoniaCustomTipPage extends BasePage { IoniaCustomTipPage( this.ioniaPurchaseViewModel, - this.billAmount, - this.merchant, ) : _amountFieldFocus = FocusNode(), _amountController = TextEditingController() { - ioniaPurchaseViewModel.setSelectedMerchant(merchant); - ioniaPurchaseViewModel.onAmountChanged(billAmount); _amountController.addListener(() { // ioniaPurchaseViewModel.onTipChanged(_amountController.text); }); } final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel; - final String billAmount; - final IoniaMerchant merchant; + @override String get title => S.current.enter_amount; @@ -135,7 +130,7 @@ class IoniaCustomTipPage extends BasePage { children: [ TextSpan(text: ' ${S.of(context).is_percentage} '), TextSpan(text: '${ioniaPurchaseViewModel.percentage}%'), - TextSpan(text: ' ${S.of(context).percentageOf(billAmount)} '), + TextSpan(text: ' ${S.of(context).percentageOf(ioniaPurchaseViewModel.amount.toString())} '), ], ), ); diff --git a/lib/view_model/ionia/ionia_buy_card_view_model.dart b/lib/view_model/ionia/ionia_buy_card_view_model.dart new file mode 100644 index 0000000000..ed5411f49a --- /dev/null +++ b/lib/view_model/ionia/ionia_buy_card_view_model.dart @@ -0,0 +1,31 @@ +import 'package:cake_wallet/ionia/ionia_merchant.dart'; +import 'package:mobx/mobx.dart'; + +part 'ionia_buy_card_view_model.g.dart'; + +class IoniaBuyCardViewModel = IoniaBuyCardViewModelBase with _$IoniaBuyCardViewModel; + +abstract class IoniaBuyCardViewModelBase with Store { + IoniaBuyCardViewModelBase({this.ioniaMerchant}) { + isEnablePurchase = false; + amount = 0; + } + + final IoniaMerchant ioniaMerchant; + + @observable + double amount; + + @observable + bool isEnablePurchase; + + @action + void onAmountChanged(String input) { + if (input.isEmpty) return; + amount = double.parse(input); + final min = ioniaMerchant.minimumCardPurchase; + final max = ioniaMerchant.maximumCardPurchase; + + isEnablePurchase = amount >= min && amount <= max; + } +} diff --git a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart index 55d200c05f..6a65a49e98 100644 --- a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart +++ b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; +import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; part 'ionia_purchase_merch_view_model.g.dart'; @@ -11,16 +12,31 @@ part 'ionia_purchase_merch_view_model.g.dart'; class IoniaMerchPurchaseViewModel = IoniaMerchPurchaseViewModelBase with _$IoniaMerchPurchaseViewModel; abstract class IoniaMerchPurchaseViewModelBase with Store { - IoniaMerchPurchaseViewModelBase(this.ioniaAnyPayService) { + IoniaMerchPurchaseViewModelBase({ + @required this.ioniaAnyPayService, + @required this.amount, + @required this.ioniaMerchant, + }) { tipAmount = 0.0; percentage = 0.0; - amount = ''; - enableCardPurchase = false; + tips = [ + IoniaTip(percentage: 0, originalAmount: amount), + IoniaTip(percentage: 10, originalAmount: amount), + IoniaTip(percentage: 20, originalAmount: amount), + ]; + selectedTip = tips.first; } - IoniaMerchant ioniaMerchant; + final double amount; - IoniaAnyPay ioniaAnyPayService; + List tips; + + @observable + IoniaTip selectedTip; + + final IoniaMerchant ioniaMerchant; + + final IoniaAnyPay ioniaAnyPayService; AnyPayPayment invoice; @@ -32,39 +48,19 @@ abstract class IoniaMerchPurchaseViewModelBase with Store { @observable ExecutionState invoiceCommittingState; - @observable - String amount; - @observable double percentage; @computed - double get giftCardAmount => double.parse(amount) + tipAmount; + double get giftCardAmount => amount + tipAmount; @observable double tipAmount; - @observable - bool enableCardPurchase; - - @action - void onAmountChanged(String input) { - if (input.isEmpty) return; - amount = input; - final inputAmount = double.parse(input); - final min = ioniaMerchant.minimumCardPurchase; - final max = ioniaMerchant.maximumCardPurchase; - - enableCardPurchase = inputAmount >= min && inputAmount <= max; - } - - void setSelectedMerchant(IoniaMerchant merchant) { - ioniaMerchant = merchant; - } - @action void addTip(IoniaTip tip) { tipAmount = tip.additionalAmount; + selectedTip = tip; } @action From 28ff890afd767e74df0b33f5c54f18fd5a611c8d Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Thu, 7 Jul 2022 20:47:27 +0300 Subject: [PATCH 17/55] Cw 115 implement gift cards list for ionia (#405) * Implement show purchased cards * fix padding --- assets/images/red_badge_discount.png | Bin 0 -> 1506 bytes .../ionia/cards/ionia_account_cards_page.dart | 157 +++++++++++------- .../ionia/cards/ionia_account_page.dart | 32 ++-- lib/src/screens/ionia/widgets/card_item.dart | 19 ++- lib/src/widgets/discount_badge.dart | 18 +- .../ionia/ionia_account_view_model.dart | 18 +- res/values/strings_en.arb | 3 +- 7 files changed, 149 insertions(+), 98 deletions(-) create mode 100644 assets/images/red_badge_discount.png diff --git a/assets/images/red_badge_discount.png b/assets/images/red_badge_discount.png new file mode 100644 index 0000000000000000000000000000000000000000..4f5dc56a298840e8fcba7e99f81d05d86801db83 GIT binary patch literal 1506 zcmV<81s(c{P)djpCvaBV$eryEb0h~Dip1=VE2rL*pz!Xg23^OIFluAj}bVNtLS6`KsnNqdN z`uzXaUg@-tYWnwOren1Y6T|bcVN)+#ZEP3OVJ4TTjA`EP{iZ&4h+10*<7}=mx_b6J zwEG(9Uo+QufA+O(&DCX--0ihi8~S6?W=9u=k2gfWXcCM zdA0|r!~uoR7^J@WeuweFDI2rlJoNVXjvN+IgQ2nU_V9idUsdxE+X?O%cPAE)KFq;d zxqdvmVa*12lo+2ioa%eISYsWMc)WOR)Y@#v@$Qtk-% zmWE-<@q-a+PM{HgdD$~PB~fKnbJ4k8pBMwwqO=D!-hpnKkfaeDFcTGd zP>~brB}&qr0mwqg8oU*n;%ZioIWou1!S9CsXrg1_k!hx-DQ6*qA;vV9DWy{2LWxn; z-PalwRA~s(5mFcgs06GPM@_}0X$g^2R5@#dQS`F1-OhtQJzeiwA(OoB)!S%rOI0Tn zQ@j{8Z31Rhi9wi*MM>xW!B6N-6dw}QFeo~x;QSRYlMw|fcGVC{dR0Ld_W9@aYkRgD zUNksBa6&ER;CnDR9RXBmMa(Kgh?GiOS7=mj4LxTL@1-#I-3KIUGS3TbY-v=8F!L07 zP%WsD2YWcxi|W6a9kB6{x(7MaY2`BLefnA~zIkb5iI`&RWA^{^sX@tLtB3oh;H1lO9L3d8^W*!okbt zq)1yLS%o#&{Tanl_lk-OBP%Y56Vsespqr*+qijXpVW`mqjVJYpn8)jVDspf)YjoJ2 zkPAL%+QMdPjhmA7aVV~rp%X<;MXoLsl4URN=Dt26J5vLaayZOPgh-UjT4nPYyn5E2 zb#~YDQ>d7mnJbyzXp~0fR>e$QT{GeusS1@|2@h^cfc>z3DXFV&6eEgaM5V5}_2&kQ zA6{fe-qVZkquG=Zmj-p@>f3TKswp0xmj)Cqd0pi@FDvaJmMTg}iZoR>)#D;$l2mZF z)Zx3@A1RObfNEE5s4H5?A+F3&YxH=(y!@T2Jsf>TV|5%*uRjA&tpUywe@XS6T>P>) zfE@?izq?4eGWvvVUV}s3`DcRW8l%=azr6QLMLxM7`mqu98;}q(W`KY5hyVZp07*qo IM6N<$f_YfQ{Qv*} literal 0 HcmV?d00001 diff --git a/lib/src/screens/ionia/cards/ionia_account_cards_page.dart b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart index a0d02e4f4b..ba2ddae55f 100644 --- a/lib/src/screens/ionia/cards/ionia_account_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart @@ -1,14 +1,17 @@ +import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; class IoniaAccountCardsPage extends BasePage { IoniaAccountCardsPage(this.ioniaAccountViewModel); final IoniaAccountViewModel ioniaAccountViewModel; - + @override Widget middle(BuildContext context) { return Text( @@ -21,11 +24,15 @@ class IoniaAccountCardsPage extends BasePage { @override Widget body(BuildContext context) { - return _IoniaCardTabs(); + return _IoniaCardTabs(ioniaAccountViewModel); } } class _IoniaCardTabs extends StatefulWidget { + _IoniaCardTabs(this.ioniaAccountViewModel); + + final IoniaAccountViewModel ioniaAccountViewModel; + @override _IoniaCardTabsState createState() => _IoniaCardTabsState(); } @@ -48,71 +55,109 @@ class _IoniaCardTabsState extends State<_IoniaCardTabs> with SingleTickerProvide @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 45, - width: 230, - padding: EdgeInsets.all(5), - decoration: BoxDecoration( - color: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), - borderRadius: BorderRadius.circular( - 25.0, - ), + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 45, + width: 230, + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + color: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), + borderRadius: BorderRadius.circular( + 25.0, ), - child: Theme( - data: ThemeData( - primaryTextTheme: TextTheme( - body2: TextStyle(backgroundColor: Colors.transparent) - ) - ), - child: TabBar( - controller: _tabController, - indicator: BoxDecoration( - borderRadius: BorderRadius.circular( - 25.0, - ), - color: Theme.of(context).accentTextTheme.body2.color, + ), + child: Theme( + data: ThemeData(primaryTextTheme: TextTheme(body2: TextStyle(backgroundColor: Colors.transparent))), + child: TabBar( + controller: _tabController, + indicator: BoxDecoration( + borderRadius: BorderRadius.circular( + 25.0, ), - labelColor: Theme.of(context).primaryTextTheme.display4.backgroundColor, - unselectedLabelColor: Theme.of(context).primaryTextTheme.title.color, - tabs: [ - Tab( - text: S.of(context).active, - ), - Tab( - text: S.of(context).redeemed, - ), - ], + color: Theme.of(context).accentTextTheme.body2.color, ), + labelColor: Theme.of(context).primaryTextTheme.display4.backgroundColor, + unselectedLabelColor: Theme.of(context).primaryTextTheme.title.color, + tabs: [ + Tab( + text: S.of(context).active, + ), + Tab( + text: S.of(context).redeemed, + ), + ], ), ), - Expanded( - child: TabBarView( + ), + SizedBox(height: 16), + Expanded( + child: Observer(builder: (_) { + final viewModel = widget.ioniaAccountViewModel; + return TabBarView( controller: _tabController, children: [ - Center( - child: Text( - S.of(context).gift_card_balance_note, - textAlign: TextAlign.center, - style: textSmall(color: Theme.of(context).primaryTextTheme.overline.color,), - ), + _IoniaCardListView( + emptyText: S.of(context).gift_card_balance_note, + merchList: viewModel.activeMechs, ), - - Center( - child: Text( - S.of(context).gift_card_redeemed_note, - textAlign: TextAlign.center, - style: textSmall(color: Theme.of(context).primaryTextTheme.overline.color,), - ), + _IoniaCardListView( + emptyText: S.of(context).gift_card_redeemed_note, + merchList: viewModel.redeemedMerchs, ), ], + ); + }), + ), + ], + ), + ); + } +} + +class _IoniaCardListView extends StatelessWidget { + _IoniaCardListView({ + Key key, + @required this.emptyText, + @required this.merchList, + }) : super(key: key); + + final String emptyText; + final List merchList; + + @override + Widget build(BuildContext context) { + return merchList.isEmpty + ? Center( + child: Text( + emptyText, + textAlign: TextAlign.center, + style: textSmall( + color: Theme.of(context).primaryTextTheme.overline.color, ), ), - ], - ), - ); + ) + : ListView.builder( + itemCount: merchList.length, + itemBuilder: (context, index) { + final merchant = merchList[index]; + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: CardItem( + title: merchant.legalName, + backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), + discount: merchant.minimumDiscount, + discountBackground: AssetImage('assets/images/red_badge_discount.png'), + titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor, + subtitleColor: Theme.of(context).hintColor, + subTitle: + '${merchant.isOnline ? '${S.of(context).online}' ' && ${S.current.in_store}' : S.of(context).offline}', + logoUrl: merchant.logoUrl, + ), + ); + }, + ); } } diff --git a/lib/src/screens/ionia/cards/ionia_account_page.dart b/lib/src/screens/ionia/cards/ionia_account_page.dart index 2612b2c666..c16aef0021 100644 --- a/lib/src/screens/ionia/cards/ionia_account_page.dart +++ b/lib/src/screens/ionia/cards/ionia_account_page.dart @@ -26,7 +26,6 @@ class IoniaAccountPage extends BasePage { @override Widget body(BuildContext context) { - final deviceWidth = MediaQuery.of(context).size.width; return ScrollableWithBottomSection( contentPadding: EdgeInsets.all(24), content: Column( @@ -35,18 +34,18 @@ class IoniaAccountPage extends BasePage { content: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Observer(builder: (_) => - RichText( - text: TextSpan( - text: '${ioniaAccountViewModel.countOfMerch}', - style: textLargeSemiBold(), - children: [ - TextSpan( - text: ' ${S.of(context).active_cards}', - style: textSmall(color: Colors.white.withOpacity(0.7))), - ], - ), - )), + Observer( + builder: (_) => RichText( + text: TextSpan( + text: '${ioniaAccountViewModel.countOfMerch}', + style: textLargeSemiBold(), + children: [ + TextSpan( + text: ' ${S.of(context).active_cards}', + style: textSmall(color: Colors.white.withOpacity(0.7))), + ], + ), + )), InkWell( onTap: () => Navigator.pushNamed(context, Routes.ioniaAccountCardsPage), child: Padding( @@ -121,10 +120,9 @@ class IoniaAccountPage extends BasePage { // ], //), SizedBox(height: 40), - Observer(builder: (_) => - IoniaTile( - title: S.of(context).email_address, - subTitle: ioniaAccountViewModel.email)), + Observer( + builder: (_) => IoniaTile(title: S.of(context).email_address, subTitle: ioniaAccountViewModel.email), + ), Divider() ], ), diff --git a/lib/src/screens/ionia/widgets/card_item.dart b/lib/src/screens/ionia/widgets/card_item.dart index 9a46072fde..bcddccdb96 100644 --- a/lib/src/screens/ionia/widgets/card_item.dart +++ b/lib/src/screens/ionia/widgets/card_item.dart @@ -2,17 +2,16 @@ import 'package:cake_wallet/src/widgets/discount_badge.dart'; import 'package:flutter/material.dart'; class CardItem extends StatelessWidget { - CardItem({ @required this.title, @required this.subTitle, @required this.backgroundColor, @required this.titleColor, @required this.subtitleColor, + this.discountBackground, this.onTap, this.logoUrl, this.discount, - }); final VoidCallback onTap; @@ -23,6 +22,7 @@ class CardItem extends StatelessWidget { final Color backgroundColor; final Color titleColor; final Color subtitleColor; + final AssetImage discountBackground; @override Widget build(BuildContext context) { @@ -70,7 +70,7 @@ class CardItem extends StatelessWidget { title, overflow: TextOverflow.ellipsis, style: TextStyle( - color: titleColor, + color: titleColor, fontSize: 20, fontWeight: FontWeight.w900, ), @@ -80,9 +80,10 @@ class CardItem extends StatelessWidget { Text( subTitle, style: TextStyle( - color: subtitleColor, - fontWeight: FontWeight.w500, - fontFamily: 'Lato'), + color: subtitleColor, + fontWeight: FontWeight.w500, + fontFamily: 'Lato', + ), ) ], ), @@ -94,7 +95,10 @@ class CardItem extends StatelessWidget { alignment: Alignment.topRight, child: Padding( padding: const EdgeInsets.only(top: 20.0), - child: DiscountBadge(percentage: discount), + child: DiscountBadge( + percentage: discount, + discountBackground: discountBackground, + ), ), ), ], @@ -104,7 +108,6 @@ class CardItem extends StatelessWidget { } class _PlaceholderContainer extends StatelessWidget { - const _PlaceholderContainer({@required this.text}); final String text; diff --git a/lib/src/widgets/discount_badge.dart b/lib/src/widgets/discount_badge.dart index d4e9836b2a..855822c60c 100644 --- a/lib/src/widgets/discount_badge.dart +++ b/lib/src/widgets/discount_badge.dart @@ -1,33 +1,35 @@ import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; - class DiscountBadge extends StatelessWidget { const DiscountBadge({ Key key, @required this.percentage, + this.discountBackground, }) : super(key: key); final double percentage; + final AssetImage discountBackground; @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: Text( S.of(context).discount(percentage.toString()), + child: Text( + S.of(context).discount(percentage.toString()), style: TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500, fontFamily: 'Lato', - ), ), + ), decoration: BoxDecoration( image: DecorationImage( fit: BoxFit.fill, - image: AssetImage('assets/images/badge_discount.png'), - ), - ), - ); - } + image: discountBackground ?? AssetImage('assets/images/badge_discount.png'), + ), + ), + ); + } } diff --git a/lib/view_model/ionia/ionia_account_view_model.dart b/lib/view_model/ionia/ionia_account_view_model.dart index 2332d57313..1b049b974f 100644 --- a/lib/view_model/ionia/ionia_account_view_model.dart +++ b/lib/view_model/ionia/ionia_account_view_model.dart @@ -7,14 +7,11 @@ part 'ionia_account_view_model.g.dart'; class IoniaAccountViewModel = IoniaAccountViewModelBase with _$IoniaAccountViewModel; abstract class IoniaAccountViewModelBase with Store { - IoniaAccountViewModelBase({this.ioniaService}) { email = ''; merchs = []; - ioniaService.getUserEmail() - .then((email) => this.email = email); - ioniaService.getCurrentUserGiftCardSummaries() - .then((merchs) => this.merchs = merchs); + ioniaService.getUserEmail().then((email) => this.email = email); + ioniaService.getCurrentUserGiftCardSummaries().then((merchs) => this.merchs = merchs); } final IoniaService ioniaService; @@ -28,9 +25,14 @@ abstract class IoniaAccountViewModelBase with Store { @computed int get countOfMerch => merchs.where((merch) => merch.isActive).length; + @computed + List get activeMechs => merchs.where((merch) => merch.isActive).toList(); + + @computed + List get redeemedMerchs => merchs.where((merch) => !merch.isActive).toList(); + @action - void logout(){ + void logout() { ioniaService.logout(); } - -} \ No newline at end of file +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index d89edde0cd..b3e6839460 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -619,5 +619,6 @@ "more_options": "More Options", "awaiting_payment_confirmation": "Awaiting payment confirmation", "transaction_sent_notice": "If the screen doesn’t proceed after 1 minute, check a block explorer and your email.", - "agree": "Agree" + "agree": "Agree", + "in_store": "In Store" } From d85cc72c4269fa8215c3786e8e757e75796108a8 Mon Sep 17 00:00:00 2001 From: M Date: Thu, 7 Jul 2022 19:54:13 +0100 Subject: [PATCH 18/55] Fixes for getting of purchased gift cards. --- lib/ionia/ionia_api.dart | 8 ++- lib/ionia/ionia_gift_card.dart | 71 +++++++++++++++++++ lib/ionia/ionia_service.dart | 3 +- .../ionia/cards/ionia_account_cards_page.dart | 8 +-- .../ionia/ionia_account_view_model.dart | 7 +- 5 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 lib/ionia/ionia_gift_card.dart diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart index b00c52217f..93960b5fde 100644 --- a/lib/ionia/ionia_api.dart +++ b/lib/ionia/ionia_api.dart @@ -6,6 +6,7 @@ import 'package:http/http.dart'; import 'package:cake_wallet/ionia/ionia_user_credentials.dart'; import 'package:cake_wallet/ionia/ionia_virtual_card.dart'; import 'package:cake_wallet/ionia/ionia_category.dart'; +import 'package:cake_wallet/ionia/ionia_gift_card.dart'; class IoniaApi { static const baseUri = 'apidev.ionia.io'; @@ -17,6 +18,7 @@ class IoniaApi { static final getMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/GetMerchants'); static final getMerchantsByFilterUrl = Uri.https(baseUri, '/$pathPrefix/GetMerchantsByFilter'); static final getPurchaseMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/PurchaseGiftCard'); + static final getCurrentUserGiftCardSummariesUrl = Uri.https(baseUri, '/$pathPrefix/GetCurrentUserGiftCardSummaries'); // Create user @@ -245,7 +247,7 @@ class IoniaApi { // Get Current User Gift Card Summaries - Future> getCurrentUserGiftCardSummaries({ + Future> getCurrentUserGiftCardSummaries({ @required String username, @required String password, @required String clientId}) async { @@ -253,7 +255,7 @@ class IoniaApi { 'clientId': clientId, 'username': username, 'password': password}; - final response = await post(getMerchantsUrl, headers: headers); + final response = await post(getCurrentUserGiftCardSummariesUrl, headers: headers); if (response.statusCode != 200) { return []; @@ -269,7 +271,7 @@ class IoniaApi { final data = decodedBody['Data'] as List; return data.map((dynamic e) { final element = e as Map; - return IoniaMerchant.fromJsonMap(element); + return IoniaGiftCard.fromJsonMap(element); }).toList(); } } \ No newline at end of file diff --git a/lib/ionia/ionia_gift_card.dart b/lib/ionia/ionia_gift_card.dart new file mode 100644 index 0000000000..ee99f79863 --- /dev/null +++ b/lib/ionia/ionia_gift_card.dart @@ -0,0 +1,71 @@ +import 'package:flutter/foundation.dart'; + +class IoniaGiftCard { + IoniaGiftCard({ + @required this.id, + @required this.merchantId, + @required this.legalName, + @required this.systemName, + @required this.barcodeUrl, + @required this.cardNumber, + @required this.cardPin, + @required this.usageInstructions, + @required this.balanceInstructions, + @required this.paymentInstructions, + @required this.cardImageUrl, + @required this.tip, + @required this.purchaseAmount, + @required this.actualAmount, + @required this.totalTransactionAmount, + @required this.totalDashTransactionAmount, + @required this.remainingAmount, + @required this.createdDateFormatted, + @required this.lastTransactionDateFormatted, + @required this.isActive, + @required this.isEmpty, + @required this.logoUrl}); + + factory IoniaGiftCard.fromJsonMap(Map element) { + return IoniaGiftCard( + id: element['Id'] as int, + merchantId: element['MerchantId'] as int, + legalName: element['LegalName'] as String, + systemName: element['SystemName'] as String, + barcodeUrl: element['BarcodeUrl'] as String, + cardNumber: element['CardNumber'] as String, + tip: element['Tip'] as double, + purchaseAmount: element['PurchaseAmount'] as double, + actualAmount: element['ActualAmount'] as double, + totalTransactionAmount: element['TotalTransactionAmount'] as double, + totalDashTransactionAmount: element['TotalDashTransactionAmount'] as double, + remainingAmount: element['RemainingAmount'] as double, + isActive: element['IsActive'] as bool, + isEmpty: element['IsEmpty'] as bool, + logoUrl: element['LogoUrl'] as String, + createdDateFormatted: element['CreatedDate'] as String, + lastTransactionDateFormatted: element['LastTransactionDate'] as String); + } + + final int id; + final int merchantId; + final String legalName; + final String systemName; + final String barcodeUrl; + final String cardNumber; + final String cardPin; + final Map usageInstructions; + final Map balanceInstructions; + final Map paymentInstructions; + final String cardImageUrl; + final double tip; + final double purchaseAmount; + final double actualAmount; + final double totalTransactionAmount; + final double totalDashTransactionAmount; + final double remainingAmount; + final String createdDateFormatted; + final String lastTransactionDateFormatted; + final bool isActive; + final bool isEmpty; + final String logoUrl; +} \ No newline at end of file diff --git a/lib/ionia/ionia_service.dart b/lib/ionia/ionia_service.dart index 19574e997d..ceb95f6fc7 100644 --- a/lib/ionia/ionia_service.dart +++ b/lib/ionia/ionia_service.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/ionia/ionia_api.dart'; +import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/ionia/ionia_category.dart'; class IoniaService { @@ -114,7 +115,7 @@ class IoniaService { // Get Current User Gift Card Summaries - Future> getCurrentUserGiftCardSummaries() async { + Future> getCurrentUserGiftCardSummaries() async { final username = await secureStorage.read(key: ioniaUsernameStorageKey); final password = await secureStorage.read(key: ioniaPasswordStorageKey); return ioniaApi.getCurrentUserGiftCardSummaries(username: username, password: password, clientId: clientId); diff --git a/lib/src/screens/ionia/cards/ionia_account_cards_page.dart b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart index ba2ddae55f..07ab4b17ea 100644 --- a/lib/src/screens/ionia/cards/ionia_account_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; @@ -125,7 +126,7 @@ class _IoniaCardListView extends StatelessWidget { }) : super(key: key); final String emptyText; - final List merchList; + final List merchList; @override Widget build(BuildContext context) { @@ -148,12 +149,11 @@ class _IoniaCardListView extends StatelessWidget { child: CardItem( title: merchant.legalName, backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), - discount: merchant.minimumDiscount, + discount: 0, discountBackground: AssetImage('assets/images/red_badge_discount.png'), titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor, subtitleColor: Theme.of(context).hintColor, - subTitle: - '${merchant.isOnline ? '${S.of(context).online}' ' && ${S.current.in_store}' : S.of(context).offline}', + subTitle: '', logoUrl: merchant.logoUrl, ), ); diff --git a/lib/view_model/ionia/ionia_account_view_model.dart b/lib/view_model/ionia/ionia_account_view_model.dart index 1b049b974f..1bdc1c9f70 100644 --- a/lib/view_model/ionia/ionia_account_view_model.dart +++ b/lib/view_model/ionia/ionia_account_view_model.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/ionia/ionia_service.dart'; import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/ionia/ionia_gift_card.dart'; part 'ionia_account_view_model.g.dart'; @@ -20,16 +21,16 @@ abstract class IoniaAccountViewModelBase with Store { String email; @observable - List merchs; + List merchs; @computed int get countOfMerch => merchs.where((merch) => merch.isActive).length; @computed - List get activeMechs => merchs.where((merch) => merch.isActive).toList(); + List get activeMechs => merchs.where((merch) => merch.isActive).toList(); @computed - List get redeemedMerchs => merchs.where((merch) => !merch.isActive).toList(); + List get redeemedMerchs => merchs.where((merch) => !merch.isActive).toList(); @action void logout() { From 2a0206c9cdbd38888df0328ab668d81d3af93d16 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Mon, 11 Jul 2022 18:25:18 +0300 Subject: [PATCH 19/55] Implement gift card details screen (#408) * Implement gift card details screen --- lib/di.dart | 6 + lib/router.dart | 5 + lib/routes.dart | 2 +- .../ionia/cards/ionia_account_cards_page.dart | 6 + .../cards/ionia_gift_card_detail_page.dart | 173 ++++++++++++++++++ res/values/strings_en.arb | 2 +- 6 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart diff --git a/lib/di.dart b/lib/di.dart index ba49c99143..d026ff1366 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -3,6 +3,8 @@ import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/wake_lock.dart'; import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_category.dart'; +import 'package:cake_wallet/ionia/ionia_gift_card.dart'; +import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart'; import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart'; @@ -711,6 +713,10 @@ Future setup( return IoniaBuyGiftCardDetailPage(getIt.get(param1: amount, param2: merchant)); }); + getIt.registerFactoryParam((IoniaGiftCard giftCard, _) { + return IoniaGiftCardDetailPage(giftCard); + }); + getIt.registerFactoryParam((List args, _) { final amount = args.first as String; final merchant = args.last as IoniaMerchant; diff --git a/lib/router.dart b/lib/router.dart index 2f336a6c5b..9764ec17a1 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -8,6 +8,7 @@ import 'package:cake_wallet/src/screens/buy/pre_order_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart'; +import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart'; import 'package:cake_wallet/src/screens/order_details/order_details_page.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart'; @@ -446,6 +447,10 @@ Route createRoute(RouteSettings settings) { final args = settings.arguments as List; return CupertinoPageRoute(builder: (_) =>getIt.get(param1: args)); + case Routes.ioniaGiftCardDetailPage: + final args = settings.arguments as List; + return CupertinoPageRoute(builder: (_) => getIt.get(param1: args.first)); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index cc4ca71143..1b04db1ebb 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -72,5 +72,5 @@ class Routes { static const ioniaAccountPage = 'ionia_account_page'; static const ioniaAccountCardsPage = 'ionia_account_cards_page'; static const ioniaCustomTipPage = 'ionia_custom_tip_page'; - + static const ioniaGiftCardDetailPage = '/ionia_gift_card_detail_page'; } diff --git a/lib/src/screens/ionia/cards/ionia_account_cards_page.dart b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart index 07ab4b17ea..5e752e5fa9 100644 --- a/lib/src/screens/ionia/cards/ionia_account_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; +import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; import 'package:cake_wallet/typography.dart'; @@ -147,6 +148,11 @@ class _IoniaCardListView extends StatelessWidget { return Padding( padding: const EdgeInsets.only(bottom: 16), child: CardItem( + onTap: () => Navigator.pushNamed( + context, + Routes.ioniaGiftCardDetailPage, + arguments: [merchant], + ), title: merchant.legalName, backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), discount: 0, diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart new file mode 100644 index 0000000000..4c47f90a75 --- /dev/null +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -0,0 +1,173 @@ +import 'package:cake_wallet/ionia/ionia_gift_card.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; +import 'package:cake_wallet/src/widgets/alert_background.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/typography.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class IoniaGiftCardDetailPage extends BasePage { + IoniaGiftCardDetailPage(this.merchant); + + final IoniaGiftCard merchant; + + @override + Widget leading(BuildContext context) { + if (ModalRoute.of(context).isFirst) { + return null; + } + + final _backButton = Icon( + Icons.arrow_back_ios, + color: Theme.of(context).primaryTextTheme.title.color, + size: 16, + ); + return Padding( + padding: const EdgeInsets.only(left: 10.0), + child: SizedBox( + height: 37, + width: 37, + child: ButtonTheme( + minWidth: double.minPositive, + child: FlatButton( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + padding: EdgeInsets.all(0), + onPressed: () => onClose(context), + child: _backButton), + ), + ), + ); + } + + @override + Widget middle(BuildContext context) { + return Text( + merchant.legalName, + style: textLargeSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor), + ); + } + + @override + Widget body(BuildContext context) { + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.all(24), + content: Column( + children: [ + if (merchant.barcodeUrl != null && merchant.barcodeUrl.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24.0, + vertical: 24, + ), + child: SizedBox(height: 96, width: double.infinity, child: Image.network(merchant.barcodeUrl)), + ), + SizedBox(height: 24), + IoniaTile( + title: S.of(context).gift_card_number, + subTitle: merchant.cardNumber, + ), + Divider(height: 30), + IoniaTile( + title: S.of(context).pin_number, + subTitle: merchant.cardPin ?? '', + ), + Divider(height: 30), + IoniaTile( + title: S.of(context).amount, + subTitle: merchant.remainingAmount.toString() ?? '0', + ), + Divider(height: 50), + TextIconButton( + label: S.of(context).how_to_use_card, + onTap: () => _showHowToUseCard(context, merchant), + ), + ], + ), + bottomSection: Padding( + padding: EdgeInsets.only(bottom: 12), + child: LoadingPrimaryButton( + isLoading: false, + onPressed: () {}, + text: S.of(context).mark_as_redeemed, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + )), + ); + } + + void _showHowToUseCard( + BuildContext context, + IoniaGiftCard merchant, + ) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertBackground( + child: Material( + color: Colors.transparent, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(height: 10), + Container( + padding: EdgeInsets.only(top: 24, left: 24, right: 24), + margin: EdgeInsets.all(24), + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + borderRadius: BorderRadius.circular(30), + ), + child: Column( + children: [ + Text( + S.of(context).how_to_use_card, + style: textLargeSemiBold( + color: Theme.of(context).textTheme.body1.color, + ), + ), + SizedBox(height: 24), + Align( + alignment: Alignment.bottomLeft, + child: Text( + '', + style: textMedium( + color: Theme.of(context).textTheme.display2.color, + ), + ), + ), + SizedBox(height: 35), + PrimaryButton( + onPressed: () => Navigator.pop(context), + text: S.of(context).send_got_it, + color: Color.fromRGBO(233, 242, 252, 1), + textColor: Theme.of(context).textTheme.display2.color, + ), + SizedBox(height: 21), + ], + ), + ), + InkWell( + onTap: () => Navigator.pop(context), + child: Container( + margin: EdgeInsets.only(bottom: 40), + child: CircleAvatar( + child: Icon( + Icons.close, + color: Colors.black, + ), + backgroundColor: Colors.white, + ), + ), + ) + ], + ), + ), + ); + }); + } +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index b3e6839460..ec420ba24e 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -620,5 +620,5 @@ "awaiting_payment_confirmation": "Awaiting payment confirmation", "transaction_sent_notice": "If the screen doesn’t proceed after 1 minute, check a block explorer and your email.", "agree": "Agree", - "in_store": "In Store" + "in_store": "In Store", } From abb6ff4933ee9844fc1ce72818ef000c6a33b78a Mon Sep 17 00:00:00 2001 From: M Date: Thu, 14 Jul 2022 17:57:40 +0100 Subject: [PATCH 20/55] Add redeem for ionia gift cards --- lib/di.dart | 9 ++- lib/ionia/ionia_api.dart | 81 +++++++++++++++++++ lib/ionia/ionia_gift_card.dart | 1 + lib/ionia/ionia_service.dart | 29 +++++++ .../cards/ionia_gift_card_detail_page.dart | 65 ++++++++++----- .../ionia/ionia_account_view_model.dart | 6 +- .../ionia_gift_card_details_view_model.dart | 35 ++++++++ res/values/strings_en.arb | 2 +- 8 files changed, 204 insertions(+), 24 deletions(-) create mode 100644 lib/view_model/ionia/ionia_gift_card_details_view_model.dart diff --git a/lib/di.dart b/lib/di.dart index d026ff1366..4451649b13 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -145,6 +145,7 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; import 'package:cake_wallet/ionia/ionia_token_service.dart'; import 'package:cake_wallet/anypay/anypay_api.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart'; final getIt = GetIt.instance; @@ -713,8 +714,14 @@ Future setup( return IoniaBuyGiftCardDetailPage(getIt.get(param1: amount, param2: merchant)); }); + getIt.registerFactoryParam((IoniaGiftCard giftCard, _) { + return IoniaGiftCardDetailsViewModel( + ioniaService: getIt.get(), + giftCard: giftCard); + }); + getIt.registerFactoryParam((IoniaGiftCard giftCard, _) { - return IoniaGiftCardDetailPage(giftCard); + return IoniaGiftCardDetailPage(getIt.get(param1: giftCard)); }); getIt.registerFactoryParam((List args, _) { diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart index 93960b5fde..da5911c845 100644 --- a/lib/ionia/ionia_api.dart +++ b/lib/ionia/ionia_api.dart @@ -19,6 +19,8 @@ class IoniaApi { static final getMerchantsByFilterUrl = Uri.https(baseUri, '/$pathPrefix/GetMerchantsByFilter'); static final getPurchaseMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/PurchaseGiftCard'); static final getCurrentUserGiftCardSummariesUrl = Uri.https(baseUri, '/$pathPrefix/GetCurrentUserGiftCardSummaries'); + static final changeGiftCardUrl = Uri.https(baseUri, '/$pathPrefix/ChargeGiftCard'); + static final getGiftCardUrl = Uri.https(baseUri, '/$pathPrefix/GetGiftCard'); // Create user @@ -274,4 +276,83 @@ class IoniaApi { return IoniaGiftCard.fromJsonMap(element); }).toList(); } + + // Charge Gift Card + + Future chargeGiftCard({ + @required String username, + @required String password, + @required String clientId, + @required int giftCardId, + @required double amount}) async { + final headers = { + 'clientId': clientId, + 'username': username, + 'password': password, + 'Content-Type': 'application/json'}; + final body = { + 'Id': giftCardId, + 'Amount': amount}; + final response = await post( + changeGiftCardUrl, + headers: headers, + body: json.encode(body)); + + if (response.statusCode != 200) { + throw Exception('Failed to update Gift Card with ID ${giftCardId};Incorrect response status: ${response.statusCode};'); + } + + final decodedBody = json.decode(response.body) as Map; + final isSuccessful = decodedBody['Successful'] as bool ?? false; + + if (!isSuccessful) { + final data = decodedBody['Data'] as Map; + final msg = data['Message'] as String ?? ''; + + if (msg.isNotEmpty) { + throw Exception(msg); + } + + throw Exception('Failed to update Gift Card with ID ${giftCardId};'); + } + } + + // Get Gift Card + + Future getGiftCard({ + @required String username, + @required String password, + @required String clientId, + @required int id}) async { + final headers = { + 'clientId': clientId, + 'username': username, + 'password': password, + 'Content-Type': 'application/json'}; + final body = {'Id': id}; + final response = await post( + getGiftCardUrl, + headers: headers, + body: json.encode(body)); + + if (response.statusCode != 200) { + throw Exception('Failed to get Gift Card with ID ${id};Incorrect response status: ${response.statusCode};'); + } + + final decodedBody = json.decode(response.body) as Map; + final isSuccessful = decodedBody['Successful'] as bool ?? false; + + if (!isSuccessful) { + final msg = decodedBody['ErrorMessage'] as String ?? ''; + + if (msg.isNotEmpty) { + throw Exception(msg); + } + + throw Exception('Failed to get Gift Card with ID ${id};'); + } + + final data = decodedBody['Data'] as Map; + return IoniaGiftCard.fromJsonMap(data); + } } \ No newline at end of file diff --git a/lib/ionia/ionia_gift_card.dart b/lib/ionia/ionia_gift_card.dart index ee99f79863..df0ee6a52a 100644 --- a/lib/ionia/ionia_gift_card.dart +++ b/lib/ionia/ionia_gift_card.dart @@ -33,6 +33,7 @@ class IoniaGiftCard { systemName: element['SystemName'] as String, barcodeUrl: element['BarcodeUrl'] as String, cardNumber: element['CardNumber'] as String, + cardPin: element['CardPin'] as String, tip: element['Tip'] as double, purchaseAmount: element['PurchaseAmount'] as double, actualAmount: element['ActualAmount'] as double, diff --git a/lib/ionia/ionia_service.dart b/lib/ionia/ionia_service.dart index ceb95f6fc7..c00b0ad18e 100644 --- a/lib/ionia/ionia_service.dart +++ b/lib/ionia/ionia_service.dart @@ -120,4 +120,33 @@ class IoniaService { final password = await secureStorage.read(key: ioniaPasswordStorageKey); return ioniaApi.getCurrentUserGiftCardSummaries(username: username, password: password, clientId: clientId); } + + // Charge Gift Card + + Future chargeGiftCard({ + @required int giftCardId, + @required double amount}) async { + final username = await secureStorage.read(key: ioniaUsernameStorageKey); + final password = await secureStorage.read(key: ioniaPasswordStorageKey); + await ioniaApi.chargeGiftCard( + username: username, + password: password, + clientId: clientId, + giftCardId: giftCardId, + amount: amount); + } + + // Redeem + + Future redeem(IoniaGiftCard giftCard) async { + await chargeGiftCard(giftCardId: giftCard.id, amount: giftCard.remainingAmount); + } + + // Get Gift Card + + Future getGiftCard({@required int id}) async { + final username = await secureStorage.read(key: ioniaUsernameStorageKey); + final password = await secureStorage.read(key: ioniaPasswordStorageKey); + return ioniaApi.getGiftCard(username: username, password: password, clientId: clientId,id: id); + } } \ No newline at end of file diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index 4c47f90a75..7f081453a5 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -1,20 +1,25 @@ +import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; import 'package:cake_wallet/src/widgets/alert_background.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.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/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/framework.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; class IoniaGiftCardDetailPage extends BasePage { - IoniaGiftCardDetailPage(this.merchant); + IoniaGiftCardDetailPage(this.viewModel); - final IoniaGiftCard merchant; + final IoniaGiftCardDetailsViewModel viewModel; @override Widget leading(BuildContext context) { @@ -48,56 +53,78 @@ class IoniaGiftCardDetailPage extends BasePage { @override Widget middle(BuildContext context) { return Text( - merchant.legalName, + viewModel.giftCard.legalName, style: textLargeSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor), ); } @override Widget body(BuildContext context) { + reaction((_) => viewModel.redeemState, (ExecutionState state) { + if (state is FailureState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: state.error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + }); + } + }); + return ScrollableWithBottomSection( contentPadding: EdgeInsets.all(24), content: Column( children: [ - if (merchant.barcodeUrl != null && merchant.barcodeUrl.isNotEmpty) + if (viewModel.giftCard.barcodeUrl != null && viewModel.giftCard.barcodeUrl.isNotEmpty) Padding( padding: const EdgeInsets.symmetric( horizontal: 24.0, vertical: 24, ), - child: SizedBox(height: 96, width: double.infinity, child: Image.network(merchant.barcodeUrl)), + child: SizedBox(height: 96, width: double.infinity, child: Image.network(viewModel.giftCard.barcodeUrl)), ), SizedBox(height: 24), IoniaTile( title: S.of(context).gift_card_number, - subTitle: merchant.cardNumber, + subTitle: viewModel.giftCard.cardNumber, ), Divider(height: 30), IoniaTile( title: S.of(context).pin_number, - subTitle: merchant.cardPin ?? '', + subTitle: viewModel.giftCard.cardPin ?? '', ), Divider(height: 30), - IoniaTile( - title: S.of(context).amount, - subTitle: merchant.remainingAmount.toString() ?? '0', - ), + Observer(builder: (_) => + IoniaTile( + title: S.of(context).amount, + subTitle: viewModel.giftCard.remainingAmount.toString() ?? '0', + )), Divider(height: 50), TextIconButton( label: S.of(context).how_to_use_card, - onTap: () => _showHowToUseCard(context, merchant), + onTap: () => _showHowToUseCard(context, viewModel.giftCard), ), ], ), bottomSection: Padding( padding: EdgeInsets.only(bottom: 12), - child: LoadingPrimaryButton( - isLoading: false, - onPressed: () {}, - text: S.of(context).mark_as_redeemed, - color: Theme.of(context).accentTextTheme.body2.color, - textColor: Colors.white, - )), + child: Observer(builder: (_) { + if (!viewModel.giftCard.isEmpty) { + return LoadingPrimaryButton( + isLoading: viewModel.redeemState is IsExecutingState, + onPressed: () => viewModel.redeem(), + text: S.of(context).mark_as_redeemed, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white); + } + + return Container(); + })), ); } diff --git a/lib/view_model/ionia/ionia_account_view_model.dart b/lib/view_model/ionia/ionia_account_view_model.dart index 1bdc1c9f70..1a57d0acf3 100644 --- a/lib/view_model/ionia/ionia_account_view_model.dart +++ b/lib/view_model/ionia/ionia_account_view_model.dart @@ -24,13 +24,13 @@ abstract class IoniaAccountViewModelBase with Store { List merchs; @computed - int get countOfMerch => merchs.where((merch) => merch.isActive).length; + int get countOfMerch => merchs.where((merch) => !merch.isEmpty).length; @computed - List get activeMechs => merchs.where((merch) => merch.isActive).toList(); + List get activeMechs => merchs.where((merch) => !merch.isEmpty).toList(); @computed - List get redeemedMerchs => merchs.where((merch) => !merch.isActive).toList(); + List get redeemedMerchs => merchs.where((merch) => merch.isEmpty).toList(); @action void logout() { diff --git a/lib/view_model/ionia/ionia_gift_card_details_view_model.dart b/lib/view_model/ionia/ionia_gift_card_details_view_model.dart new file mode 100644 index 0000000000..7c811ad623 --- /dev/null +++ b/lib/view_model/ionia/ionia_gift_card_details_view_model.dart @@ -0,0 +1,35 @@ +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/ionia/ionia_service.dart'; +import 'package:cake_wallet/ionia/ionia_gift_card.dart'; +import 'package:mobx/mobx.dart'; + +part 'ionia_gift_card_details_view_model.g.dart'; + +class IoniaGiftCardDetailsViewModel = IoniaGiftCardDetailsViewModelBase with _$IoniaGiftCardDetailsViewModel; + +abstract class IoniaGiftCardDetailsViewModelBase with Store { + + IoniaGiftCardDetailsViewModelBase({this.ioniaService, this.giftCard}) { + redeemState = InitialExecutionState(); + } + + final IoniaService ioniaService; + + @observable + IoniaGiftCard giftCard; + + @observable + ExecutionState redeemState; + + @action + Future redeem() async { + try { + redeemState = InitialExecutionState(); + await ioniaService.redeem(giftCard); + giftCard = await ioniaService.getGiftCard(id: giftCard.id); + redeemState = ExecutedSuccessfullyState(); + } catch(e) { + redeemState = FailureState(e.toString()); + } + } +} \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index ec420ba24e..b3e6839460 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -620,5 +620,5 @@ "awaiting_payment_confirmation": "Awaiting payment confirmation", "transaction_sent_notice": "If the screen doesn’t proceed after 1 minute, check a block explorer and your email.", "agree": "Agree", - "in_store": "In Store", + "in_store": "In Store" } From b9cc776614ecdbe3daccf5758eef874c9930ca8a Mon Sep 17 00:00:00 2001 From: M Date: Thu, 14 Jul 2022 19:04:55 +0100 Subject: [PATCH 21/55] Fix navigation after ionia opt redirection. --- lib/src/screens/ionia/auth/ionia_verify_otp_page.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart index 909d8b89a3..d1253c784a 100644 --- a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart +++ b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart @@ -143,5 +143,6 @@ class IoniaVerifyIoniaOtp extends BasePage { } void _onOtpSuccessful(BuildContext context) => - Navigator.pushNamedAndRemoveUntil(context, Routes.ioniaManageCardsPage, ModalRoute.withName(Routes.dashboard)); + Navigator.of(context) + .pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst); } From ce479849ea68165e30cc1f43b46cabe162922a4b Mon Sep 17 00:00:00 2001 From: M Date: Thu, 14 Jul 2022 19:59:34 +0100 Subject: [PATCH 22/55] Fix update gift cards list. --- .../ionia/cards/ionia_account_cards_page.dart | 26 ++++++++++++++----- .../ionia/cards/ionia_account_page.dart | 5 +++- .../ionia/ionia_account_view_model.dart | 17 +++++++----- .../ionia_gift_card_details_view_model.dart | 2 +- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/lib/src/screens/ionia/cards/ionia_account_cards_page.dart b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart index 5e752e5fa9..f1d08a75e9 100644 --- a/lib/src/screens/ionia/cards/ionia_account_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart @@ -1,3 +1,5 @@ +import 'dart:ffi'; + import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/routes.dart'; @@ -104,11 +106,23 @@ class _IoniaCardTabsState extends State<_IoniaCardTabs> with SingleTickerProvide _IoniaCardListView( emptyText: S.of(context).gift_card_balance_note, merchList: viewModel.activeMechs, - ), + onTap: (giftCard) { + Navigator.pushNamed( + context, + Routes.ioniaGiftCardDetailPage, + arguments: [giftCard]) + .then((_) => viewModel.updateUserGiftCards()); + }), _IoniaCardListView( emptyText: S.of(context).gift_card_redeemed_note, merchList: viewModel.redeemedMerchs, - ), + onTap: (giftCard) { + Navigator.pushNamed( + context, + Routes.ioniaGiftCardDetailPage, + arguments: [giftCard]) + .then((_) => viewModel.updateUserGiftCards()); + }), ], ); }), @@ -124,10 +138,12 @@ class _IoniaCardListView extends StatelessWidget { Key key, @required this.emptyText, @required this.merchList, + @required this.onTap, }) : super(key: key); final String emptyText; final List merchList; + final void Function(IoniaGiftCard giftCard) onTap; @override Widget build(BuildContext context) { @@ -148,11 +164,7 @@ class _IoniaCardListView extends StatelessWidget { return Padding( padding: const EdgeInsets.only(bottom: 16), child: CardItem( - onTap: () => Navigator.pushNamed( - context, - Routes.ioniaGiftCardDetailPage, - arguments: [merchant], - ), + onTap: () => onTap?.call(merchant), title: merchant.legalName, backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), discount: 0, diff --git a/lib/src/screens/ionia/cards/ionia_account_page.dart b/lib/src/screens/ionia/cards/ionia_account_page.dart index c16aef0021..2a34dc74cb 100644 --- a/lib/src/screens/ionia/cards/ionia_account_page.dart +++ b/lib/src/screens/ionia/cards/ionia_account_page.dart @@ -47,7 +47,10 @@ class IoniaAccountPage extends BasePage { ), )), InkWell( - onTap: () => Navigator.pushNamed(context, Routes.ioniaAccountCardsPage), + onTap: () { + Navigator.pushNamed(context, Routes.ioniaAccountCardsPage) + .then((_) => ioniaAccountViewModel.updateUserGiftCards()); + }, child: Padding( padding: const EdgeInsets.all(8.0), child: Text( diff --git a/lib/view_model/ionia/ionia_account_view_model.dart b/lib/view_model/ionia/ionia_account_view_model.dart index 1a57d0acf3..a4875ec613 100644 --- a/lib/view_model/ionia/ionia_account_view_model.dart +++ b/lib/view_model/ionia/ionia_account_view_model.dart @@ -10,9 +10,9 @@ class IoniaAccountViewModel = IoniaAccountViewModelBase with _$IoniaAccountViewM abstract class IoniaAccountViewModelBase with Store { IoniaAccountViewModelBase({this.ioniaService}) { email = ''; - merchs = []; + giftCards = []; ioniaService.getUserEmail().then((email) => this.email = email); - ioniaService.getCurrentUserGiftCardSummaries().then((merchs) => this.merchs = merchs); + updateUserGiftCards(); } final IoniaService ioniaService; @@ -21,19 +21,24 @@ abstract class IoniaAccountViewModelBase with Store { String email; @observable - List merchs; + List giftCards; @computed - int get countOfMerch => merchs.where((merch) => !merch.isEmpty).length; + int get countOfMerch => giftCards.where((giftCard) => !giftCard.isEmpty).length; @computed - List get activeMechs => merchs.where((merch) => !merch.isEmpty).toList(); + List get activeMechs => giftCards.where((giftCard) => !giftCard.isEmpty).toList(); @computed - List get redeemedMerchs => merchs.where((merch) => merch.isEmpty).toList(); + List get redeemedMerchs => giftCards.where((giftCard) => giftCard.isEmpty).toList(); @action void logout() { ioniaService.logout(); } + + @action + Future updateUserGiftCards() async { + giftCards = await ioniaService.getCurrentUserGiftCardSummaries(); + } } diff --git a/lib/view_model/ionia/ionia_gift_card_details_view_model.dart b/lib/view_model/ionia/ionia_gift_card_details_view_model.dart index 7c811ad623..e6138bb533 100644 --- a/lib/view_model/ionia/ionia_gift_card_details_view_model.dart +++ b/lib/view_model/ionia/ionia_gift_card_details_view_model.dart @@ -24,7 +24,7 @@ abstract class IoniaGiftCardDetailsViewModelBase with Store { @action Future redeem() async { try { - redeemState = InitialExecutionState(); + redeemState = IsExecutingState(); await ioniaService.redeem(giftCard); giftCard = await ioniaService.getGiftCard(id: giftCard.id); redeemState = ExecutedSuccessfullyState(); From 3b61edcc5a6dca2893ed11d36978f63931657c7f Mon Sep 17 00:00:00 2001 From: M Date: Mon, 18 Jul 2022 16:23:53 +0100 Subject: [PATCH 23/55] Add payment status update for ionia. --- lib/di.dart | 15 ++ lib/ionia/ionia_any_pay_payment_info.dart | 9 + lib/ionia/ionia_anypay.dart | 7 +- lib/ionia/ionia_api.dart | 43 +++ lib/ionia/ionia_order.dart | 2 +- lib/ionia/ionia_service.dart | 10 + lib/router.dart | 11 + lib/routes.dart | 1 + .../cards/ionia_buy_card_detail_page.dart | 18 +- .../cards/ionia_payment_status_page.dart | 249 ++++++++++++++++++ .../ionia_payment_status_view_model.dart | 58 ++++ .../ionia_purchase_merch_view_model.dart | 11 +- res/values/strings_en.arb | 9 +- 13 files changed, 423 insertions(+), 20 deletions(-) create mode 100644 lib/ionia/ionia_any_pay_payment_info.dart create mode 100644 lib/src/screens/ionia/cards/ionia_payment_status_page.dart create mode 100644 lib/view_model/ionia/ionia_payment_status_view_model.dart diff --git a/lib/di.dart b/lib/di.dart index 4451649b13..76d21e1452 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -146,6 +146,10 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; import 'package:cake_wallet/ionia/ionia_token_service.dart'; import 'package:cake_wallet/anypay/anypay_api.dart'; import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart'; +import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart'; +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart'; final getIt = GetIt.instance; @@ -741,5 +745,16 @@ Future setup( getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get())); + getIt.registerFactoryParam( + (IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo) + => IoniaPaymentStatusViewModel( + getIt.get(), + paymentInfo: paymentInfo, + committedInfo: committedInfo)); + + getIt.registerFactoryParam( + (IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo) + => IoniaPaymentStatusPage(getIt.get(param1: paymentInfo, param2: committedInfo))); + _isSetupFinished = true; } diff --git a/lib/ionia/ionia_any_pay_payment_info.dart b/lib/ionia/ionia_any_pay_payment_info.dart new file mode 100644 index 0000000000..6146a46fea --- /dev/null +++ b/lib/ionia/ionia_any_pay_payment_info.dart @@ -0,0 +1,9 @@ +import 'package:cake_wallet/anypay/any_pay_payment.dart'; +import 'package:cake_wallet/ionia/ionia_order.dart'; + +class IoniaAnyPayPaymentInfo { + const IoniaAnyPayPaymentInfo(this.ioniaOrder, this.anyPayPayment); + + final IoniaOrder ioniaOrder; + final AnyPayPayment anyPayPayment; +} diff --git a/lib/ionia/ionia_anypay.dart b/lib/ionia/ionia_anypay.dart index d4b6d4d59d..b9b53498ae 100644 --- a/lib/ionia/ionia_anypay.dart +++ b/lib/ionia/ionia_anypay.dart @@ -13,6 +13,8 @@ import 'package:cake_wallet/anypay/any_pay_trasnaction.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart'; +import 'package:cake_wallet/ionia/ionia_order.dart'; class IoniaAnyPay { IoniaAnyPay(this.ioniaService, this.anyPayApi, this.wallet); @@ -21,14 +23,15 @@ class IoniaAnyPay { final AnyPayApi anyPayApi; final WalletBase wallet; - Future purchase({ + Future purchase({ @required String merchId, @required double amount}) async { final invoice = await ioniaService.purchaseGiftCard( merchId: merchId, amount: amount, currency: wallet.currency.title.toUpperCase()); - return anyPayApi.paymentRequest(invoice.uri); + final anypayPayment = await anyPayApi.paymentRequest(invoice.uri); + return IoniaAnyPayPaymentInfo(invoice, anypayPayment); } Future commitInvoice(AnyPayPayment payment) async { diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart index da5911c845..72312f0854 100644 --- a/lib/ionia/ionia_api.dart +++ b/lib/ionia/ionia_api.dart @@ -21,6 +21,7 @@ class IoniaApi { static final getCurrentUserGiftCardSummariesUrl = Uri.https(baseUri, '/$pathPrefix/GetCurrentUserGiftCardSummaries'); static final changeGiftCardUrl = Uri.https(baseUri, '/$pathPrefix/ChargeGiftCard'); static final getGiftCardUrl = Uri.https(baseUri, '/$pathPrefix/GetGiftCard'); + static final getPaymentStatusUrl = Uri.https(baseUri, '/$pathPrefix/PaymentStatus'); // Create user @@ -355,4 +356,46 @@ class IoniaApi { final data = decodedBody['Data'] as Map; return IoniaGiftCard.fromJsonMap(data); } + + // Payment Status + + Future getPaymentStatus({ + @required String username, + @required String password, + @required String clientId, + @required String orderId, + @required String paymentId}) async { + final headers = { + 'clientId': clientId, + 'username': username, + 'password': password, + 'Content-Type': 'application/json'}; + final body = { + 'order_id': orderId, + 'paymentId': paymentId}; + final response = await post( + getPaymentStatusUrl, + headers: headers, + body: json.encode(body)); + + if (response.statusCode != 200) { + throw Exception('Failed to get Payment Status for order_id ${orderId} paymentId ${paymentId};Incorrect response status: ${response.statusCode};'); + } + + final decodedBody = json.decode(response.body) as Map; + final isSuccessful = decodedBody['Successful'] as bool ?? false; + + if (!isSuccessful) { + final msg = decodedBody['ErrorMessage'] as String ?? ''; + + if (msg.isNotEmpty) { + throw Exception(msg); + } + + throw Exception('Failed to get Payment Status for order_id ${orderId} paymentId ${paymentId}'); + } + + final data = decodedBody['Data'] as Map; + return data['gift_card_id'] as int; + } } \ No newline at end of file diff --git a/lib/ionia/ionia_order.dart b/lib/ionia/ionia_order.dart index 691327e43b..f9c35ea701 100644 --- a/lib/ionia/ionia_order.dart +++ b/lib/ionia/ionia_order.dart @@ -12,7 +12,7 @@ class IoniaOrder { uri: obj['uri'] as String, currency: obj['currency'] as String, amount: obj['amount'] as double, - paymentId: obj['payment_id'] as String); + paymentId: obj['paymentId'] as String); } final String id; diff --git a/lib/ionia/ionia_service.dart b/lib/ionia/ionia_service.dart index c00b0ad18e..b5ed5a4958 100644 --- a/lib/ionia/ionia_service.dart +++ b/lib/ionia/ionia_service.dart @@ -149,4 +149,14 @@ class IoniaService { final password = await secureStorage.read(key: ioniaPasswordStorageKey); return ioniaApi.getGiftCard(username: username, password: password, clientId: clientId,id: id); } + + // Payment Status + + Future getPaymentStatus({ + @required String orderId, + @required String paymentId}) async { + final username = await secureStorage.read(key: ioniaUsernameStorageKey); + final password = await secureStorage.read(key: ioniaPasswordStorageKey); + return ioniaApi.getPaymentStatus(username: username, password: password, clientId: clientId, orderId: orderId, paymentId: paymentId); + } } \ No newline at end of file diff --git a/lib/router.dart b/lib/router.dart index 9764ec17a1..aa2b53cd98 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -75,6 +75,9 @@ import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; import 'package:cake_wallet/src/screens/ionia/ionia.dart'; +import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart'; +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart'; RouteSettings currentRouteSettings; @@ -451,6 +454,14 @@ Route createRoute(RouteSettings settings) { final args = settings.arguments as List; return CupertinoPageRoute(builder: (_) => getIt.get(param1: args.first)); + case Routes.ioniaPaymentStatusPage: + final args = settings.arguments as List; + final paymentInfo = args.first as IoniaAnyPayPaymentInfo; + final commitedInfo = args[1] as AnyPayPaymentCommittedInfo; + return CupertinoPageRoute(builder: (_) => getIt.get( + param1: paymentInfo, + param2: commitedInfo)); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index 1b04db1ebb..bd070cc2b2 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -73,4 +73,5 @@ class Routes { static const ioniaAccountCardsPage = 'ionia_account_cards_page'; static const ioniaCustomTipPage = 'ionia_custom_tip_page'; static const ioniaGiftCardDetailPage = '/ionia_gift_card_detail_page'; + static const ioniaPaymentStatusPage = '/ionia_payment_status_page'; } diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index b27dc9a5a4..abf9b3032a 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -1,11 +1,11 @@ import 'dart:ui'; - import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/confirm_modal.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; import 'package:cake_wallet/src/widgets/alert_background.dart'; @@ -109,18 +109,12 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { } if (state is ExecutedSuccessfullyState) { - final transactionInfo = state.payload as AnyPayPaymentCommittedInfo; WidgetsBinding.instance.addPostFrameCallback((_) { - showDialog( - context: context, - barrierDismissible: true, - barrierColor: PaletteDark.darkNightBlue.withOpacity(0.75), - builder: (BuildContext context) { - return Center( - child: _IoniaTransactionCommitedAlert(transactionInfo: transactionInfo), - ); - }, - ); + Navigator.of(context).pushReplacementNamed( + Routes.ioniaPaymentStatusPage, + arguments: [ + ioniaPurchaseViewModel.paymentInfo, + ioniaPurchaseViewModel.committedInfo]); }); } }); diff --git a/lib/src/screens/ionia/cards/ionia_payment_status_page.dart b/lib/src/screens/ionia/cards/ionia_payment_status_page.dart new file mode 100644 index 0000000000..92bc62b91b --- /dev/null +++ b/lib/src/screens/ionia/cards/ionia_payment_status_page.dart @@ -0,0 +1,249 @@ +import 'package:cake_wallet/ionia/ionia_gift_card.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.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/typography.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; + +class IoniaPaymentStatusPage extends BasePage { + IoniaPaymentStatusPage(this.viewModel); + + final IoniaPaymentStatusViewModel viewModel; + + @override + Widget middle(BuildContext context) { + return Text( + S.of(context).generating_gift_card, + textAlign: TextAlign.center, + style: textMediumSemiBold( + color: Theme.of(context).accentTextTheme.display4.backgroundColor)); + } + + @override + Widget body(BuildContext context) { + return _IoniaPaymentStatusPageBody(viewModel); + } +} + +class _IoniaPaymentStatusPageBody extends StatefulWidget { + _IoniaPaymentStatusPageBody(this.viewModel); + + final IoniaPaymentStatusViewModel viewModel; + + @override + _IoniaPaymentStatusPageBodyBodyState createState() => _IoniaPaymentStatusPageBodyBodyState(); +} + +class _IoniaPaymentStatusPageBodyBodyState extends State<_IoniaPaymentStatusPageBody> { + ReactionDisposer _onErrorReaction; + ReactionDisposer _onGiftCardReaction; + + @override + void initState() { + if (widget.viewModel.giftCard != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(context) + .pushReplacementNamed(Routes.ioniaGiftCardDetailPage, arguments: [widget.viewModel.giftCard]); + }); + } + + if (widget.viewModel.error != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: widget.viewModel.error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + }); + } + + _onErrorReaction = reaction((_) => widget.viewModel.error, (String error) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + }); + }); + + _onGiftCardReaction = reaction((_) => widget.viewModel.giftCard, (IoniaGiftCard giftCard) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(context) + .pushReplacementNamed(Routes.ioniaGiftCardDetailPage, arguments: [giftCard]); + }); + }); + + super.initState(); + } + + @override + void dispose() { + _onErrorReaction?.reaction?.dispose(); + _onGiftCardReaction?.reaction?.dispose(); + widget.viewModel.timer.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.all(24), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row(children: [ + Padding( + padding: EdgeInsets.only(right: 10), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.green), + height: 10, + width: 10)), + Text( + S.of(context).awaiting_payment_confirmation, + style: textLargeSemiBold( + color: Theme.of(context).primaryTextTheme.title.color)) + ]), + SizedBox(height: 40), + Row(children: [ + SizedBox(width: 20), + Expanded(child: + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ...widget.viewModel + .committedInfo + .transactions + .map((transaction) => buildDescriptionTileWithCopy(context, S.of(context).transaction_details_transaction_id, transaction.id)), + Divider(height: 30), + buildDescriptionTileWithCopy(context, S.of(context).order_id, widget.viewModel.paymentInfo.ioniaOrder.id), + Divider(height: 30), + buildDescriptionTileWithCopy(context, S.of(context).payment_id, widget.viewModel.paymentInfo.ioniaOrder.paymentId), + ])) + ]), + SizedBox(height: 40), + Observer(builder: (_) { + if (widget.viewModel.giftCard != null) { + return Container( + padding: EdgeInsets.only(top: 40), + child: Row(children: [ + Padding( + padding: EdgeInsets.only(right: 10,), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.green), + height: 10, + width: 10)), + Text( + S.of(context).gift_card_is_generated, + style: textLargeSemiBold( + color: Theme.of(context).primaryTextTheme.title.color)) + ])); + } + + return Row(children: [ + Padding( + padding: EdgeInsets.only(right: 10), + child: Observer(builder: (_) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: widget.viewModel.giftCard == null ? Colors.grey : Colors.green), + height: 10, + width: 10); + })), + Text( + S.of(context).generating_gift_card, + style: textLargeSemiBold( + color: Theme.of(context).primaryTextTheme.title.color))]); + }), + ], + ), + bottomSection: Padding( + padding: EdgeInsets.only(bottom: 12), + child: Column(children: [ + Container( + padding: EdgeInsets.only(left: 40, right: 40, bottom: 20), + child: Text( + S.of(context).proceed_after_one_minute, + style: textMedium( + color: Theme.of(context).primaryTextTheme.title.color, + ).copyWith(fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + )), + Observer(builder: (_) { + if (widget.viewModel.giftCard != null) { + return PrimaryButton( + onPressed: () => Navigator.of(context) + .pushReplacementNamed( + Routes.ioniaGiftCardDetailPage, + arguments: [widget.viewModel.giftCard]), + text: S.of(context).open_gift_card, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white); + } + + return PrimaryButton( + onPressed: () => Navigator.of(context).pushNamed(Routes.support), + text: S.of(context).contact_support, + color: Theme.of(context).accentTextTheme.caption.color, + textColor: Theme.of(context).primaryTextTheme.title.color); + }) + ]) + ), + ); + } + + Widget buildDescriptionTile(BuildContext context, String title, String subtitle, VoidCallback onTap) { + return GestureDetector( + onTap: () => onTap(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: textXSmall( + color: Theme.of(context).primaryTextTheme.overline.color, + ), + ), + SizedBox(height: 8), + Text( + subtitle, + style: textMedium( + color: Theme.of(context).primaryTextTheme.title.color, + ), + ), + ], + )); + } + + Widget buildDescriptionTileWithCopy(BuildContext context, String title, String subtitle) { + return buildDescriptionTile(context, title, subtitle, () { + Clipboard.setData(ClipboardData(text: subtitle)); + showBar(context, + S.of(context).transaction_details_copied(title)); + }); + } +} \ No newline at end of file diff --git a/lib/view_model/ionia/ionia_payment_status_view_model.dart b/lib/view_model/ionia/ionia_payment_status_view_model.dart new file mode 100644 index 0000000000..187e24856e --- /dev/null +++ b/lib/view_model/ionia/ionia_payment_status_view_model.dart @@ -0,0 +1,58 @@ +import 'dart:async'; +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/ionia/ionia_service.dart'; +import 'package:cake_wallet/ionia/ionia_gift_card.dart'; +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart'; + +part 'ionia_payment_status_view_model.g.dart'; + +class IoniaPaymentStatusViewModel = IoniaPaymentStatusViewModelBase with _$IoniaPaymentStatusViewModel; + +abstract class IoniaPaymentStatusViewModelBase with Store { + IoniaPaymentStatusViewModelBase( + this.ioniaService,{ + @required this.paymentInfo, + @required this.committedInfo}) { + _timer = Timer.periodic(updateTime, (timer) async { + await updatePaymentStatus(); + + if (giftCard != null) { + timer?.cancel(); + } + }); + } + + static const updateTime = Duration(seconds: 3); + + final IoniaService ioniaService; + final IoniaAnyPayPaymentInfo paymentInfo; + final AnyPayPaymentCommittedInfo committedInfo; + + @observable + IoniaGiftCard giftCard; + + @observable + String error; + + Timer get timer => _timer; + + Timer _timer; + + @action + Future updatePaymentStatus() async { + try { + final giftCardId = await ioniaService.getPaymentStatus( + orderId: paymentInfo.ioniaOrder.id, + paymentId: paymentInfo.ioniaOrder.paymentId); + + if (giftCardId != null) { + giftCard = await ioniaService.getGiftCard(id: giftCardId); + } + + } catch (e) { + error = e.toString(); + } + } +} diff --git a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart index 6a65a49e98..1e537ac373 100644 --- a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart +++ b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart @@ -1,11 +1,12 @@ +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; import 'package:cake_wallet/anypay/any_pay_payment.dart'; import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; -import 'package:flutter/foundation.dart'; -import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart'; part 'ionia_purchase_merch_view_model.g.dart'; @@ -38,7 +39,9 @@ abstract class IoniaMerchPurchaseViewModelBase with Store { final IoniaAnyPay ioniaAnyPayService; - AnyPayPayment invoice; + IoniaAnyPayPaymentInfo paymentInfo; + + AnyPayPayment get invoice => paymentInfo?.anyPayPayment; AnyPayPaymentCommittedInfo committedInfo; @@ -67,7 +70,7 @@ abstract class IoniaMerchPurchaseViewModelBase with Store { Future createInvoice() async { try { invoiceCreationState = IsExecutingState(); - invoice = await ioniaAnyPayService.purchase(merchId: ioniaMerchant.id.toString(), amount: giftCardAmount); + paymentInfo = await ioniaAnyPayService.purchase(merchId: ioniaMerchant.id.toString(), amount: giftCardAmount); invoiceCreationState = ExecutedSuccessfullyState(); } catch (e) { invoiceCreationState = FailureState(e.toString()); diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index b3e6839460..80421655c7 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -620,5 +620,12 @@ "awaiting_payment_confirmation": "Awaiting payment confirmation", "transaction_sent_notice": "If the screen doesn’t proceed after 1 minute, check a block explorer and your email.", "agree": "Agree", - "in_store": "In Store" + "in_store": "In Store", + "generating_gift_card": "Generating Gift Card", + "payment_was_received": "Your payment was received.", + "proceed_after_one_minute": "If the screen doesn’t proceed after 1 minute, check your email.", + "order_id": "Order ID", + "gift_card_is_generated": "Gift Card is generated", + "open_gift_card": "Open Gift Card", + "contact_support": "Contact Support" } From d47887e1ec809732bc9c735f511c7a2411ead82f Mon Sep 17 00:00:00 2001 From: M Date: Mon, 18 Jul 2022 17:11:34 +0100 Subject: [PATCH 24/55] Add usage instruction to gift card. --- lib/ionia/ionia_gift_card.dart | 25 ++++++++++++-- .../cards/ionia_gift_card_detail_page.dart | 34 +++++++++++++++---- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/lib/ionia/ionia_gift_card.dart b/lib/ionia/ionia_gift_card.dart index df0ee6a52a..cc8c3dbaab 100644 --- a/lib/ionia/ionia_gift_card.dart +++ b/lib/ionia/ionia_gift_card.dart @@ -1,5 +1,20 @@ +import 'dart:convert'; + import 'package:flutter/foundation.dart'; +class IoniaGiftCardInstruction { + IoniaGiftCardInstruction(this.header, this.body); + + factory IoniaGiftCardInstruction.fromJsonMap(Map element) { + return IoniaGiftCardInstruction( + element['header'] as String, + element['body'] as String); + } + + final String header; + final String body; +} + class IoniaGiftCard { IoniaGiftCard({ @required this.id, @@ -26,6 +41,11 @@ class IoniaGiftCard { @required this.logoUrl}); factory IoniaGiftCard.fromJsonMap(Map element) { + final decodedInstructions = json.decode(element['UsageInstructions'] as String) as Map; + final instruction = decodedInstructions['instruction'] as List; + final instructions = instruction + .map((dynamic e) =>IoniaGiftCardInstruction.fromJsonMap(e as Map)) + .toList(); return IoniaGiftCard( id: element['Id'] as int, merchantId: element['MerchantId'] as int, @@ -44,7 +64,8 @@ class IoniaGiftCard { isEmpty: element['IsEmpty'] as bool, logoUrl: element['LogoUrl'] as String, createdDateFormatted: element['CreatedDate'] as String, - lastTransactionDateFormatted: element['LastTransactionDate'] as String); + lastTransactionDateFormatted: element['LastTransactionDate'] as String, + usageInstructions: instructions); } final int id; @@ -54,7 +75,7 @@ class IoniaGiftCard { final String barcodeUrl; final String cardNumber; final String cardPin; - final Map usageInstructions; + final List usageInstructions; final Map balanceInstructions; final Map paymentInstructions; final String cardImageUrl; diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index 7f081453a5..0d84bfaa91 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -157,15 +157,35 @@ class IoniaGiftCardDetailPage extends BasePage { color: Theme.of(context).textTheme.body1.color, ), ), - SizedBox(height: 24), Align( alignment: Alignment.bottomLeft, - child: Text( - '', - style: textMedium( - color: Theme.of(context).textTheme.display2.color, - ), - ), + child: Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.5), + child: Expanded( + child: ListView.builder( + itemCount: viewModel.giftCard.usageInstructions.length, + itemBuilder: (_, int index) { + final instruction = viewModel.giftCard.usageInstructions[index]; + return Expanded( + child: Column( + children: [ + Padding( + padding: EdgeInsets.all(10), + child: Text( + instruction.header, + style: textLargeSemiBold( + color: Theme.of(context).textTheme.display2.color, + ), + )), + Text( + instruction.body, + style: textMedium( + color: Theme.of(context).textTheme.display2.color, + ), + ) + ])); + }))) ), SizedBox(height: 35), PrimaryButton( From 8ad3a4065b49d8939dccfe7a3e58c15be13d614a Mon Sep 17 00:00:00 2001 From: M Date: Mon, 18 Jul 2022 17:25:09 +0100 Subject: [PATCH 25/55] Add copy for ionia gift card info. --- .../cards/ionia_gift_card_detail_page.dart | 22 +++++++++++++-- lib/src/screens/ionia/widgets/ionia_tile.dart | 28 +++++-------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index 0d84bfaa91..eeead6d55d 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -8,9 +8,11 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.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/typography.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/src/widgets/framework.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -89,18 +91,21 @@ class IoniaGiftCardDetailPage extends BasePage { child: SizedBox(height: 96, width: double.infinity, child: Image.network(viewModel.giftCard.barcodeUrl)), ), SizedBox(height: 24), - IoniaTile( + buildIoniaTile( + context, title: S.of(context).gift_card_number, subTitle: viewModel.giftCard.cardNumber, ), Divider(height: 30), - IoniaTile( + buildIoniaTile( + context, title: S.of(context).pin_number, subTitle: viewModel.giftCard.cardPin ?? '', ), Divider(height: 30), Observer(builder: (_) => - IoniaTile( + buildIoniaTile( + context, title: S.of(context).amount, subTitle: viewModel.giftCard.remainingAmount.toString() ?? '0', )), @@ -128,6 +133,17 @@ class IoniaGiftCardDetailPage extends BasePage { ); } + Widget buildIoniaTile(BuildContext context, {@required String title, @required String subTitle}) { + return IoniaTile( + title: title, + subTitle: subTitle, + onTap: () { + Clipboard.setData(ClipboardData(text: subTitle)); + showBar(context, + S.of(context).transaction_details_copied(title)); + }); + } + void _showHowToUseCard( BuildContext context, IoniaGiftCard merchant, diff --git a/lib/src/screens/ionia/widgets/ionia_tile.dart b/lib/src/screens/ionia/widgets/ionia_tile.dart index 9b34202626..1e066312c6 100644 --- a/lib/src/screens/ionia/widgets/ionia_tile.dart +++ b/lib/src/screens/ionia/widgets/ionia_tile.dart @@ -6,18 +6,18 @@ class IoniaTile extends StatelessWidget { Key key, @required this.title, @required this.subTitle, - this.trailing, - this.onTapTrailing, + this.onTap, }) : super(key: key); - final Widget trailing; - final VoidCallback onTapTrailing; + final VoidCallback onTap; final String title; final String subTitle; @override Widget build(BuildContext context) { - return Row( + return GestureDetector( + onTap: () => onTap(), + child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( @@ -37,22 +37,8 @@ class IoniaTile extends StatelessWidget { ), ), ], - ), - trailing != null - ? InkWell( - onTap: () => onTapTrailing, - child: Center( - child: Container( - padding: EdgeInsets.symmetric(horizontal: 6, vertical: 6), - decoration: BoxDecoration( - color: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(4)), - child: trailing, - ), - ), - ) - : Offstage(), + ) ], - ); + )); } } From effe163d82aba10bf54d09b728f6e7166fe84da6 Mon Sep 17 00:00:00 2001 From: M Date: Mon, 18 Jul 2022 18:02:21 +0100 Subject: [PATCH 26/55] Change version for Cake Wallet ios. --- scripts/ios/app_env.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index ca7e5de705..5ce771af89 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -18,8 +18,8 @@ MONERO_COM_BUILD_NUMBER=16 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.4.1" -CAKEWALLET_BUILD_NUMBER=100 +CAKEWALLET_VERSION="4.4.4" +CAKEWALLET_BUILD_NUMBER=105 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" From c8b18ad1afae68f0ae827944a0fb86f339512989 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Tue, 19 Jul 2022 12:30:55 +0300 Subject: [PATCH 27/55] Add localisation (#414) --- res/values/strings_de.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_es.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_fr.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_hi.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_hr.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_it.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_ja.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_ko.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_nl.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_pl.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_pt.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_ru.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_uk.arb | 99 ++++++++++++++++++++++++++++++++++++++- res/values/strings_zh.arb | 99 ++++++++++++++++++++++++++++++++++++++- 14 files changed, 1372 insertions(+), 14 deletions(-) diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index a501cd72e9..fb56dbd470 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -530,5 +530,102 @@ "learn_more" : "Erfahren Sie mehr", "search": "Suche", "new_template" : "neue Vorlage", - "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin" + "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin", + "market_place": "Marktplatz", + "cake_pay_title": "Geschenkkarten und Debitkarten", + "cake_pay_subtitle": "Geschenkkarten kaufen und Nicht-KYC-Debitkarten aufladen", + "about_cake_pay": "CakePay macht es einfach, Geschenkkarten zu kaufen und Prepaid-Debitkarten mit Kryptowährungen aufzuladen, die bei Millionen von Händlern in den Vereinigten Staaten ausgegeben werden können.", + "cake_pay_account_note": "Erstellen Sie ein Konto, um die verfügbaren Karten zu sehen. Einige sind sogar mit Rabatt erhältlich!", + "already_have_account": "Sie haben bereits ein Konto?", + "create_account": "Konto erstellen", + "privacy_policy": "Datenschutzrichtlinie", + "welcome_to_cakepay": "Willkommen bei CakePay!", + "sign_up": "Anmelden", + "forgot_password": "Passwort vergessen", + "reset_password": "Passwort zurücksetzen", + "manage_cards": "Karten verwalten", + "setup_your_debit_card": "Richten Sie Ihre Debitkarte ein", + "no_id_required": "Keine ID erforderlich. Upgraden und überall ausgeben", + "how_to_use_card": "Wie man diese Karte benutzt", + "purchase_gift_card": "Geschenkkarte kaufen", + "verification": "Verifizierung", + "fill_code": "Geben Sie den Bestätigungscode ein, den Sie per E-Mail erhalten haben", + "dont_get_code": "Kein Code?", + "resend_code": "Bitte erneut senden", + "debit_card": "Debitkarte", + "cakepay_prepaid_card": "CakePay-Prepaid-Debitkarte", + "no_id_needed": "Keine ID erforderlich!", + "frequently_asked_questions": "Häufig gestellte Fragen", + "debit_card_terms": "Die Speicherung und Nutzung Ihrer Zahlungskartennummer (und Ihrer Zahlungskartennummer entsprechenden Anmeldeinformationen) in dieser digitalen Geldbörse unterliegt den Allgemeinen Geschäftsbedingungen des geltenden Karteninhabervertrags mit dem Zahlungskartenaussteller, gültig ab von Zeit zu Zeit.", + "Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.", + "cardholder_agreement": "Karteninhabervertrag", + "e_sign_consent": "E-Sign-Zustimmung", + "agree_and_continue": "Zustimmen & fortfahren", + "email_address": "E-Mail-Adresse", + "agree_to": "Indem Sie ein Konto erstellen, stimmen Sie den ", + "und": "und", + "enter_code": "Code eingeben", + "congratulations": "Glückwunsch!", + "you_now_have_debit_card": "Sie haben jetzt eine Debitkarte", + "min_amount": "Min: ${value}", + "max_amount": "Max: ${value}", + "enter_amount": "Betrag eingeben", + "billing_address_info": "Wenn Sie nach einer Rechnungsadresse gefragt werden, geben Sie bitte Ihre Lieferadresse an", + "order_physical_card": "Physische Karte bestellen", + "add_value": "Wert hinzufügen", + "activate": "aktivieren", + "get_a": "Hole ein", + "digital_and_physical_card": "digitale en fysieke prepaid debetkaart", + "get_card_note": " die u kunt herladen met digitale valuta. Geen aanvullende informatie nodig!", + "signup_for_card_accept_terms": "Meld je aan voor de kaart en accepteer de voorwaarden.", + "add_fund_to_card": "Voeg prepaid tegoed toe aan de kaarten (tot ${value})", + "use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.", + "use_card_info_three": "Gebruik de digitale kaart online of met contactloze betaalmethoden.", + "optioneel_order_card": "Optioneel een fysieke kaart bestellen.", + "hide_details" : "Details verbergen", + "show_details" : "Toon details", + "upto": "tot ${value}", + "discount": "Bespaar ${value}%", + "gift_card_amount": "Bedrag cadeaubon", + "bill_amount": "Bill bedrag", + "you_pay": "U betaalt", + "tip": "Tip:", + "custom": "aangepast", + "by_cake_pay": "door CakePay", + "expires": "Verloopt", + "mm": "MM", + "yy": "JJ", + "online": "online", + "offline": "Offline", + "gift_card_number": "Cadeaukaartnummer", + "pin_number": "PIN-nummer", + "total_saving": "Totale besparingen", + "last_30_days": "Laatste 30 dagen", + "avg_savings": "Gem. besparingen", + "view_all": "Alles bekijken", + "active_cards": "Actieve kaarten", + "delete_account": "Account verwijderen", + "cards": "Kaarten", + "active": "Actief", + "redeemed": "Verzilverd", + "gift_card_balance_note": "Cadeaukaarten met een resterend saldo verschijnen hier", + "gift_card_redeemed_note": "Cadeaubonnen die je hebt ingewisseld, verschijnen hier", + "logout": "Uitloggen", + "add_tip": "Tip toevoegen", + "percentageOf": "van ${amount}", + "is_percentage": "is", + "search_category": "Zoek categorie", + "mark_as_redeemed": "Markeer als ingewisseld", + "more_options": "Meer opties", + "waiting_payment_confirmation": "In afwachting van betalingsbevestiging", + "transaction_sent_notice": "Als het scherm na 1 minuut niet verder gaat, controleer dan een blokverkenner en je e-mail.", + "agree": "mee eens", + "in_store": "In winkel", + "generating_gift_card": "Cadeaubon genereren", + "payment_was_received": "Uw betaling is ontvangen.", + "proceed_after_one_minute": "Als het scherm na 1 minuut niet verder gaat, controleer dan uw e-mail.", + "order_id": "Bestell-ID", + "gift_card_is_generated": "Geschenkkarte wird generiert", + "open_gift_card": "Geschenkkarte öffnen", + "contact_support": "Support kontaktieren" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index a033025f34..4fd603f01e 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -530,5 +530,102 @@ "learn_more" : "Aprende más", "search": "Búsqueda", "new_template" : "Nueva plantilla", - "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando" + "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando", + "market_place": "Lugar de mercado", + "cake_pay_title": "Tarjetas de regalo y tarjetas de débito", + "cake_pay_subtitle": "Compre tarjetas de regalo y recargue tarjetas de débito sin KYC", + "about_cake_pay": "CakePay te permite comprar fácilmente tarjetas de regalo y cargar tarjetas de débito prepagas con criptomonedas, gastables en millones de comerciantes en los Estados Unidos.", + "cake_pay_account_note": "Crea una cuenta para ver las tarjetas disponibles. ¡Algunas incluso están disponibles con descuento!", + "already_have_account": "¿Ya tienes una cuenta?", + "create_account": "Crear Cuenta", + "privacy_policy": "Política de privacidad", + "welcome_to_cakepay": "¡Bienvenido a CakePay!", + "sign_up": "Registrarse", + "forgot_password": "Olvidé mi contraseña", + "reset_password": "Restablecer contraseña", + "manage_cards": "Administrar tarjetas", + "setup_your_debit_card": "Configura tu tarjeta de débito", + "no_id_required": "No se requiere identificación. Recargue y gaste en cualquier lugar", + "how_to_use_card": "Cómo usar esta tarjeta", + "purchase_gift_card": "Comprar tarjeta de regalo", + "verification": "Verificación", + "fill_code": "Por favor complete el código de verificación proporcionado a su correo electrónico", + "dont_get_code": "¿No obtienes el código?", + "resend_code": "Por favor reenvíalo", + "debit_card": "Tarjeta de Débito", + "cakepay_prepaid_card": "Tarjeta de Débito Prepago CakePay", + "no_id_needed": "¡No se necesita identificación!", + "frequently_asked_questions": "Preguntas frecuentes", + "debit_card_terms": "El almacenamiento y el uso de su número de tarjeta de pago (y las credenciales correspondientes a su número de tarjeta de pago) en esta billetera digital están sujetos a los Términos y condiciones del acuerdo del titular de la tarjeta aplicable con el emisor de la tarjeta de pago, en vigor desde tiempo al tiempo.", + "please_reference_document": "Consulte los documentos a continuación para obtener más información.", + "cardholder_agreement": "Acuerdo del titular de la tarjeta", + "e_sign_consent": "Consentimiento de firma electrónica", + "agree_and_continue": "Aceptar y continuar", + "email_address": "Dirección de correo electrónico", + "agree_to": "Al crear una cuenta, aceptas ", + "and": "y", + "enter_code": "Ingresar código", + "congratulations": "Felicidades!", + "you_now_have_debit_card": "Ahora tiene una tarjeta de débito", + "min_amount" : "Mínimo: ${value}", + "max_amount" : "Máx: ${value}", + "enter_amount": "Ingrese la cantidad", + "billing_address_info": "Si se le solicita una dirección de facturación, proporcione su dirección de envío", + "order_physical_card": "Pedir tarjeta física", + "add_value": "Añadir valor", + "activate": "Activar", + "get_a": "Obtener un", + "digital_and_physical_card": " tarjeta de débito prepago digital y física", + "get_card_note": " que puedes recargar con monedas digitales. ¡No se necesita información adicional!", + "signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.", + "add_fund_to_card": "Agregar fondos prepagos a las tarjetas (hasta ${value})", + "use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.", + "use_card_info_three": "Utilice la tarjeta digital en línea o con métodos de pago sin contacto.", + "Optionally_order_card": "Opcionalmente pide una tarjeta física.", + "hide_details" : "Ocultar detalles", + "show_details": "Mostrar detalles", + "upto": "hasta ${value}", + "discount": "Ahorra ${value}%", + "gift_card_amount": "Cantidad de la tarjeta de regalo", + "bill_amount": "Importe de la factura", + "you_pay": "Tú pagas", + "tip": "Consejo:", + "personalizado": "personalizado", + "by_cake_pay": "por CakePay", + "expires": "Caduca", + "mm": "mm", + "yy": "YY", + "online": "En línea", + "offline": "fuera de línea", + "gift_card_number": "Número de tarjeta de regalo", + "pin_number": "Número PIN", + "total_saving": "Ahorro Total", + "last_30_days": "Últimos 30 días", + "avg_savings": "Ahorro promedio", + "view_all": "Ver todo", + "active_cards": "Tarjetas activas", + "delete_account": "Eliminar cuenta", + "cards": "Cartas", + "active": "Activo", + "redeemed": "Redimido", + "gift_card_balance_note": "Las tarjetas de regalo con saldo restante aparecerán aquí", + "gift_card_redeemed_note": "Las tarjetas de regalo que hayas canjeado aparecerán aquí", + "logout": "Cerrar sesión", + "add_tip": "Agregar sugerencia", + "percentageOf": "de ${amount}", + "is_percentage": "es", + "search_category": "Categoría de búsqueda", + "mark_as_redeemed": "Marcar como canjeado", + "more_options": "Más Opciones", + "awaiting_payment_confirmation": "Esperando confirmación de pago", + "transaction_sent_notice": "Si la pantalla no continúa después de 1 minuto, revisa un explorador de bloques y tu correo electrónico.", + "agree": "De acuerdo", + "in_store": "En la tienda", + "generating_gift_card": "Generando tarjeta de regalo", + "payment_was_received": "Su pago fue recibido.", + "proceed_after_one_minute": "Si la pantalla no continúa después de 1 minuto, revisa tu correo electrónico.", + "order_id": "Identificación del pedido", + "gift_card_is_generated": "Se genera la tarjeta de regalo", + "open_gift_card": "Abrir tarjeta de regalo", + "contact_support": "Contactar con Soporte" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 9a3ef3cb3f..2d8310b65a 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -528,5 +528,102 @@ "learn_more" : "En savoir plus", "new_template" : "Nouveau Modèle", - "electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner" + "electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner", + "market_place": "Place du marché", + "cake_pay_title": "Cartes cadeaux et cartes de débit", + "cake_pay_subtitle": "Achetez des cartes-cadeaux et rechargez des cartes de débit sans KYC", + "about_cake_pay": "CakePay vous permet d'acheter facilement des cartes-cadeaux et de charger des cartes de débit prépayées avec des crypto-monnaies, utilisables chez des millions de marchands aux États-Unis.", + "cake_pay_account_note": "Créez un compte pour voir les cartes disponibles. Certaines sont même disponibles à prix réduit !", + "already_have_account": "Vous avez déjà un compte ?", + "create_account": "Créer un compte", + "privacy_policy": "Politique de confidentialité", + "welcome_to_cakepay": "Bienvenue sur CakePay !", + "sign_up": "S'inscrire", + "forgot_password": "Mot de passe oublié", + "reset_password": "Réinitialiser le mot de passe", + "manage_cards": "Gérer les cartes", + "setup_your_debit_card": "Configurer votre carte de débit", + "no_id_required": "Aucune pièce d'identité requise. Rechargez et dépensez n'importe où", + "how_to_use_card": "Comment utiliser cette carte", + "purchase_gift_card": "Acheter une carte-cadeau", + "verification": "Vérification", + "fill_code": "Veuillez remplir le code de vérification fourni sur votre e-mail", + "dont_get_code": "Vous ne recevez pas le code ?", + "resend_code": "Veuillez le renvoyer", + "debit_card": "Carte de débit", + "cakepay_prepaid_card": "Carte de débit prépayée CakePay", + "no_id_needed": "Aucune pièce d'identité nécessaire !", + "frequently_asked_questions": "Foire aux questions", + "debit_card_terms": "Le stockage et l'utilisation de votre numéro de carte de paiement (et des informations d'identification correspondant à votre numéro de carte de paiement) dans ce portefeuille numérique sont soumis aux conditions générales de l'accord du titulaire de carte applicable avec l'émetteur de la carte de paiement, en vigueur à partir de de temps en temps.", + "please_reference_document": "Veuillez vous référer aux documents ci-dessous pour plus d'informations.", + "cardholder_agreement": "Contrat de titulaire de carte", + "e_sign_consent": "Consentement de signature électronique", + "agree_and_continue": "Accepter et continuer", + "email_address": "Adresse e-mail", + "agree_to": "En créant un compte, vous acceptez les ", + "and": "et", + "enter_code": "Entrez le code", + "congratulations": "Félicitations !", + "you_now_have_debit_card": "Vous avez maintenant une carte de débit", + "min_amount" : "Min : ${value}", + "max_amount" : "Max : ${value}", + "enter_amount": "Entrez le montant", + "billing_address_info": "Si une adresse de facturation vous est demandée, indiquez votre adresse de livraison", + "order_physical_card": "Commander une carte physique", + "add_value": "Ajouter une valeur", + "activate": "Activer", + "get_a": "Obtenir un ", + "digital_and_physical_card": "carte de débit prépayée numérique et physique", + "get_card_note": " que vous pouvez recharger avec des devises numériques. Aucune information supplémentaire n'est nécessaire !", + "signup_for_card_accept_terms": "Inscrivez-vous pour la carte et acceptez les conditions.", + "add_fund_to_card": "Ajouter des fonds prépayés aux cartes (jusqu'à ${value})", + "use_card_info_two": "Les fonds sont convertis en USD lorsqu'ils sont détenus sur le compte prépayé, et non en devises numériques.", + "use_card_info_three": "Utilisez la carte numérique en ligne ou avec des méthodes de paiement sans contact.", + "optionally_order_card": "Commander éventuellement une carte physique.", + "hide_details" : "Masquer les détails", + "show_details" : "Afficher les détails", + "upto": "jusqu'à ${value}", + "discount": "Économisez ${value}%", + "gift_card_amount": "Montant de la carte-cadeau", + "bill_amount": "Montant de la facture", + "you_pay": "Vous payez", + "tip": "Astuce :", + "custom": "personnalisé", + "by_cake_pay": "par CakePay", + "expire": "Expire", + "mm": "MM", + "yy": "AA", + "online": "En ligne", + "offline": "Hors ligne", + "gift_card_number": "Numéro de carte cadeau", + "pin_number": "Numéro PIN", + "total_saving": "Économies totales", + "last_30_days": "30 derniers jours", + "avg_savings": "Économies moy.", + "view_all": "Voir tout", + "active_cards": "Cartes actives", + "delete_account": "Supprimer le compte", + "cards": "Cartes", + "active": "Actif", + "redeemed": "racheté", + "gift_card_balance_note": "Les cartes-cadeaux avec un solde restant apparaîtront ici", + "gift_card_redeemed_note": "Les cartes-cadeaux que vous avez utilisées apparaîtront ici", + "logout": "Déconnexion", + "add_tip": "Ajouter une astuce", + "percentageOf": "sur ${amount}", + "is_percentage": "est", + "search_category": "Catégorie de recherche", + "mark_as_redeemed": "Marquer comme échangé", + "more_options": "Plus d'options", + "awaiting_payment_confirmation": "En attente de confirmation de paiement", + "transaction_sent_notice": "Si l'écran ne continue pas après 1 minute, vérifiez un explorateur de blocs et votre e-mail.", + "agree": "d'accord", + "in_store": "En magasin", + "generating_gift_card": "Génération d'une carte-cadeau", + "payment_was_received": "Votre paiement a été reçu.", + "proceed_after_one_minute": "Si l'écran ne s'affiche pas après 1 minute, vérifiez vos e-mails.", + "order_id": "Numéro de commande", + "gift_card_is_generated": "La carte-cadeau est générée", + "open_gift_card": "Ouvrir la carte-cadeau", + "contact_support": "Contacter l'assistance" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 4efe457c3c..85b4973527 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -530,5 +530,102 @@ "learn_more" : "और अधिक जानें", "search": "खोज", "new_template" : "नया टेम्पलेट", - "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं" + "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं", + "market_place": "मार्केट प्लेस", + "cake_pay_title": "उपहार कार्ड और डेबिट कार्ड", + "cake_pay_subtitle": "गिफ्ट कार्ड खरीदें और नो-केवाईसी डेबिट कार्ड टॉप अप करें", + "about_cake_pay": "केकपे आपको आसानी से उपहार कार्ड खरीदने और क्रिप्टोकरंसी के साथ प्रीपेड डेबिट कार्ड लोड करने की अनुमति देता है, जो संयुक्त राज्य में लाखों व्यापारियों पर खर्च करने योग्य है।", + "cake_pay_account_note": "उपलब्ध कार्ड देखने के लिए एक खाता बनाएं। कुछ छूट पर भी उपलब्ध हैं!", + "ready_have_account": "क्या आपके पास पहले से ही एक खाता है?", + "create_account": "खाता बनाएं", + "privacy_policy": "गोपनीयता नीति", + "welcome_to_cakepay": "केकपे में आपका स्वागत है!", + "sign_up": "साइन अप करें", + "forgot_password": "पासवर्ड भूल गए", + "reset_password": "पासवर्ड रीसेट करें", + "manage_cards": "कार्ड मैनेज करें", + "setup_your_debit_card": "अपना डेबिट कार्ड सेट करें", + "no_id_required": "कोई आईडी आवश्यक नहीं है। टॉप अप करें और कहीं भी खर्च करें", + "how_to_use_card": "इस कार्ड का उपयोग कैसे करें", + "purchase_gift_card": "गिफ्ट कार्ड खरीदें", + "verification": "सत्यापन", + "fill_code": "कृपया अपने ईमेल पर प्रदान किया गया सत्यापन कोड भरें", + "dont_get_code": "कोड नहीं मिला?", + "resend_code": "कृपया इसे फिर से भेजें", + "debit_card": "डेबिट कार्ड", + "cakepay_prepaid_card": "केकपे प्रीपेड डेबिट कार्ड", + "no_id_needed": "कोई आईडी नहीं चाहिए!", + "frequently_asked_questions": "अक्सर पूछे जाने वाले प्रश्न", + "debit_card_terms": "इस डिजिटल वॉलेट में आपके भुगतान कार्ड नंबर (और आपके भुगतान कार्ड नंबर से संबंधित क्रेडेंशियल) का भंडारण और उपयोग भुगतान कार्ड जारीकर्ता के साथ लागू कार्डधारक समझौते के नियमों और शर्तों के अधीन है, जैसा कि प्रभावी है समय - समय पर।", + "please_reference_document": "कृपया अधिक जानकारी के लिए नीचे दिए गए दस्तावेज़ देखें।", + "cardholder_agreement": "कार्डधारक अनुबंध", + "e_sign_consent": "ई-साइन सहमति", + "agree_and_continue": "सहमत और जारी रखें", + "email_address": "ईमेल पता", + "agree_to": "खाता बनाकर आप इससे सहमत होते हैं ", + "and": "और", + "enter_code": "कोड दर्ज करें", + "congratulations":"बधाई!", + "you_now_have_debit_card": "अब आपके पास डेबिट कार्ड है", + "min_amount" : "न्यूनतम: ${value}", + "max_amount" : "अधिकतम: ${value}", + "enter_amount": "राशि दर्ज करें", + "billing_address_info": "यदि बिलिंग पता मांगा जाए, तो अपना शिपिंग पता प्रदान करें", + "order_physical_card": "फिजिकल कार्ड ऑर्डर करें", + "add_value": "मूल्य जोड़ें", + "activate": "सक्रिय करें", + "get_a": "एक प्राप्त करें", + "digital_and_physical_card": "डिजिटल और भौतिक प्रीपेड डेबिट कार्ड", + "get_card_note": " कि आप डिजिटल मुद्राओं के साथ पुनः लोड कर सकते हैं। कोई अतिरिक्त जानकारी की आवश्यकता नहीं है!", + "signup_for_card_accept_terms": "कार्ड के लिए साइन अप करें और शर्तें स्वीकार करें।", + "add_fund_to_card": "कार्ड में प्रीपेड धनराशि जोड़ें (${value} तक)", + "use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।", + "use_card_info_three": "डिजिटल कार्ड का ऑनलाइन या संपर्क रहित भुगतान विधियों के साथ उपयोग करें।", + "optionally_order_card": "वैकल्पिक रूप से एक भौतिक कार्ड ऑर्डर करें।", + "hide_details": "विवरण छुपाएं", + "show_details": "विवरण दिखाएं", + "upto": "${value} तक", + "discount": "${value}% बचाएं", + "gift_card_amount": "गिफ्ट कार्ड राशि", + "bill_amount": "बिल राशि", + "you_pay": "आप भुगतान करते हैं", + "tip": "टिप:", + "custom": "कस्टम", + "by_cake_pay": "केकपे द्वारा", + "expires": "समाप्त हो जाता है", + "mm": "एमएम", + "yy": "वाईवाई", + "online": "ऑनलाइन", + "offline": "ऑफ़लाइन", + "gift_card_number": "गिफ्ट कार्ड नंबर", + "pin_number": "पिन नंबर", + "total_saving": "कुल बचत", + "last_30_days": "पिछले 30 दिन", + "avg_savings": "औसत बचत", + "view_all": "सभी देखें", + "active_cards": "सक्रिय कार्ड", + "delete_account": "खाता हटाएं", + "cards": "कार्ड", + "active": "सक्रिय", + "redeemed": "रिडीम किया गया", + "gift_card_balance_note": "गिफ्ट कार्ड शेष राशि के साथ यहां दिखाई देंगे", + "gift_card_redeemed_note": "आपके द्वारा भुनाए गए उपहार कार्ड यहां दिखाई देंगे", + "logout": "लॉगआउट", + "add_tip": "टिप जोड़ें", + "percentageOf": "${amount} का", + "is_percentage": "है", + "search_category": "खोज श्रेणी", + "mark_as_redeemed": "रिडीम किए गए के रूप में चिह्नित करें", + "more_options": "और विकल्प", + "awaiting_payment_confirmation": "भुगतान की पुष्टि की प्रतीक्षा में", + "transaction_sent_notice": "अगर 1 मिनट के बाद भी स्क्रीन आगे नहीं बढ़ती है, तो ब्लॉक एक्सप्लोरर और अपना ईमेल देखें।", + "agree": "सहमत", + "in_store": "स्टोर में", + "generating_gift_card": "गिफ्ट कार्ड जनरेट कर रहा है", + "Payment_was_received": "आपका भुगतान प्राप्त हो गया था।", + "proceed_after_one_minute": "यदि 1 मिनट के बाद भी स्क्रीन आगे नहीं बढ़ती है, तो अपना ईमेल देखें।", + "order_id": "ऑर्डर आईडी", + "gift_card_is_generated": "गिफ्ट कार्ड जनरेट हुआ", + "open_gift_card": "गिफ्ट कार्ड खोलें", + "contact_support": "सहायता से संपर्क करें" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 95004d689a..5bb1c05b49 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -530,5 +530,102 @@ "learn_more" : "Saznajte više", "search": "Traži", "new_template" : "novi predložak", - "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek" + "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek", + "market_place": "Tržnica", + "cake_pay_title": "Poklon kartice i debitne kartice", + "cake_pay_subtitle": "Kupite darovne kartice i nadopunite debitne kartice bez KYC-a", + "about_cake_pay": "CakePay vam omogućuje jednostavnu kupnju darovnih kartica i punjenje unaprijed plaćenih debitnih kartica kriptovalutama koje možete potrošiti kod milijuna trgovaca u Sjedinjenim Državama.", + "cake_pay_account_note": "Napravite račun da vidite dostupne kartice. Neke su čak dostupne uz popust!", + "already_have_account": "Već imate račun?", + "create_account": "Stvori račun", + "privacy_policy": "Pravila privatnosti", + "welcome_to_cakepay": "Dobro došli u CakePay!", + "sign_up": "Prijavite se", + "forgot_password": "Zaboravljena lozinka", + "reset_password": "Poništi lozinku", + "manage_cards": "Upravljanje karticama", + "setup_your_debit_card": "Postavite svoju debitnu karticu", + "no_id_required": "Nije potreban ID. Nadopunite i potrošite bilo gdje", + "how_to_use_card": "Kako koristiti ovu karticu", + "purchase_gift_card": "Kupnja darovne kartice", + "verification": "Potvrda", + "fill_code": "Molimo vas da ispunite kontrolni kod koji ste dobili na svojoj e-pošti", + "dont_get_code": "Ne dobivate kod?", + "resend_code": "Molimo da ga ponovno pošaljete", + "debit_card": "Debitna kartica", + "cakepay_prepaid_card": "CakePay unaprijed plaćena debitna kartica", + "no_id_needed": "Nije potreban ID!", + "frequently_asked_questions": "Često postavljana pitanja", + "debit_card_terms": "Pohranjivanje i korištenje broja vaše platne kartice (i vjerodajnica koje odgovaraju broju vaše platne kartice) u ovom digitalnom novčaniku podliježu Uvjetima i odredbama važećeg ugovora vlasnika kartice s izdavateljem platne kartice, koji su na snazi ​​od S vremena na vrijeme.", + "please_reference_document": "Molimo pogledajte dokumente ispod za više informacija.", + "cardholder_agreement": "Ugovor s vlasnikom kartice", + "e_sign_consent": "E-Sign pristanak", + "agree_and_continue": "Slažem se i nastavi", + "email_address": "Adresa e-pošte", + "agree_to": "Stvaranjem računa pristajete na ", + "and": "i", + "enter_code": "Unesite kod", + "congratulations": "Čestitamo!", + "you_now_have_debit_card": "Sada imate debitnu karticu", + "min_amount" : "Minimalno: ${value}", + "max_amount" : "Maksimum: ${value}", + "enter_amount": "Unesite iznos", + "billing_address_info": "Ako se od vas zatraži adresa za naplatu, navedite svoju adresu za dostavu", + "order_physical_card": "Naručite fizičku karticu", + "add_value": "Dodaj vrijednost", + "activate": "Aktiviraj", + "get_a": "Nabavite ", + "digital_and_physical_card": "digitalna i fizička unaprijed plaćena debitna kartica", + "get_card_note": " koju možete ponovno napuniti digitalnim valutama. Nisu potrebne dodatne informacije!", + "signup_for_card_accept_terms": "Prijavite se za karticu i prihvatite uvjete.", + "add_fund_to_card": "Dodajte unaprijed uplaćena sredstva na kartice (do ${value})", + "use_card_info_two": "Sredstva se pretvaraju u USD kada se drže na prepaid računu, a ne u digitalnim valutama.", + "use_card_info_three": "Koristite digitalnu karticu online ili s beskontaktnim metodama plaćanja.", + "optionally_order_card": "Opcionalno naručite fizičku karticu.", + "hide_details" : "Sakrij pojedinosti", + "show_details": "Prikaži pojedinosti", + "upto": "do ${value}", + "discount": "Uštedite ${value}%", + "gift_card_amount": "Iznos darovne kartice", + "bill_amount": "Iznos računa", + "you_pay": "Vi plaćate", + "tip": "Savjet:", + "custom": "prilagođeno", + "by_cake_pay": "od CakePaya", + "expires": "Ističe", + "mm": "MM", + "yy": "GG", + "online": "Na mreži", + "offline": "izvan mreže", + "gift_card_number": "Broj darovne kartice", + "pin_number": "PIN broj", + "total_saving": "Ukupna ušteda", + "last_30_days": "Zadnjih 30 dana", + "avg_savings": "Prosj. ušteda", + "view_all": "Prikaži sve", + "active_cards": "Aktivne kartice", + "delete_account": "Izbriši račun", + "cards": "Kartice", + "active": "Aktivno", + "redeemed": "otkupljeno", + "gift_card_balance_note": "Ovdje će se pojaviti darovne kartice s preostalim saldom", + "gift_card_redeemed_note": "Poklon kartice koje ste iskoristili pojavit će se ovdje", + "logout": "Odjava", + "add_tip": "Dodaj savjet", + "percentageOf": "od ${amount}", + "is_percentage": "je", + "search_category": "Kategorija pretraživanja", + "mark_as_redeemed": "Označi kao otkupljeno", + "more_options": "Više opcija", + "awaiting_payment_confirmation": "Čeka se potvrda plaćanja", + "transaction_sent_notice": "Ako se zaslon ne nastavi nakon 1 minute, provjerite block explorer i svoju e-poštu.", + "agree": "Slažem se", + "in_store": "U trgovini", + "generating_gift_card": "Generiranje darovne kartice", + "payment_was_received": "Vaša uplata je primljena.", + "proceed_after_one_minute": "Ako se zaslon ne nastavi nakon 1 minute, provjerite svoju e-poštu.", + "order_id": "ID narudžbe", + "gift_card_is_generated": "Poklon kartica je generirana", + "open_gift_card": "Otvori darovnu karticu", + "contact_support": "Kontaktirajte podršku" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index a65078b3e5..b10e3fce2f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -530,5 +530,102 @@ "learn_more" : "Impara di più", "search": "Ricerca", "new_template" : "Nuovo modello", - "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare" + "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare", + "market_place": "Mercato", + "cake_pay_title": "Carte regalo e carte di debito", + "cake_pay_subtitle": "Acquista carte regalo e ricarica carte di debito senza KYC", + "about_cake_pay": "CakePay ti consente di acquistare facilmente buoni regalo e caricare carte di debito prepagate con criptovalute, spendibili presso milioni di commercianti negli Stati Uniti.", + "cake_pay_account_note": "Crea un account per vedere le carte disponibili. Alcune sono anche disponibili con uno sconto!", + "already_have_account": "Hai già un account?", + "create_account": "Crea account", + "privacy_policy": "Informativa sulla privacy", + "welcome_to_cakepay": "Benvenuto in CakePay!", + "sign_up": "Registrati", + "forgot_password": "Password dimenticata", + "reset_password": "Reimposta password", + "manage_cards": "Gestisci carte", + "setup_your_debit_card": "Configura la tua carta di debito", + "no_id_required": "Nessun ID richiesto. Ricarica e spendi ovunque", + "how_to_use_card": "Come usare questa carta", + "purchase_gift_card": "Acquista carta regalo", + "verification": "Verifica", + "fill_code": "Compila il codice di verifica fornito alla tua email", + "dont_get_code": "Non ricevi il codice?", + "resend_code": "Per favore, invialo nuovamente", + "debit_card": "Carta di debito", + "cakepay_prepaid_card": "Carta di debito prepagata CakePay", + "no_id_needed": "Nessun ID necessario!", + "frequently_asked_questions": "Domande frequenti", + "debit_card_terms": "L'archiviazione e l'utilizzo del numero della carta di pagamento (e delle credenziali corrispondenti al numero della carta di pagamento) in questo portafoglio digitale sono soggetti ai Termini e condizioni del contratto applicabile con il titolare della carta con l'emittente della carta di pagamento, come in vigore da tempo al tempo.", + "please_reference_document": "Si prega di fare riferimento ai documenti di seguito per ulteriori informazioni.", + "cardholder_agreement": "Contratto del titolare della carta", + "e_sign_consent": "Consenso alla firma elettronica", + "agree_and_continue": "Accetta e continua", + "email_address": "Indirizzo e-mail", + "agree_to": "Creando un account accetti il ​​", + "and": "e", + "enter_code": "Inserisci codice", + "congratulation": "Congratulazioni!", + "you_now_have_debit_card": "Ora hai una carta di debito", + "min_amount" : "Min: ${value}", + "max_amount" : "Max: ${value}", + "enter_amount": "Inserisci importo", + "billing_address_info": "Se ti viene richiesto un indirizzo di fatturazione, fornisci il tuo indirizzo di spedizione", + "order_physical_card": "Ordine carta fisica", + "add_value": "Aggiungi valore", + "activate": "Attiva", + "get_a": "Prendi un ", + "digital_and_physical_card": "carta di debito prepagata digitale e fisica", + "get_card_note": "che puoi ricaricare con le valute digitali. Non sono necessarie informazioni aggiuntive!", + "signup_for_card_accept_terms": "Registrati per la carta e accetta i termini.", + "add_fund_to_card": "Aggiungi fondi prepagati alle carte (fino a ${value})", + "use_card_info_two": "I fondi vengono convertiti in USD quando sono detenuti nel conto prepagato, non in valute digitali.", + "use_card_info_three": "Utilizza la carta digitale online o con metodi di pagamento contactless.", + "optional_order_card": "Ordina facoltativamente una carta fisica.", + "hide_details" : "Nascondi dettagli", + "show_details": "Mostra dettagli", + "upto": "fino a ${value}", + "discount": "Risparmia ${value}%", + "gift_card_amount": "Importo del buono regalo", + "bill_amount": "Importo della fattura", + "you_pay": "Tu paghi", + "tip": "Suggerimento:", + "custom": "personalizzato", + "by_cake_pay": "da CakePay", + "expires": "Scade", + "mm": "mm", + "yy": "YY", + "online": "in linea", + "offline": "Offline", + "gift_card_number": "Numero del buono regalo", + "pin_number": "Numero PIN", + "total_saving": "Risparmio totale", + "last_30_days": "Ultimi 30 giorni", + "avg_savings": "Risparmio medio", + "view_all": "Visualizza tutto", + "active_cards": "Carte attive", + "delete_account": "Elimina account", + "cards": "Carte", + "active": "Attivo", + "redeemed": "Redento", + "gift_card_balance_note": "Le carte regalo con un saldo residuo appariranno qui", + "gift_card_redeemed_note": "Le carte regalo che hai riscattato appariranno qui", + "logout": "Logout", + "add_tip": "Aggiungi suggerimento", + "percentageOf": "di ${amount}", + "is_percentage": "è", + "search_category": "Categoria di ricerca", + "mark_as_redeemed": "Segna come riscattato", + "more_options": "Altre opzioni", + "waiting_payment_confirmation": "In attesa di conferma del pagamento", + "transaction_sent_notice": "Se lo schermo non procede dopo 1 minuto, controlla un block explorer e la tua email.", + "agree": "d'accordo", + "in_store": "In negozio", + "generating_gift_card": "Generazione carta regalo", + "payment_was_received": "Il tuo pagamento è stato ricevuto.", + "proceed_after_one_minute": "Se lo schermo non procede dopo 1 minuto, controlla la tua email.", + "order_id": "ID ordine", + "gift_card_is_generated": "Il buono regalo è stato generato", + "open_gift_card": "Apri carta regalo", + "contact_support": "Contatta l'assistenza" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 5cb3039c12..970c25ecb5 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -530,5 +530,102 @@ "learn_more" : "もっと詳しく知る", "search": "検索", "new_template" : "新しいテンプレート", - "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します" + "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します", + "market_place": "マーケットプレイス", + "cake_pay_title": "ギフトカードとデビットカード", + "cake_pay_subtitle": "ギフトカードを購入し、KYCなしのデビットカードを補充する", + "about_cake_pay": "CakePayを使用すると、ギフトカードを簡単に購入し、米国内の数百万の加盟店で使用できる暗号通貨を使用してプリペイドデビットカードをロードできます。", + "cake_pay_account_note": "アカウントを作成して、利用可能なカードを確認してください。割引価格で利用できるカードもあります!", + "already_have_account": "すでにアカウントをお持ちですか?", + "create_account": "アカウントの作成", + "privacy_policy": "プライバシーポリシー", + "welcome_to_cakepay": "CakePayへようこそ!", + "sign_up": "サインアップ", + "forgot_password": "パスワードを忘れた", + "reset_password": "パスワードのリセット", + "manage_cards": "カードの管理", + "setup_your_debit_card": "デビットカードを設定してください", + "no_id_required": "IDは必要ありません。どこにでも補充して使用できます", + "how_to_use_card": "このカードの使用方法", + "purchase_gift_card": "ギフトカードを購入", + "verification" : "検証", + "fill_code": "メールアドレスに記載されている確認コードを入力してください", + "dont_get_code": "コードを取得しませんか?", + "resend_code": "再送してください", + "debit_card": "デビットカード", + "cakepay_prepaid_card": "CakePayプリペイドデビットカード", + "no_id_needed": "IDは必要ありません!", + "frequently_asked_questions": "よくある質問", + "debit_card_terms": "このデジタルウォレットでの支払いカード番号(および支払いカード番号に対応する資格情報)の保存と使用には、支払いカード発行者との該当するカード所有者契約の利用規約が適用されます。時々。", + "please_reference_document": "詳細については、以下のドキュメントを参照してください。", + "cardholder_agreement": "カード所有者契約", + "e_sign_consent": "電子署名の同意", + "agree_and_continue": "同意して続行", + "email_address": "メールアドレス", + "agree_to": "アカウントを作成することにより、", + "and": "と", + "enter_code": "コードを入力", + "congratulations": "おめでとうございます!", + "you_now_have_debit_card": "デビットカードができました", + "min_amount": "最小: {value}", + "max_amount": "最大: {value}", + "enter_amount": "金額を入力", + "billing_address_info": "請求先住所を尋ねられた場合は、配送先住所を入力してください", + "order_physical_card": "物理カードの注文", + "add_value": "付加価値", + "activate": "アクティブ化", + "get_a": "Get a", + "digital_and_physical_card": "デジタルおよび物理プリペイドデビットカード", + "get_card_note": "デジタル通貨でリロードできます。追加情報は必要ありません!", + "signup_for_card_accept_terms": "カードにサインアップして、利用規約に同意してください。", + "add_fund_to_card": "プリペイド資金をカードに追加します(最大 ${value})", + "use_card_info_two": "デジタル通貨ではなく、プリペイドアカウントで保持されている場合、資金は米ドルに変換されます。", + "use_card_info_three": "デジタルカードをオンラインまたは非接触型決済方法で使用してください。", + "optionally_order_card": "オプションで物理カードを注文します。", + "hide_details": "詳細を非表示", + "show_details": "詳細を表示", + "upto": "up up ${value}", + "discount": "${value}%を節約", + "gift_card_amount": "ギフトカードの金額", + "bill_amount": "請求額", + "you_pay": "あなたが支払う", + "tip": "ヒント: ", + "custom": "カスタム", + "by_cake_pay": "by CakePay", + "expires": "Expires", + "mm": "んん", + "yy": "YY", + "online": "オンライン", + "offline": "オフライン", + "gift_card_number": "ギフトカード番号", + "pin_number": "PIN番号", + "total_saving": "合計節約額", + "last_30_days": "過去30日", + "avg_savings": "平均節約額", + "view_all": "すべて表示", + "active_cards": "アクティブカード", + "delete_account": "アカウントの削除", + "cards": "カード", + "active": "アクティブ", + "redeemed": "償還", + "gift_card_balance_note": "残高が残っているギフトカードがここに表示されます", + "gift_card_redeemed_note": "利用したギフトカードがここに表示されます", + "logout": "ログアウト", + "add_tip": "ヒントを追加", + "percentageOf": "of ${amount}", + "is_percentage": "is", + "search_category": "検索カテゴリ", + "mark_as_redeemed": "償還済みとしてマーク", + "more_options": "その他のオプション", + "awaiting_payment_confirmation": "支払い確認を待っています", + "transaction_sent_notice": "1分経っても画面が進まない場合は、ブロックエクスプローラーとメールアドレスを確認してください。", + "agree": "同意する", + "in_store": "インストア", + "generated_gift_card": "ギフトカードの生成", + "payment_was_received": "お支払いを受け取りました。", + "proceed_after_one_minute": "1分経っても画面が進まない場合は、メールを確認してください。", + "order_id": "注文ID", + "gift_card_is_generated": "ギフトカードが生成されます", + "open_gift_card": "オープンギフトカード", + "contact_support": "サポートに連絡する" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index db099be633..f3d19855af 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -530,5 +530,102 @@ "learn_more" : "더 알아보기", "search": "찾다", "new_template" : "새 템플릿", - "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다." + "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다.", + "market_place": "시장", + "cake_pay_title": "기프트 카드 및 직불 카드", + "cake_pay_subtitle": "기프트 카드를 구매하고 KYC가 없는 직불 카드를 충전하세요.", + "about_cake_pay": "CakePay를 사용하면 기프트 카드를 쉽게 구매하고 미국의 수백만 판매자에서 사용할 수 있는 암호화폐가 포함된 선불 직불 카드를 충전할 수 있습니다.", + "cake_pay_account_note": "사용 가능한 카드를 보려면 계정을 만드십시오. 일부는 할인된 가격으로 사용 가능합니다!", + "already_have_account": "이미 계정이 있습니까?", + "create_account": "계정 만들기", + "privacy_policy": "개인 정보 보호 정책", + "welcome_to_cakepay": "CakePay에 오신 것을 환영합니다!", + "sign_up": "가입", + "forgot_password": "비밀번호 찾기", + "reset_password": "비밀번호 재설정", + "manage_cards": "카드 관리", + "setup_your_debit_card": "직불카드 설정", + "no_id_required": "신분증이 필요하지 않습니다. 충전하고 어디에서나 사용하세요", + "how_to_use_card": "이 카드를 사용하는 방법", + "purchase_gift_card": "기프트 카드 구매", + "verification": "검증", + "fill_code": "이메일에 제공된 인증 코드를 입력하세요.", + "dont_get_code": "코드를 받지 못하셨습니까?", + "resend_code": "다시 보내주세요", + "debit_card": "직불 카드", + "cakepay_prepaid_card": "CakePay 선불 직불 카드", + "no_id_needed": "ID가 필요하지 않습니다!", + "frequently_asked_questions": "자주 묻는 질문", + "debit_card_terms": "이 디지털 지갑에 있는 귀하의 지불 카드 번호(및 귀하의 지불 카드 번호에 해당하는 자격 증명)의 저장 및 사용은 부터 발효되는 지불 카드 발행자와의 해당 카드 소지자 계약의 이용 약관을 따릅니다. 수시로.", + "Please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.", + "cardholder_agreement": "카드 소유자 계약", + "e_sign_consent": "전자 서명 동의", + "agree_and_continue": "동의 및 계속", + "email_address": "이메일 주소", + "agree_to": "계정을 생성하면 ", + "and": "그리고", + "enter_code": "코드 입력", + "congratulations": "축하합니다!", + "you_now_have_debit_card": "이제 직불카드가 있습니다.", + "min_amount" : "최소: ${value}", + "max_amount" : "최대: ${value}", + "enter_amount": "금액 입력", + "billing_address_info": "청구서 수신 주소를 묻는 메시지가 표시되면 배송 주소를 입력하세요.", + "order_physical_card": "물리적 카드 주문", + "add_value": "값 추가", + "activate": "활성화", + "get_a": "가져오기", + "digital_and_physical_card": " 디지털 및 실제 선불 직불 카드", + "get_card_note": " 디지털 통화로 충전할 수 있습니다. 추가 정보가 필요하지 않습니다!", + "signup_for_card_accept_terms": "카드에 가입하고 약관에 동의합니다.", + "add_fund_to_card": "카드에 선불 금액 추가(최대 ${value})", + "use_card_info_two": "디지털 화폐가 아닌 선불 계정에 보유하면 자금이 USD로 변환됩니다.", + "use_card_info_three": "디지털 카드를 온라인 또는 비접촉식 결제 수단으로 사용하십시오.", + "optionally_order_card": "선택적으로 실제 카드를 주문하십시오.", + "hide_details" : "세부 정보 숨기기", + "show_details" : "세부정보 표시", + "upto": "최대 ${value}", + "discount": "${value}% 절약", + "gift_card_amount": "기프트 카드 금액", + "bill_amount": "청구 금액", + "you_pay": "당신이 지불합니다", + "tip": "팁:", + "custom": "커스텀", + "by_cake_pay": "CakePay로", + "expires": "만료", + "mm": "mm", + "YY": "YY", + "online": "온라인", + "offline": "오프라인", + "gift_card_number": "기프트 카드 번호", + "pin_number": "PIN 번호", + "total_saving": "총 절감액", + "last_30_days": "지난 30일", + "avg_savings": "평균 절감액", + "view_all": "모두 보기", + "active_cards": "활성 카드", + "delete_account": "계정 삭제", + "cards": "카드", + "active": "활성", + "redeemed": "구함", + "gift_card_balance_note": "잔액이 남아 있는 기프트 카드가 여기에 표시됩니다.", + "gift_card_redeemed_note": "사용한 기프트 카드가 여기에 표시됩니다.", + "logout": "로그아웃", + "add_tip": "팁 추가", + "percentageOf": "${amount} 중", + "is_percentage": "이다", + "search_category": "검색 카테고리", + "mark_as_redeemed": "사용한 것으로 표시", + "more_options": "추가 옵션", + "awaiting_payment_confirmation": "결제 확인 대기 중", + "transaction_sent_notice": "1분 후에도 화면이 진행되지 않으면 블록 익스플로러와 이메일을 확인하세요.", + "agree": "동의하다", + "in_store": "매장 내", + "generating_gift_card": "기프트 카드 생성 중", + "payment_was_received": "결제가 접수되었습니다.", + "proceed_after_one_minute": "1분 후에도 화면이 진행되지 않으면 이메일을 확인하세요.", + "order_id": "주문 ID", + "gift_card_is_generated": "기프트 카드가 생성되었습니다", + "open_gift_card": "기프트 카드 열기", + "contact_support": "지원팀에 문의" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 2079794628..0bdc0e129c 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -530,5 +530,102 @@ "learn_more" : "Kom meer te weten", "search": "Zoekopdracht", "new_template" : "Nieuwe sjabloon", - "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work" + "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work", + "market_place": "Marktplaats", + "cake_pay_title": "Cadeaukaarten en debetkaarten", + "cake_pay_subtitle": "Koop cadeaubonnen en herlaad niet-KYC-betaalkaarten", + "about_cake_pay": "Met CakePay kun je gemakkelijk cadeaubonnen kopen en prepaid-betaalkaarten opladen met cryptocurrencies, te besteden bij miljoenen verkopers in de Verenigde Staten.", + "cake_pay_account_note": "Maak een account aan om de beschikbare kaarten te zien. Sommige zijn zelfs met korting verkrijgbaar!", + "already_have_account": "Heb je al een account?", + "create_account": "Account aanmaken", + "privacy_policy": "Privacybeleid", + "welcome_to_cakepay": "Welkom bij CakePay!", + "sign_up": "Aanmelden", + "forgot_password": "Wachtwoord vergeten", + "reset_password": "Wachtwoord resetten", + "manage_cards": "Kaarten beheren", + "setup_your_debit_card": "Stel uw debetkaart in", + "no_id_required": "Geen ID vereist. Opwaarderen en overal uitgeven", + "how_to_use_card": "Hoe deze kaart te gebruiken", + "purchase_gift_card": "Cadeaubon kopen", + "verification": "Verificatie", + "fill_code": "Vul de verificatiecode in die u in uw e-mail hebt ontvangen", + "dont_get_code": "Geen code?", + "resend_code": "Stuur het alstublieft opnieuw", + "debit_card": "Debetkaart", + "cakepay_prepaid_card": "CakePay Prepaid Debetkaart", + "no_id_needed": "Geen ID nodig!", + "frequently_asked_questions": "Veelgestelde vragen", + "debit_card_terms": "De opslag en het gebruik van uw betaalkaartnummer (en inloggegevens die overeenkomen met uw betaalkaartnummer) in deze digitale portemonnee zijn onderworpen aan de Algemene voorwaarden van de toepasselijke kaarthouderovereenkomst met de uitgever van de betaalkaart, zoals van kracht vanaf tijd tot tijd.", + "please_reference_document": "Raadpleeg de onderstaande documenten voor meer informatie.", + "cardholder_agreement": "Kaarthouderovereenkomst", + "e_sign_consent": "Toestemming e-ondertekenen", + "agree_and_continue": "Akkoord & doorgaan", + "email_address": "E-mailadres", + "agree_to": "Door een account aan te maken gaat u akkoord met de ", + "and": "en", + "enter_code": "Voer code in", + "congratulations": "gefeliciteerd!", + "you_now_have_debit_card": "Je hebt nu een debetkaart", + "min_amount" : "Min: ${value}", + "max_amount" : "Max: ${value}", + "enter_amount": "Voer Bedrag in", + "billing_address_info": "Als u om een ​​factuuradres wordt gevraagd, geef dan uw verzendadres op", + "order_physical_card": "Fysieke kaart bestellen", + "add_value": "Waarde toevoegen", + "activate": "Activeren", + "get_a": "Krijg een ", + "digital_and_physical_card": "digitale und physische Prepaid-Debitkarte", + "get_card_note": " die Sie mit digitaler Währung aufladen können. Keine zusätzlichen Informationen erforderlich!", + "signup_for_card_accept_terms": "Melden Sie sich für die Karte an und akzeptieren Sie die Bedingungen.", + "add_fund_to_card": "Prepaid-Guthaben zu den Karten hinzufügen (bis zu ${value})", + "use_card_info_two": "Guthaben werden auf dem Prepaid-Konto in USD umgerechnet, nicht in digitale Währung.", + "use_card_info_three": "Verwenden Sie die digitale Karte online oder mit kontaktlosen Zahlungsmethoden.", + "optional_order_card": "Optional eine physische Karte bestellen.", + "hide_details": "Details ausblenden", + "show_details": "Details anzeigen", + "bis": "bis zu ${value}", + "discount": "${value} % sparen", + "gift_card_amount": "Gutscheinbetrag", + "bill_amount": "Rechnungsbetrag", + "you_pay": "Sie bezahlen", + "tip": "Hinweis:", + "custom": "benutzerdefiniert", + "by_cake_pay": "von CakePay", + "expires": "Läuft ab", + "mm": "MM", + "yy": "YY", + "online": "online", + "offline": "offline", + "gift_card_number": "Geschenkkartennummer", + "pin_number": "PIN-Nummer", + "total_saving": "Gesamteinsparungen", + "last_30_days": "Letzte 30 Tage", + "avg_savings": "Durchschn. Einsparungen", + "view_all": "Alle anzeigen", + "active_cards": "Aktive Karten", + "delete_account": "Konto löschen", + "cards": "Karten", + "active": "Aktiv", + "redeemed": "Versilbert", + "gift_card_balance_note": "Geschenkkarten mit Restguthaben erscheinen hier", + "gift_card_redeemed_note": "Gutscheine, die Sie eingelöst haben, werden hier angezeigt", + "abmelden": "Abmelden", + "add_tip": "Tipp hinzufügen", + "percentageOf": "von ${amount}", + "is_percentage": "ist", + "search_category": "Suchkategorie", + "mark_as_redeemed": "Als eingelöst markieren", + "more_options": "Weitere Optionen", + "waiting_payment_confirmation": "Warte auf Zahlungsbestätigung", + "transaction_sent_notice": "Wenn der Bildschirm nach 1 Minute nicht weitergeht, überprüfen Sie einen Block-Explorer und Ihre E-Mail.", + "stimme zu": "stimme zu", + "in_store": "Im Geschäft", + "generating_gift_card": "Geschenkkarte wird erstellt", + "payment_was_received": "Ihre Zahlung ist eingegangen.", + "proceed_after_one_minute": "Wenn der Bildschirm nach 1 Minute nicht weitergeht, überprüfen Sie bitte Ihre E-Mail.", + "order_id": "Order-ID", + "gift_card_is_generated": "Cadeaukaart is gegenereerd", + "open_gift_card": "Geschenkkaart openen", + "contact_support": "Contact opnemen met ondersteuning" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 3da2cc94d8..f5f22716d0 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -533,5 +533,102 @@ "learn_more" : "Ucz się więcej", "search": "Szukaj", "new_template" : "Nowy szablon", - "electrum_address_disclaimer": "Za każdym razem, gdy korzystasz z jednego z nich, generujemy nowe adresy, ale poprzednie adresy nadal działają" + "electrum_address_disclaimer": "Za każdym razem, gdy korzystasz z jednego z nich, generujemy nowe adresy, ale poprzednie adresy nadal działają", + "market_place": "Rynek", + "cake_pay_title": "Karty podarunkowe i debetowe", + "cake_pay_subtitle": "Kup karty podarunkowe i doładuj karty debetowe bez KYC", + "about_cake_pay": "CakePay umożliwia łatwe kupowanie kart podarunkowych i doładowanie przedpłaconych kart debetowych kryptowalutami, które można wydać u milionów sprzedawców w Stanach Zjednoczonych.", + "cake_pay_account_note": "Załóż konto, aby zobaczyć dostępne karty. Niektóre są nawet dostępne ze zniżką!", + "already_have_account": "Masz już konto?", + "create_account": "Utwórz konto", + "privacy_policy": "Polityka prywatności", + "welcome_to_cakepay": "Witamy w CakePay!", + "sign_up": "Zarejestruj się", + "forgot_password": "Zapomniałem hasła", + "reset_password": "Zresetuj hasło", + "manage_cards": "Zarządzaj kartami", + "setup_your_debit_card": "Skonfiguruj swoją kartę debetową", + "no_id_required": "Nie wymagamy ID. Doładuj i wydawaj gdziekolwiek", + "how_to_use_card": "Jak korzystać z tej karty", + "purchase_gift_card": "Kup kartę podarunkową", + "verification": "Weryfikacja", + "fill_code": "Proszę wpisać kod weryfikacyjny podany w wiadomości e-mail", + "dont_get_code": "Nie odbierasz kodu?", + "resend_code": "Wyślij go ponownie", + "debit_card": "Karta debetowa", + "cakepay_prepaid_card": "Przedpłacona karta debetowa CakePay", + "no_id_needed": "Nie potrzeba ID!", + "frequently_asked_questions": "Często zadawane pytania", + "debit_card_terms": "Przechowywanie i używanie numeru karty płatniczej (oraz danych uwierzytelniających odpowiadających numerowi karty płatniczej) w tym portfelu cyfrowym podlega Warunkom odpowiedniej umowy posiadacza karty z wydawcą karty płatniczej, zgodnie z obowiązującym od od czasu do czasu.", + "please_reference_document": "Proszę odwołać się do poniższych dokumentów, aby uzyskać więcej informacji.", + "cardholder_agreement": "Umowa posiadacza karty", + "e_sign_consent": "Zgoda na podpis elektroniczny", + "agree_and_continue": "Zgadzam się i kontynuuj", + "email_address": "Adres e-mail", + "agree_to": "Tworząc konto wyrażasz zgodę na ", + "and": "i", + "enter_code": "Wprowadź kod", + "congratulations": "gratulacje!", + "you_now_have_debit_card": "Masz teraz kartę debetową", + "min_amount" : "Min: ${value}", + "max_amount" : "Max: ${value}", + "enter_amount": "Wprowadź kwotę", + "billing_address_info": "Jeśli zostaniesz poproszony o podanie adresu rozliczeniowego, podaj swój adres wysyłki", + "order_physical_card": "Zamów kartę fizyczną", + "add_value": "Dodaj wartość", + "activate": "Aktywuj", + "get_a": "Zdobądź ", + "digital_and_physical_card": " cyfrowa i fizyczna przedpłacona karta debetowa", + "get_card_note": " które możesz doładować walutami cyfrowymi. Nie są potrzebne żadne dodatkowe informacje!", + "signup_for_card_accept_terms": "Zarejestruj się, aby otrzymać kartę i zaakceptuj warunki.", + "add_fund_to_card": "Dodaj przedpłacone środki do kart (do ${value})", + "use_card_info_two": "Środki są przeliczane na USD, gdy są przechowywane na koncie przedpłaconym, a nie w walutach cyfrowych.", + "use_card_info_three": "Użyj cyfrowej karty online lub za pomocą zbliżeniowych metod płatności.", + "opcjonalne_zamówienie_card": "Opcjonalnie zamów kartę fizyczną.", + "hide_details" : "Ukryj szczegóły", + "show_details" : "Pokaż szczegóły", + "upto": "do ${value}", + "discount": "Zaoszczędź {value}% $", + "gift_card_amount": "Kwota karty podarunkowej", + "bill_amount": "Kwota rachunku", + "you_pay": "Płacisz", + "tip": "wskazówka:", + "custom": "niestandardowy", + "by_cake_pay": "przez CakePay", + "expires": "Wygasa", + "mm": "MM", + "yy": "RR", + "online": "online", + "offline": "Offline", + "gift_card_number": "Numer karty podarunkowej", + "pin_number": "Numer PIN", + "total_saving": "Całkowite oszczędności", + "last_30_days": "Ostatnie 30 dni", + "avg_savings": "Śr. oszczędności", + "view_all": "Wyświetl wszystko", + "active_cards": "Aktywne karty", + "delete_account": "Usuń konto", + "cards": "Karty", + "active": "Aktywny", + "redeemed": "wykupione", + "gift_card_balance_note": "Tutaj pojawią się karty podarunkowe z pozostałym saldem", + "gift_card_redeemed_note": "Karty podarunkowe, które wykorzystałeś, pojawią się tutaj", + "logout": "Wyloguj", + "add_tip": "Dodaj wskazówkę", + "percentageOf": "z {amount} $", + "is_percentage": "jest", + "search_category": "Kategoria wyszukiwania", + "mark_as_redeemed": "Oznacz jako wykorzystany", + "more_options": "Więcej opcji", + "awaiting_payment_confirmation": "Oczekiwanie na potwierdzenie płatności", + "transaction_sent_notice": "Jeśli ekran nie pojawi się po 1 minucie, sprawdź eksplorator bloków i swój e-mail.", + "agree": "Zgadzam się", + "in_store": "W Sklepie", + "generating_gift_card": "Generowanie karty podarunkowej", + "payment_was_received": "Twoja płatność została otrzymana.", + "proceed_after_one_minute": "Jeśli ekran nie przejdzie dalej po 1 minucie, sprawdź pocztę.", + "order_id": "Identyfikator zamówienia", + "gift_card_is_generated": "Karta podarunkowa jest generowana", + "open_gift_card": "Otwórz kartę podarunkową", + "contact_support": "Skontaktuj się z pomocą techniczną" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index d32e47bd08..6f70efd00d 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -530,5 +530,102 @@ "learn_more" : "Saber mais", "search": "Procurar", "new_template" : "Novo modelo", - "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando" + "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando", + "market_place": "Mercado", + "cake_pay_title": "Cartões de presente e cartões de débito", + "cake_pay_subtitle": "Compre vales-presente e carregue cartões de débito sem KYC", + "about_cake_pay": "O CakePay permite que você compre facilmente cartões-presente e carregue cartões de débito pré-pagos com criptomoedas, que podem ser gastos em milhões de comerciantes nos Estados Unidos.", + "cake_pay_account_note": "Faça uma conta para ver os cartões disponíveis. Alguns estão até com desconto!", + "already_have_account": "Já tem uma conta?", + "create_account": "Criar conta", + "privacy_policy": "Política de privacidade", + "welcome_to_cakepay": "Bem-vindo ao CakePay!", + "create_account": "Registar-se", + "forgot_password": "Esqueci a senha", + "reset_password": "Redefinir senha", + "manage_cards": "Gerenciar Cartões", + "setup_your_debit_card": "Configure seu cartão de débito", + "no_id_required": "Não é necessário ID. Recarregue e gaste em qualquer lugar", + "how_to_use_card": "Como usar este cartão", + "purchase_gift_card": "Comprar vale-presente", + "verification": "Verificação", + "fill_code": "Por favor, preencha o código de verificação fornecido ao seu e-mail", + "dont_get_code": "Não recebeu o código?", + "resend_code": "Por favor, reenvie", + "debit_card": "Cartão de débito", + "cakepay_prepaid_card": "Cartão de débito pré-pago CakePay", + "no_id_needed": "Nenhum ID necessário!", + "frequently_asked_questions": "Perguntas frequentes", + "debit_card_terms": "O armazenamento e uso do número do cartão de pagamento (e credenciais correspondentes ao número do cartão de pagamento) nesta carteira digital estão sujeitos aos Termos e Condições do contrato do titular do cartão aplicável com o emissor do cartão de pagamento, em vigor a partir de tempo ao tempo.", + "please_reference_document": "Por favor, consulte os documentos abaixo para mais informações.", + "cardholder_agreement": "Acordo do titular do cartão", + "e_sign_consent": "Consentimento de assinatura eletrônica", + "agree_and_continue": "Concordar e continuar", + "email_address": "Endereço de e-mail", + "agree_to": "Ao criar conta você concorda com ", + "and": "e", + "enter_code": "Digite o código", + "congratulations": "Parabéns!", + "you_now_have_debit_card": "Agora você tem um cartão de débito", + "min_amount" : "Mínimo: ${valor}", + "max_amount" : "Máx.: ${valor}", + "enter_amount": "Digite o valor", + "billing_address_info": "Se for solicitado um endereço de cobrança, forneça seu endereço de entrega", + "order_physical_card": "Pedir Cartão Físico", + "add_value": "Adicionar valor", + "activate": "Ativar", + "get_a": "Obter um ", + "digital_and_physical_card": "cartão de débito pré-pago digital e físico", + "get_card_note": " que você pode recarregar com moedas digitais. Nenhuma informação adicional é necessária!", + "signup_for_card_accept_terms": "Cadastre-se no cartão e aceite os termos.", + "add_fund_to_card": "Adicionar fundos pré-pagos aos cartões (até ${value})", + "use_card_info_two": "Os fundos são convertidos para USD quando mantidos na conta pré-paga, não em moedas digitais.", + "use_card_info_three": "Use o cartão digital online ou com métodos de pagamento sem contato.", + "opcionalmente_order_card": "Opcionalmente, peça um cartão físico.", + "hide_details" : "Ocultar detalhes", + "show_details" : "Mostrar detalhes", + "upto": "até ${value}", + "discount": "Economize ${value}%", + "gift_card_amount": "Valor do Cartão Presente", + "bill_amount": "Valor da conta", + "you_pay": "Você paga", + "tip": "Dica:", + "custom": "personalizado", + "by_cake_pay": "por CakePay", + "expires": "Expira", + "mm": "MM", + "yy": "aa", + "online": "Online", + "offline": "offline", + "gift_card_number": "Número do cartão-presente", + "pin_number": "Número PIN", + "total_saving": "Economia total", + "last_30_days": "Últimos 30 dias", + "avg_savings": "Poupança média", + "view_all": "Ver todos", + "active_cards": "Cartões ativos", + "delete_account": "Excluir conta", + "cards": "Cartões", + "active": "Ativo", + "redeemed": "Resgatado", + "gift_card_balance_note": "Os cartões-presente com saldo restante aparecerão aqui", + "gift_card_redeemed_note": "Os cartões-presente que você resgatou aparecerão aqui", + "logout": "Logout", + "add_tip": "Adicionar Dica", + "percentageOf": "de ${amount}", + "is_percentage": "é", + "search_category": "Categoria de pesquisa", + "mark_as_redemed": "Marcar como resgatado", + "more_options": "Mais opções", + "waiting_payment_confirmation": "Aguardando confirmação de pagamento", + "transaction_sent_notice": "Se a tela não prosseguir após 1 minuto, verifique um explorador de blocos e seu e-mail.", + "agree": "Concordo", + "in_store": "Na loja", + "generating_gift_card": "Gerando Cartão Presente", + "payment_was_received": "Seu pagamento foi recebido.", + "proceed_after_one_minute": "Se a tela não prosseguir após 1 minuto, verifique seu e-mail.", + "order_id": "ID do pedido", + "gift_card_is_generated": "Cartão presente é gerado", + "open_gift_card": "Abrir vale-presente", + "contact_support": "Contatar Suporte" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 78c67943a6..cb0586bfac 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -530,5 +530,102 @@ "learn_more" : "Узнать больше", "search": "Поиск", "new_template" : "Новый шаблон", - "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать." + "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать.", + "market_place": "Рыночная площадь", + "cake_pay_title": "Подарочные и дебетовые карты", + "cake_pay_subtitle": "Покупайте подарочные карты и пополняйте дебетовые карты без KYC", + "about_cake_pay": "CakePay позволяет вам легко покупать подарочные карты и пополнять предоплаченные дебетовые карты криптовалютой, которую можно потратить в миллионах магазинов в США.", + "cake_pay_account_note": "Создайте учетную запись, чтобы увидеть доступные карты. Некоторые даже доступны со скидкой!", + "already_have_account": "У вас уже есть аккаунт?", + "create_account": "Создать аккаунт", + "privacy_policy": "Политика конфиденциальности", + "welcome_to_cakepay": "Добро пожаловать в CakePay!", + "sign_up": "Зарегистрироваться", + "forgot_password": "Забыли пароль", + "reset_password": "Сбросить пароль", + "manage_cards": "Управление картами", + "setup_your_debit_card": "Настройте свою дебетовую карту", + "no_id_required": "Идентификатор не требуется. Пополняйте и тратьте где угодно", + "how_to_use_card": "Как использовать эту карту", + "purchase_gift_card": "Купить подарочную карту", + "verification": "Проверка", + "fill_code": "Пожалуйста, введите код подтверждения, отправленный на вашу электронную почту", + "dont_get_code": "Не получить код?", + "resend_code": "Пожалуйста, отправьте еще раз", + "debit_card": "Дебетовая карта", + "cakepay_prepaid_card": "Предоплаченная дебетовая карта CakePay", + "no_id_needed": "Идентификатор не нужен!", + "frequently_asked_questions": "Часто задаваемые вопросы", + "debit_card_terms": "Хранение и использование номера вашей платежной карты (и учетных данных, соответствующих номеру вашей платежной карты) в этом цифровом кошельке регулируются положениями и условиями применимого соглашения держателя карты с эмитентом платежной карты, действующим с время от времени.", + "please_reference_document": "Пожалуйста, обратитесь к документам ниже для получения дополнительной информации.", + "cardholder_agreement": "Соглашение с держателем карты", + "e_sign_consent": "Согласие электронной подписи", + "agree_and_continue": "Согласиться и продолжить", + "email_address": "Адрес электронной почты", + "agree_to": "Создавая аккаунт, вы соглашаетесь с ", + "and" :"и", + "enter_code": "Введите код", + "congratulations": "Поздравляем!", + "you_now_have_debit_card": "Теперь у вас есть дебетовая карта", + "min_amount": "Минимум: ${value}", + "max_amount": "Макс.: ${value}", + "enter_amount": "Введите сумму", + "billing_address_info": "Если вас попросят указать платежный адрес, укажите адрес доставки", + "order_physical_card": "Заказать физическую карту", + "add_value": "Добавить значение", + "activate": "Активировать", + "get_a": "Получить ", + "digital_and_physical_card": "цифровая и физическая предоплаченная дебетовая карта", + "get_card_note": " которую вы можете пополнить цифровой валютой. Дополнительная информация не требуется!", + "signup_for_card_accept_terms": "Подпишитесь на карту и примите условия.", + "add_fund_to_card": "Добавить предоплаченные средства на карты (до ${value})", + "use_card_info_two": "Средства конвертируются в доллары США, когда они хранятся на предоплаченном счете, а не в цифровых валютах.", + "use_card_info_three": "Используйте цифровую карту онлайн или с помощью бесконтактных способов оплаты.", + "optionly_order_card": "При желании закажите физическую карту.", + "hide_details": "Скрыть детали", + "show_details": "Показать детали", + "upto": "до ${value}", + "discount": "Сэкономьте ${value}%", + "gift_card_amount": "Сумма подарочной карты", + "bill_amount": "Сумма счета", + "you_pay": "Вы платите", + "tip": "Совет:", + "custom": "обычай", + "by_cake_pay": "от CakePay", + "expires": "Истекает", + "mm": "ММ", + "yy": "ГГ", + "online": "Онлайн", + "offline": "Не в сети", + "gift_card_number": "Номер подарочной карты", + "pin_number": "ПИН-код", + "total_saving": "Общая экономия", + "last_30_days": "Последние 30 дней", + "avg_savings": "Средняя экономия", + "view_all": "Просмотреть все", + "active_cards": "Активные карты", + "delete_account": "Удалить аккаунт", + "cards": "Карты", + "active": "Активный", + "redeemed": "искуплен", + "gift_card_balance_note": "Здесь будут отображаться подарочные карты с остатком на балансе", + "gift_card_redeemed_note": "Здесь будут отображаться использованные вами подарочные карты", + "logout": "Выйти", + "add_tip": "Добавить подсказку", + "percentageOf": "из ${amount}", + "is_percentage": "есть", + "search_category": "Категория поиска", + "mark_as_redeemed": "Отметить как погашенный", + "more_options": "Дополнительные параметры", + "awaiting_payment_confirmation": "Ожидается подтверждения платежа", + "transaction_sent_notice": "Если экран не отображается через 1 минуту, проверьте обозреватель блоков и свою электронную почту.", + "agree": "согласен", + "in_store": "В магазине", + "generating_gift_card": "Создание подарочной карты", + "payment_was_received": "Ваш платеж получен.", + "proceed_after_one_minute": "Если через 1 минуту экран не отображается, проверьте свою электронную почту.", + "order_id": "Идентификатор заказа", + "gift_card_is_generated": "Подарочная карта сгенерирована", + "open_gift_card": "Открыть подарочную карту", + "contact_support": "Связаться со службой поддержки" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index c2a96ab495..3723e02d2b 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -529,5 +529,102 @@ "learn_more" : "Дізнатися більше", "search": "Пошук", "new_template" : "Новий шаблон", - "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати" + "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати", + "market_place": "Ринок", + "cake_pay_title": "Подарункові та дебетові картки", + "cake_pay_subtitle": "Купуйте подарункові картки та поповнюйте дебетові картки без KYC", + "about_cake_pay": "CakePay дозволяє легко купувати подарункові картки та завантажувати передплачені дебетові картки криптовалютами, які можна витратити в мільйонах продавців у Сполучених Штатах.", + "cake_pay_account_note": "Створіть обліковий запис, щоб побачити доступні картки. Деякі навіть доступні зі знижкою!", + "already_have_account": "Вже є обліковий запис?", + "create_account": "Створити обліковий запис", + "privacy_policy": "Політика конфіденційності", + "welcome_to_cakepay": "Ласкаво просимо до CakePay!", + "sign_up": "Зареєструватися", + "forgot_password": "Забули пароль", + "reset_password": "Скинути пароль", + "manage_cards": "Керувати картками", + "setup_your_debit_card": "Налаштуйте свою дебетову картку", + "no_id_required": "Ідентифікатор не потрібен. Поповнюйте та витрачайте будь-де", + "how_to_use_card": "Як використовувати цю картку", + "purchase_gift_card": "Придбати подарункову картку", + "verification": "Перевірка", + "fill_code": "Будь ласка, введіть код підтвердження, надісланий на вашу електронну адресу", + "dont_get_code": "Не отримуєте код?", + "resend_code": "Будь ласка, надішліть його повторно", + "debit_card": "Дебетова картка", + "cakepay_prepaid_card": "Передплачена дебетова картка CakePay", + "no_id_needed": "Ідентифікатор не потрібен!", + "frequently_asked_questions": "Часті запитання", + "debit_card_terms": "Зберігання та використання номера вашої платіжної картки (та облікових даних, які відповідають номеру вашої платіжної картки) у цьому цифровому гаманці регулюються Умовами відповідної угоди власника картки з емітентом платіжної картки, що діє з час від часу.", + "please_reference_document": "Для отримання додаткової інформації зверніться до документів нижче.", + "cardholder_agreement": "Угода власника картки", + "e_sign_consent": "Згода електронного підпису", + "agree_and_continue": "Погодитися та продовжити", + "email_address": "Адреса електронної пошти", + "agree_to": "Створюючи обліковий запис, ви погоджуєтеся з ", + "and": "і", + "enter_code": "Введіть код", + "congratulations": "Вітаємо!", + "you_now_have_debit_card": "Тепер у вас є дебетова картка", + "min_amount": "Мінімум: ${value}", + "max_amount": "Макс: ${value}", + "enter_amount": "Введіть суму", + "billing_address_info": "Якщо буде запропоновано платіжну адресу, вкажіть свою адресу доставки", + "order_physical_card": "Замовити фізичну картку", + "add_value": "Додати значення", + "activate": "Активувати", + "get_a": "Отримати ", + "digital_and_physical_card": " цифрова та фізична передплачена дебетова картка", + "get_card_note": " яку можна перезавантажувати цифровими валютами. Додаткова інформація не потрібна!", + "signup_for_card_accept_terms": "Зареєструйтеся на картку та прийміть умови.", + "add_fund_to_card": "Додайте передплачені кошти на картки (до ${value})", + "use_card_info_two": "Кошти конвертуються в долари США, якщо вони зберігаються на передплаченому рахунку, а не в цифрових валютах.", + "use_card_info_three": "Використовуйте цифрову картку онлайн або за допомогою безконтактних методів оплати.", + "optionally_order_card": "Необов'язково замовте фізичну картку.", + "hide_details": "Приховати деталі", + "show_details": "Показати деталі", + "do": "до ${value}", + "discount": "Зекономте ${value}%", + "gift_card_amount": "Сума подарункової картки", + "bill_amount": "Сума рахунку", + "you_pay": "Ви платите", + "tip": "Порада:", + "custom": "на замовлення", + "by_cake_pay": "від CakePay", + "expires": "Закінчується", + "mm": "MM", + "yy": "YY", + "online": "Онлайн", + "offline": "Офлайн", + "gift_card_number": "Номер подарункової картки", + "pin_number": "PIN-код", + "total_saving": "Загальна економія", + "last_30_days": "Останні 30 днів", + "avg_savings": "Середня економія", + "view_all": "Переглянути все", + "active_cards": "Активні картки", + "delete_account": "Видалити обліковий запис", + "cards": "Картки", + "active": "Активний", + "redeeded": "Викуплено", + "gift_card_balance_note": "Тут з'являться подарункові картки із залишком на балансі", + "gift_card_redeemed_note": "Подарункові картки, які ви активували, відображатимуться тут", + "logout": "Вийти", + "add_tip": "Додати підказку", + "percentageOf": "${amount}", + "is_percentage": "є", + "search_category": "Категорія пошуку", + "mark_as_redeemed": "Позначити як погашене", + "more_options": "Більше параметрів", + "awaiting_payment_confirmation": "Очікується підтвердження платежу", + "transaction_sent_notice": "Якщо екран не відображається через 1 хвилину, перевірте провідник блоків і свою електронну пошту.", + "agree": "Згоден", + "in_store": "У магазині", + "generating_gift_card": "Створення подарункової картки", + "payment_was_received": "Ваш платіж отримано.", + "proceed_after_one_minute": "Якщо екран не продовжується через 1 хвилину, перевірте свою електронну пошту.", + "order_id": "Ідентифікатор замовлення", + "gift_card_is_generated": "Подарункова картка створена", + "open_gift_card": "Відкрити подарункову картку", + "contact_support": "Звернутися до служби підтримки" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 43132041ac..564a81b1a1 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -528,5 +528,102 @@ "learn_more" : "了解更多", "search": "搜索", "new_template" : "新模板", - "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效" + "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效", + "market_place": "市场", + "cake_pay_title": "礼品卡和借记卡", + "cake_pay_subtitle": "购买礼品卡并为非 KYC 借记卡充值", + "about_cake_pay": "CakePay 让您可以轻松购买礼品卡并使用加密货币加载预付借记卡,可在美国数百万商家消费。", + "cake_pay_account_note": "注册一个账户来查看可用的卡片。有些甚至可以打折!", + "already_have_account": "已经有账号了?", + "create_account": "创建账户", + "privacy_policy": "隐私政策", + "welcome_to_cakepay": "欢迎来到 CakePay!", + "sign_up": "注册", + "forgot_password": "忘记密码", + "reset_password": "重置密码", + "manage_cards": "管理卡片", + "setup_your_debit_card": "设置你的借记卡", + "no_id_required": "不需要身份证。充值并在任何地方消费", + "how_to_use_card": "如何使用这张卡", + "purchase_gift_card": "购买礼品卡", + "verification": "验证", + "fill_code": "请填写提供给您邮箱的验证码", + "dont_get_code": "没有获取代码?", + "resend_code": "请重新发送", + "debit_card": "借记卡", + "cakepay_prepaid_card": "CakePay 预付借记卡", + "no_id_needed": "不需要 ID!", + "frequently_asked_questions": "常见问题", + "debit_card_terms": "您的支付卡号(以及与您的支付卡号对应的凭证)在此数字钱包中的存储和使用受适用的持卡人与支付卡发卡机构签订的协议的条款和条件的约束,自时不时。", + "please_reference_document": "请参考以下文档以获取更多信息。", + "cardholder_agreement": "持卡人协议", + "e_sign_consent": "电子签名同意", + "agree_and_continue": "同意并继续", + "email_address": "电子邮件地址", + "agree_to": "创建账户即表示您同意 ", + "and": "和", + "enter_code": "输入代码", + "congratulations": "恭喜!", + "you_now_have_debit_card": "你现在有一张借记卡", + "min_amount" : "最小值: ${value}", + "max_amount" : "最大值: ${value}", + "enter_amount": "输入金额", + "billing_address_info": "如果要求提供帐单地址,请提供您的送货地址", + "order_physical_card": "订购实体卡", + "add_value": "增加价值", + "activate": "激活", + "get_a": "得到一个", + "digital_and_physical_card": "数字和物理预付借记卡", + "get_card_note": "你可以用数字货币重新加载。不需要额外的信息!", + "signup_for_card_accept_terms": "注册卡并接受条款。", + "add_fund_to_card": "向卡中添加预付资金(最多 ${value})", + "use_card_info_two": "预付账户中的资金转换为美元,不是数字货币。", + "use_card_info_three": "在线使用电子卡或使用非接触式支付方式。", + "optionally_order_card": "可选择订购实体卡。", + "hide_details": "隐藏细节", + "show_details": "显示详细信息", + "upto": "最高 ${value}", + "discount": "节省 ${value}%", + "gift_card_amount": "礼品卡金额", + "bill_amount": "账单金额", + "you_pay": "你付钱", + "tip": "提示:", + "custom": "自定义", + "by_cake_pay": "通过 CakePay", + "expires": "过期", + "mm": "毫米", + "yy": "YY", + "online": "在线", + "offline": "离线", + "gift_card_number": "礼品卡号", + "pin_number": "PIN 码", + "total_saving": "总储蓄", + "last_30_days": "过去 30 天", + "avg_savings": "平均储蓄", + "view_all": "查看全部", + "active_cards": "活动卡", + "delete_account": "删除账户", + "cards": "卡片", + "active": "活跃", + "redeemed": "赎回", + "gift_card_balance_note": "有余额的礼品卡会出现在这里", + "gift_card_redeemed_note": "您兑换的礼品卡会出现在这里", + "logout": "注销", + "add_tip": "添加提示", + "percentageOf": "${amount}", + "is_percentage": "是", + "search_category": "搜索类别", + "mark_as_redeemed": "标记为已赎回", + "more_options": "更多选项", + "awaiting_payment_confirmation": "等待付款确认", + "transaction_sent_notice": "如果屏幕在 1 分钟后没有继续,请检查区块浏览器和您的电子邮件。", + "agree": "同意", + "in_store": "店内", + "generating_gift_card": "生成礼品卡", + "payment_was_received": "您的付款已收到。", + "proceed_after_one_minute": "如果屏幕在 1 分钟后没有继续,请检查您的电子邮件。", + "order_id": "订单编号", + "gift_card_is_generated": "礼品卡生成", + "open_gift_card": "打开礼品卡", + "contact_support": "联系支持" } From 9d3e2653928f16001a051c50428920607e692aeb Mon Sep 17 00:00:00 2001 From: M Date: Tue, 19 Jul 2022 14:42:20 +0100 Subject: [PATCH 28/55] Fixes for fiat amounts for ionia. --- lib/ionia/ionia_tip.dart | 2 +- lib/src/screens/ionia/cards/ionia_buy_gift_card.dart | 4 +++- lib/view_model/ionia/ionia_buy_card_view_model.dart | 2 +- lib/view_model/ionia/ionia_purchase_merch_view_model.dart | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/ionia/ionia_tip.dart b/lib/ionia/ionia_tip.dart index fbd6e4e4c3..340c6226f2 100644 --- a/lib/ionia/ionia_tip.dart +++ b/lib/ionia/ionia_tip.dart @@ -2,7 +2,7 @@ class IoniaTip { const IoniaTip({this.originalAmount, this.percentage}); final double originalAmount; final double percentage; - double get additionalAmount => originalAmount * percentage / 100; + double get additionalAmount => double.parse((originalAmount * percentage / 100).toStringAsFixed(2)); static const tipList = [ IoniaTip(originalAmount: 0, percentage: 0), diff --git a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart index e7c6dabf83..882dfdc882 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart @@ -85,7 +85,9 @@ class IoniaBuyGiftCardPage extends BasePage { controller: _amountController, focusNode: _amountFieldFocus, keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\-|\ ]'))], + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp('[\-|\ ]')), + WhitelistingTextInputFormatter(RegExp(r'^\d+(\.|\,)?\d{0,2}'))], hintText: '1000', placeholderTextStyle: TextStyle( color: Theme.of(context).primaryTextTheme.headline.color, diff --git a/lib/view_model/ionia/ionia_buy_card_view_model.dart b/lib/view_model/ionia/ionia_buy_card_view_model.dart index ed5411f49a..b6314be80c 100644 --- a/lib/view_model/ionia/ionia_buy_card_view_model.dart +++ b/lib/view_model/ionia/ionia_buy_card_view_model.dart @@ -22,7 +22,7 @@ abstract class IoniaBuyCardViewModelBase with Store { @action void onAmountChanged(String input) { if (input.isEmpty) return; - amount = double.parse(input); + amount = double.parse(input.replaceAll(',', '.')); final min = ioniaMerchant.minimumCardPurchase; final max = ioniaMerchant.maximumCardPurchase; diff --git a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart index 1e537ac373..10e7c1f5e4 100644 --- a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart +++ b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart @@ -55,7 +55,7 @@ abstract class IoniaMerchPurchaseViewModelBase with Store { double percentage; @computed - double get giftCardAmount => amount + tipAmount; + double get giftCardAmount => double.parse((amount + tipAmount).toStringAsFixed(2)); @observable double tipAmount; From 622914fd6159d5408c3817f9e4f210453cb289d5 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Tue, 19 Jul 2022 19:09:44 +0300 Subject: [PATCH 29/55] CW-128 marketplace screen text changes (#416) * Change text on marketplace * fix build issues * fix build --- res/values/strings_de.arb | 6 +++--- res/values/strings_en.arb | 6 +++--- res/values/strings_es.arb | 8 ++++---- res/values/strings_fr.arb | 6 +++--- res/values/strings_hi.arb | 6 +++--- res/values/strings_hr.arb | 4 ++-- res/values/strings_it.arb | 4 ++-- res/values/strings_ja.arb | 10 +++++----- res/values/strings_ko.arb | 6 +++--- res/values/strings_nl.arb | 8 ++++---- res/values/strings_pl.arb | 10 +++++----- res/values/strings_pt.arb | 4 ++-- res/values/strings_ru.arb | 6 +++--- res/values/strings_uk.arb | 6 +++--- res/values/strings_zh.arb | 4 ++-- 15 files changed, 47 insertions(+), 47 deletions(-) diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index fb56dbd470..cbf45b1616 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -532,8 +532,8 @@ "new_template" : "neue Vorlage", "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin", "market_place": "Marktplatz", - "cake_pay_title": "Geschenkkarten und Debitkarten", - "cake_pay_subtitle": "Geschenkkarten kaufen und Nicht-KYC-Debitkarten aufladen", + "cake_pay_title": "Cake Pay-Geschenkkarten", + "cake_pay_subtitle": "Geschenkkarten kaufen und sofort einlösen", "about_cake_pay": "CakePay macht es einfach, Geschenkkarten zu kaufen und Prepaid-Debitkarten mit Kryptowährungen aufzuladen, die bei Millionen von Händlern in den Vereinigten Staaten ausgegeben werden können.", "cake_pay_account_note": "Erstellen Sie ein Konto, um die verfügbaren Karten zu sehen. Einige sind sogar mit Rabatt erhältlich!", "already_have_account": "Sie haben bereits ein Konto?", @@ -581,7 +581,7 @@ "add_fund_to_card": "Voeg prepaid tegoed toe aan de kaarten (tot ${value})", "use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.", "use_card_info_three": "Gebruik de digitale kaart online of met contactloze betaalmethoden.", - "optioneel_order_card": "Optioneel een fysieke kaart bestellen.", + "optionally_order_card": "Optioneel een fysieke kaart bestellen.", "hide_details" : "Details verbergen", "show_details" : "Toon details", "upto": "tot ${value}", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 80421655c7..f49446d04d 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -531,9 +531,9 @@ "search": "Search", "new_template" : "New Template", "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work", - "market_place": "Market place", - "cake_pay_title": "Gift cards and debit cards", - "cake_pay_subtitle": "Buy gift cards and top up no-KYC debit cards", + "market_place": "Marketplace", + "cake_pay_title": "Cake Pay Gift Cards", + "cake_pay_subtitle": "Buy gift cards and redeem instantly", "about_cake_pay": "CakePay allows you to easily buy gift cards and load up prepaid debit cards with cryptocurrencies, spendable at millions of merchants in the United States.", "cake_pay_account_note": "Make an account to see the available cards. Some are even available at a discount!", "already_have_account": "Already have an account?", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 4fd603f01e..88b328e9b2 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -531,9 +531,9 @@ "search": "Búsqueda", "new_template" : "Nueva plantilla", "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando", - "market_place": "Lugar de mercado", - "cake_pay_title": "Tarjetas de regalo y tarjetas de débito", - "cake_pay_subtitle": "Compre tarjetas de regalo y recargue tarjetas de débito sin KYC", + "market_place": "Mercado", + "cake_pay_title": "Tarjetas de regalo Cake Pay", + "cake_pay_subtitle": "Compra tarjetas de regalo y canjéalas al instante", "about_cake_pay": "CakePay te permite comprar fácilmente tarjetas de regalo y cargar tarjetas de débito prepagas con criptomonedas, gastables en millones de comerciantes en los Estados Unidos.", "cake_pay_account_note": "Crea una cuenta para ver las tarjetas disponibles. ¡Algunas incluso están disponibles con descuento!", "already_have_account": "¿Ya tienes una cuenta?", @@ -581,7 +581,7 @@ "add_fund_to_card": "Agregar fondos prepagos a las tarjetas (hasta ${value})", "use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.", "use_card_info_three": "Utilice la tarjeta digital en línea o con métodos de pago sin contacto.", - "Optionally_order_card": "Opcionalmente pide una tarjeta física.", + "optionally_order_card": "Opcionalmente pide una tarjeta física.", "hide_details" : "Ocultar detalles", "show_details": "Mostrar detalles", "upto": "hasta ${value}", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 2d8310b65a..fa4dd6bfc0 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -529,9 +529,9 @@ "new_template" : "Nouveau Modèle", "electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner", - "market_place": "Place du marché", - "cake_pay_title": "Cartes cadeaux et cartes de débit", - "cake_pay_subtitle": "Achetez des cartes-cadeaux et rechargez des cartes de débit sans KYC", + "market_place": "Place de marché", + "cake_pay_title": "Cartes cadeaux Cake Pay", + "cake_pay_subtitle": "Achetez des cartes-cadeaux et échangez-les instantanément", "about_cake_pay": "CakePay vous permet d'acheter facilement des cartes-cadeaux et de charger des cartes de débit prépayées avec des crypto-monnaies, utilisables chez des millions de marchands aux États-Unis.", "cake_pay_account_note": "Créez un compte pour voir les cartes disponibles. Certaines sont même disponibles à prix réduit !", "already_have_account": "Vous avez déjà un compte ?", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 85b4973527..78a7ec422d 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -531,9 +531,9 @@ "search": "खोज", "new_template" : "नया टेम्पलेट", "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं", - "market_place": "मार्केट प्लेस", - "cake_pay_title": "उपहार कार्ड और डेबिट कार्ड", - "cake_pay_subtitle": "गिफ्ट कार्ड खरीदें और नो-केवाईसी डेबिट कार्ड टॉप अप करें", + "market_place": "मार्केटप्लेस", + "cake_pay_title": "केक पे गिफ्ट कार्ड्स", + "cake_pay_subtitle": "उपहार कार्ड खरीदें और तुरंत रिडीम करें", "about_cake_pay": "केकपे आपको आसानी से उपहार कार्ड खरीदने और क्रिप्टोकरंसी के साथ प्रीपेड डेबिट कार्ड लोड करने की अनुमति देता है, जो संयुक्त राज्य में लाखों व्यापारियों पर खर्च करने योग्य है।", "cake_pay_account_note": "उपलब्ध कार्ड देखने के लिए एक खाता बनाएं। कुछ छूट पर भी उपलब्ध हैं!", "ready_have_account": "क्या आपके पास पहले से ही एक खाता है?", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 5bb1c05b49..2187b889d0 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -532,8 +532,8 @@ "new_template" : "novi predložak", "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek", "market_place": "Tržnica", - "cake_pay_title": "Poklon kartice i debitne kartice", - "cake_pay_subtitle": "Kupite darovne kartice i nadopunite debitne kartice bez KYC-a", + "cake_pay_title": "Cake Pay poklon kartice", + "cake_pay_subtitle": "Kupite darovne kartice i odmah ih iskoristite", "about_cake_pay": "CakePay vam omogućuje jednostavnu kupnju darovnih kartica i punjenje unaprijed plaćenih debitnih kartica kriptovalutama koje možete potrošiti kod milijuna trgovaca u Sjedinjenim Državama.", "cake_pay_account_note": "Napravite račun da vidite dostupne kartice. Neke su čak dostupne uz popust!", "already_have_account": "Već imate račun?", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index b10e3fce2f..b85f79766d 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -532,8 +532,8 @@ "new_template" : "Nuovo modello", "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare", "market_place": "Mercato", - "cake_pay_title": "Carte regalo e carte di debito", - "cake_pay_subtitle": "Acquista carte regalo e ricarica carte di debito senza KYC", + "cake_pay_title": "Carte regalo Cake Pay", + "cake_pay_subtitle": "Acquista carte regalo e riscattale all'istante", "about_cake_pay": "CakePay ti consente di acquistare facilmente buoni regalo e caricare carte di debito prepagate con criptovalute, spendibili presso milioni di commercianti negli Stati Uniti.", "cake_pay_account_note": "Crea un account per vedere le carte disponibili. Alcune sono anche disponibili con uno sconto!", "already_have_account": "Hai già un account?", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 970c25ecb5..882ed330c6 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -531,9 +531,9 @@ "search": "検索", "new_template" : "新しいテンプレート", "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します", - "market_place": "マーケットプレイス", - "cake_pay_title": "ギフトカードとデビットカード", - "cake_pay_subtitle": "ギフトカードを購入し、KYCなしのデビットカードを補充する", + "market_place": "Marketplace", + "cake_pay_title": "ケーキペイギフトカード", + "cake_pay_subtitle": "ギフトカードを購入してすぐに利用できます", "about_cake_pay": "CakePayを使用すると、ギフトカードを簡単に購入し、米国内の数百万の加盟店で使用できる暗号通貨を使用してプリペイドデビットカードをロードできます。", "cake_pay_account_note": "アカウントを作成して、利用可能なカードを確認してください。割引価格で利用できるカードもあります!", "already_have_account": "すでにアカウントをお持ちですか?", @@ -567,8 +567,8 @@ "enter_code": "コードを入力", "congratulations": "おめでとうございます!", "you_now_have_debit_card": "デビットカードができました", - "min_amount": "最小: {value}", - "max_amount": "最大: {value}", + "min_amount": "最小: ${value}", + "max_amount": "最大: ${value}", "enter_amount": "金額を入力", "billing_address_info": "請求先住所を尋ねられた場合は、配送先住所を入力してください", "order_physical_card": "物理カードの注文", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index f3d19855af..39a092b12c 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -531,9 +531,9 @@ "search": "찾다", "new_template" : "새 템플릿", "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다.", - "market_place": "시장", - "cake_pay_title": "기프트 카드 및 직불 카드", - "cake_pay_subtitle": "기프트 카드를 구매하고 KYC가 없는 직불 카드를 충전하세요.", + "market_place": "마켓플레이스", + "cake_pay_title": "케이크 페이 기프트 카드", + "cake_pay_subtitle": "기프트 카드를 구매하고 즉시 사용", "about_cake_pay": "CakePay를 사용하면 기프트 카드를 쉽게 구매하고 미국의 수백만 판매자에서 사용할 수 있는 암호화폐가 포함된 선불 직불 카드를 충전할 수 있습니다.", "cake_pay_account_note": "사용 가능한 카드를 보려면 계정을 만드십시오. 일부는 할인된 가격으로 사용 가능합니다!", "already_have_account": "이미 계정이 있습니까?", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 0bdc0e129c..f4d8791206 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -532,8 +532,8 @@ "new_template" : "Nieuwe sjabloon", "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work", "market_place": "Marktplaats", - "cake_pay_title": "Cadeaukaarten en debetkaarten", - "cake_pay_subtitle": "Koop cadeaubonnen en herlaad niet-KYC-betaalkaarten", + "cake_pay_title": "Cake Pay-cadeaubonnen", + "cake_pay_subtitle": "Koop cadeaubonnen en wissel ze direct in", "about_cake_pay": "Met CakePay kun je gemakkelijk cadeaubonnen kopen en prepaid-betaalkaarten opladen met cryptocurrencies, te besteden bij miljoenen verkopers in de Verenigde Staten.", "cake_pay_account_note": "Maak een account aan om de beschikbare kaarten te zien. Sommige zijn zelfs met korting verkrijgbaar!", "already_have_account": "Heb je al een account?", @@ -584,7 +584,7 @@ "optional_order_card": "Optional eine physische Karte bestellen.", "hide_details": "Details ausblenden", "show_details": "Details anzeigen", - "bis": "bis zu ${value}", + "upto": "bis zu ${value}", "discount": "${value} % sparen", "gift_card_amount": "Gutscheinbetrag", "bill_amount": "Rechnungsbetrag", @@ -619,7 +619,7 @@ "more_options": "Weitere Optionen", "waiting_payment_confirmation": "Warte auf Zahlungsbestätigung", "transaction_sent_notice": "Wenn der Bildschirm nach 1 Minute nicht weitergeht, überprüfen Sie einen Block-Explorer und Ihre E-Mail.", - "stimme zu": "stimme zu", + "agree": "stimme zu", "in_store": "Im Geschäft", "generating_gift_card": "Geschenkkarte wird erstellt", "payment_was_received": "Ihre Zahlung ist eingegangen.", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index f5f22716d0..ea31bcef81 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -535,8 +535,8 @@ "new_template" : "Nowy szablon", "electrum_address_disclaimer": "Za każdym razem, gdy korzystasz z jednego z nich, generujemy nowe adresy, ale poprzednie adresy nadal działają", "market_place": "Rynek", - "cake_pay_title": "Karty podarunkowe i debetowe", - "cake_pay_subtitle": "Kup karty podarunkowe i doładuj karty debetowe bez KYC", + "cake_pay_title": "Karty podarunkowe Cake Pay", + "cake_pay_subtitle": "Kup karty podarunkowe i wykorzystaj je natychmiast", "about_cake_pay": "CakePay umożliwia łatwe kupowanie kart podarunkowych i doładowanie przedpłaconych kart debetowych kryptowalutami, które można wydać u milionów sprzedawców w Stanach Zjednoczonych.", "cake_pay_account_note": "Załóż konto, aby zobaczyć dostępne karty. Niektóre są nawet dostępne ze zniżką!", "already_have_account": "Masz już konto?", @@ -584,11 +584,11 @@ "add_fund_to_card": "Dodaj przedpłacone środki do kart (do ${value})", "use_card_info_two": "Środki są przeliczane na USD, gdy są przechowywane na koncie przedpłaconym, a nie w walutach cyfrowych.", "use_card_info_three": "Użyj cyfrowej karty online lub za pomocą zbliżeniowych metod płatności.", - "opcjonalne_zamówienie_card": "Opcjonalnie zamów kartę fizyczną.", + "optionally_order_card": "Opcjonalnie zamów kartę fizyczną.", "hide_details" : "Ukryj szczegóły", "show_details" : "Pokaż szczegóły", "upto": "do ${value}", - "discount": "Zaoszczędź {value}% $", + "discount": "Zaoszczędź ${value}%", "gift_card_amount": "Kwota karty podarunkowej", "bill_amount": "Kwota rachunku", "you_pay": "Płacisz", @@ -615,7 +615,7 @@ "gift_card_redeemed_note": "Karty podarunkowe, które wykorzystałeś, pojawią się tutaj", "logout": "Wyloguj", "add_tip": "Dodaj wskazówkę", - "percentageOf": "z {amount} $", + "percentageOf": "z ${amount}", "is_percentage": "jest", "search_category": "Kategoria wyszukiwania", "mark_as_redeemed": "Oznacz jako wykorzystany", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 6f70efd00d..9dee3ca4b4 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -532,8 +532,8 @@ "new_template" : "Novo modelo", "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando", "market_place": "Mercado", - "cake_pay_title": "Cartões de presente e cartões de débito", - "cake_pay_subtitle": "Compre vales-presente e carregue cartões de débito sem KYC", + "cake_pay_title": "Cartões de presente de pagamento de bolo", + "cake_pay_subtitle": "Compre vales-presente e resgate instantaneamente", "about_cake_pay": "O CakePay permite que você compre facilmente cartões-presente e carregue cartões de débito pré-pagos com criptomoedas, que podem ser gastos em milhões de comerciantes nos Estados Unidos.", "cake_pay_account_note": "Faça uma conta para ver os cartões disponíveis. Alguns estão até com desconto!", "already_have_account": "Já tem uma conta?", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index cb0586bfac..7a3dba9ca5 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -531,9 +531,9 @@ "search": "Поиск", "new_template" : "Новый шаблон", "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать.", - "market_place": "Рыночная площадь", - "cake_pay_title": "Подарочные и дебетовые карты", - "cake_pay_subtitle": "Покупайте подарочные карты и пополняйте дебетовые карты без KYC", + "market_place": "Торговая площадка", + "cake_pay_title": "Подарочные карты Cake Pay", + "cake_pay_subtitle": "Купите подарочные карты и моментально погасите их", "about_cake_pay": "CakePay позволяет вам легко покупать подарочные карты и пополнять предоплаченные дебетовые карты криптовалютой, которую можно потратить в миллионах магазинов в США.", "cake_pay_account_note": "Создайте учетную запись, чтобы увидеть доступные карты. Некоторые даже доступны со скидкой!", "already_have_account": "У вас уже есть аккаунт?", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 3723e02d2b..f9e594f845 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -531,8 +531,8 @@ "new_template" : "Новий шаблон", "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати", "market_place": "Ринок", - "cake_pay_title": "Подарункові та дебетові картки", - "cake_pay_subtitle": "Купуйте подарункові картки та поповнюйте дебетові картки без KYC", + "cake_pay_title": "Подарункові картки Cake Pay", + "cake_pay_subtitle": "Купуйте подарункові картки та використовуйте їх миттєво", "about_cake_pay": "CakePay дозволяє легко купувати подарункові картки та завантажувати передплачені дебетові картки криптовалютами, які можна витратити в мільйонах продавців у Сполучених Штатах.", "cake_pay_account_note": "Створіть обліковий запис, щоб побачити доступні картки. Деякі навіть доступні зі знижкою!", "already_have_account": "Вже є обліковий запис?", @@ -583,7 +583,7 @@ "optionally_order_card": "Необов'язково замовте фізичну картку.", "hide_details": "Приховати деталі", "show_details": "Показати деталі", - "do": "до ${value}", + "upto": "до ${value}", "discount": "Зекономте ${value}%", "gift_card_amount": "Сума подарункової картки", "bill_amount": "Сума рахунку", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 564a81b1a1..38484570cc 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -530,8 +530,8 @@ "new_template" : "新模板", "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效", "market_place": "市场", - "cake_pay_title": "礼品卡和借记卡", - "cake_pay_subtitle": "购买礼品卡并为非 KYC 借记卡充值", + "cake_pay_title": "Cake Pay 礼品卡", + "cake_pay_subtitle": "购买礼品卡并立即兑换", "about_cake_pay": "CakePay 让您可以轻松购买礼品卡并使用加密货币加载预付借记卡,可在美国数百万商家消费。", "cake_pay_account_note": "注册一个账户来查看可用的卡片。有些甚至可以打折!", "already_have_account": "已经有账号了?", From 61bf2f11c24b4467b32292f1d6ecde607aa49201 Mon Sep 17 00:00:00 2001 From: M Date: Wed, 20 Jul 2022 12:57:34 +0100 Subject: [PATCH 30/55] UI fixes for ionia. --- .../cards/ionia_buy_card_detail_page.dart | 49 ++++++++++++--- .../ionia/cards/ionia_buy_gift_card.dart | 6 +- .../cards/ionia_gift_card_detail_page.dart | 60 +++++++++---------- 3 files changed, 71 insertions(+), 44 deletions(-) diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index abf9b3032a..793b244461 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -15,6 +15,7 @@ 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/standart_list_row.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/themes/dark_theme.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -334,8 +335,8 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { PrimaryButton( onPressed: () => Navigator.pop(context), text: S.of(context).send_got_it, - color: Color.fromRGBO(233, 242, 252, 1), - textColor: Theme.of(context).textTheme.display2.color, + color: Theme.of(context).accentTextTheme.caption.color, + textColor: Theme.of(context).primaryTextTheme.title.color, ), SizedBox(height: 21), ], @@ -569,6 +570,40 @@ class TipButton extends StatelessWidget { final bool isSelected; final void Function() onTap; + bool isDark(BuildContext context) => Theme.of(context).brightness == Brightness.dark; + + Color captionTextColor(BuildContext context) { + if (isDark(context)) { + return Theme.of(context).primaryTextTheme.title.color; + } + + return isSelected + ? Theme.of(context).accentTextTheme.title.color + : Theme.of(context).primaryTextTheme.title.color; + } + + Color subTitleTextColor(BuildContext context) { + if (isDark(context)) { + return Theme.of(context).primaryTextTheme.title.color; + } + + return isSelected + ? Theme.of(context).accentTextTheme.title.color + : Theme.of(context).primaryTextTheme.overline.color; + } + + Color backgroundColor(BuildContext context) { + if (isDark(context)) { + return isSelected + ? null + : Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.01); + } + + return isSelected + ? null + : Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1); + } + @override Widget build(BuildContext context) { return InkWell( @@ -580,17 +615,13 @@ class TipButton extends StatelessWidget { children: [ Text(caption, style: textSmallSemiBold( - color: isSelected - ? Theme.of(context).accentTextTheme.title.color - : Theme.of(context).primaryTextTheme.title.color)), + color: captionTextColor(context))), if (subTitle != null) ...[ SizedBox(height: 4), Text( subTitle, style: textXxSmallSemiBold( - color: isSelected - ? Theme.of(context).accentTextTheme.title.color - : Theme.of(context).primaryTextTheme.overline.color, + color: subTitleTextColor(context), ), ), ] @@ -599,7 +630,7 @@ class TipButton extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 18, vertical: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), - color: Color.fromRGBO(242, 240, 250, 1), + color: backgroundColor(context), gradient: isSelected ? LinearGradient( colors: [ diff --git a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart index 882dfdc882..e846825595 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart @@ -124,13 +124,13 @@ class IoniaBuyGiftCardPage extends BasePage { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - S.of(context).min_amount(merchant.minimumCardPurchase.toString()), + S.of(context).min_amount(merchant.minimumCardPurchase.toStringAsFixed(2)), style: TextStyle( color: Theme.of(context).primaryTextTheme.headline.color, ), ), Text( - S.of(context).max_amount(merchant.maximumCardPurchase.toString()), + S.of(context).max_amount(merchant.maximumCardPurchase.toStringAsFixed(2)), style: TextStyle( color: Theme.of(context).primaryTextTheme.headline.color, ), @@ -171,7 +171,7 @@ class IoniaBuyGiftCardPage extends BasePage { text: S.of(context).continue_text, isDisabled: !ioniaBuyCardViewModel.isEnablePurchase, color: Theme.of(context).accentTextTheme.body2.color, - textColor: Theme.of(context).accentTextTheme.headline.decorationColor, + textColor: Colors.white, ), ); }), diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index eeead6d55d..b1347a1b13 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -107,7 +107,7 @@ class IoniaGiftCardDetailPage extends BasePage { buildIoniaTile( context, title: S.of(context).amount, - subTitle: viewModel.giftCard.remainingAmount.toString() ?? '0', + subTitle: viewModel.giftCard.remainingAmount.toStringAsFixed(2) ?? '0.00', )), Divider(height: 50), TextIconButton( @@ -174,41 +174,37 @@ class IoniaGiftCardDetailPage extends BasePage { ), ), Align( - alignment: Alignment.bottomLeft, - child: Container( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.5), - child: Expanded( - child: ListView.builder( - itemCount: viewModel.giftCard.usageInstructions.length, - itemBuilder: (_, int index) { - final instruction = viewModel.giftCard.usageInstructions[index]; - return Expanded( - child: Column( - children: [ - Padding( - padding: EdgeInsets.all(10), - child: Text( - instruction.header, - style: textLargeSemiBold( - color: Theme.of(context).textTheme.display2.color, - ), - )), - Text( - instruction.body, - style: textMedium( - color: Theme.of(context).textTheme.display2.color, - ), - ) - ])); - }))) - ), + alignment: Alignment.bottomLeft, + child: Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.5), + child: SingleChildScrollView( + child: Column(children: viewModel.giftCard.usageInstructions.map((instruction) { + return [ + Padding( + padding: EdgeInsets.all(10), + child: Text( + instruction.header, + style: textLargeSemiBold( + color: Theme.of(context).textTheme.display2.color, + ), + )), + Text( + instruction.body, + style: textMedium( + color: Theme.of(context).textTheme.display2.color, + ), + ) + ]; + }).expand((e) => e).toList()) + ) + )), SizedBox(height: 35), PrimaryButton( onPressed: () => Navigator.pop(context), text: S.of(context).send_got_it, - color: Color.fromRGBO(233, 242, 252, 1), - textColor: Theme.of(context).textTheme.display2.color, + color: Theme.of(context).accentTextTheme.caption.color, + textColor: Theme.of(context).primaryTextTheme.title.color, ), SizedBox(height: 21), ], From 4eedc723d256a6f79e61a3af6ac2930bde645e4a Mon Sep 17 00:00:00 2001 From: mkyq <53115730+mkyq@users.noreply.github.com> Date: Wed, 20 Jul 2022 12:59:14 +0100 Subject: [PATCH 31/55] UI fixes for ionia. (#421) --- .../cards/ionia_buy_card_detail_page.dart | 49 ++++++++++++--- .../ionia/cards/ionia_buy_gift_card.dart | 6 +- .../cards/ionia_gift_card_detail_page.dart | 60 +++++++++---------- 3 files changed, 71 insertions(+), 44 deletions(-) diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index abf9b3032a..793b244461 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -15,6 +15,7 @@ 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/standart_list_row.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/themes/dark_theme.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -334,8 +335,8 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { PrimaryButton( onPressed: () => Navigator.pop(context), text: S.of(context).send_got_it, - color: Color.fromRGBO(233, 242, 252, 1), - textColor: Theme.of(context).textTheme.display2.color, + color: Theme.of(context).accentTextTheme.caption.color, + textColor: Theme.of(context).primaryTextTheme.title.color, ), SizedBox(height: 21), ], @@ -569,6 +570,40 @@ class TipButton extends StatelessWidget { final bool isSelected; final void Function() onTap; + bool isDark(BuildContext context) => Theme.of(context).brightness == Brightness.dark; + + Color captionTextColor(BuildContext context) { + if (isDark(context)) { + return Theme.of(context).primaryTextTheme.title.color; + } + + return isSelected + ? Theme.of(context).accentTextTheme.title.color + : Theme.of(context).primaryTextTheme.title.color; + } + + Color subTitleTextColor(BuildContext context) { + if (isDark(context)) { + return Theme.of(context).primaryTextTheme.title.color; + } + + return isSelected + ? Theme.of(context).accentTextTheme.title.color + : Theme.of(context).primaryTextTheme.overline.color; + } + + Color backgroundColor(BuildContext context) { + if (isDark(context)) { + return isSelected + ? null + : Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.01); + } + + return isSelected + ? null + : Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1); + } + @override Widget build(BuildContext context) { return InkWell( @@ -580,17 +615,13 @@ class TipButton extends StatelessWidget { children: [ Text(caption, style: textSmallSemiBold( - color: isSelected - ? Theme.of(context).accentTextTheme.title.color - : Theme.of(context).primaryTextTheme.title.color)), + color: captionTextColor(context))), if (subTitle != null) ...[ SizedBox(height: 4), Text( subTitle, style: textXxSmallSemiBold( - color: isSelected - ? Theme.of(context).accentTextTheme.title.color - : Theme.of(context).primaryTextTheme.overline.color, + color: subTitleTextColor(context), ), ), ] @@ -599,7 +630,7 @@ class TipButton extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 18, vertical: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), - color: Color.fromRGBO(242, 240, 250, 1), + color: backgroundColor(context), gradient: isSelected ? LinearGradient( colors: [ diff --git a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart index 882dfdc882..e846825595 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart @@ -124,13 +124,13 @@ class IoniaBuyGiftCardPage extends BasePage { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - S.of(context).min_amount(merchant.minimumCardPurchase.toString()), + S.of(context).min_amount(merchant.minimumCardPurchase.toStringAsFixed(2)), style: TextStyle( color: Theme.of(context).primaryTextTheme.headline.color, ), ), Text( - S.of(context).max_amount(merchant.maximumCardPurchase.toString()), + S.of(context).max_amount(merchant.maximumCardPurchase.toStringAsFixed(2)), style: TextStyle( color: Theme.of(context).primaryTextTheme.headline.color, ), @@ -171,7 +171,7 @@ class IoniaBuyGiftCardPage extends BasePage { text: S.of(context).continue_text, isDisabled: !ioniaBuyCardViewModel.isEnablePurchase, color: Theme.of(context).accentTextTheme.body2.color, - textColor: Theme.of(context).accentTextTheme.headline.decorationColor, + textColor: Colors.white, ), ); }), diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index eeead6d55d..b1347a1b13 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -107,7 +107,7 @@ class IoniaGiftCardDetailPage extends BasePage { buildIoniaTile( context, title: S.of(context).amount, - subTitle: viewModel.giftCard.remainingAmount.toString() ?? '0', + subTitle: viewModel.giftCard.remainingAmount.toStringAsFixed(2) ?? '0.00', )), Divider(height: 50), TextIconButton( @@ -174,41 +174,37 @@ class IoniaGiftCardDetailPage extends BasePage { ), ), Align( - alignment: Alignment.bottomLeft, - child: Container( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.5), - child: Expanded( - child: ListView.builder( - itemCount: viewModel.giftCard.usageInstructions.length, - itemBuilder: (_, int index) { - final instruction = viewModel.giftCard.usageInstructions[index]; - return Expanded( - child: Column( - children: [ - Padding( - padding: EdgeInsets.all(10), - child: Text( - instruction.header, - style: textLargeSemiBold( - color: Theme.of(context).textTheme.display2.color, - ), - )), - Text( - instruction.body, - style: textMedium( - color: Theme.of(context).textTheme.display2.color, - ), - ) - ])); - }))) - ), + alignment: Alignment.bottomLeft, + child: Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.5), + child: SingleChildScrollView( + child: Column(children: viewModel.giftCard.usageInstructions.map((instruction) { + return [ + Padding( + padding: EdgeInsets.all(10), + child: Text( + instruction.header, + style: textLargeSemiBold( + color: Theme.of(context).textTheme.display2.color, + ), + )), + Text( + instruction.body, + style: textMedium( + color: Theme.of(context).textTheme.display2.color, + ), + ) + ]; + }).expand((e) => e).toList()) + ) + )), SizedBox(height: 35), PrimaryButton( onPressed: () => Navigator.pop(context), text: S.of(context).send_got_it, - color: Color.fromRGBO(233, 242, 252, 1), - textColor: Theme.of(context).textTheme.display2.color, + color: Theme.of(context).accentTextTheme.caption.color, + textColor: Theme.of(context).primaryTextTheme.title.color, ), SizedBox(height: 21), ], From c1fbd744b7159f42b30ccffe8716792933b35de8 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Wed, 20 Jul 2022 15:03:37 +0300 Subject: [PATCH 32/55] CW-129 ionia welcome screen text changes (#418) * update welcome text * Update localization --- res/values/strings_de.arb | 4 ++-- res/values/strings_en.arb | 4 ++-- res/values/strings_es.arb | 4 ++-- res/values/strings_fr.arb | 4 ++-- res/values/strings_hi.arb | 2 +- res/values/strings_hr.arb | 4 ++-- res/values/strings_it.arb | 4 ++-- res/values/strings_ja.arb | 4 ++-- res/values/strings_ko.arb | 4 ++-- res/values/strings_nl.arb | 4 ++-- res/values/strings_pl.arb | 4 ++-- res/values/strings_pt.arb | 4 ++-- res/values/strings_ru.arb | 4 ++-- res/values/strings_uk.arb | 4 ++-- res/values/strings_zh.arb | 4 ++-- 15 files changed, 29 insertions(+), 29 deletions(-) diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index cbf45b1616..27b76e1150 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -534,12 +534,12 @@ "market_place": "Marktplatz", "cake_pay_title": "Cake Pay-Geschenkkarten", "cake_pay_subtitle": "Geschenkkarten kaufen und sofort einlösen", - "about_cake_pay": "CakePay macht es einfach, Geschenkkarten zu kaufen und Prepaid-Debitkarten mit Kryptowährungen aufzuladen, die bei Millionen von Händlern in den Vereinigten Staaten ausgegeben werden können.", + "about_cake_pay": "Mit Cake Pay können Sie ganz einfach Geschenkkarten mit virtuellen Vermögenswerten kaufen, die Sie sofort bei über 150.000 Händlern in den Vereinigten Staaten ausgeben können.", "cake_pay_account_note": "Erstellen Sie ein Konto, um die verfügbaren Karten zu sehen. Einige sind sogar mit Rabatt erhältlich!", "already_have_account": "Sie haben bereits ein Konto?", "create_account": "Konto erstellen", "privacy_policy": "Datenschutzrichtlinie", - "welcome_to_cakepay": "Willkommen bei CakePay!", + "welcome_to_cakepay": "Willkommen bei Cake Pay!", "sign_up": "Anmelden", "forgot_password": "Passwort vergessen", "reset_password": "Passwort zurücksetzen", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index f49446d04d..1d71d4a5a8 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -534,12 +534,12 @@ "market_place": "Marketplace", "cake_pay_title": "Cake Pay Gift Cards", "cake_pay_subtitle": "Buy gift cards and redeem instantly", - "about_cake_pay": "CakePay allows you to easily buy gift cards and load up prepaid debit cards with cryptocurrencies, spendable at millions of merchants in the United States.", + "about_cake_pay": "Cake Pay allows you to easily buy gift cards with virtual assets, spendable instantly at over 150,000 merchants in the United States.", "cake_pay_account_note": "Make an account to see the available cards. Some are even available at a discount!", "already_have_account": "Already have an account?", "create_account": "Create Account", "privacy_policy": "Privacy policy", - "welcome_to_cakepay": "Welcome to CakePay!", + "welcome_to_cakepay": "Welcome to Cake Pay!", "sign_up": "Sign Up", "forgot_password": "Forgot Password", "reset_password": "Reset Password", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 88b328e9b2..935c4c83aa 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -534,12 +534,12 @@ "market_place": "Mercado", "cake_pay_title": "Tarjetas de regalo Cake Pay", "cake_pay_subtitle": "Compra tarjetas de regalo y canjéalas al instante", - "about_cake_pay": "CakePay te permite comprar fácilmente tarjetas de regalo y cargar tarjetas de débito prepagas con criptomonedas, gastables en millones de comerciantes en los Estados Unidos.", + "about_cake_pay": "Cake Pay le permite comprar fácilmente tarjetas de regalo con activos virtuales, gastables instantáneamente en más de 150 000 comerciantes en los Estados Unidos.", "cake_pay_account_note": "Crea una cuenta para ver las tarjetas disponibles. ¡Algunas incluso están disponibles con descuento!", "already_have_account": "¿Ya tienes una cuenta?", "create_account": "Crear Cuenta", "privacy_policy": "Política de privacidad", - "welcome_to_cakepay": "¡Bienvenido a CakePay!", + "welcome_to_cakepay": "¡Bienvenido a Cake Pay!", "sign_up": "Registrarse", "forgot_password": "Olvidé mi contraseña", "reset_password": "Restablecer contraseña", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index fa4dd6bfc0..dce0f07418 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -532,12 +532,12 @@ "market_place": "Place de marché", "cake_pay_title": "Cartes cadeaux Cake Pay", "cake_pay_subtitle": "Achetez des cartes-cadeaux et échangez-les instantanément", - "about_cake_pay": "CakePay vous permet d'acheter facilement des cartes-cadeaux et de charger des cartes de débit prépayées avec des crypto-monnaies, utilisables chez des millions de marchands aux États-Unis.", + "about_cake_pay": "Cake Pay vous permet d'acheter facilement des cartes-cadeaux avec des actifs virtuels, utilisables instantanément chez plus de 150 000 marchands aux États-Unis.", "cake_pay_account_note": "Créez un compte pour voir les cartes disponibles. Certaines sont même disponibles à prix réduit !", "already_have_account": "Vous avez déjà un compte ?", "create_account": "Créer un compte", "privacy_policy": "Politique de confidentialité", - "welcome_to_cakepay": "Bienvenue sur CakePay !", + "welcome_to_cakepay": "Bienvenue sur Cake Pay!", "sign_up": "S'inscrire", "forgot_password": "Mot de passe oublié", "reset_password": "Réinitialiser le mot de passe", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 78a7ec422d..922e3e81f4 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -534,7 +534,7 @@ "market_place": "मार्केटप्लेस", "cake_pay_title": "केक पे गिफ्ट कार्ड्स", "cake_pay_subtitle": "उपहार कार्ड खरीदें और तुरंत रिडीम करें", - "about_cake_pay": "केकपे आपको आसानी से उपहार कार्ड खरीदने और क्रिप्टोकरंसी के साथ प्रीपेड डेबिट कार्ड लोड करने की अनुमति देता है, जो संयुक्त राज्य में लाखों व्यापारियों पर खर्च करने योग्य है।", + "about_cake_pay": "केक पे आपको वर्चुअल संपत्ति के साथ आसानी से उपहार कार्ड खरीदने की अनुमति देता है, जिसे संयुक्त राज्य में 150,000 से अधिक व्यापारियों पर तुरंत खर्च किया जा सकता है।", "cake_pay_account_note": "उपलब्ध कार्ड देखने के लिए एक खाता बनाएं। कुछ छूट पर भी उपलब्ध हैं!", "ready_have_account": "क्या आपके पास पहले से ही एक खाता है?", "create_account": "खाता बनाएं", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 2187b889d0..cbdb1a8ad0 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -534,12 +534,12 @@ "market_place": "Tržnica", "cake_pay_title": "Cake Pay poklon kartice", "cake_pay_subtitle": "Kupite darovne kartice i odmah ih iskoristite", - "about_cake_pay": "CakePay vam omogućuje jednostavnu kupnju darovnih kartica i punjenje unaprijed plaćenih debitnih kartica kriptovalutama koje možete potrošiti kod milijuna trgovaca u Sjedinjenim Državama.", + "about_cake_pay": "Cake Pay vam omogućuje jednostavnu kupnju darovnih kartica s virtualnim sredstvima, koja se trenutno mogu potrošiti kod više od 150 000 trgovaca u Sjedinjenim Državama.", "cake_pay_account_note": "Napravite račun da vidite dostupne kartice. Neke su čak dostupne uz popust!", "already_have_account": "Već imate račun?", "create_account": "Stvori račun", "privacy_policy": "Pravila privatnosti", - "welcome_to_cakepay": "Dobro došli u CakePay!", + "welcome_to_cakepay": "Dobro došli u Cake Pay!", "sign_up": "Prijavite se", "forgot_password": "Zaboravljena lozinka", "reset_password": "Poništi lozinku", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index b85f79766d..bfb99ce18f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -534,12 +534,12 @@ "market_place": "Mercato", "cake_pay_title": "Carte regalo Cake Pay", "cake_pay_subtitle": "Acquista carte regalo e riscattale all'istante", - "about_cake_pay": "CakePay ti consente di acquistare facilmente buoni regalo e caricare carte di debito prepagate con criptovalute, spendibili presso milioni di commercianti negli Stati Uniti.", + "about_cake_pay": "Cake Pay ti consente di acquistare facilmente buoni regalo con asset virtuali, spendibili istantaneamente presso oltre 150.000 commercianti negli Stati Uniti.", "cake_pay_account_note": "Crea un account per vedere le carte disponibili. Alcune sono anche disponibili con uno sconto!", "already_have_account": "Hai già un account?", "create_account": "Crea account", "privacy_policy": "Informativa sulla privacy", - "welcome_to_cakepay": "Benvenuto in CakePay!", + "welcome_to_cakepay": "Benvenuto in Cake Pay!", "sign_up": "Registrati", "forgot_password": "Password dimenticata", "reset_password": "Reimposta password", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 882ed330c6..b9cef6505b 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -534,12 +534,12 @@ "market_place": "Marketplace", "cake_pay_title": "ケーキペイギフトカード", "cake_pay_subtitle": "ギフトカードを購入してすぐに利用できます", - "about_cake_pay": "CakePayを使用すると、ギフトカードを簡単に購入し、米国内の数百万の加盟店で使用できる暗号通貨を使用してプリペイドデビットカードをロードできます。", + "about_cake_pay": "Cake Payを使用すると、仮想資産を含むギフトカードを簡単に購入でき、米国内の150,000を超える加盟店ですぐに利用できます。", "cake_pay_account_note": "アカウントを作成して、利用可能なカードを確認してください。割引価格で利用できるカードもあります!", "already_have_account": "すでにアカウントをお持ちですか?", "create_account": "アカウントの作成", "privacy_policy": "プライバシーポリシー", - "welcome_to_cakepay": "CakePayへようこそ!", + "welcome_to_cakepay": "Cake Payへようこそ!", "sign_up": "サインアップ", "forgot_password": "パスワードを忘れた", "reset_password": "パスワードのリセット", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 39a092b12c..623f10fc53 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -534,12 +534,12 @@ "market_place": "마켓플레이스", "cake_pay_title": "케이크 페이 기프트 카드", "cake_pay_subtitle": "기프트 카드를 구매하고 즉시 사용", - "about_cake_pay": "CakePay를 사용하면 기프트 카드를 쉽게 구매하고 미국의 수백만 판매자에서 사용할 수 있는 암호화폐가 포함된 선불 직불 카드를 충전할 수 있습니다.", + "about_cake_pay": "Cake Pay를 사용하면 미국 내 150,000개 이상의 가맹점에서 즉시 사용할 수 있는 가상 자산이 포함된 기프트 카드를 쉽게 구입할 수 있습니다.", "cake_pay_account_note": "사용 가능한 카드를 보려면 계정을 만드십시오. 일부는 할인된 가격으로 사용 가능합니다!", "already_have_account": "이미 계정이 있습니까?", "create_account": "계정 만들기", "privacy_policy": "개인 정보 보호 정책", - "welcome_to_cakepay": "CakePay에 오신 것을 환영합니다!", + "welcome_to_cakepay": "Cake Pay에 오신 것을 환영합니다!", "sign_up": "가입", "forgot_password": "비밀번호 찾기", "reset_password": "비밀번호 재설정", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index f4d8791206..4a56da196f 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -534,12 +534,12 @@ "market_place": "Marktplaats", "cake_pay_title": "Cake Pay-cadeaubonnen", "cake_pay_subtitle": "Koop cadeaubonnen en wissel ze direct in", - "about_cake_pay": "Met CakePay kun je gemakkelijk cadeaubonnen kopen en prepaid-betaalkaarten opladen met cryptocurrencies, te besteden bij miljoenen verkopers in de Verenigde Staten.", + "about_cake_pay": "Met Cake Pay kunt u eenvoudig cadeaubonnen kopen met virtuele activa, die direct kunnen worden uitgegeven bij meer dan 150.000 handelaren in de Verenigde Staten.", "cake_pay_account_note": "Maak een account aan om de beschikbare kaarten te zien. Sommige zijn zelfs met korting verkrijgbaar!", "already_have_account": "Heb je al een account?", "create_account": "Account aanmaken", "privacy_policy": "Privacybeleid", - "welcome_to_cakepay": "Welkom bij CakePay!", + "welcome_to_cakepay": "Welkom bij Cake Pay!", "sign_up": "Aanmelden", "forgot_password": "Wachtwoord vergeten", "reset_password": "Wachtwoord resetten", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index ea31bcef81..3754335c1e 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -537,12 +537,12 @@ "market_place": "Rynek", "cake_pay_title": "Karty podarunkowe Cake Pay", "cake_pay_subtitle": "Kup karty podarunkowe i wykorzystaj je natychmiast", - "about_cake_pay": "CakePay umożliwia łatwe kupowanie kart podarunkowych i doładowanie przedpłaconych kart debetowych kryptowalutami, które można wydać u milionów sprzedawców w Stanach Zjednoczonych.", + "about_cake_pay": "Cake Pay umożliwia łatwe kupowanie kart podarunkowych z wirtualnymi aktywami, które można natychmiast wydać u ponad 150 000 sprzedawców w Stanach Zjednoczonych.", "cake_pay_account_note": "Załóż konto, aby zobaczyć dostępne karty. Niektóre są nawet dostępne ze zniżką!", "already_have_account": "Masz już konto?", "create_account": "Utwórz konto", "privacy_policy": "Polityka prywatności", - "welcome_to_cakepay": "Witamy w CakePay!", + "welcome_to_cakepay": "Witamy w Cake Pay!", "sign_up": "Zarejestruj się", "forgot_password": "Zapomniałem hasła", "reset_password": "Zresetuj hasło", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 9dee3ca4b4..bc9cdf74dc 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -534,12 +534,12 @@ "market_place": "Mercado", "cake_pay_title": "Cartões de presente de pagamento de bolo", "cake_pay_subtitle": "Compre vales-presente e resgate instantaneamente", - "about_cake_pay": "O CakePay permite que você compre facilmente cartões-presente e carregue cartões de débito pré-pagos com criptomoedas, que podem ser gastos em milhões de comerciantes nos Estados Unidos.", + "about_cake_pay": "O Cake Pay permite que você compre facilmente cartões-presente com ativos virtuais, que podem ser gastos instantaneamente em mais de 150.000 comerciantes nos Estados Unidos.", "cake_pay_account_note": "Faça uma conta para ver os cartões disponíveis. Alguns estão até com desconto!", "already_have_account": "Já tem uma conta?", "create_account": "Criar conta", "privacy_policy": "Política de privacidade", - "welcome_to_cakepay": "Bem-vindo ao CakePay!", + "welcome_to_cakepay": "Bem-vindo ao Cake Pay!", "create_account": "Registar-se", "forgot_password": "Esqueci a senha", "reset_password": "Redefinir senha", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 7a3dba9ca5..752e21eeb3 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -534,12 +534,12 @@ "market_place": "Торговая площадка", "cake_pay_title": "Подарочные карты Cake Pay", "cake_pay_subtitle": "Купите подарочные карты и моментально погасите их", - "about_cake_pay": "CakePay позволяет вам легко покупать подарочные карты и пополнять предоплаченные дебетовые карты криптовалютой, которую можно потратить в миллионах магазинов в США.", + "about_cake_pay": "Cake Pay позволяет вам легко покупать подарочные карты с виртуальными активами, которые можно мгновенно потратить в более чем 150 000 продавцов в Соединенных Штатах.", "cake_pay_account_note": "Создайте учетную запись, чтобы увидеть доступные карты. Некоторые даже доступны со скидкой!", "already_have_account": "У вас уже есть аккаунт?", "create_account": "Создать аккаунт", "privacy_policy": "Политика конфиденциальности", - "welcome_to_cakepay": "Добро пожаловать в CakePay!", + "welcome_to_cakepay": "Добро пожаловать в Cake Pay!", "sign_up": "Зарегистрироваться", "forgot_password": "Забыли пароль", "reset_password": "Сбросить пароль", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index f9e594f845..84b9e6b26f 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -533,12 +533,12 @@ "market_place": "Ринок", "cake_pay_title": "Подарункові картки Cake Pay", "cake_pay_subtitle": "Купуйте подарункові картки та використовуйте їх миттєво", - "about_cake_pay": "CakePay дозволяє легко купувати подарункові картки та завантажувати передплачені дебетові картки криптовалютами, які можна витратити в мільйонах продавців у Сполучених Штатах.", + "about_cake_pay": "Cake Pay дозволяє вам легко купувати подарункові картки з віртуальними активами, які можна миттєво витратити в понад 150 000 продавців у Сполучених Штатах.", "cake_pay_account_note": "Створіть обліковий запис, щоб побачити доступні картки. Деякі навіть доступні зі знижкою!", "already_have_account": "Вже є обліковий запис?", "create_account": "Створити обліковий запис", "privacy_policy": "Політика конфіденційності", - "welcome_to_cakepay": "Ласкаво просимо до CakePay!", + "welcome_to_cakepay": "Ласкаво просимо до Cake Pay!", "sign_up": "Зареєструватися", "forgot_password": "Забули пароль", "reset_password": "Скинути пароль", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 38484570cc..e673b98963 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -532,12 +532,12 @@ "market_place": "市场", "cake_pay_title": "Cake Pay 礼品卡", "cake_pay_subtitle": "购买礼品卡并立即兑换", - "about_cake_pay": "CakePay 让您可以轻松购买礼品卡并使用加密货币加载预付借记卡,可在美国数百万商家消费。", + "about_cake_pay": "Cake Pay 让您可以轻松购买带有虚拟资产的礼品卡,可立即在美国超过 150,000 家商家消费。", "cake_pay_account_note": "注册一个账户来查看可用的卡片。有些甚至可以打折!", "already_have_account": "已经有账号了?", "create_account": "创建账户", "privacy_policy": "隐私政策", - "welcome_to_cakepay": "欢迎来到 CakePay!", + "welcome_to_cakepay": "欢迎来到 Cake Pay!", "sign_up": "注册", "forgot_password": "忘记密码", "reset_password": "重置密码", From 8b4fa5a12d4ba97563cdfbd738a5eb506274ea4f Mon Sep 17 00:00:00 2001 From: mkyq <53115730+mkyq@users.noreply.github.com> Date: Wed, 20 Jul 2022 13:28:22 +0100 Subject: [PATCH 33/55] Cw 133 (#422) * UI fixes for ionia. * Fixes for display card item on gift cards screen. --- lib/src/screens/ionia/widgets/card_item.dart | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/src/screens/ionia/widgets/card_item.dart b/lib/src/screens/ionia/widgets/card_item.dart index bcddccdb96..ae35543310 100644 --- a/lib/src/screens/ionia/widgets/card_item.dart +++ b/lib/src/screens/ionia/widgets/card_item.dart @@ -62,7 +62,9 @@ class CardItem extends StatelessWidget { SizedBox(width: 5), ], Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: (subTitle?.isEmpty ?? false) + ? CrossAxisAlignment.center + : CrossAxisAlignment.start, children: [ SizedBox( width: 200, @@ -76,14 +78,15 @@ class CardItem extends StatelessWidget { ), ), ), - SizedBox(height: 5), - Text( - subTitle, - style: TextStyle( - color: subtitleColor, - fontWeight: FontWeight.w500, - fontFamily: 'Lato', - ), + if (subTitle?.isNotEmpty ?? false) + Padding( + padding: EdgeInsets.only(top: 5), + child: Text( + subTitle, + style: TextStyle( + color: subtitleColor, + fontWeight: FontWeight.w500, + fontFamily: 'Lato')), ) ], ), From c23c6482bb3fda7669b76f3838ed11230dee226f Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Wed, 20 Jul 2022 16:01:49 +0300 Subject: [PATCH 34/55] Fix signup page (#419) --- .../ionia/auth/ionia_create_account_page.dart | 24 ++++++++++++++----- res/values/strings_de.arb | 2 +- res/values/strings_en.arb | 6 ++--- res/values/strings_es.arb | 2 +- res/values/strings_fr.arb | 4 ++-- res/values/strings_hr.arb | 2 +- res/values/strings_it.arb | 2 +- res/values/strings_ja.arb | 2 +- res/values/strings_ko.arb | 2 +- res/values/strings_nl.arb | 2 +- res/values/strings_pl.arb | 2 +- res/values/strings_pt.arb | 2 +- res/values/strings_ru.arb | 2 +- res/values/strings_uk.arb | 2 +- res/values/strings_zh.arb | 2 +- 15 files changed, 35 insertions(+), 23 deletions(-) diff --git a/lib/src/screens/ionia/auth/ionia_create_account_page.dart b/lib/src/screens/ionia/auth/ionia_create_account_page.dart index 3466df5d59..0b613d0a42 100644 --- a/lib/src/screens/ionia/auth/ionia_create_account_page.dart +++ b/lib/src/screens/ionia/auth/ionia_create_account_page.dart @@ -9,10 +9,12 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; +import 'package:url_launcher/url_launcher.dart'; class IoniaCreateAccountPage extends BasePage { IoniaCreateAccountPage(this._authViewModel) @@ -30,6 +32,9 @@ class IoniaCreateAccountPage extends BasePage { final FocusNode _emailFocus; final TextEditingController _emailController; + static const privacyPolicyUrl = 'https://ionia.docsend.com/view/jaqsmbq9w7dzvnqf'; + static const termsAndConditionsUrl = 'https://ionia.docsend.com/view/hi9awnwxr6mqgiqj'; + @override Widget middle(BuildContext context) { return Text( @@ -102,15 +107,22 @@ class IoniaCreateAccountPage extends BasePage { color: Theme.of(context).accentTextTheme.body2.color, fontWeight: FontWeight.w700, ), + recognizer: TapGestureRecognizer() + ..onTap = () async { + if (await canLaunch(termsAndConditionsUrl)) await launch(termsAndConditionsUrl); + }, ), TextSpan(text: ' ${S.of(context).and} '), TextSpan( - text: S.of(context).privacy_policy, - style: TextStyle( - color: Theme.of(context).accentTextTheme.body2.color, - fontWeight: FontWeight.w700, - ), - ), + text: S.of(context).privacy_policy, + style: TextStyle( + color: Theme.of(context).accentTextTheme.body2.color, + fontWeight: FontWeight.w700, + ), + recognizer: TapGestureRecognizer() + ..onTap = () async { + if (await canLaunch(privacyPolicyUrl)) await launch(privacyPolicyUrl); + }), TextSpan(text: ' ${S.of(context).by_cake_pay}'), ], ), diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 27b76e1150..e8e043af4a 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -591,7 +591,7 @@ "you_pay": "U betaalt", "tip": "Tip:", "custom": "aangepast", - "by_cake_pay": "door CakePay", + "by_cake_pay": "door Cake Pay", "expires": "Verloopt", "mm": "MM", "yy": "JJ", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 1d71d4a5a8..fa81ce8c3e 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -244,7 +244,7 @@ "settings_only_transactions" : "Only transactions", "settings_none" : "None", "settings_support" : "Support", - "settings_terms_and_conditions" : "Terms and conditions", + "settings_terms_and_conditions" : "Terms and Conditions", "pin_is_incorrect" : "PIN is incorrect", @@ -538,7 +538,7 @@ "cake_pay_account_note": "Make an account to see the available cards. Some are even available at a discount!", "already_have_account": "Already have an account?", "create_account": "Create Account", - "privacy_policy": "Privacy policy", + "privacy_policy": "Privacy Policy", "welcome_to_cakepay": "Welcome to Cake Pay!", "sign_up": "Sign Up", "forgot_password": "Forgot Password", @@ -591,7 +591,7 @@ "you_pay": "You pay", "tip": "Tip:", "custom": "custom", - "by_cake_pay": "by CakePay", + "by_cake_pay": "by Cake Pay", "expires": "Expires", "mm": "MM", "yy": "YY", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 935c4c83aa..cc34236efb 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -591,7 +591,7 @@ "you_pay": "Tú pagas", "tip": "Consejo:", "personalizado": "personalizado", - "by_cake_pay": "por CakePay", + "by_cake_pay": "por Cake Pay", "expires": "Caduca", "mm": "mm", "yy": "YY", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index dce0f07418..4758478da7 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -242,7 +242,7 @@ "settings_only_transactions" : "Seulement les transactions", "settings_none" : "Rien", "settings_support" : "Support", - "settings_terms_and_conditions" : "Termes et conditions", + "settings_terms_and_conditions" : "Termes et Conditions", "pin_is_incorrect" : "Le code PIN est incorrect", @@ -589,7 +589,7 @@ "you_pay": "Vous payez", "tip": "Astuce :", "custom": "personnalisé", - "by_cake_pay": "par CakePay", + "by_cake_pay": "par Cake Pay", "expire": "Expire", "mm": "MM", "yy": "AA", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index cbdb1a8ad0..2d06d88ab0 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -591,7 +591,7 @@ "you_pay": "Vi plaćate", "tip": "Savjet:", "custom": "prilagođeno", - "by_cake_pay": "od CakePaya", + "by_cake_pay": "od Cake Paya", "expires": "Ističe", "mm": "MM", "yy": "GG", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index bfb99ce18f..133f2113b3 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -591,7 +591,7 @@ "you_pay": "Tu paghi", "tip": "Suggerimento:", "custom": "personalizzato", - "by_cake_pay": "da CakePay", + "by_cake_pay": "da Cake Pay", "expires": "Scade", "mm": "mm", "yy": "YY", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index b9cef6505b..262113673f 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -591,7 +591,7 @@ "you_pay": "あなたが支払う", "tip": "ヒント: ", "custom": "カスタム", - "by_cake_pay": "by CakePay", + "by_cake_pay": "by Cake Pay", "expires": "Expires", "mm": "んん", "yy": "YY", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 623f10fc53..165584f290 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -591,7 +591,7 @@ "you_pay": "당신이 지불합니다", "tip": "팁:", "custom": "커스텀", - "by_cake_pay": "CakePay로", + "by_cake_pay": "Cake Pay로", "expires": "만료", "mm": "mm", "YY": "YY", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 4a56da196f..ca9654917c 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -591,7 +591,7 @@ "you_pay": "Sie bezahlen", "tip": "Hinweis:", "custom": "benutzerdefiniert", - "by_cake_pay": "von CakePay", + "by_cake_pay": "von Cake Pay", "expires": "Läuft ab", "mm": "MM", "yy": "YY", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 3754335c1e..5bfe763c74 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -594,7 +594,7 @@ "you_pay": "Płacisz", "tip": "wskazówka:", "custom": "niestandardowy", - "by_cake_pay": "przez CakePay", + "by_cake_pay": "przez Cake Pay", "expires": "Wygasa", "mm": "MM", "yy": "RR", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index bc9cdf74dc..016346df31 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -591,7 +591,7 @@ "you_pay": "Você paga", "tip": "Dica:", "custom": "personalizado", - "by_cake_pay": "por CakePay", + "by_cake_pay": "por Cake Pay", "expires": "Expira", "mm": "MM", "yy": "aa", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 752e21eeb3..964a945116 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -591,7 +591,7 @@ "you_pay": "Вы платите", "tip": "Совет:", "custom": "обычай", - "by_cake_pay": "от CakePay", + "by_cake_pay": "от Cake Pay", "expires": "Истекает", "mm": "ММ", "yy": "ГГ", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 84b9e6b26f..e0a7f68200 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -590,7 +590,7 @@ "you_pay": "Ви платите", "tip": "Порада:", "custom": "на замовлення", - "by_cake_pay": "від CakePay", + "by_cake_pay": "від Cake Pay", "expires": "Закінчується", "mm": "MM", "yy": "YY", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index e673b98963..6788743290 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -589,7 +589,7 @@ "you_pay": "你付钱", "tip": "提示:", "custom": "自定义", - "by_cake_pay": "通过 CakePay", + "by_cake_pay": "通过 Cake Pay", "expires": "过期", "mm": "毫米", "yy": "YY", From f849e96f914cbb93b682bce4c91b2cec3c68c106 Mon Sep 17 00:00:00 2001 From: M Date: Wed, 20 Jul 2022 17:57:46 +0100 Subject: [PATCH 35/55] Changed tips for ionia. --- .../cards/ionia_buy_card_detail_page.dart | 31 ++++++++++--------- .../ionia_purchase_merch_view_model.dart | 3 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index 793b244461..01a9125683 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -539,21 +539,22 @@ class TipButtonGroup extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - children: [ - ...[ - for (var i = 0; i < tipsList.length; i++) ...[ - TipButton( - isSelected: _isSelected(tipsList[i].percentage), - onTap: () => onSelect(tipsList[i]), - caption: '${tipsList[i].percentage}%', - subTitle: '\$${tipsList[i].additionalAmount}', - ), - SizedBox(width: 4), - ] - ], - ], - ); + return Container( + height: 50, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: tipsList.length, + itemBuilder: (BuildContext context, int index) { + final tip = tipsList[index]; + return Padding( + padding: EdgeInsets.only(right: 5), + child: TipButton( + isSelected: _isSelected(tip.percentage), + onTap: () => onSelect(tip), + caption: '${tip.percentage}%', + subTitle: '\$${tip.additionalAmount}', + )); + })); } } diff --git a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart index 10e7c1f5e4..7fc66bc377 100644 --- a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart +++ b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart @@ -22,7 +22,8 @@ abstract class IoniaMerchPurchaseViewModelBase with Store { percentage = 0.0; tips = [ IoniaTip(percentage: 0, originalAmount: amount), - IoniaTip(percentage: 10, originalAmount: amount), + IoniaTip(percentage: 15, originalAmount: amount), + IoniaTip(percentage: 18, originalAmount: amount), IoniaTip(percentage: 20, originalAmount: amount), ]; selectedTip = tips.first; From 32f5a790795f7b54711e55b776f2e8a94d14bbf2 Mon Sep 17 00:00:00 2001 From: mkyq <53115730+mkyq@users.noreply.github.com> Date: Wed, 20 Jul 2022 17:59:43 +0100 Subject: [PATCH 36/55] Cw 132 (#425) * UI fixes for ionia. * Changed tips for ionia. --- .../cards/ionia_buy_card_detail_page.dart | 31 ++++++++++--------- .../ionia_purchase_merch_view_model.dart | 3 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index 793b244461..01a9125683 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -539,21 +539,22 @@ class TipButtonGroup extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - children: [ - ...[ - for (var i = 0; i < tipsList.length; i++) ...[ - TipButton( - isSelected: _isSelected(tipsList[i].percentage), - onTap: () => onSelect(tipsList[i]), - caption: '${tipsList[i].percentage}%', - subTitle: '\$${tipsList[i].additionalAmount}', - ), - SizedBox(width: 4), - ] - ], - ], - ); + return Container( + height: 50, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: tipsList.length, + itemBuilder: (BuildContext context, int index) { + final tip = tipsList[index]; + return Padding( + padding: EdgeInsets.only(right: 5), + child: TipButton( + isSelected: _isSelected(tip.percentage), + onTap: () => onSelect(tip), + caption: '${tip.percentage}%', + subTitle: '\$${tip.additionalAmount}', + )); + })); } } diff --git a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart index 10e7c1f5e4..7fc66bc377 100644 --- a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart +++ b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart @@ -22,7 +22,8 @@ abstract class IoniaMerchPurchaseViewModelBase with Store { percentage = 0.0; tips = [ IoniaTip(percentage: 0, originalAmount: amount), - IoniaTip(percentage: 10, originalAmount: amount), + IoniaTip(percentage: 15, originalAmount: amount), + IoniaTip(percentage: 18, originalAmount: amount), IoniaTip(percentage: 20, originalAmount: amount), ]; selectedTip = tips.first; From 1d4a9f93cc9aec12619c6ce6c4a9ad2f12fc5afd Mon Sep 17 00:00:00 2001 From: mkyq <53115730+mkyq@users.noreply.github.com> Date: Wed, 20 Jul 2022 18:33:34 +0100 Subject: [PATCH 37/55] Cw 131 (#426) * UI fixes for ionia. * Changed tips for ionia. * Fixes for IoniaBuyGiftCardDetailPage screen. Renamed 'Manage Cards' to 'Gift Cards'. Hide discount badge label for 0 discount. --- .../cards/ionia_buy_card_detail_page.dart | 71 +++---------------- .../ionia/cards/ionia_manage_cards_page.dart | 2 +- res/values/strings_de.arb | 2 +- res/values/strings_en.arb | 2 +- res/values/strings_es.arb | 2 +- res/values/strings_fr.arb | 2 +- res/values/strings_hi.arb | 2 +- res/values/strings_hr.arb | 2 +- res/values/strings_it.arb | 2 +- res/values/strings_ja.arb | 2 +- res/values/strings_ko.arb | 2 +- res/values/strings_nl.arb | 2 +- res/values/strings_pl.arb | 2 +- res/values/strings_pt.arb | 2 +- res/values/strings_ru.arb | 2 +- res/values/strings_uk.arb | 2 +- res/values/strings_zh.arb | 2 +- 17 files changed, 27 insertions(+), 76 deletions(-) diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index 01a9125683..6eab3aa198 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -1,7 +1,6 @@ import 'dart:ui'; import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; import 'package:cake_wallet/core/execution_state.dart'; -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; import 'package:cake_wallet/palette.dart'; @@ -14,9 +13,6 @@ import 'package:cake_wallet/src/widgets/discount_badge.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/standart_list_row.dart'; -import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cake_wallet/themes/dark_theme.dart'; -import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; @@ -24,48 +20,14 @@ import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; -class IoniaBuyGiftCardDetailPage extends StatelessWidget { +class IoniaBuyGiftCardDetailPage extends BasePage { IoniaBuyGiftCardDetailPage(this.ioniaPurchaseViewModel); final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel; - ThemeBase get currentTheme => getIt.get().currentTheme; - - Color get backgroundLightColor => Colors.white; - - Color get backgroundDarkColor => PaletteDark.backgroundColor; - - void onClose(BuildContext context) => Navigator.of(context).pop(); - - Widget leading(BuildContext context) { - if (ModalRoute.of(context).isFirst) { - return null; - } - - final _backButton = Icon( - Icons.arrow_back_ios, - color: Theme.of(context).primaryTextTheme.title.color, - size: 16, - ); - return Padding( - padding: const EdgeInsets.only(left: 10.0), - child: SizedBox( - height: 37, - width: 37, - child: ButtonTheme( - minWidth: double.minPositive, - child: FlatButton( - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - padding: EdgeInsets.all(0), - onPressed: () => onClose(context), - child: _backButton), - ), - ), - ); - } - + @override Widget middle(BuildContext context) { return Text( ioniaPurchaseViewModel.ioniaMerchant.legalName, @@ -74,10 +36,13 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { } @override - Widget build(BuildContext context) { - final merchant = ioniaPurchaseViewModel.ioniaMerchant; - final _backgroundColor = currentTheme.type == ThemeType.dark ? backgroundDarkColor : backgroundLightColor; + Widget trailing(BuildContext context) + => ioniaPurchaseViewModel.ioniaMerchant.minimumDiscount > 0 + ? DiscountBadge(percentage: ioniaPurchaseViewModel.ioniaMerchant.minimumDiscount) + : null; + @override + Widget body(BuildContext context) { reaction((_) => ioniaPurchaseViewModel.invoiceCreationState, (ExecutionState state) { if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -120,25 +85,12 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { } }); - return Scaffold( - backgroundColor: _backgroundColor, - body: ScrollableWithBottomSection( + return ScrollableWithBottomSection( contentPadding: EdgeInsets.zero, content: Observer(builder: (_) { final tipAmount = ioniaPurchaseViewModel.tipAmount; return Column( children: [ - SizedBox(height: 60), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - leading(context), - middle(context), - DiscountBadge( - percentage: merchant.minimumDiscount, - ) - ], - ), SizedBox(height: 36), Container( padding: EdgeInsets.symmetric(vertical: 24), @@ -234,7 +186,7 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 24.0), child: TextIconButton( label: S.of(context).how_to_use_card, - onTap: () => _showHowToUseCard(context, merchant), + onTap: () => _showHowToUseCard(context, ioniaPurchaseViewModel.ioniaMerchant), ), ), ], @@ -266,7 +218,6 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget { SizedBox(height: 16) ], ), - ), ); } diff --git a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart index 2d0d0db1c2..5c9d8f28d9 100644 --- a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart @@ -90,7 +90,7 @@ class IoniaManageCardsPage extends BasePage { @override Widget middle(BuildContext context) { return Text( - S.of(context).manage_cards, + S.of(context).gift_cards, style: textLargeSemiBold( color: Theme.of(context).accentTextTheme.display3.backgroundColor, ), diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index e8e043af4a..a6c938631b 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -543,7 +543,7 @@ "sign_up": "Anmelden", "forgot_password": "Passwort vergessen", "reset_password": "Passwort zurücksetzen", - "manage_cards": "Karten verwalten", + "gift_cards": "Geschenkkarten", "setup_your_debit_card": "Richten Sie Ihre Debitkarte ein", "no_id_required": "Keine ID erforderlich. Upgraden und überall ausgeben", "how_to_use_card": "Wie man diese Karte benutzt", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index fa81ce8c3e..6048b7b676 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -543,7 +543,7 @@ "sign_up": "Sign Up", "forgot_password": "Forgot Password", "reset_password": "Reset Password", - "manage_cards": "Manage Cards", + "gift_cards": "Gift Cards", "setup_your_debit_card": "Set up your debit card", "no_id_required": "No ID required. Top up and spend anywhere", "how_to_use_card": "How to use this card", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index cc34236efb..fa2c558ec8 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -543,7 +543,7 @@ "sign_up": "Registrarse", "forgot_password": "Olvidé mi contraseña", "reset_password": "Restablecer contraseña", - "manage_cards": "Administrar tarjetas", + "gift_cards": "Tarjetas de regalo", "setup_your_debit_card": "Configura tu tarjeta de débito", "no_id_required": "No se requiere identificación. Recargue y gaste en cualquier lugar", "how_to_use_card": "Cómo usar esta tarjeta", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 4758478da7..03818ed1f2 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -541,7 +541,7 @@ "sign_up": "S'inscrire", "forgot_password": "Mot de passe oublié", "reset_password": "Réinitialiser le mot de passe", - "manage_cards": "Gérer les cartes", + "manage_cards": "Cartes cadeaux", "setup_your_debit_card": "Configurer votre carte de débit", "no_id_required": "Aucune pièce d'identité requise. Rechargez et dépensez n'importe où", "how_to_use_card": "Comment utiliser cette carte", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 922e3e81f4..4d32b1181c 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -543,7 +543,7 @@ "sign_up": "साइन अप करें", "forgot_password": "पासवर्ड भूल गए", "reset_password": "पासवर्ड रीसेट करें", - "manage_cards": "कार्ड मैनेज करें", + "gift_cards": "उपहार कार्ड", "setup_your_debit_card": "अपना डेबिट कार्ड सेट करें", "no_id_required": "कोई आईडी आवश्यक नहीं है। टॉप अप करें और कहीं भी खर्च करें", "how_to_use_card": "इस कार्ड का उपयोग कैसे करें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 2d06d88ab0..a4e0f277fc 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -543,7 +543,7 @@ "sign_up": "Prijavite se", "forgot_password": "Zaboravljena lozinka", "reset_password": "Poništi lozinku", - "manage_cards": "Upravljanje karticama", + "gift_cards": "Ajándékkártya", "setup_your_debit_card": "Postavite svoju debitnu karticu", "no_id_required": "Nije potreban ID. Nadopunite i potrošite bilo gdje", "how_to_use_card": "Kako koristiti ovu karticu", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 133f2113b3..d3d8cf8db8 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -543,7 +543,7 @@ "sign_up": "Registrati", "forgot_password": "Password dimenticata", "reset_password": "Reimposta password", - "manage_cards": "Gestisci carte", + "gift_cards": "Carte regalo", "setup_your_debit_card": "Configura la tua carta di debito", "no_id_required": "Nessun ID richiesto. Ricarica e spendi ovunque", "how_to_use_card": "Come usare questa carta", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 262113673f..72c228ddd4 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -543,7 +543,7 @@ "sign_up": "サインアップ", "forgot_password": "パスワードを忘れた", "reset_password": "パスワードのリセット", - "manage_cards": "カードの管理", + "gift_cards": "ギフトカード", "setup_your_debit_card": "デビットカードを設定してください", "no_id_required": "IDは必要ありません。どこにでも補充して使用できます", "how_to_use_card": "このカードの使用方法", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 165584f290..dca10c91d7 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -543,7 +543,7 @@ "sign_up": "가입", "forgot_password": "비밀번호 찾기", "reset_password": "비밀번호 재설정", - "manage_cards": "카드 관리", + "gift_cards": "기프트 카드", "setup_your_debit_card": "직불카드 설정", "no_id_required": "신분증이 필요하지 않습니다. 충전하고 어디에서나 사용하세요", "how_to_use_card": "이 카드를 사용하는 방법", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index ca9654917c..8f6de0e79c 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -543,7 +543,7 @@ "sign_up": "Aanmelden", "forgot_password": "Wachtwoord vergeten", "reset_password": "Wachtwoord resetten", - "manage_cards": "Kaarten beheren", + "gift_cards": "Cadeaubonnen", "setup_your_debit_card": "Stel uw debetkaart in", "no_id_required": "Geen ID vereist. Opwaarderen en overal uitgeven", "how_to_use_card": "Hoe deze kaart te gebruiken", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 5bfe763c74..6556c63cd7 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -546,7 +546,7 @@ "sign_up": "Zarejestruj się", "forgot_password": "Zapomniałem hasła", "reset_password": "Zresetuj hasło", - "manage_cards": "Zarządzaj kartami", + "gift_cards": "Karty podarunkowe", "setup_your_debit_card": "Skonfiguruj swoją kartę debetową", "no_id_required": "Nie wymagamy ID. Doładuj i wydawaj gdziekolwiek", "how_to_use_card": "Jak korzystać z tej karty", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 016346df31..1c12f960d7 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -543,7 +543,7 @@ "create_account": "Registar-se", "forgot_password": "Esqueci a senha", "reset_password": "Redefinir senha", - "manage_cards": "Gerenciar Cartões", + "gift_cards": "Cartões de presente", "setup_your_debit_card": "Configure seu cartão de débito", "no_id_required": "Não é necessário ID. Recarregue e gaste em qualquer lugar", "how_to_use_card": "Como usar este cartão", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 964a945116..6003369e47 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -543,7 +543,7 @@ "sign_up": "Зарегистрироваться", "forgot_password": "Забыли пароль", "reset_password": "Сбросить пароль", - "manage_cards": "Управление картами", + "gift_cards": "Подарочные карты", "setup_your_debit_card": "Настройте свою дебетовую карту", "no_id_required": "Идентификатор не требуется. Пополняйте и тратьте где угодно", "how_to_use_card": "Как использовать эту карту", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index e0a7f68200..de524071aa 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -542,7 +542,7 @@ "sign_up": "Зареєструватися", "forgot_password": "Забули пароль", "reset_password": "Скинути пароль", - "manage_cards": "Керувати картками", + "gift_cards": "Подарункові карти", "setup_your_debit_card": "Налаштуйте свою дебетову картку", "no_id_required": "Ідентифікатор не потрібен. Поповнюйте та витрачайте будь-де", "how_to_use_card": "Як використовувати цю картку", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 6788743290..e0e4b678ac 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -541,7 +541,7 @@ "sign_up": "注册", "forgot_password": "忘记密码", "reset_password": "重置密码", - "manage_cards": "管理卡片", + "gift_cards": "礼品卡", "setup_your_debit_card": "设置你的借记卡", "no_id_required": "不需要身份证。充值并在任何地方消费", "how_to_use_card": "如何使用这张卡", From bfa904f12df01f85130cdf247fe462a115d8f172 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Thu, 21 Jul 2022 16:25:47 +0300 Subject: [PATCH 38/55] Change ionia heading font style (#427) --- lib/src/screens/ionia/auth/ionia_create_account_page.dart | 2 +- lib/src/screens/ionia/auth/ionia_login_page.dart | 2 +- lib/src/screens/ionia/auth/ionia_verify_otp_page.dart | 2 +- lib/src/screens/ionia/auth/ionia_welcome_page.dart | 2 +- lib/src/screens/ionia/cards/ionia_account_page.dart | 2 +- lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart | 2 +- lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart | 2 +- lib/src/screens/ionia/cards/ionia_debit_card_page.dart | 2 +- lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart | 2 +- lib/src/screens/ionia/cards/ionia_manage_cards_page.dart | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/src/screens/ionia/auth/ionia_create_account_page.dart b/lib/src/screens/ionia/auth/ionia_create_account_page.dart index 0b613d0a42..6f8cd8b132 100644 --- a/lib/src/screens/ionia/auth/ionia_create_account_page.dart +++ b/lib/src/screens/ionia/auth/ionia_create_account_page.dart @@ -39,7 +39,7 @@ class IoniaCreateAccountPage extends BasePage { Widget middle(BuildContext context) { return Text( S.current.sign_up, - style: textLargeSemiBold( + style: textMediumSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, ), ); diff --git a/lib/src/screens/ionia/auth/ionia_login_page.dart b/lib/src/screens/ionia/auth/ionia_login_page.dart index f7f0023572..7aadce1980 100644 --- a/lib/src/screens/ionia/auth/ionia_login_page.dart +++ b/lib/src/screens/ionia/auth/ionia_login_page.dart @@ -35,7 +35,7 @@ class IoniaLoginPage extends BasePage { Widget middle(BuildContext context) { return Text( S.current.login, - style: textLargeSemiBold( + style: textMediumSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, ), ); diff --git a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart index d1253c784a..95df9c25a7 100644 --- a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart +++ b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart @@ -39,7 +39,7 @@ class IoniaVerifyIoniaOtp extends BasePage { Widget middle(BuildContext context) { return Text( S.current.verification, - style: textLargeSemiBold( + style: textMediumSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, ), ); diff --git a/lib/src/screens/ionia/auth/ionia_welcome_page.dart b/lib/src/screens/ionia/auth/ionia_welcome_page.dart index 95ee129470..bd01aed068 100644 --- a/lib/src/screens/ionia/auth/ionia_welcome_page.dart +++ b/lib/src/screens/ionia/auth/ionia_welcome_page.dart @@ -16,7 +16,7 @@ class IoniaWelcomePage extends BasePage { Widget middle(BuildContext context) { return Text( S.current.welcome_to_cakepay, - style: textLargeSemiBold( + style: textMediumSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, ), ); diff --git a/lib/src/screens/ionia/cards/ionia_account_page.dart b/lib/src/screens/ionia/cards/ionia_account_page.dart index 2a34dc74cb..28e7e9b42c 100644 --- a/lib/src/screens/ionia/cards/ionia_account_page.dart +++ b/lib/src/screens/ionia/cards/ionia_account_page.dart @@ -18,7 +18,7 @@ class IoniaAccountPage extends BasePage { Widget middle(BuildContext context) { return Text( S.current.account, - style: textLargeSemiBold( + style: textMediumSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, ), ); diff --git a/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart b/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart index 421b2a4fe0..4f688a1290 100644 --- a/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart +++ b/lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart @@ -22,7 +22,7 @@ class IoniaActivateDebitCardPage extends BasePage { Widget middle(BuildContext context) { return Text( S.current.debit_card, - style: textLargeSemiBold( + style: textMediumSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, ), ); diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index 6eab3aa198..a06a053f64 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -31,7 +31,7 @@ class IoniaBuyGiftCardDetailPage extends BasePage { Widget middle(BuildContext context) { return Text( ioniaPurchaseViewModel.ioniaMerchant.legalName, - style: textLargeSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor), + style: textMediumSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor), ); } diff --git a/lib/src/screens/ionia/cards/ionia_debit_card_page.dart b/lib/src/screens/ionia/cards/ionia_debit_card_page.dart index 33ec9c9aff..f863f57806 100644 --- a/lib/src/screens/ionia/cards/ionia_debit_card_page.dart +++ b/lib/src/screens/ionia/cards/ionia_debit_card_page.dart @@ -22,7 +22,7 @@ class IoniaDebitCardPage extends BasePage { Widget middle(BuildContext context) { return Text( S.current.debit_card, - style: textLargeSemiBold( + style: textMediumSemiBold( color: Theme.of(context).accentTextTheme.display4.backgroundColor, ), ); diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index b1347a1b13..9a8e9b36ab 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -56,7 +56,7 @@ class IoniaGiftCardDetailPage extends BasePage { Widget middle(BuildContext context) { return Text( viewModel.giftCard.legalName, - style: textLargeSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor), + style: textMediumSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor), ); } diff --git a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart index 32ddb50539..103ee64ddd 100644 --- a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart @@ -91,7 +91,7 @@ class IoniaManageCardsPage extends BasePage { Widget middle(BuildContext context) { return Text( S.of(context).gift_cards, - style: textLargeSemiBold( + style: textMediumSemiBold( color: Theme.of(context).accentTextTheme.display3.backgroundColor, ), ); From 83245b2037def25a93cb8deb99b2e43f86d508ca Mon Sep 17 00:00:00 2001 From: M Date: Thu, 21 Jul 2022 15:48:13 +0100 Subject: [PATCH 39/55] Fix for AddressResolver in di --- lib/di.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/di.dart b/lib/di.dart index 86aa309fe6..0ffc2a233e 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -668,7 +668,6 @@ Future setup( getIt.registerFactoryParam( (String qrData, bool isLight) => FullscreenQRPage(qrData: qrData, isLight: isLight,)); - getIt.registerFactory(() => AddressResolver(yatService: getIt.get())); getIt.registerFactory(() => IoniaApi()); From 41064b4d9041c0ccaeae0f92d4547f4e9351572f Mon Sep 17 00:00:00 2001 From: M Date: Thu, 21 Jul 2022 15:49:40 +0100 Subject: [PATCH 40/55] Changed build number for Cake Wallet ios. --- scripts/ios/app_env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 29e58481d3..ab9e9d4f12 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -19,7 +19,7 @@ MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.4.4" -CAKEWALLET_BUILD_NUMBER=105 +CAKEWALLET_BUILD_NUMBER=106 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" From 58d135e7ad23b1535325cacdbb23b6d90c8899cf Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Fri, 22 Jul 2022 12:26:13 +0300 Subject: [PATCH 41/55] fix currency format for card details and routing for mark as redeemed (#431) --- ios/Podfile.lock | 6 ++++++ lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart | 6 +++--- .../screens/ionia/cards/ionia_gift_card_detail_page.dart | 5 ++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d90ef8ca39..d527812b2b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -57,6 +57,8 @@ PODS: - Flutter - cw_shared_external/Sodium (0.0.1): - Flutter + - device_display_brightness (0.0.1): + - Flutter - devicelocale (0.0.1): - Flutter - DKImagePickerController/Core (4.3.2): @@ -134,6 +136,7 @@ DEPENDENCIES: - cw_haven (from `.symlinks/plugins/cw_haven/ios`) - cw_monero (from `.symlinks/plugins/cw_monero/ios`) - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`) + - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`) - esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) @@ -174,6 +177,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/cw_monero/ios" cw_shared_external: :path: ".symlinks/plugins/cw_shared_external/ios" + device_display_brightness: + :path: ".symlinks/plugins/device_display_brightness/ios" devicelocale: :path: ".symlinks/plugins/devicelocale/ios" esys_flutter_share: @@ -211,6 +216,7 @@ SPEC CHECKSUMS: cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a cw_monero: 88c5e7aa596c6848330750f5f8bcf05fb9c66375 cw_shared_external: 2972d872b8917603478117c9957dfca611845a92 + device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 devicelocale: b22617f40038496deffba44747101255cee005b0 DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index a06a053f64..4178006870 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -114,7 +114,7 @@ class IoniaBuyGiftCardDetailPage extends BasePage { ), SizedBox(height: 4), Text( - '\$${ioniaPurchaseViewModel.giftCardAmount}', + '\$${ioniaPurchaseViewModel.giftCardAmount.toStringAsFixed(2)}', style: textXLargeSemiBold(), ), SizedBox(height: 24), @@ -132,7 +132,7 @@ class IoniaBuyGiftCardDetailPage extends BasePage { ), SizedBox(height: 4), Text( - '\$${ioniaPurchaseViewModel.amount}', + '\$${ioniaPurchaseViewModel.amount.toStringAsFixed(2)}', style: textLargeSemiBold(), ), ], @@ -146,7 +146,7 @@ class IoniaBuyGiftCardDetailPage extends BasePage { ), SizedBox(height: 4), Text( - '\$$tipAmount', + '\$${tipAmount.toStringAsFixed(2)}', style: textLargeSemiBold(), ), ], diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index 9a8e9b36ab..a990dcd201 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/ionia/ionia_gift_card.dart'; +import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; @@ -122,7 +123,9 @@ class IoniaGiftCardDetailPage extends BasePage { if (!viewModel.giftCard.isEmpty) { return LoadingPrimaryButton( isLoading: viewModel.redeemState is IsExecutingState, - onPressed: () => viewModel.redeem(), + onPressed: () => viewModel.redeem().then((_){ + Navigator.of(context).pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst); + }), text: S.of(context).mark_as_redeemed, color: Theme.of(context).accentTextTheme.body2.color, textColor: Colors.white); From 83d5c17b36841a36681a5a57fa9c318888172124 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Fri, 22 Jul 2022 15:46:21 +0300 Subject: [PATCH 42/55] fix terms and condition overflow in ionia (#430) * fix terms and condition scroll * fix color issues * reuse * refactor widget --- .../cards/ionia_buy_card_detail_page.dart | 180 +++--------------- .../cards/ionia_gift_card_detail_page.dart | 100 +++------- .../ionia/widgets/ionia_alert_model.dart | 86 +++++++++ 3 files changed, 141 insertions(+), 225 deletions(-) create mode 100644 lib/src/screens/ionia/widgets/ionia_alert_model.dart diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index 4178006870..ddc1f0f3da 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -1,18 +1,16 @@ import 'dart:ui'; -import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/confirm_modal.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/ionia_alert_model.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; -import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/discount_badge.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/standart_list_row.dart'; import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; @@ -225,14 +223,22 @@ class IoniaBuyGiftCardDetailPage extends BasePage { showPopUp( context: context, builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: '', - alertContent: ioniaPurchaseViewModel.ioniaMerchant.termsAndConditions, - buttonText: S.of(context).agree, - buttonAction: () => Navigator.of(context).pop(), + return IoniaAlertModal( + title: S.of(context).settings_terms_and_conditions, + content: Align( + alignment: Alignment.bottomLeft, + child: Text( + ioniaPurchaseViewModel.ioniaMerchant.termsAndConditions, + style: textMedium( + color: Theme.of(context).textTheme.display2.color, + ), + ), + ), + actionTitle: S.of(context).agree, + showCloseButton: false, + heightFactor: 0.6, ); - }, - ); + }); } Future purchaseCard(BuildContext context) async { @@ -249,68 +255,21 @@ class IoniaBuyGiftCardDetailPage extends BasePage { ) { showPopUp( context: context, - builder: (BuildContext context) { - return AlertBackground( - child: Material( - color: Colors.transparent, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(height: 10), - Container( - padding: EdgeInsets.only(top: 24, left: 24, right: 24), - margin: EdgeInsets.all(24), - decoration: BoxDecoration( - color: Theme.of(context).backgroundColor, - borderRadius: BorderRadius.circular(30), - ), - child: Column( - children: [ - Text( - S.of(context).how_to_use_card, - style: textLargeSemiBold( - color: Theme.of(context).textTheme.body1.color, - ), - ), - SizedBox(height: 24), - Align( - alignment: Alignment.bottomLeft, - child: Text( - merchant.usageInstructionsBak, - style: textMedium( - color: Theme.of(context).textTheme.display2.color, - ), - ), - ), - SizedBox(height: 35), - PrimaryButton( - onPressed: () => Navigator.pop(context), - text: S.of(context).send_got_it, - color: Theme.of(context).accentTextTheme.caption.color, - textColor: Theme.of(context).primaryTextTheme.title.color, - ), - SizedBox(height: 21), - ], - ), - ), - InkWell( - onTap: () => Navigator.pop(context), - child: Container( - margin: EdgeInsets.only(bottom: 40), - child: CircleAvatar( - child: Icon( - Icons.close, - color: Colors.black, - ), - backgroundColor: Colors.white, - ), - ), - ) - ], + builder: (BuildContext context) { + return IoniaAlertModal( + title: S.of(context).how_to_use_card, + content: Align( + alignment: Alignment.bottomLeft, + child: Text( + merchant.usageInstructionsBak, + style: textMedium( + color: Theme.of(context).textTheme.display2.color, ), ), - ); - }); + ), + actionTitle: S.current.send_got_it, + ); + }); } Future _presentSuccessfulInvoiceCreationPopup(BuildContext context) async { @@ -393,87 +352,6 @@ class IoniaBuyGiftCardDetailPage extends BasePage { } } -class _IoniaTransactionCommitedAlert extends StatelessWidget { - const _IoniaTransactionCommitedAlert({ - Key key, - @required this.transactionInfo, - }) : super(key: key); - - final AnyPayPaymentCommittedInfo transactionInfo; - - @override - Widget build(BuildContext context) { - return ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(30)), - child: Container( - width: 327, - height: 340, - color: Theme.of(context).accentTextTheme.title.decorationColor, - child: Material( - color: Colors.transparent, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.fromLTRB(40, 20, 40, 0), - child: Text( - S.of(context).awaiting_payment_confirmation, - textAlign: TextAlign.center, - style: textMediumSemiBold( - color: Theme.of(context).accentTextTheme.display4.backgroundColor, - ), - ), - ), - Padding( - padding: EdgeInsets.only(top: 16, bottom: 8), - child: Container( - height: 1, - color: Theme.of(context).dividerColor, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - S.of(context).transaction_sent, - style: textMedium( - color: Theme.of(context).primaryTextTheme.title.color, - ).copyWith(fontWeight: FontWeight.w500), - ), - SizedBox(height: 20), - Text( - S.of(context).transaction_sent_notice, - style: textMedium( - color: Theme.of(context).primaryTextTheme.title.color, - ).copyWith(fontWeight: FontWeight.w500), - ), - ], - ), - ), - Padding( - padding: EdgeInsets.only(top: 16, bottom: 8), - child: Container( - height: 1, - color: Theme.of(context).dividerColor, - ), - ), - StandartListRow( - title: '${S.current.transaction_details_transaction_id}:', - value: transactionInfo.chain, - ), - StandartListRow( - title: '${S.current.view_in_block_explorer}:', - value: '${S.current.view_transaction_on} XMRChain.net'), - ], - ), - ), - ), - ); - } -} - class TipButtonGroup extends StatelessWidget { const TipButtonGroup({ Key key, diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index a990dcd201..56b32aedb8 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/ionia/widgets/ionia_alert_model.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; import 'package:cake_wallet/src/widgets/alert_background.dart'; @@ -154,82 +155,33 @@ class IoniaGiftCardDetailPage extends BasePage { showPopUp( context: context, builder: (BuildContext context) { - return AlertBackground( - child: Material( - color: Colors.transparent, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(height: 10), - Container( - padding: EdgeInsets.only(top: 24, left: 24, right: 24), - margin: EdgeInsets.all(24), - decoration: BoxDecoration( - color: Theme.of(context).backgroundColor, - borderRadius: BorderRadius.circular(30), - ), - child: Column( - children: [ + return IoniaAlertModal( + title: S.of(context).how_to_use_card, + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: viewModel.giftCard.usageInstructions + .map((instruction) { + return [ + Padding( + padding: EdgeInsets.all(10), + child: Text( + instruction.header, + style: textLargeSemiBold( + color: Theme.of(context).textTheme.display2.color, + ), + )), Text( - S.of(context).how_to_use_card, - style: textLargeSemiBold( - color: Theme.of(context).textTheme.body1.color, + instruction.body, + style: textMedium( + color: Theme.of(context).textTheme.display2.color, ), - ), - Align( - alignment: Alignment.bottomLeft, - child: Container( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.5), - child: SingleChildScrollView( - child: Column(children: viewModel.giftCard.usageInstructions.map((instruction) { - return [ - Padding( - padding: EdgeInsets.all(10), - child: Text( - instruction.header, - style: textLargeSemiBold( - color: Theme.of(context).textTheme.display2.color, - ), - )), - Text( - instruction.body, - style: textMedium( - color: Theme.of(context).textTheme.display2.color, - ), - ) - ]; - }).expand((e) => e).toList()) - ) - )), - SizedBox(height: 35), - PrimaryButton( - onPressed: () => Navigator.pop(context), - text: S.of(context).send_got_it, - color: Theme.of(context).accentTextTheme.caption.color, - textColor: Theme.of(context).primaryTextTheme.title.color, - ), - SizedBox(height: 21), - ], - ), - ), - InkWell( - onTap: () => Navigator.pop(context), - child: Container( - margin: EdgeInsets.only(bottom: 40), - child: CircleAvatar( - child: Icon( - Icons.close, - color: Colors.black, - ), - backgroundColor: Colors.white, - ), - ), - ) - ], - ), - ), - ); + ) + ]; + }) + .expand((e) => e) + .toList()), + actionTitle: S.of(context).send_got_it, + ); }); } } diff --git a/lib/src/screens/ionia/widgets/ionia_alert_model.dart b/lib/src/screens/ionia/widgets/ionia_alert_model.dart new file mode 100644 index 0000000000..abe442d8e4 --- /dev/null +++ b/lib/src/screens/ionia/widgets/ionia_alert_model.dart @@ -0,0 +1,86 @@ +import 'package:cake_wallet/src/widgets/alert_background.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/typography.dart'; +import 'package:flutter/material.dart'; + +class IoniaAlertModal extends StatelessWidget { + const IoniaAlertModal({ + Key key, + @required this.title, + @required this.content, + @required this.actionTitle, + this.heightFactor = 0.4, + this.showCloseButton = true, + }) : super(key: key); + + final String title; + final Widget content; + final String actionTitle; + final bool showCloseButton; + final double heightFactor; + + @override + Widget build(BuildContext context) { + return AlertBackground( + child: Material( + color: Colors.transparent, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Spacer(), + Container( + padding: EdgeInsets.only(top: 24, left: 24, right: 24), + margin: EdgeInsets.all(24), + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + borderRadius: BorderRadius.circular(30), + ), + child: Column( + children: [ + if (title.isNotEmpty) + Text( + title, + style: textLargeSemiBold( + color: Theme.of(context).textTheme.body1.color, + ), + ), + Container( + constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * heightFactor), + child: ListView( + children: [ + content, + SizedBox(height: 35), + ], + ), + ), + PrimaryButton( + onPressed: () => Navigator.pop(context), + text: actionTitle, + color: Theme.of(context).accentTextTheme.caption.color, + textColor: Theme.of(context).primaryTextTheme.title.color, + ), + SizedBox(height: 21), + ], + ), + ), + Spacer(), + if(showCloseButton) + InkWell( + onTap: () => Navigator.pop(context), + child: Container( + margin: EdgeInsets.only(bottom: 40), + child: CircleAvatar( + child: Icon( + Icons.close, + color: Colors.black, + ), + backgroundColor: Colors.white, + ), + ), + ) + ], + ), + ), + ); + } +} \ No newline at end of file From 0b60a03e745d4ad53ce193e7bd118a893722d12a Mon Sep 17 00:00:00 2001 From: M Date: Tue, 26 Jul 2022 12:54:41 +0100 Subject: [PATCH 43/55] Remove IoniaTokenService --- lib/di.dart | 3 -- lib/ionia/ionia_token_service.dart | 72 ------------------------------ 2 files changed, 75 deletions(-) delete mode 100644 lib/ionia/ionia_token_service.dart diff --git a/lib/di.dart b/lib/di.dart index 0ffc2a233e..9b2d70b38e 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -141,7 +141,6 @@ import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; -import 'package:cake_wallet/ionia/ionia_token_service.dart'; import 'package:cake_wallet/anypay/anypay_api.dart'; import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart'; @@ -250,8 +249,6 @@ Future setup( getIt.get(), (WalletType type) => getIt.get(param1: type))); - getIt.registerFactory(() => IoniaTokenService(getIt.get())); - getIt.registerFactoryParam((type, _) => WalletNewVM(getIt.get(), getIt.get(param1: type), _walletInfoSource, diff --git a/lib/ionia/ionia_token_service.dart b/lib/ionia/ionia_token_service.dart deleted file mode 100644 index 1915959956..0000000000 --- a/lib/ionia/ionia_token_service.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'dart:convert'; -import 'package:http/http.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:cake_wallet/ionia/ionia_token_data.dart'; -import 'package:cake_wallet/.secrets.g.dart'; - -String basicAuth(String username, String password) => - 'Basic ' + base64Encode(utf8.encode('$username:$password')); - -abstract class TokenService { - TokenService(this.flutterSecureStorage); - - String get serviceName; - String get oauthUrl; - final FlutterSecureStorage flutterSecureStorage; - - String get _storeKey => '${serviceName}_oauth_token'; - - Future getToken() async { - final storedTokenJson = await flutterSecureStorage.read(key: _storeKey); - IoniaTokenData token; - - if (storedTokenJson != null) { - token = IoniaTokenData.fromJson(storedTokenJson); - } else { - token = await _fetchNewToken(); - await _storeToken(token); - } - - if (token.isExpired) { - token = await _fetchNewToken(); - await _storeToken(token); - } - - return token; - } - - Future _fetchNewToken() async { - final basic = basicAuth(ioniaClientId, ioniaClientSecret); - final body = {'grant_type': 'client_credentials', 'scope': 'cake_dev'}; - final response = await post( - oauthUrl, - headers: { - 'Authorization': basic, - 'Content-Type': 'application/x-www-form-urlencoded'}, - encoding: Encoding.getByName('utf-8'), - body: body); - - if (response.statusCode != 200) { - // throw exception - return null; - } - - return IoniaTokenData.fromJson(response.body); - } - - Future _storeToken(IoniaTokenData token) async { - await flutterSecureStorage.write(key: _storeKey, value: token.toJson()); - } - -} - -class IoniaTokenService extends TokenService { - IoniaTokenService(FlutterSecureStorage flutterSecureStorage) - : super(flutterSecureStorage); - - @override - String get serviceName => 'Ionia'; - - @override - String get oauthUrl => 'https://auth.craypay.com/connect/token'; -} \ No newline at end of file From 02ac69027292dbec1f3a2ef11af6e9566ea7c191 Mon Sep 17 00:00:00 2001 From: M Date: Tue, 26 Jul 2022 13:56:17 +0100 Subject: [PATCH 44/55] Change api for ionia to staging --- lib/ionia/ionia_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart index 72312f0854..c99a5e1ae6 100644 --- a/lib/ionia/ionia_api.dart +++ b/lib/ionia/ionia_api.dart @@ -9,7 +9,7 @@ import 'package:cake_wallet/ionia/ionia_category.dart'; import 'package:cake_wallet/ionia/ionia_gift_card.dart'; class IoniaApi { - static const baseUri = 'apidev.ionia.io'; + static const baseUri = 'apistaging.ionia.io'; static const pathPrefix = 'cake'; static final createUserUri = Uri.https(baseUri, '/$pathPrefix/CreateUser'); static final verifyEmailUri = Uri.https(baseUri, '/$pathPrefix/VerifyEmail'); From 232ed8936fe82aee2a846f7d35830db5e20e08ed Mon Sep 17 00:00:00 2001 From: M Date: Tue, 26 Jul 2022 13:58:53 +0100 Subject: [PATCH 45/55] Update versions for Cake Wallet for android and ios. --- scripts/android/app_env.sh | 4 ++-- scripts/ios/app_env.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 9245c9c1f8..91c8fdeaab 100755 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -20,8 +20,8 @@ MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.4.3" -CAKEWALLET_BUILD_NUMBER=105 +CAKEWALLET_VERSION="4.4.4" +CAKEWALLET_BUILD_NUMBER=106 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index ab9e9d4f12..773e823674 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -19,7 +19,7 @@ MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.4.4" -CAKEWALLET_BUILD_NUMBER=106 +CAKEWALLET_BUILD_NUMBER=107 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" From 1cfd1df45b757342cf6cef1b2cf63961d4ecea67 Mon Sep 17 00:00:00 2001 From: M Date: Tue, 26 Jul 2022 19:31:38 +0100 Subject: [PATCH 46/55] Fixes for instructions. Remove diplay error on payment status screen. --- lib/ionia/ionia_api.dart | 33 ++- lib/ionia/ionia_gift_card.dart | 32 +-- lib/ionia/ionia_gift_card_instruction.dart | 28 +++ lib/ionia/ionia_merchant.dart | 205 +++++++++--------- .../cards/ionia_buy_card_detail_page.dart | 32 ++- .../cards/ionia_gift_card_detail_page.dart | 2 +- .../cards/ionia_payment_status_page.dart | 32 --- 7 files changed, 188 insertions(+), 176 deletions(-) create mode 100644 lib/ionia/ionia_gift_card_instruction.dart diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart index c99a5e1ae6..80280860ed 100644 --- a/lib/ionia/ionia_api.dart +++ b/lib/ionia/ionia_api.dart @@ -159,9 +159,14 @@ class IoniaApi { final data = decodedBody['Data'] as List; return data.map((dynamic e) { - final element = e as Map; - return IoniaMerchant.fromJsonMap(element); - }).toList(); + try { + final element = e as Map; + return IoniaMerchant.fromJsonMap(element); + } catch(_) { + return null; + } + }).where((e) => e != null) + .toList(); } // Get Merchants By Filter @@ -208,9 +213,14 @@ class IoniaApi { final data = decodedBody['Data'] as List; return data.map((dynamic e) { - final element = e['Merchant'] as Map; - return IoniaMerchant.fromJsonMap(element); - }).toList(); + try { + final element = e['Merchant'] as Map; + return IoniaMerchant.fromJsonMap(element); + } catch(_) { + return null; + } + }).where((e) => e != null) + .toList(); } // Purchase Gift Card @@ -273,9 +283,14 @@ class IoniaApi { final data = decodedBody['Data'] as List; return data.map((dynamic e) { - final element = e as Map; - return IoniaGiftCard.fromJsonMap(element); - }).toList(); + try { + final element = e as Map; + return IoniaGiftCard.fromJsonMap(element); + } catch(e) { + return null; + } + }).where((e) => e != null) + .toList(); } // Charge Gift Card diff --git a/lib/ionia/ionia_gift_card.dart b/lib/ionia/ionia_gift_card.dart index cc8c3dbaab..ebe8084f8e 100644 --- a/lib/ionia/ionia_gift_card.dart +++ b/lib/ionia/ionia_gift_card.dart @@ -1,20 +1,7 @@ import 'dart:convert'; - +import 'package:cake_wallet/ionia/ionia_gift_card_instruction.dart'; import 'package:flutter/foundation.dart'; -class IoniaGiftCardInstruction { - IoniaGiftCardInstruction(this.header, this.body); - - factory IoniaGiftCardInstruction.fromJsonMap(Map element) { - return IoniaGiftCardInstruction( - element['header'] as String, - element['body'] as String); - } - - final String header; - final String body; -} - class IoniaGiftCard { IoniaGiftCard({ @required this.id, @@ -24,10 +11,7 @@ class IoniaGiftCard { @required this.barcodeUrl, @required this.cardNumber, @required this.cardPin, - @required this.usageInstructions, - @required this.balanceInstructions, - @required this.paymentInstructions, - @required this.cardImageUrl, + @required this.instructions, @required this.tip, @required this.purchaseAmount, @required this.actualAmount, @@ -41,11 +25,6 @@ class IoniaGiftCard { @required this.logoUrl}); factory IoniaGiftCard.fromJsonMap(Map element) { - final decodedInstructions = json.decode(element['UsageInstructions'] as String) as Map; - final instruction = decodedInstructions['instruction'] as List; - final instructions = instruction - .map((dynamic e) =>IoniaGiftCardInstruction.fromJsonMap(e as Map)) - .toList(); return IoniaGiftCard( id: element['Id'] as int, merchantId: element['MerchantId'] as int, @@ -65,7 +44,7 @@ class IoniaGiftCard { logoUrl: element['LogoUrl'] as String, createdDateFormatted: element['CreatedDate'] as String, lastTransactionDateFormatted: element['LastTransactionDate'] as String, - usageInstructions: instructions); + instructions: IoniaGiftCardInstruction.parseListOfInstructions(element['PaymentInstructions'] as String)); } final int id; @@ -75,10 +54,7 @@ class IoniaGiftCard { final String barcodeUrl; final String cardNumber; final String cardPin; - final List usageInstructions; - final Map balanceInstructions; - final Map paymentInstructions; - final String cardImageUrl; + final List instructions; final double tip; final double purchaseAmount; final double actualAmount; diff --git a/lib/ionia/ionia_gift_card_instruction.dart b/lib/ionia/ionia_gift_card_instruction.dart new file mode 100644 index 0000000000..da1fdae1b7 --- /dev/null +++ b/lib/ionia/ionia_gift_card_instruction.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; +import 'package:intl/intl.dart' show toBeginningOfSentenceCase; + +class IoniaGiftCardInstruction { + IoniaGiftCardInstruction(this.header, this.body); + + factory IoniaGiftCardInstruction.fromJsonMap(Map element) { + return IoniaGiftCardInstruction( + toBeginningOfSentenceCase(element['title'] as String ?? ''), + element['description'] as String); + } + + static List parseListOfInstructions(String instructionsJSON) { + List instructions = []; + + if (instructionsJSON.isNotEmpty) { + final decodedInstructions = json.decode(instructionsJSON) as List; + instructions = decodedInstructions + .map((dynamic e) =>IoniaGiftCardInstruction.fromJsonMap(e as Map)) + .toList(); + } + + return instructions; + } + + final String header; + final String body; +} \ No newline at end of file diff --git a/lib/ionia/ionia_merchant.dart b/lib/ionia/ionia_merchant.dart index 92ce0c03f6..96e6716520 100644 --- a/lib/ionia/ionia_merchant.dart +++ b/lib/ionia/ionia_merchant.dart @@ -1,4 +1,6 @@ import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/ionia/ionia_gift_card_instruction.dart'; + class IoniaMerchant { IoniaMerchant({ @required this.id, @@ -48,7 +50,7 @@ class IoniaMerchant { @required this.isVoidable, @required this.receiptMessage, @required this.cssBorderCode, - @required this.paymentInstructions, + @required this.instructions, @required this.alderSku, @required this.ngcSku, @required this.acceptedCurrency, @@ -59,103 +61,112 @@ class IoniaMerchant { factory IoniaMerchant.fromJsonMap(Map element) { return IoniaMerchant( id: element["Id"] as int, - legalName: element["LegalName"] as String, - systemName: element["SystemName"] as String, - description: element["Description"] as String, - website: element["Website"] as String, - termsAndConditions: element["TermsAndConditions"] as String, - logoUrl: element["LogoUrl"] as String, - cardImageUrl: element["CardImageUrl"] as String, - cardholderAgreement: element["CardholderAgreement"] as String, - purchaseFee: element["PurchaseFee"] as double, - revenueShare: element["RevenueShare"] as double, - marketingFee: element["MarketingFee"] as double, - minimumDiscount: element["MinimumDiscount"] as double, - level1: element["Level1"] as double, - level2: element["Level2"] as double, - level3: element["Level3"] as double, - level4: element["Level4"] as double, - level5: element["Level5"] as double, - level6: element["Level6"] as double, - level7: element["Level7"] as double, - isActive: element["IsActive"] as bool, - isDeleted: element["IsDeleted"] as bool, - isOnline: element["IsOnline"] as bool, - isPhysical: element["IsPhysical"] as bool, - isVariablePurchase: element["IsVariablePurchase"] as bool, - minimumCardPurchase: element["MinimumCardPurchase"] as double, - maximumCardPurchase: element["MaximumCardPurchase"] as double, - acceptsTips: element["AcceptsTips"] as bool, - createdDateFormatted: element["CreatedDate"] as String, - createdBy: element["CreatedBy"] as int, - isRegional: element["IsRegional"] as bool, - modifiedDateFormatted: element["ModifiedDate"] as String, - modifiedBy: element["ModifiedBy"] as int, - usageInstructions: element["UsageInstructions"] as String, - usageInstructionsBak: element["UsageInstructionsBak"] as String, - paymentGatewayId: element["PaymentGatewayId"] as int, - giftCardGatewayId: element["GiftCardGatewayId"] as int , - isHtmlDescription: element["IsHtmlDescription"] as bool, - purchaseInstructions: element["PurchaseInstructions"] as String, - balanceInstructions: element["BalanceInstructions"] as String, - amountPerCard: element["AmountPerCard"] as double, - processingMessage: element["ProcessingMessage"] as String, - hasBarcode: element["HasBarcode"] as bool, - hasInventory: element["HasInventory"] as bool, - isVoidable: element["IsVoidable"] as bool, - receiptMessage: element["ReceiptMessage"] as String, - cssBorderCode: element["CssBorderCode"] as String, - paymentInstructions: element["PaymentInstructions"] as String, - alderSku: element["AlderSku"] as String, - ngcSku: element["NgcSku"] as String, - acceptedCurrency: element["AcceptedCurrency"] as String, - deepLink: element["DeepLink"] as String, - isPayLater: element["IsPayLater"] as bool); + legalName: element["LegalName"] as String, + systemName: element["SystemName"] as String, + description: element["Description"] as String, + website: element["Website"] as String, + termsAndConditions: element["TermsAndConditions"] as String, + logoUrl: element["LogoUrl"] as String, + cardImageUrl: element["CardImageUrl"] as String, + cardholderAgreement: element["CardholderAgreement"] as String, + purchaseFee: element["PurchaseFee"] as double, + revenueShare: element["RevenueShare"] as double, + marketingFee: element["MarketingFee"] as double, + minimumDiscount: element["MinimumDiscount"] as double, + level1: element["Level1"] as double, + level2: element["Level2"] as double, + level3: element["Level3"] as double, + level4: element["Level4"] as double, + level5: element["Level5"] as double, + level6: element["Level6"] as double, + level7: element["Level7"] as double, + isActive: element["IsActive"] as bool, + isDeleted: element["IsDeleted"] as bool, + isOnline: element["IsOnline"] as bool, + isPhysical: element["IsPhysical"] as bool, + isVariablePurchase: element["IsVariablePurchase"] as bool, + minimumCardPurchase: element["MinimumCardPurchase"] as double, + maximumCardPurchase: element["MaximumCardPurchase"] as double, + acceptsTips: element["AcceptsTips"] as bool, + createdDateFormatted: element["CreatedDate"] as String, + createdBy: element["CreatedBy"] as int, + isRegional: element["IsRegional"] as bool, + modifiedDateFormatted: element["ModifiedDate"] as String, + modifiedBy: element["ModifiedBy"] as int, + usageInstructions: element["UsageInstructions"] as String, + usageInstructionsBak: element["UsageInstructionsBak"] as String, + paymentGatewayId: element["PaymentGatewayId"] as int, + giftCardGatewayId: element["GiftCardGatewayId"] as int , + isHtmlDescription: element["IsHtmlDescription"] as bool, + purchaseInstructions: element["PurchaseInstructions"] as String, + balanceInstructions: element["BalanceInstructions"] as String, + amountPerCard: element["AmountPerCard"] as double, + processingMessage: element["ProcessingMessage"] as String, + hasBarcode: element["HasBarcode"] as bool, + hasInventory: element["HasInventory"] as bool, + isVoidable: element["IsVoidable"] as bool, + receiptMessage: element["ReceiptMessage"] as String, + cssBorderCode: element["CssBorderCode"] as String, + instructions: IoniaGiftCardInstruction.parseListOfInstructions(element['PaymentInstructions'] as String), + alderSku: element["AlderSku"] as String, + ngcSku: element["NgcSku"] as String, + acceptedCurrency: element["AcceptedCurrency"] as String, + deepLink: element["DeepLink"] as String, + isPayLater: element["IsPayLater"] as bool); } - final int id; final String legalName; final String systemName; final String description; final String website; final String termsAndConditions; final String logoUrl; final String cardImageUrl; final String cardholderAgreement; final double purchaseFee; - final double revenueShare; - final double marketingFee; - final double minimumDiscount; - final double level1; - final double level2; - final double level3; - final double level4; - final double level5; - final double level6; - final double level7; - final bool isActive; - final bool isDeleted; - final bool isOnline; - final bool isPhysical; - final bool isVariablePurchase; - final double minimumCardPurchase; - final double maximumCardPurchase; - final bool acceptsTips; - final String createdDateFormatted; - final int createdBy; - final bool isRegional; - final String modifiedDateFormatted; - final int modifiedBy; - final String usageInstructions; - final String usageInstructionsBak; - final int paymentGatewayId; - final int giftCardGatewayId; - final bool isHtmlDescription; - final String purchaseInstructions; - final String balanceInstructions; - final double amountPerCard; - final String processingMessage; - final bool hasBarcode; - final bool hasInventory; - final bool isVoidable; - final String receiptMessage; - final String cssBorderCode; - final String paymentInstructions; - final String alderSku; - final String ngcSku; - final String acceptedCurrency; - final String deepLink; - final bool isPayLater; + final int id; + final String legalName; + final String systemName; + final String description; + final String website; + final String termsAndConditions; + final String logoUrl; + final String cardImageUrl; + final String cardholderAgreement; + final double purchaseFee; + final double revenueShare; + final double marketingFee; + final double minimumDiscount; + final double level1; + final double level2; + final double level3; + final double level4; + final double level5; + final double level6; + final double level7; + final bool isActive; + final bool isDeleted; + final bool isOnline; + final bool isPhysical; + final bool isVariablePurchase; + final double minimumCardPurchase; + final double maximumCardPurchase; + final bool acceptsTips; + final String createdDateFormatted; + final int createdBy; + final bool isRegional; + final String modifiedDateFormatted; + final int modifiedBy; + final String usageInstructions; + final String usageInstructionsBak; + final int paymentGatewayId; + final int giftCardGatewayId; + final bool isHtmlDescription; + final String purchaseInstructions; + final String balanceInstructions; + final double amountPerCard; + final String processingMessage; + final bool hasBarcode; + final bool hasInventory; + final bool isVoidable; + final String receiptMessage; + final String cssBorderCode; + final List instructions; + final String alderSku; + final String ngcSku; + final String acceptedCurrency; + final String deepLink; + final bool isPayLater; } diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index ddc1f0f3da..e5a085eb1d 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -258,15 +258,29 @@ class IoniaBuyGiftCardDetailPage extends BasePage { builder: (BuildContext context) { return IoniaAlertModal( title: S.of(context).how_to_use_card, - content: Align( - alignment: Alignment.bottomLeft, - child: Text( - merchant.usageInstructionsBak, - style: textMedium( - color: Theme.of(context).textTheme.display2.color, - ), - ), - ), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: merchant.instructions + .map((instruction) { + return [ + Padding( + padding: EdgeInsets.all(10), + child: Text( + instruction.header, + style: textLargeSemiBold( + color: Theme.of(context).textTheme.display2.color, + ), + )), + Text( + instruction.body, + style: textMedium( + color: Theme.of(context).textTheme.display2.color, + ), + ) + ]; + }) + .expand((e) => e) + .toList()), actionTitle: S.current.send_got_it, ); }); diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index 56b32aedb8..c9efe7a99e 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -159,7 +159,7 @@ class IoniaGiftCardDetailPage extends BasePage { title: S.of(context).how_to_use_card, content: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: viewModel.giftCard.usageInstructions + children: viewModel.giftCard.instructions .map((instruction) { return [ Padding( diff --git a/lib/src/screens/ionia/cards/ionia_payment_status_page.dart b/lib/src/screens/ionia/cards/ionia_payment_status_page.dart index 92bc62b91b..d264842e65 100644 --- a/lib/src/screens/ionia/cards/ionia_payment_status_page.dart +++ b/lib/src/screens/ionia/cards/ionia_payment_status_page.dart @@ -1,12 +1,10 @@ import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/alert_with_one_action.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/typography.dart'; import 'package:cake_wallet/utils/show_bar.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -44,7 +42,6 @@ class _IoniaPaymentStatusPageBody extends StatefulWidget { } class _IoniaPaymentStatusPageBodyBodyState extends State<_IoniaPaymentStatusPageBody> { - ReactionDisposer _onErrorReaction; ReactionDisposer _onGiftCardReaction; @override @@ -55,34 +52,6 @@ class _IoniaPaymentStatusPageBodyBodyState extends State<_IoniaPaymentStatusPage .pushReplacementNamed(Routes.ioniaGiftCardDetailPage, arguments: [widget.viewModel.giftCard]); }); } - - if (widget.viewModel.error != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).error, - alertContent: widget.viewModel.error, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); - }); - } - - _onErrorReaction = reaction((_) => widget.viewModel.error, (String error) { - WidgetsBinding.instance.addPostFrameCallback((_) { - showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).error, - alertContent: error, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); - }); - }); _onGiftCardReaction = reaction((_) => widget.viewModel.giftCard, (IoniaGiftCard giftCard) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -96,7 +65,6 @@ class _IoniaPaymentStatusPageBodyBodyState extends State<_IoniaPaymentStatusPage @override void dispose() { - _onErrorReaction?.reaction?.dispose(); _onGiftCardReaction?.reaction?.dispose(); widget.viewModel.timer.cancel(); super.dispose(); From 2bc5b7055b4b02a8b438e8696b10e84f20622391 Mon Sep 17 00:00:00 2001 From: M Date: Tue, 26 Jul 2022 19:45:15 +0100 Subject: [PATCH 47/55] Change build versions for Cake Wallet --- scripts/android/app_env.sh | 2 +- scripts/ios/app_env.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 91c8fdeaab..0aa2c6751e 100755 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -21,7 +21,7 @@ MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.4.4" -CAKEWALLET_BUILD_NUMBER=106 +CAKEWALLET_BUILD_NUMBER=107 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 773e823674..3367c8c7ed 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -19,7 +19,7 @@ MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.4.4" -CAKEWALLET_BUILD_NUMBER=107 +CAKEWALLET_BUILD_NUMBER=108 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" From 0c8caf8847082c4ebb89db6c8d0934c02191a858 Mon Sep 17 00:00:00 2001 From: M Date: Wed, 27 Jul 2022 13:16:42 +0100 Subject: [PATCH 48/55] Add ionia sign in. --- lib/di.dart | 4 +-- lib/ionia/ionia_api.dart | 32 +++++++++++++++++-- lib/ionia/ionia_create_state.dart | 2 ++ lib/ionia/ionia_service.dart | 12 ++++++- .../ionia/auth/ionia_create_account_page.dart | 2 +- .../screens/ionia/auth/ionia_login_page.dart | 8 ++--- .../ionia/auth/ionia_verify_otp_page.dart | 7 ++-- .../ionia/ionia_auth_view_model.dart | 28 +++++++++++----- 8 files changed, 75 insertions(+), 20 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index 9b2d70b38e..bd911335d8 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -705,9 +705,9 @@ Future setup( getIt.registerFactoryParam((List args, _) { final email = args.first as String; - final ioniaAuthViewModel = args[1] as IoniaAuthViewModel; + final isSignIn = args[1] as bool; - return IoniaVerifyIoniaOtp(ioniaAuthViewModel, email); + return IoniaVerifyIoniaOtp(getIt.get(), email, isSignIn); }); getIt.registerFactory(() => IoniaWelcomePage(getIt.get())); diff --git a/lib/ionia/ionia_api.dart b/lib/ionia/ionia_api.dart index 80280860ed..6784e2ba18 100644 --- a/lib/ionia/ionia_api.dart +++ b/lib/ionia/ionia_api.dart @@ -13,6 +13,7 @@ class IoniaApi { static const pathPrefix = 'cake'; static final createUserUri = Uri.https(baseUri, '/$pathPrefix/CreateUser'); static final verifyEmailUri = Uri.https(baseUri, '/$pathPrefix/VerifyEmail'); + static final signInUri = Uri.https(baseUri, '/$pathPrefix/SignIn'); static final createCardUri = Uri.https(baseUri, '/$pathPrefix/CreateCard'); static final getCardsUri = Uri.https(baseUri, '/$pathPrefix/GetCards'); static final getMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/GetMerchants'); @@ -51,11 +52,13 @@ class IoniaApi { Future verifyEmail({ @required String username, + @required String email, @required String code, @required String clientId}) async { final headers = { 'clientId': clientId, - 'username': username}; + 'username': username, + 'EmailAddress': email}; final query = {'verificationCode': code}; final uri = verifyEmailUri.replace(queryParameters: query); final response = await put(uri, headers: headers); @@ -70,13 +73,38 @@ class IoniaApi { final isSuccessful = bodyJson['Successful'] as bool; if (!isSuccessful) { - throw Exception(data['ErrorMessage'] as String); + throw Exception(bodyJson['ErrorMessage'] as String); } final password = data['password'] as String; + username = data['username'] as String; return IoniaUserCredentials(username, password); } + // Sign In + + Future signIn(String email, {@required String clientId}) async { + final headers = {'clientId': clientId}; + final query = {'emailAddress': email}; + final uri = signInUri.replace(queryParameters: query); + final response = await put(uri, headers: headers); + + if (response.statusCode != 200) { + // throw exception + return null; + } + + final bodyJson = json.decode(response.body) as Map; + final data = bodyJson['Data'] as Map; + final isSuccessful = bodyJson['Successful'] as bool; + + if (!isSuccessful) { + throw Exception(data['ErrorMessage'] as String); + } + + return data['username'] as String; + } + // Get virtual card Future getCards({ diff --git a/lib/ionia/ionia_create_state.dart b/lib/ionia/ionia_create_state.dart index 199d72c735..b0277be452 100644 --- a/lib/ionia/ionia_create_state.dart +++ b/lib/ionia/ionia_create_state.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; abstract class IoniaCreateAccountState {} +class IoniaInitialCreateState extends IoniaCreateAccountState {} + class IoniaCreateStateSuccess extends IoniaCreateAccountState {} class IoniaCreateStateLoading extends IoniaCreateAccountState {} diff --git a/lib/ionia/ionia_service.dart b/lib/ionia/ionia_service.dart index b5ed5a4958..d1a2052f0c 100644 --- a/lib/ionia/ionia_service.dart +++ b/lib/ionia/ionia_service.dart @@ -32,8 +32,18 @@ class IoniaService { Future verifyEmail(String code) async { final username = await secureStorage.read(key: ioniaUsernameStorageKey); - final credentials = await ioniaApi.verifyEmail(username: username, code: code, clientId: clientId); + final email = await secureStorage.read(key: ioniaEmailStorageKey); + final credentials = await ioniaApi.verifyEmail(email: email, username: username, code: code, clientId: clientId); await secureStorage.write(key: ioniaPasswordStorageKey, value: credentials.password); + await secureStorage.write(key: ioniaUsernameStorageKey, value: credentials.username); + } + + // Sign In + + Future signIn(String email) async { + final username = await ioniaApi.signIn(email, clientId: clientId); + await secureStorage.write(key: ioniaEmailStorageKey, value: email); + await secureStorage.write(key: ioniaUsernameStorageKey, value: username); } Future getUserEmail() async { diff --git a/lib/src/screens/ionia/auth/ionia_create_account_page.dart b/lib/src/screens/ionia/auth/ionia_create_account_page.dart index 6f8cd8b132..5e27977683 100644 --- a/lib/src/screens/ionia/auth/ionia_create_account_page.dart +++ b/lib/src/screens/ionia/auth/ionia_create_account_page.dart @@ -149,6 +149,6 @@ class IoniaCreateAccountPage extends BasePage { void _onCreateSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed( context, Routes.ioniaVerifyIoniaOtpPage, - arguments: [authViewModel.email, authViewModel], + arguments: [authViewModel.email, false], ); } diff --git a/lib/src/screens/ionia/auth/ionia_login_page.dart b/lib/src/screens/ionia/auth/ionia_login_page.dart index 7aadce1980..bcbc0fee32 100644 --- a/lib/src/screens/ionia/auth/ionia_login_page.dart +++ b/lib/src/screens/ionia/auth/ionia_login_page.dart @@ -43,7 +43,7 @@ class IoniaLoginPage extends BasePage { @override Widget body(BuildContext context) { - reaction((_) => _authViewModel.createUserState, (IoniaCreateAccountState state) { + reaction((_) => _authViewModel.signInState, (IoniaCreateAccountState state) { if (state is IoniaCreateStateFailure) { _onLoginUserFailure(context, state.error); } @@ -75,9 +75,9 @@ class IoniaLoginPage extends BasePage { if (!_formKey.currentState.validate()) { return; } - await _authViewModel.createUser(_emailController.text); + await _authViewModel.signIn(_emailController.text); }, - isLoading: _authViewModel.createUserState is IoniaCreateStateLoading, + isLoading: _authViewModel.signInState is IoniaCreateStateLoading, color: Theme.of(context).accentTextTheme.body2.color, textColor: Colors.white, ), @@ -107,6 +107,6 @@ class IoniaLoginPage extends BasePage { void _onLoginSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed( context, Routes.ioniaVerifyIoniaOtpPage, - arguments: [authViewModel.email, authViewModel], + arguments: [authViewModel.email, true], ); } diff --git a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart index 95df9c25a7..c219781cc3 100644 --- a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart +++ b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart @@ -17,7 +17,7 @@ import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:mobx/mobx.dart'; class IoniaVerifyIoniaOtp extends BasePage { - IoniaVerifyIoniaOtp(this._authViewModel, this._email) + IoniaVerifyIoniaOtp(this._authViewModel, this._email, this.isSignIn) : _codeController = TextEditingController(), _codeFocus = FocusNode() { _codeController.addListener(() { @@ -32,6 +32,7 @@ class IoniaVerifyIoniaOtp extends BasePage { } final IoniaAuthViewModel _authViewModel; + final bool isSignIn; final String _email; @@ -94,7 +95,9 @@ class IoniaVerifyIoniaOtp extends BasePage { Text(S.of(context).dont_get_code), SizedBox(width: 20), InkWell( - onTap: () => _authViewModel.createUser(_email), + onTap: () => isSignIn + ? _authViewModel.signIn(_email) + : _authViewModel.createUser(_email), child: Text( S.of(context).resend_code, style: textSmallSemiBold(color: Palette.blueCraiola), diff --git a/lib/view_model/ionia/ionia_auth_view_model.dart b/lib/view_model/ionia/ionia_auth_view_model.dart index f610168669..5e662b462f 100644 --- a/lib/view_model/ionia/ionia_auth_view_model.dart +++ b/lib/view_model/ionia/ionia_auth_view_model.dart @@ -9,17 +9,18 @@ class IoniaAuthViewModel = IoniaAuthViewModelBase with _$IoniaAuthViewModel; abstract class IoniaAuthViewModelBase with Store { IoniaAuthViewModelBase({this.ioniaService}): - createUserState = IoniaCreateStateSuccess(), - otpState = IoniaOtpSendDisabled(){ - - - } + createUserState = IoniaInitialCreateState(), + signInState = IoniaInitialCreateState(), + otpState = IoniaOtpSendDisabled(); final IoniaService ioniaService; @observable IoniaCreateAccountState createUserState; + @observable + IoniaCreateAccountState signInState; + @observable IoniaOtpState otpState; @@ -42,14 +43,25 @@ abstract class IoniaAuthViewModelBase with Store { @action Future createUser(String email) async { - createUserState = IoniaCreateStateLoading(); try { + createUserState = IoniaCreateStateLoading(); await ioniaService.createUser(email); - createUserState = IoniaCreateStateSuccess(); - } on Exception catch (e) { + } catch (e) { createUserState = IoniaCreateStateFailure(error: e.toString()); } } + + @action + Future signIn(String email) async { + try { + signInState = IoniaCreateStateLoading(); + await ioniaService.signIn(email); + signInState = IoniaCreateStateSuccess(); + } catch (e) { + signInState = IoniaCreateStateFailure(error: e.toString()); + } + } + } \ No newline at end of file From 8bd66b64e79e968396fbe72511bdb70f86fbdf87 Mon Sep 17 00:00:00 2001 From: M Date: Wed, 27 Jul 2022 14:55:42 +0100 Subject: [PATCH 49/55] Update for discounts and statuses for ionia merch. --- lib/ionia/ionia_merchant.dart | 2 ++ .../cards/ionia_buy_card_detail_page.dart | 6 +++--- .../ionia/cards/ionia_buy_gift_card.dart | 2 +- .../ionia/cards/ionia_manage_cards_page.dart | 18 ++++++++++++++++-- .../ionia/ionia_purchase_merch_view_model.dart | 3 +++ 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/ionia/ionia_merchant.dart b/lib/ionia/ionia_merchant.dart index 96e6716520..6b85a18b38 100644 --- a/lib/ionia/ionia_merchant.dart +++ b/lib/ionia/ionia_merchant.dart @@ -168,5 +168,7 @@ class IoniaMerchant { final String acceptedCurrency; final String deepLink; final bool isPayLater; + + double get discount => level3; } diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index e5a085eb1d..95202f4725 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -35,8 +35,8 @@ class IoniaBuyGiftCardDetailPage extends BasePage { @override Widget trailing(BuildContext context) - => ioniaPurchaseViewModel.ioniaMerchant.minimumDiscount > 0 - ? DiscountBadge(percentage: ioniaPurchaseViewModel.ioniaMerchant.minimumDiscount) + => ioniaPurchaseViewModel.ioniaMerchant.discount > 0 + ? DiscountBadge(percentage: ioniaPurchaseViewModel.ioniaMerchant.discount) : null; @override @@ -130,7 +130,7 @@ class IoniaBuyGiftCardDetailPage extends BasePage { ), SizedBox(height: 4), Text( - '\$${ioniaPurchaseViewModel.amount.toStringAsFixed(2)}', + '\$${ioniaPurchaseViewModel.billAmount.toStringAsFixed(2)}', style: textLargeSemiBold(), ), ], diff --git a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart index e846825595..899b1387dd 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart @@ -146,7 +146,7 @@ class IoniaBuyGiftCardPage extends BasePage { child: CardItem( title: merchant.legalName, backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), - discount: merchant.minimumDiscount, + discount: merchant.discount, titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor, subtitleColor: Theme.of(context).hintColor, subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline, diff --git a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart index 103ee64ddd..656b8676b4 100644 --- a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart @@ -216,17 +216,31 @@ class _IoniaManageCardsPageBodyState extends State { separatorBuilder: (_, __) => SizedBox(height: 4), itemBuilder: (_, index) { final merchant = merchantsList[index]; + var subTitle = ''; + + if (merchant.isOnline) { + subTitle += S.of(context).online; + } + + if (merchant.isPhysical) { + if (subTitle.isNotEmpty) { + subTitle = '$subTitle & '; + } + + subTitle = '${subTitle}${S.of(context).in_store}'; + } + return CardItem( logoUrl: merchant.logoUrl, onTap: () { Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]); }, title: merchant.legalName, - subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline, + subTitle: subTitle, backgroundColor: Theme.of(context).textTheme.title.backgroundColor, titleColor: Theme.of(context).accentTextTheme.display3.backgroundColor, subtitleColor: Theme.of(context).accentTextTheme.display2.backgroundColor, - discount: merchant.minimumDiscount, + discount: merchant.discount, ); }, ), diff --git a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart index 7fc66bc377..b8c8eaa969 100644 --- a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart +++ b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart @@ -58,6 +58,9 @@ abstract class IoniaMerchPurchaseViewModelBase with Store { @computed double get giftCardAmount => double.parse((amount + tipAmount).toStringAsFixed(2)); + @computed + double get billAmount => double.parse((giftCardAmount * (1 - (ioniaMerchant.discount / 100))).toStringAsFixed(2)); + @observable double tipAmount; From cb1ca8c59535e9f229a245f819198d9f5df3f5ea Mon Sep 17 00:00:00 2001 From: M Date: Wed, 27 Jul 2022 16:13:10 +0100 Subject: [PATCH 50/55] Fixes for qr/barcode on ionia gift card screen. --- .../ionia/cards/ionia_gift_card_detail_page.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index c9efe7a99e..9aa094f4e6 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -90,7 +90,7 @@ class IoniaGiftCardDetailPage extends BasePage { horizontal: 24.0, vertical: 24, ), - child: SizedBox(height: 96, width: double.infinity, child: Image.network(viewModel.giftCard.barcodeUrl)), + child: Image.network(viewModel.giftCard.barcodeUrl), ), SizedBox(height: 24), buildIoniaTile( @@ -98,12 +98,13 @@ class IoniaGiftCardDetailPage extends BasePage { title: S.of(context).gift_card_number, subTitle: viewModel.giftCard.cardNumber, ), - Divider(height: 30), - buildIoniaTile( - context, - title: S.of(context).pin_number, - subTitle: viewModel.giftCard.cardPin ?? '', - ), + if (viewModel.giftCard.cardPin?.isNotEmpty ?? false) + ...[Divider(height: 30), + buildIoniaTile( + context, + title: S.of(context).pin_number, + subTitle: viewModel.giftCard.cardPin, + )], Divider(height: 30), Observer(builder: (_) => buildIoniaTile( From 3a9210c8cbefde85f77c9e2bdf187de93dfb288a Mon Sep 17 00:00:00 2001 From: M Date: Wed, 27 Jul 2022 16:15:18 +0100 Subject: [PATCH 51/55] Fixed formatting for display ionia discounts. --- lib/src/screens/ionia/cards/ionia_manage_cards_page.dart | 2 +- lib/src/widgets/discount_badge.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart index 656b8676b4..23597877b7 100644 --- a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart @@ -240,7 +240,7 @@ class _IoniaManageCardsPageBodyState extends State { backgroundColor: Theme.of(context).textTheme.title.backgroundColor, titleColor: Theme.of(context).accentTextTheme.display3.backgroundColor, subtitleColor: Theme.of(context).accentTextTheme.display2.backgroundColor, - discount: merchant.discount, + discount: merchant.discount.toStringAsFixed(2), ); }, ), diff --git a/lib/src/widgets/discount_badge.dart b/lib/src/widgets/discount_badge.dart index 855822c60c..1cbbf3e89a 100644 --- a/lib/src/widgets/discount_badge.dart +++ b/lib/src/widgets/discount_badge.dart @@ -16,7 +16,7 @@ class DiscountBadge extends StatelessWidget { return Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Text( - S.of(context).discount(percentage.toString()), + S.of(context).discount(percentage.toStringAsFixed(2)), style: TextStyle( color: Colors.white, fontSize: 12, From 5148272e42d5d41633d4dd150318fbfb1ba8208a Mon Sep 17 00:00:00 2001 From: M Date: Wed, 27 Jul 2022 18:40:37 +0100 Subject: [PATCH 52/55] Fix merchant.discount.toStringAsFixed issue --- lib/src/screens/ionia/cards/ionia_manage_cards_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart index 23597877b7..656b8676b4 100644 --- a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart @@ -240,7 +240,7 @@ class _IoniaManageCardsPageBodyState extends State { backgroundColor: Theme.of(context).textTheme.title.backgroundColor, titleColor: Theme.of(context).accentTextTheme.display3.backgroundColor, subtitleColor: Theme.of(context).accentTextTheme.display2.backgroundColor, - discount: merchant.discount.toStringAsFixed(2), + discount: merchant.discount, ); }, ), From 7e32ba44638a0390b0154dd398c0d4228c896a01 Mon Sep 17 00:00:00 2001 From: M Date: Thu, 28 Jul 2022 11:22:24 +0100 Subject: [PATCH 53/55] Add savingsPercentage to ionia merch discount. --- lib/ionia/ionia_merchant.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/ionia/ionia_merchant.dart b/lib/ionia/ionia_merchant.dart index 6b85a18b38..16df62da79 100644 --- a/lib/ionia/ionia_merchant.dart +++ b/lib/ionia/ionia_merchant.dart @@ -55,8 +55,8 @@ class IoniaMerchant { @required this.ngcSku, @required this.acceptedCurrency, @required this.deepLink, - @required this.isPayLater - }); + @required this.isPayLater, + @required this.savingsPercentage}); factory IoniaMerchant.fromJsonMap(Map element) { return IoniaMerchant( @@ -112,7 +112,8 @@ class IoniaMerchant { ngcSku: element["NgcSku"] as String, acceptedCurrency: element["AcceptedCurrency"] as String, deepLink: element["DeepLink"] as String, - isPayLater: element["IsPayLater"] as bool); + isPayLater: element["IsPayLater"] as bool, + savingsPercentage: element["SavingsPercentage"] as double); } final int id; @@ -168,7 +169,8 @@ class IoniaMerchant { final String acceptedCurrency; final String deepLink; final bool isPayLater; + final double savingsPercentage; - double get discount => level3; + double get discount => savingsPercentage; } From eeaecae0c76041372f362ae3008080d5ebba8020 Mon Sep 17 00:00:00 2001 From: M Date: Thu, 28 Jul 2022 12:06:26 +0100 Subject: [PATCH 54/55] Change build number for Cake Wallet ios and android. --- scripts/android/app_env.sh | 2 +- scripts/ios/app_env.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 0aa2c6751e..4345c02301 100755 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -21,7 +21,7 @@ MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.4.4" -CAKEWALLET_BUILD_NUMBER=107 +CAKEWALLET_BUILD_NUMBER=108 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 3367c8c7ed..99f306343a 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -19,7 +19,7 @@ MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.4.4" -CAKEWALLET_BUILD_NUMBER=108 +CAKEWALLET_BUILD_NUMBER=109 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" From 2cf053282ea758efd9882d3cf59269ff3e256a66 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Thu, 28 Jul 2022 18:53:12 +0300 Subject: [PATCH 55/55] Disable ionia for haven (#440) --- lib/src/screens/dashboard/dashboard_page.dart | 2 +- .../dashboard/widgets/market_place_page.dart | 30 ++++++++++++++++++- res/values/strings_de.arb | 3 +- res/values/strings_en.arb | 3 +- res/values/strings_es.arb | 3 +- res/values/strings_fr.arb | 3 +- res/values/strings_hi.arb | 3 +- res/values/strings_hr.arb | 3 +- res/values/strings_it.arb | 3 +- res/values/strings_ja.arb | 3 +- res/values/strings_ko.arb | 3 +- res/values/strings_nl.arb | 3 +- res/values/strings_pl.arb | 3 +- res/values/strings_pt.arb | 3 +- res/values/strings_ru.arb | 3 +- res/values/strings_uk.arb | 3 +- res/values/strings_zh.arb | 3 +- 17 files changed, 60 insertions(+), 17 deletions(-) diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index f11398564b..6ed07850fb 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -217,7 +217,7 @@ class DashboardPage extends BasePage { if (_isEffectsInstalled) { return; } - pages.add(MarketPlacePage()); + pages.add(MarketPlacePage(dashboardViewModel: walletViewModel)); pages.add(balancePage); pages.add(TransactionsPage(dashboardViewModel: walletViewModel)); _isEffectsInstalled = true; diff --git a/lib/src/screens/dashboard/widgets/market_place_page.dart b/lib/src/screens/dashboard/widgets/market_place_page.dart index b616e955f1..2484ae32a6 100644 --- a/lib/src/screens/dashboard/widgets/market_place_page.dart +++ b/lib/src/screens/dashboard/widgets/market_place_page.dart @@ -1,9 +1,17 @@ import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/market_place_item.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; class MarketPlacePage extends StatelessWidget { + + MarketPlacePage({@required this.dashboardViewModel}); + + final DashboardViewModel dashboardViewModel; final _scrollController = ScrollController(); @override @@ -36,7 +44,7 @@ class MarketPlacePage extends StatelessWidget { children: [ SizedBox(height: 20), MarketPlaceItem( - onTap: () => Navigator.of(context).pushNamed(Routes.ioniaWelcomePage), + onTap: () =>_navigatorToGiftCardsPage(context), title: S.of(context).cake_pay_title, subTitle: S.of(context).cake_pay_subtitle, ), @@ -49,4 +57,24 @@ class MarketPlacePage extends StatelessWidget { ), ); } + void _navigatorToGiftCardsPage(BuildContext context) { + final walletType = dashboardViewModel.type; + + switch (walletType) { + case WalletType.haven: + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: S.of(context).gift_cards_unavailable, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + break; + default: + Navigator.of(context).pushNamed(Routes.ioniaWelcomePage); + } + } + } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index ac6f337c9f..d9687bc369 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -631,5 +631,6 @@ "order_id": "Bestell-ID", "gift_card_is_generated": "Geschenkkarte wird generiert", "open_gift_card": "Geschenkkarte öffnen", - "contact_support": "Support kontaktieren" + "contact_support": "Support kontaktieren", + "gift_cards_unavailable": "Geschenkkarten können derzeit nur über Monero, Bitcoin und Litecoin erworben werden" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index a528be77c5..c66f9a6e63 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -631,5 +631,6 @@ "order_id": "Order ID", "gift_card_is_generated": "Gift Card is generated", "open_gift_card": "Open Gift Card", - "contact_support": "Contact Support" + "contact_support": "Contact Support", + "gift_cards_unavailable": "Gift cards are available to purchase only through Monero, Bitcoin, and Litecoin at this time" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index bee38acc0e..0846580816 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -631,5 +631,6 @@ "order_id": "Identificación del pedido", "gift_card_is_generated": "Se genera la tarjeta de regalo", "open_gift_card": "Abrir tarjeta de regalo", - "contact_support": "Contactar con Soporte" + "contact_support": "Contactar con Soporte", + "gift_cards_unavailable": "Las tarjetas de regalo están disponibles para comprar solo a través de Monero, Bitcoin y Litecoin en este momento" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index eca018d7c8..9817e7befe 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -629,5 +629,6 @@ "order_id": "Numéro de commande", "gift_card_is_generated": "La carte-cadeau est générée", "open_gift_card": "Ouvrir la carte-cadeau", - "contact_support": "Contacter l'assistance" + "contact_support": "Contacter l'assistance", + "gift_cards_unavailable": "Les cartes-cadeaux ne sont disponibles à l'achat que via Monero, Bitcoin et Litecoin pour le moment" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index c85fca07a7..03a891169c 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -631,5 +631,6 @@ "order_id": "ऑर्डर आईडी", "gift_card_is_generated": "गिफ्ट कार्ड जनरेट हुआ", "open_gift_card": "गिफ्ट कार्ड खोलें", - "contact_support": "सहायता से संपर्क करें" + "contact_support": "सहायता से संपर्क करें", + "gift_cards_unavailable": "उपहार कार्ड इस समय केवल मोनेरो, बिटकॉइन और लिटकोइन के माध्यम से खरीदने के लिए उपलब्ध हैं" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 5cd45d0355..c25b54eb85 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -631,5 +631,6 @@ "order_id": "ID narudžbe", "gift_card_is_generated": "Poklon kartica je generirana", "open_gift_card": "Otvori darovnu karticu", - "contact_support": "Kontaktirajte podršku" + "contact_support": "Kontaktirajte podršku", + "gift_cards_unavailable": "Poklon kartice trenutno su dostupne za kupnju samo putem Monera, Bitcoina i Litecoina" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 1447be72a8..8383c9ad9c 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -631,5 +631,6 @@ "order_id": "ID ordine", "gift_card_is_generated": "Il buono regalo è stato generato", "open_gift_card": "Apri carta regalo", - "contact_support": "Contatta l'assistenza" + "contact_support": "Contatta l'assistenza", + "gift_cards_unavailable": "Le carte regalo sono disponibili per l'acquisto solo tramite Monero, Bitcoin e Litecoin in questo momento" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 6f9a0198ef..0ee7b5f29e 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -631,5 +631,6 @@ "order_id": "注文ID", "gift_card_is_generated": "ギフトカードが生成されます", "open_gift_card": "オープンギフトカード", - "contact_support": "サポートに連絡する" + "contact_support": "サポートに連絡する", + "gift_cards_unavailable": "現時点では、ギフトカードはMonero、Bitcoin、Litecoinからのみ購入できます。" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 091ad1251e..d7ee849aa7 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -631,5 +631,6 @@ "order_id": "주문 ID", "gift_card_is_generated": "기프트 카드가 생성되었습니다", "open_gift_card": "기프트 카드 열기", - "contact_support": "지원팀에 문의" + "contact_support": "지원팀에 문의", + "gift_cards_unavailable": "기프트 카드는 현재 Monero, Bitcoin 및 Litecoin을 통해서만 구매할 수 있습니다." } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 5902966b1a..967cf1e386 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -631,5 +631,6 @@ "order_id": "Order-ID", "gift_card_is_generated": "Cadeaukaart is gegenereerd", "open_gift_card": "Geschenkkaart openen", - "contact_support": "Contact opnemen met ondersteuning" + "contact_support": "Contact opnemen met ondersteuning", + "gift_cards_unavailable": "Cadeaubonnen kunnen momenteel alleen worden gekocht via Monero, Bitcoin en Litecoin" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 4fe8611fc1..be58b6a174 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -631,5 +631,6 @@ "order_id": "Identyfikator zamówienia", "gift_card_is_generated": "Karta podarunkowa jest generowana", "open_gift_card": "Otwórz kartę podarunkową", - "contact_support": "Skontaktuj się z pomocą techniczną" + "contact_support": "Skontaktuj się z pomocą techniczną", + "gift_cards_unavailable": "Karty podarunkowe można obecnie kupić tylko za pośrednictwem Monero, Bitcoin i Litecoin" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 67fab322c7..50aa251005 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -631,5 +631,6 @@ "order_id": "ID do pedido", "gift_card_is_generated": "Cartão presente é gerado", "open_gift_card": "Abrir vale-presente", - "contact_support": "Contatar Suporte" + "contact_support": "Contatar Suporte", + "gift_cards_unavailable": "Os cartões-presente estão disponíveis para compra apenas através do Monero, Bitcoin e Litecoin no momento" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index adf3984b4f..ce81095124 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -631,5 +631,6 @@ "order_id": "Идентификатор заказа", "gift_card_is_generated": "Подарочная карта сгенерирована", "open_gift_card": "Открыть подарочную карту", - "contact_support": "Связаться со службой поддержки" + "contact_support": "Связаться со службой поддержки", + "gift_cards_unavailable": "В настоящее время подарочные карты можно приобрести только через Monero, Bitcoin и Litecoin." } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 60ff1212e6..04e270d578 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -630,5 +630,6 @@ "order_id": "Ідентифікатор замовлення", "gift_card_is_generated": "Подарункова картка створена", "open_gift_card": "Відкрити подарункову картку", - "contact_support": "Звернутися до служби підтримки" + "contact_support": "Звернутися до служби підтримки", + "gift_cards_unavailable": "Наразі подарункові картки можна придбати лише через Monero, Bitcoin і Litecoin" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 8d6dbc001b..14af481376 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -629,5 +629,6 @@ "order_id": "订单编号", "gift_card_is_generated": "礼品卡生成", "open_gift_card": "打开礼品卡", - "contact_support": "联系支持" + "contact_support": "联系支持", + "gift_cards_unavailable": "目前只能通过门罗币、比特币和莱特币购买礼品卡" }