Ionia (#437)

* Initial ionia service * 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 * Add ionia merchant sevic * Add anypay. Add purschase gift card. * display virtual card (#385) * display virtual card * fix formatting * Remove IoniaMerchantService from IoniaViewModel * Add hex and txKey for monero pending transaction. * Changed monero version and monero repo to cake tech. * Add anypay payment. Add filter by search for ionia, add get purchased items for ionia. * Fix for get transactions for hidden addresses for electrum wallet * Add ionia categories. * Add anypay commited info for payments. * Update UI with new fixes (#400) * Change ionia base url. Add exception throwing for error messaging for some of ionia calls. * CW-102 fix logic for ionia issues (#403) * refactor tips (#406) * refactor tips * refactor ionia tips implementation * Cw 115 implement gift cards list for ionia (#405) * Implement show purchased cards * fix padding * Fixes for getting of purchased gift cards. * Implement gift card details screen (#408) * Implement gift card details screen * Add redeem for ionia gift cards * Fix navigation after ionia opt redirection. * Fix update gift cards list. * Add payment status update for ionia. * Add usage instruction to gift card. * Add copy for ionia gift card info. * Change version for Cake Wallet ios. * Add localisation (#414) * Fixes for fiat amounts for ionia. * CW-128 marketplace screen text changes (#416) * Change text on marketplace * fix build issues * fix build * UI fixes for ionia. * UI fixes for ionia. (#421) * CW-129 ionia welcome screen text changes (#418) * update welcome text * Update localization * Cw 133 (#422) * UI fixes for ionia. * Fixes for display card item on gift cards screen. * Fix signup page (#419) * Changed tips for ionia. * Cw 132 (#425) * UI fixes for ionia. * Changed tips for ionia. * 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. * Change ionia heading font style (#427) * Fix for AddressResolver in di * Changed build number for Cake Wallet ios. * fix currency format for card details and routing for mark as redeemed (#431) * fix terms and condition overflow in ionia (#430) * fix terms and condition scroll * fix color issues * reuse * refactor widget * Remove IoniaTokenService * Change api for ionia to staging * Update versions for Cake Wallet for android and ios. * Fixes for instructions. Remove diplay error on payment status screen. * Change build versions for Cake Wallet * Add ionia sign in. * Update for discounts and statuses for ionia merch. * Fixes for qr/barcode on ionia gift card screen. * Fixed formatting for display ionia discounts. * Fix merchant.discount.toStringAsFixed issue * Add savingsPercentage to ionia merch discount. * Change build number for Cake Wallet ios and android. * Disable ionia for haven (#440) Co-authored-by: Godwin Asuquo <41484542+godilite@users.noreply.github.com>

mkyq committed Jul 28, 2022 at 18:03 UTC 418c9563fe4997e953ab67910514d7998a2c2cfa
115 files changed +7719 -177
assets/images/airplane.png
Binary files /dev/null and b/assets/images/airplane.png differ
assets/images/badge_discount.png
Binary files /dev/null and b/assets/images/badge_discount.png differ
assets/images/card.png
Binary files /dev/null and b/assets/images/card.png differ
assets/images/category.png
Binary files /dev/null and b/assets/images/category.png differ
assets/images/copy.png
Binary files /dev/null and b/assets/images/copy.png differ
assets/images/delivery.png
Binary files /dev/null and b/assets/images/delivery.png differ
assets/images/filter.png
Binary files /dev/null and b/assets/images/filter.png differ
assets/images/food.png
Binary files /dev/null and b/assets/images/food.png differ
assets/images/gaming.png
Binary files /dev/null and b/assets/images/gaming.png differ
assets/images/global.png
Binary files /dev/null and b/assets/images/global.png differ
assets/images/mastercard.png
Binary files /dev/null and b/assets/images/mastercard.png differ
assets/images/mini_search_icon.png
Binary files /dev/null and b/assets/images/mini_search_icon.png differ
assets/images/profile.png
Binary files /dev/null and b/assets/images/profile.png differ
assets/images/red_badge_discount.png
Binary files /dev/null and b/assets/images/red_badge_discount.png differ
assets/images/tshirt.png
Binary files /dev/null and b/assets/images/tshirt.png differ
assets/images/wifi.png
Binary files /dev/null and b/assets/images/wifi.png differ
cw_bitcoin/lib/bitcoin_transaction_credentials.dart
+3 -2
@@ -2,8 +2,9 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
2 import 'package:cw_core/output_info.dart';
3
4 class BitcoinTransactionCredentials {
5 - BitcoinTransactionCredentials(this.outputs, this.priority);
5 + BitcoinTransactionCredentials(this.outputs, {this.priority, this.feeRate});
6
7 final List<OutputInfo> outputs;
8 - BitcoinTransactionPriority priority;
8 + final BitcoinTransactionPriority priority;
9 + final int feeRate;
10 }
cw_bitcoin/lib/electrum_wallet.dart
+59 -33
@@ -208,8 +208,14 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
208 }
209
210 amount = credentialsAmount;
211 - fee = calculateEstimatedFee(transactionCredentials.priority, amount,
212 - outputsCount: outputs.length + 1);
211 +
212 + if (transactionCredentials.feeRate != null) {
213 + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate, amount,
214 + outputsCount: outputs.length + 1);
215 + } else {
216 + fee = calculateEstimatedFee(transactionCredentials.priority, amount,
217 + outputsCount: outputs.length + 1);
218 + }
219 } else {
220 final output = outputs.first;
221 credentialsAmount = !output.sendAll
@@ -223,9 +229,14 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
229 amount = output.sendAll || allAmount - credentialsAmount < minAmount
230 ? allAmount
231 : credentialsAmount;
226 - fee = output.sendAll || amount == allAmount
227 - ? allAmountFee
228 - : calculateEstimatedFee(transactionCredentials.priority, amount);
232 +
233 + if (output.sendAll || amount == allAmount) {
234 + fee = allAmountFee;
235 + } else if (transactionCredentials.feeRate != null) {
236 + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate, amount);
237 + } else {
238 + fee = calculateEstimatedFee(transactionCredentials.priority, amount);
239 + }
240 }
241
242 if (fee == 0) {
@@ -296,7 +307,14 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
307
308 final estimatedSize =
309 estimatedTransactionSize(inputs.length, outputs.length + 1);
299 - final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
310 + var feeAmount = 0;
311 +
312 + if (transactionCredentials.feeRate != null) {
313 + feeAmount = transactionCredentials.feeRate * estimatedSize;
314 + } else {
315 + feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
316 + }
317 +
318 final changeValue = totalInputAmount - amount - feeAmount;
319
320 if (changeValue > minAmount) {
@@ -346,43 +364,55 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
364 int outputsCount) =>
365 feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
366
367 + int feeAmountWithFeeRate(int feeRate, int inputsCount,
368 + int outputsCount) =>
369 + feeRate * estimatedTransactionSize(inputsCount, outputsCount);
370 +
371 @override
372 int calculateEstimatedFee(TransactionPriority priority, int amount,
373 {int outputsCount}) {
374 if (priority is BitcoinTransactionPriority) {
353 - int inputsCount = 0;
375 + return calculateEstimatedFeeWithFeeRate(
376 + feeRate(priority),
377 + amount,
378 + outputsCount: outputsCount);
379 + }
380
355 - if (amount != null) {
356 - int totalValue = 0;
381 + return 0;
382 + }
383
358 - for (final input in unspentCoins) {
359 - if (totalValue >= amount) {
360 - break;
361 - }
384 + int calculateEstimatedFeeWithFeeRate(int feeRate, int amount,
385 + {int outputsCount}) {
386 + int inputsCount = 0;
387
363 - if (input.isSending) {
364 - totalValue += input.value;
365 - inputsCount += 1;
366 - }
388 + if (amount != null) {
389 + int totalValue = 0;
390 +
391 + for (final input in unspentCoins) {
392 + if (totalValue >= amount) {
393 + break;
394 }
395
369 - if (totalValue < amount) return 0;
370 - } else {
371 - for (final input in unspentCoins) {
372 - if (input.isSending) {
373 - inputsCount += 1;
374 - }
396 + if (input.isSending) {
397 + totalValue += input.value;
398 + inputsCount += 1;
399 }
400 }
401
378 - // If send all, then we have no change value
379 - final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
380 -
381 - return feeAmountForPriority(
382 - priority, inputsCount, _outputsCount);
402 + if (totalValue < amount) return 0;
403 + } else {
404 + for (final input in unspentCoins) {
405 + if (input.isSending) {
406 + inputsCount += 1;
407 + }
408 + }
409 }
410
385 - return 0;
411 + // If send all, then we have no change value
412 + final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
413 +
414 + return feeAmountWithFeeRate(
415 + feeRate, inputsCount, _outputsCount);
416 }
417
418 @override
@@ -525,10 +555,6 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
555 final addressHashes = <String, BitcoinAddressRecord>{};
556 final normalizedHistories = <Map<String, dynamic>>[];
557 walletAddresses.addresses.forEach((addressRecord) {
528 - if (addressRecord.isHidden) {
529 - return;
530 - }
531 -
558 final sh = scriptHash(addressRecord.address, networkType: networkType);
559 addressHashes[sh] = addressRecord;
560 });
cw_bitcoin/lib/pending_bitcoin_transaction.dart
+3
@@ -24,6 +24,9 @@ class PendingBitcoinTransaction with PendingTransaction {
24 @override
25 String get id => _tx.getId();
26
27 + @override
28 + String get hex => _tx.toHex();
29 +
30 @override
31 String get amountFormatted => bitcoinAmountToString(amount: amount);
32
cw_core/lib/pending_transaction.dart
+1
@@ -2,6 +2,7 @@ mixin PendingTransaction {
2 String get id;
3 String get amountFormatted;
4 String get feeFormatted;
5 + String get hex;
6
7 Future<void> commit();
8 }
\ No newline at end of file
cw_haven/lib/pending_haven_transaction.dart
+3
@@ -22,6 +22,9 @@ class PendingHavenTransaction with PendingTransaction {
22 @override
23 String get id => pendingTransactionDescription.hash;
24
25 + @override
26 + String get hex => '';
27 +
28 @override
29 String get amountFormatted => AmountConverter.amountIntToString(
30 cryptoCurrency, pendingTransactionDescription.amount);
cw_haven/pubspec.lock
-7
@@ -169,13 +169,6 @@ packages:
169 relative: true
170 source: path
171 version: "0.0.1"
172 - cw_monero:
173 - dependency: "direct main"
174 - description:
175 - path: "../cw_monero"
176 - relative: true
177 - source: path
178 - version: "0.0.1"
172 dart_style:
173 dependency: transitive
174 description:
cw_monero/ios/Classes/monero_api.cpp
+4 -2
@@ -166,6 +166,8 @@ extern "C"
166 uint64_t amount;
167 uint64_t fee;
168 char *hash;
169 + char *hex;
170 + char *txKey;
171 Monero::PendingTransaction *transaction;
172
173 PendingTransactionRaw(Monero::PendingTransaction *_transaction)
@@ -174,6 +176,8 @@ extern "C"
176 amount = _transaction->amount();
177 fee = _transaction->fee();
178 hash = strdup(_transaction->txid()[0].c_str());
179 + hex = strdup(_transaction->hex()[0].c_str());
180 + txKey = strdup(_transaction->txKey()[0].c_str());
181 }
182 };
183
@@ -228,8 +232,6 @@ extern "C"
232
233 bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error)
234 {
231 - Monero::WalletManagerFactory::setLogLevel(4);
232 -
235 Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
236 Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager();
237 Monero::Wallet *wallet = walletManager->createWallet(path, password, language, _networkType);
cw_monero/lib/api/structs/pending_transaction.dart
+17 -1
@@ -10,14 +10,30 @@ class PendingTransactionRaw extends Struct {
10
11 Pointer<Utf8> hash;
12
13 + Pointer<Utf8> hex;
14 +
15 + Pointer<Utf8> txKey;
16 +
17 String getHash() => Utf8.fromUtf8(hash);
18 +
19 + String getHex() => Utf8.fromUtf8(hex);
20 +
21 + String getKey() => Utf8.fromUtf8(txKey);
22 }
23
24 class PendingTransactionDescription {
17 - PendingTransactionDescription({this.amount, this.fee, this.hash, this.pointerAddress});
25 + PendingTransactionDescription({
26 + this.amount,
27 + this.fee,
28 + this.hash,
29 + this.hex,
30 + this.txKey,
31 + this.pointerAddress});
32
33 final int amount;
34 final int fee;
35 final String hash;
36 + final String hex;
37 + final String txKey;
38 final int pointerAddress;
39 }
\ No newline at end of file
cw_monero/lib/api/transaction_history.dart
+4
@@ -104,6 +104,8 @@ PendingTransactionDescription createTransactionSync(
104 amount: pendingTransactionRawPointer.ref.amount,
105 fee: pendingTransactionRawPointer.ref.fee,
106 hash: pendingTransactionRawPointer.ref.getHash(),
107 + hex: pendingTransactionRawPointer.ref.getHex(),
108 + txKey: pendingTransactionRawPointer.ref.getKey(),
109 pointerAddress: pendingTransactionRawPointer.address);
110 }
111
@@ -157,6 +159,8 @@ PendingTransactionDescription createTransactionMultDestSync(
159 amount: pendingTransactionRawPointer.ref.amount,
160 fee: pendingTransactionRawPointer.ref.fee,
161 hash: pendingTransactionRawPointer.ref.getHash(),
162 + hex: pendingTransactionRawPointer.ref.getHex(),
163 + txKey: pendingTransactionRawPointer.ref.getKey(),
164 pointerAddress: pendingTransactionRawPointer.address);
165 }
166
cw_monero/lib/pending_monero_transaction.dart
+5
@@ -22,6 +22,11 @@ class PendingMoneroTransaction with PendingTransaction {
22 @override
23 String get id => pendingTransactionDescription.hash;
24
25 + @override
26 + String get hex => pendingTransactionDescription.hex;
27 +
28 + String get txKey => pendingTransactionDescription.txKey;
29 +
30 @override
31 String get amountFormatted => AmountConverter.amountIntToString(
32 CryptoCurrency.xmr, pendingTransactionDescription.amount);
ios/Podfile.lock
+6
@@ -57,6 +57,8 @@ PODS:
57 - Flutter
58 - cw_shared_external/Sodium (0.0.1):
59 - Flutter
60 + - device_display_brightness (0.0.1):
61 + - Flutter
62 - devicelocale (0.0.1):
63 - Flutter
64 - DKImagePickerController/Core (4.3.2):
@@ -134,6 +136,7 @@ DEPENDENCIES:
136 - cw_haven (from `.symlinks/plugins/cw_haven/ios`)
137 - cw_monero (from `.symlinks/plugins/cw_monero/ios`)
138 - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
139 + - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
140 - devicelocale (from `.symlinks/plugins/devicelocale/ios`)
141 - esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`)
142 - file_picker (from `.symlinks/plugins/file_picker/ios`)
@@ -174,6 +177,8 @@ EXTERNAL SOURCES:
177 :path: ".symlinks/plugins/cw_monero/ios"
178 cw_shared_external:
179 :path: ".symlinks/plugins/cw_shared_external/ios"
180 + device_display_brightness:
181 + :path: ".symlinks/plugins/device_display_brightness/ios"
182 devicelocale:
183 :path: ".symlinks/plugins/devicelocale/ios"
184 esys_flutter_share:
@@ -211,6 +216,7 @@ SPEC CHECKSUMS:
216 cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
217 cw_monero: 88c5e7aa596c6848330750f5f8bcf05fb9c66375
218 cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
219 + device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
220 devicelocale: b22617f40038496deffba44747101255cee005b0
221 DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
222 DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
lib/anypay/any_pay_chain.dart new
+5
@@ -0,0 +1,5 @@
1 +class AnyPayChain {
2 + static const xmr = 'XMR';
3 + static const btc = 'BTC';
4 + static const ltc = 'LTC';
5 +}
\ No newline at end of file
lib/anypay/any_pay_payment.dart new
+64
@@ -0,0 +1,64 @@
1 +import 'package:cake_wallet/anypay/any_pay_chain.dart';
2 +import 'package:cw_bitcoin/bitcoin_amount_format.dart';
3 +import 'package:cw_core/monero_amount_format.dart';
4 +import 'package:flutter/foundation.dart';
5 +import 'package:cake_wallet/anypay/any_pay_payment_instruction.dart';
6 +
7 +class AnyPayPayment {
8 + AnyPayPayment({
9 + @required this.time,
10 + @required this.expires,
11 + @required this.memo,
12 + @required this.paymentUrl,
13 + @required this.paymentId,
14 + @required this.chain,
15 + @required this.network,
16 + @required this.instructions});
17 +
18 + factory AnyPayPayment.fromMap(Map<String, dynamic> obj) {
19 + final instructions = (obj['instructions'] as List<dynamic>)
20 + .map((dynamic instruction) => AnyPayPaymentInstruction.fromMap(instruction as Map<String, dynamic>))
21 + .toList();
22 + return AnyPayPayment(
23 + time: DateTime.parse(obj['time'] as String),
24 + expires: DateTime.parse(obj['expires'] as String),
25 + memo: obj['memo'] as String,
26 + paymentUrl: obj['paymentUrl'] as String,
27 + paymentId: obj['paymentId'] as String,
28 + chain: obj['chain'] as String,
29 + network: obj['network'] as String,
30 + instructions: instructions);
31 + }
32 +
33 + final DateTime time;
34 + final DateTime expires;
35 + final String memo;
36 + final String paymentUrl;
37 + final String paymentId;
38 + final String chain;
39 + final String network;
40 + final List<AnyPayPaymentInstruction> instructions;
41 +
42 + String get totalAmount {
43 + final total = instructions
44 + .fold<int>(0, (int acc, instruction) => acc + instruction.outputs
45 + .fold<int>(0, (int outAcc, out) => outAcc + out.amount));
46 + switch (chain) {
47 + case AnyPayChain.xmr:
48 + return moneroAmountToString(amount: total);
49 + case AnyPayChain.btc:
50 + return bitcoinAmountToString(amount: total);
51 + case AnyPayChain.ltc:
52 + return bitcoinAmountToString(amount: total);
53 + default:
54 + return null;
55 + }
56 + }
57 +
58 + List<String> get outAddresses {
59 + return instructions
60 + .map((instuction) => instuction.outputs.map((out) => out.address))
61 + .expand((e) => e)
62 + .toList();
63 + }
64 +}
\ No newline at end of file
lib/anypay/any_pay_payment_committed_info.dart new
+17
@@ -0,0 +1,17 @@
1 +import 'package:flutter/foundation.dart';
2 +import 'package:cake_wallet/anypay/any_pay_trasnaction.dart';
3 +
4 +class AnyPayPaymentCommittedInfo {
5 + const AnyPayPaymentCommittedInfo({
6 + @required this.uri,
7 + @required this.currency,
8 + @required this.chain,
9 + @required this.transactions,
10 + @required this.memo});
11 +
12 + final String uri;
13 + final String currency;
14 + final String chain;
15 + final List<AnyPayTransaction> transactions;
16 + final String memo;
17 +}
\ No newline at end of file
lib/anypay/any_pay_payment_instruction.dart new
+32
@@ -0,0 +1,32 @@
1 +import 'package:flutter/foundation.dart';
2 +import 'package:cake_wallet/anypay/any_pay_payment_instruction_output.dart';
3 +
4 +class AnyPayPaymentInstruction {
5 + AnyPayPaymentInstruction({
6 + @required this.type,
7 + @required this.requiredFeeRate,
8 + @required this.txKey,
9 + @required this.txHash,
10 + @required this.outputs});
11 +
12 + factory AnyPayPaymentInstruction.fromMap(Map<String, dynamic> obj) {
13 + final outputs = (obj['outputs'] as List<dynamic>)
14 + .map((dynamic out) =>
15 + AnyPayPaymentInstructionOutput.fromMap(out as Map<String, dynamic>))
16 + .toList();
17 + return AnyPayPaymentInstruction(
18 + type: obj['type'] as String,
19 + requiredFeeRate: obj['requiredFeeRate'] as int,
20 + txKey: obj['tx_key'] as bool,
21 + txHash: obj['tx_hash'] as bool,
22 + outputs: outputs);
23 + }
24 +
25 + static const transactionType = 'transaction';
26 +
27 + final String type;
28 + final int requiredFeeRate;
29 + final bool txKey;
30 + final bool txHash;
31 + final List<AnyPayPaymentInstructionOutput> outputs;
32 +}
\ No newline at end of file
lib/anypay/any_pay_payment_instruction_output.dart new
+10
@@ -0,0 +1,10 @@
1 +class AnyPayPaymentInstructionOutput {
2 + const AnyPayPaymentInstructionOutput(this.address, this.amount);
3 +
4 + factory AnyPayPaymentInstructionOutput.fromMap(Map<String, dynamic> obj) {
5 + return AnyPayPaymentInstructionOutput(obj['address'] as String, obj['amount'] as int);
6 + }
7 +
8 + final String address;
9 + final int amount;
10 +}
\ No newline at end of file
lib/anypay/any_pay_trasnaction.dart new
+9
@@ -0,0 +1,9 @@
1 +import 'package:flutter/foundation.dart';
2 +
3 +class AnyPayTransaction {
4 + const AnyPayTransaction(this.tx, {@required this.id, @required this.key});
5 +
6 + final String tx;
7 + final String id;
8 + final String key;
9 +}
\ No newline at end of file
lib/anypay/anypay_api.dart new
+92
@@ -0,0 +1,92 @@
1 +import 'dart:convert';
2 +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
3 +import 'package:flutter/foundation.dart';
4 +import 'package:http/http.dart';
5 +import 'package:cw_core/crypto_currency.dart';
6 +import 'package:cake_wallet/anypay/any_pay_payment.dart';
7 +import 'package:cake_wallet/anypay/any_pay_trasnaction.dart';
8 +
9 +class AnyPayApi {
10 + static const contentTypePaymentRequest = 'application/payment-request';
11 + static const contentTypePayment = 'application/payment';
12 + static const xPayproVersion = '2';
13 +
14 + static String chainByScheme(String scheme) {
15 + switch (scheme.toLowerCase()) {
16 + case 'monero':
17 + return CryptoCurrency.xmr.title;
18 + case 'bitcoin':
19 + return CryptoCurrency.btc.title;
20 + case 'litecoin':
21 + return CryptoCurrency.ltc.title;
22 + default:
23 + return '';
24 + }
25 + }
26 +
27 + static CryptoCurrency currencyByScheme(String scheme) {
28 + switch (scheme.toLowerCase()) {
29 + case 'monero':
30 + return CryptoCurrency.xmr;
31 + case 'bitcoin':
32 + return CryptoCurrency.btc;
33 + case 'litecoin':
34 + return CryptoCurrency.ltc;
35 + default:
36 + return null;
37 + }
38 + }
39 +
40 + Future<AnyPayPayment> paymentRequest(String uri) async {
41 + final fragments = uri.split(':?r=');
42 + final scheme = fragments.first;
43 + final url = fragments[1];
44 + final headers = <String, String>{
45 + 'Content-Type': contentTypePaymentRequest,
46 + 'X-Paypro-Version': xPayproVersion,
47 + 'Accept': '*/*',};
48 + final body = <String, dynamic>{
49 + 'chain': chainByScheme(scheme),
50 + 'currency': currencyByScheme(scheme).title};
51 + final response = await post(url, headers: headers, body: utf8.encode(json.encode(body)));
52 +
53 + if (response.statusCode != 200) {
54 + return null;
55 + }
56 +
57 + final decodedBody = json.decode(response.body) as Map<String, dynamic>;
58 + return AnyPayPayment.fromMap(decodedBody);
59 + }
60 +
61 + Future<AnyPayPaymentCommittedInfo> payment(
62 + String uri,
63 + {@required String chain,
64 + @required String currency,
65 + @required List<AnyPayTransaction> transactions}) async {
66 + final headers = <String, String>{
67 + 'Content-Type': contentTypePayment,
68 + 'X-Paypro-Version': xPayproVersion,
69 + 'Accept': '*/*',};
70 + final body = <String, dynamic>{
71 + 'chain': chain,
72 + 'currency': currency,
73 + 'transactions': transactions.map((tx) => {'tx': tx.tx, 'tx_hash': tx.id, 'tx_key': tx.key}).toList()};
74 + final response = await post(uri, headers: headers, body: utf8.encode(json.encode(body)));
75 + if (response.statusCode == 400) {
76 + final decodedBody = json.decode(response.body) as Map<String, dynamic>;
77 + throw Exception(decodedBody['message'] as String);
78 + }
79 +
80 + if (response.statusCode != 200) {
81 + throw Exception('Unexpected response');
82 + }
83 +
84 + final decodedBody = json.decode(response.body) as Map<String, dynamic>;
85 + return AnyPayPaymentCommittedInfo(
86 + uri: uri,
87 + currency: currency,
88 + chain: chain,
89 + transactions: transactions,
90 + memo: decodedBody['memo'] as String);
91 + }
92 +}
\ No newline at end of file
lib/bitcoin/cw_bitcoin.dart
+10 -2
@@ -55,7 +55,7 @@ class CWBitcoin extends Bitcoin {
55 }
56
57 @override
58 - Object createBitcoinTransactionCredentials(List<Output> outputs, TransactionPriority priority)
58 + Object createBitcoinTransactionCredentials(List<Output> outputs, {TransactionPriority priority, int feeRate})
59 => BitcoinTransactionCredentials(
60 outputs.map((out) => OutputInfo(
61 fiatAmount: out.fiatAmount,
@@ -67,7 +67,15 @@ class CWBitcoin extends Bitcoin {
67 isParsedAddress: out.isParsedAddress,
68 formattedCryptoAmount: out.formattedCryptoAmount))
69 .toList(),
70 - priority as BitcoinTransactionPriority);
70 + priority: priority != null ? priority as BitcoinTransactionPriority : null,
71 + feeRate: feeRate);
72 +
73 + @override
74 + Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs, {TransactionPriority priority, int feeRate})
75 + => BitcoinTransactionCredentials(
76 + outputs,
77 + priority: priority != null ? priority as BitcoinTransactionPriority : null,
78 + feeRate: feeRate);
79
80 @override
81 List<String> getAddresses(Object wallet) {
lib/core/email_validator.dart new
+11
@@ -0,0 +1,11 @@
1 +import 'package:cake_wallet/core/validator.dart';
2 +import 'package:cake_wallet/generated/i18n.dart';
3 +
4 +class EmailValidator extends TextValidator {
5 + EmailValidator()
6 + : super(
7 + errorMessage: 'Invalid email address',
8 + pattern:
9 + '^[^@]+@[^@]+\.[^@]+',
10 + );
11 +}
lib/di.dart
+121 -9
@@ -1,10 +1,27 @@
1 import 'package:cake_wallet/core/yat_service.dart';
2 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
3 import 'package:cake_wallet/entities/wake_lock.dart';
4 +import 'package:cake_wallet/ionia/ionia_anypay.dart';
5 +import 'package:cake_wallet/ionia/ionia_category.dart';
6 +import 'package:cake_wallet/ionia/ionia_gift_card.dart';
7 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
8 +import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
9 +import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart';
10 +import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
11 +import 'package:cake_wallet/ionia/ionia_service.dart';
12 +import 'package:cake_wallet/ionia/ionia_api.dart';
13 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
14 import 'package:cake_wallet/monero/monero.dart';
15 import 'package:cake_wallet/haven/haven.dart';
16 import 'package:cake_wallet/bitcoin/bitcoin.dart';
17 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart';
18 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart';
19 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart';
20 +import 'package:cake_wallet/src/screens/ionia/ionia.dart';
21 import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
22 +import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
23 +import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
24 +import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
25 import 'package:cw_core/unspent_coins_info.dart';
26 import 'package:cake_wallet/core/backup_service.dart';
27 import 'package:cw_core/wallet_service.dart';
@@ -100,6 +117,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
117 import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
118 import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
119 import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
120 +import 'package:flutter/foundation.dart';
121 import 'package:flutter/widgets.dart';
122 import 'package:get_it/get_it.dart';
123 import 'package:hive/hive.dart';
@@ -123,6 +141,12 @@ import 'package:cake_wallet/entities/template.dart';
141 import 'package:cake_wallet/exchange/exchange_template.dart';
142 import 'package:cake_wallet/.secrets.g.dart' as secrets;
143 import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart';
144 +import 'package:cake_wallet/anypay/anypay_api.dart';
145 +import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart';
146 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart';
147 +import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart';
148 +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
149 +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
150 import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
151 import 'package:cake_wallet/core/wallet_loading_service.dart';
152
@@ -261,7 +285,6 @@ Future setup(
285 fiatConvertationStore: getIt.get<FiatConversionStore>()));
286
287 getIt.registerFactory(() => DashboardViewModel(
264 -
288 balanceViewModel: getIt.get<BalanceViewModel>(),
289 appStore: getIt.get<AppStore>(),
290 tradesStore: getIt.get<TradesStore>(),
@@ -560,10 +583,6 @@ Future setup(
583
584 getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
585
563 - getIt.registerFactory(() => EditBackupPasswordViewModel(
564 - getIt.get<FlutterSecureStorage>(), getIt.get<SecretStore>())
565 - ..init());
566 -
586 getIt.registerFactory(
587 () => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
588
@@ -596,10 +615,7 @@ Future setup(
615 final url = args.first as String;
616 final buyViewModel = args[1] as BuyViewModel;
617
599 - return BuyWebViewPage(
600 - buyViewModel: buyViewModel,
601 - ordersStore: getIt.get<OrdersStore>(),
602 - url: url);
618 + return BuyWebViewPage(buyViewModel: buyViewModel, ordersStore: getIt.get<OrdersStore>(), url: url);
619 });
620
621 getIt.registerFactoryParam<OrderDetailsViewModel, Order, void>((order, _) {
@@ -649,6 +665,102 @@ Future setup(
665
666 getIt.registerFactoryParam<FullscreenQRPage, String, bool>(
667 (String qrData, bool isLight) => FullscreenQRPage(qrData: qrData, isLight: isLight,));
668 +
669 + getIt.registerFactory(() => IoniaApi());
670 +
671 + getIt.registerFactory(() => AnyPayApi());
672 +
673 + getIt.registerFactory<IoniaService>(
674 + () => IoniaService(getIt.get<FlutterSecureStorage>(), getIt.get<IoniaApi>()));
675 +
676 + getIt.registerFactory<IoniaAnyPay>(
677 + () => IoniaAnyPay(
678 + getIt.get<IoniaService>(),
679 + getIt.get<AnyPayApi>(),
680 + getIt.get<AppStore>().wallet));
681 +
682 + getIt.registerFactory<IoniaFilterViewModel>(() => IoniaFilterViewModel());
683 +
684 + getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
685 +
686 + getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));
687 +
688 + getIt.registerFactoryParam<IoniaMerchPurchaseViewModel, double, IoniaMerchant>((double amount, merchant) {
689 + return IoniaMerchPurchaseViewModel(
690 + ioniaAnyPayService: getIt.get<IoniaAnyPay>(),
691 + amount: amount,
692 + ioniaMerchant: merchant,
693 + );
694 + });
695 +
696 + getIt.registerFactoryParam<IoniaBuyCardViewModel, IoniaMerchant, void>((IoniaMerchant merchant, _) {
697 + return IoniaBuyCardViewModel(ioniaMerchant: merchant);
698 + });
699 +
700 + getIt.registerFactory(() => IoniaAccountViewModel(ioniaService: getIt.get<IoniaService>()));
701 +
702 + getIt.registerFactory(() => IoniaCreateAccountPage(getIt.get<IoniaAuthViewModel>()));
703 +
704 + getIt.registerFactory(() => IoniaLoginPage(getIt.get<IoniaAuthViewModel>()));
705 +
706 + getIt.registerFactoryParam<IoniaVerifyIoniaOtp, List, void>((List args, _) {
707 + final email = args.first as String;
708 + final isSignIn = args[1] as bool;
709 +
710 + return IoniaVerifyIoniaOtp(getIt.get<IoniaAuthViewModel>(), email, isSignIn);
711 + });
712 +
713 + getIt.registerFactory(() => IoniaWelcomePage(getIt.get<IoniaGiftCardsListViewModel>()));
714 +
715 + getIt.registerFactoryParam<IoniaBuyGiftCardPage, List, void>((List args, _) {
716 + final merchant = args.first as IoniaMerchant;
717 +
718 + return IoniaBuyGiftCardPage(getIt.get<IoniaBuyCardViewModel>(param1: merchant));
719 + });
720 +
721 + getIt.registerFactoryParam<IoniaBuyGiftCardDetailPage, List, void>((List args, _) {
722 + final amount = args.first as double;
723 + final merchant = args.last as IoniaMerchant;
724 + return IoniaBuyGiftCardDetailPage(getIt.get<IoniaMerchPurchaseViewModel>(param1: amount, param2: merchant));
725 + });
726 +
727 + getIt.registerFactoryParam<IoniaGiftCardDetailsViewModel, IoniaGiftCard, void>((IoniaGiftCard giftCard, _) {
728 + return IoniaGiftCardDetailsViewModel(
729 + ioniaService: getIt.get<IoniaService>(),
730 + giftCard: giftCard);
731 + });
732 +
733 + getIt.registerFactoryParam<IoniaGiftCardDetailPage, IoniaGiftCard, void>((IoniaGiftCard giftCard, _) {
734 + return IoniaGiftCardDetailPage(getIt.get<IoniaGiftCardDetailsViewModel>(param1: giftCard));
735 + });
736 +
737 + getIt.registerFactoryParam<IoniaCustomTipPage, List, void>((List args, _) {
738 + final amount = args.first as String;
739 + final merchant = args.last as IoniaMerchant;
740 +
741 + return IoniaCustomTipPage(getIt.get<IoniaMerchPurchaseViewModel>(param1: amount, param2: merchant));
742 + });
743 +
744 + getIt.registerFactory(() => IoniaManageCardsPage(getIt.get<IoniaGiftCardsListViewModel>()));
745 +
746 + getIt.registerFactory(() => IoniaDebitCardPage(getIt.get<IoniaGiftCardsListViewModel>()));
747 +
748 + getIt.registerFactory(() => IoniaActivateDebitCardPage(getIt.get<IoniaGiftCardsListViewModel>()));
749 +
750 + getIt.registerFactory(() => IoniaAccountPage(getIt.get<IoniaAccountViewModel>()));
751 +
752 + getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get<IoniaAccountViewModel>()));
753 +
754 + getIt.registerFactoryParam<IoniaPaymentStatusViewModel, IoniaAnyPayPaymentInfo, AnyPayPaymentCommittedInfo>(
755 + (IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo)
756 + => IoniaPaymentStatusViewModel(
757 + getIt.get<IoniaService>(),
758 + paymentInfo: paymentInfo,
759 + committedInfo: committedInfo));
760 +
761 + getIt.registerFactoryParam<IoniaPaymentStatusPage, IoniaAnyPayPaymentInfo, AnyPayPaymentCommittedInfo>(
762 + (IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo)
763 + => IoniaPaymentStatusPage(getIt.get<IoniaPaymentStatusViewModel>(param1: paymentInfo, param2: committedInfo)));
764
765 _isSetupFinished = true;
766 }
lib/ionia/ionia_any_pay_payment_info.dart new
+9
@@ -0,0 +1,9 @@
1 +import 'package:cake_wallet/anypay/any_pay_payment.dart';
2 +import 'package:cake_wallet/ionia/ionia_order.dart';
3 +
4 +class IoniaAnyPayPaymentInfo {
5 + const IoniaAnyPayPaymentInfo(this.ioniaOrder, this.anyPayPayment);
6 +
7 + final IoniaOrder ioniaOrder;
8 + final AnyPayPayment anyPayPayment;
9 +}
lib/ionia/ionia_anypay.dart new
+92
@@ -0,0 +1,92 @@
1 +import 'package:flutter/foundation.dart';
2 +import 'package:cw_core/monero_amount_format.dart';
3 +import 'package:cw_core/monero_transaction_priority.dart';
4 +import 'package:cw_core/output_info.dart';
5 +import 'package:cw_core/pending_transaction.dart';
6 +import 'package:cw_core/wallet_base.dart';
7 +import 'package:cake_wallet/anypay/any_pay_payment.dart';
8 +import 'package:cake_wallet/anypay/any_pay_payment_instruction.dart';
9 +import 'package:cake_wallet/ionia/ionia_service.dart';
10 +import 'package:cake_wallet/anypay/anypay_api.dart';
11 +import 'package:cake_wallet/anypay/any_pay_chain.dart';
12 +import 'package:cake_wallet/anypay/any_pay_trasnaction.dart';
13 +import 'package:cake_wallet/bitcoin/bitcoin.dart';
14 +import 'package:cake_wallet/monero/monero.dart';
15 +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
16 +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
17 +import 'package:cake_wallet/ionia/ionia_order.dart';
18 +
19 +class IoniaAnyPay {
20 + IoniaAnyPay(this.ioniaService, this.anyPayApi, this.wallet);
21 +
22 + final IoniaService ioniaService;
23 + final AnyPayApi anyPayApi;
24 + final WalletBase wallet;
25 +
26 + Future<IoniaAnyPayPaymentInfo> purchase({
27 + @required String merchId,
28 + @required double amount}) async {
29 + final invoice = await ioniaService.purchaseGiftCard(
30 + merchId: merchId,
31 + amount: amount,
32 + currency: wallet.currency.title.toUpperCase());
33 + final anypayPayment = await anyPayApi.paymentRequest(invoice.uri);
34 + return IoniaAnyPayPaymentInfo(invoice, anypayPayment);
35 + }
36 +
37 + Future<AnyPayPaymentCommittedInfo> commitInvoice(AnyPayPayment payment) async {
38 + final transactionCredentials = payment.instructions
39 + .where((instruction) => instruction.type == AnyPayPaymentInstruction.transactionType)
40 + .map((AnyPayPaymentInstruction instruction) {
41 + switch(payment.chain.toUpperCase()) {
42 + case AnyPayChain.xmr:
43 + return monero.createMoneroTransactionCreationCredentialsRaw(
44 + outputs: instruction.outputs.map((out) =>
45 + OutputInfo(
46 + isParsedAddress: false,
47 + address: out.address,
48 + cryptoAmount: moneroAmountToString(amount: out.amount),
49 + sendAll: false)).toList(),
50 + priority: MoneroTransactionPriority.medium); // FIXME: HARDCODED PRIORITY
51 + case AnyPayChain.btc:
52 + return bitcoin.createBitcoinTransactionCredentialsRaw(
53 + instruction.outputs.map((out) =>
54 + OutputInfo(
55 + isParsedAddress: false,
56 + address: out.address,
57 + formattedCryptoAmount: out.amount,
58 + sendAll: false)).toList(),
59 + feeRate: instruction.requiredFeeRate);
60 + case AnyPayChain.ltc:
61 + return bitcoin.createBitcoinTransactionCredentialsRaw(
62 + instruction.outputs.map((out) =>
63 + OutputInfo(
64 + isParsedAddress: false,
65 + address: out.address,
66 + formattedCryptoAmount: out.amount,
67 + sendAll: false)).toList(),
68 + feeRate: instruction.requiredFeeRate);
69 + default:
70 + throw Exception('Incorrect transaction chain: ${payment.chain.toUpperCase()}');
71 + }
72 + });
73 + final transactions = (await Future.wait(transactionCredentials
74 + .map((Object credentials) async => await wallet.createTransaction(credentials))))
75 + .map((PendingTransaction pendingTransaction) {
76 + switch (payment.chain.toUpperCase()){
77 + case AnyPayChain.xmr:
78 + final ptx = monero.pendingTransactionInfo(pendingTransaction);
79 + return AnyPayTransaction(ptx['hex'], id: ptx['id'], key: ptx['key']);
80 + default:
81 + return AnyPayTransaction(pendingTransaction.hex, id: pendingTransaction.id, key: null);
82 + }
83 + })
84 + .toList();
85 +
86 + return await anyPayApi.payment(
87 + payment.paymentUrl,
88 + chain: payment.chain,
89 + currency: payment.chain,
90 + transactions: transactions);
91 + }
92 +}
\ No newline at end of file
lib/ionia/ionia_api.dart new
+444
@@ -0,0 +1,444 @@
1 +import 'dart:convert';
2 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
3 +import 'package:cake_wallet/ionia/ionia_order.dart';
4 +import 'package:flutter/foundation.dart';
5 +import 'package:http/http.dart';
6 +import 'package:cake_wallet/ionia/ionia_user_credentials.dart';
7 +import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
8 +import 'package:cake_wallet/ionia/ionia_category.dart';
9 +import 'package:cake_wallet/ionia/ionia_gift_card.dart';
10 +
11 +class IoniaApi {
12 + static const baseUri = 'apistaging.ionia.io';
13 + static const pathPrefix = 'cake';
14 + static final createUserUri = Uri.https(baseUri, '/$pathPrefix/CreateUser');
15 + static final verifyEmailUri = Uri.https(baseUri, '/$pathPrefix/VerifyEmail');
16 + static final signInUri = Uri.https(baseUri, '/$pathPrefix/SignIn');
17 + static final createCardUri = Uri.https(baseUri, '/$pathPrefix/CreateCard');
18 + static final getCardsUri = Uri.https(baseUri, '/$pathPrefix/GetCards');
19 + static final getMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/GetMerchants');
20 + static final getMerchantsByFilterUrl = Uri.https(baseUri, '/$pathPrefix/GetMerchantsByFilter');
21 + static final getPurchaseMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/PurchaseGiftCard');
22 + static final getCurrentUserGiftCardSummariesUrl = Uri.https(baseUri, '/$pathPrefix/GetCurrentUserGiftCardSummaries');
23 + static final changeGiftCardUrl = Uri.https(baseUri, '/$pathPrefix/ChargeGiftCard');
24 + static final getGiftCardUrl = Uri.https(baseUri, '/$pathPrefix/GetGiftCard');
25 + static final getPaymentStatusUrl = Uri.https(baseUri, '/$pathPrefix/PaymentStatus');
26 +
27 + // Create user
28 +
29 + Future<String> createUser(String email, {@required String clientId}) async {
30 + final headers = <String, String>{'clientId': clientId};
31 + final query = <String, String>{'emailAddress': email};
32 + final uri = createUserUri.replace(queryParameters: query);
33 + final response = await put(uri, headers: headers);
34 +
35 + if (response.statusCode != 200) {
36 + // throw exception
37 + return null;
38 + }
39 +
40 + final bodyJson = json.decode(response.body) as Map<String, Object>;
41 + final data = bodyJson['Data'] as Map<String, Object>;
42 + final isSuccessful = bodyJson['Successful'] as bool;
43 +
44 + if (!isSuccessful) {
45 + throw Exception(data['ErrorMessage'] as String);
46 + }
47 +
48 + return data['username'] as String;
49 + }
50 +
51 + // Verify email
52 +
53 + Future<IoniaUserCredentials> verifyEmail({
54 + @required String username,
55 + @required String email,
56 + @required String code,
57 + @required String clientId}) async {
58 + final headers = <String, String>{
59 + 'clientId': clientId,
60 + 'username': username,
61 + 'EmailAddress': email};
62 + final query = <String, String>{'verificationCode': code};
63 + final uri = verifyEmailUri.replace(queryParameters: query);
64 + final response = await put(uri, headers: headers);
65 +
66 + if (response.statusCode != 200) {
67 + // throw exception
68 + return null;
69 + }
70 +
71 + final bodyJson = json.decode(response.body) as Map<String, Object>;
72 + final data = bodyJson['Data'] as Map<String, Object>;
73 + final isSuccessful = bodyJson['Successful'] as bool;
74 +
75 + if (!isSuccessful) {
76 + throw Exception(bodyJson['ErrorMessage'] as String);
77 + }
78 +
79 + final password = data['password'] as String;
80 + username = data['username'] as String;
81 + return IoniaUserCredentials(username, password);
82 + }
83 +
84 + // Sign In
85 +
86 + Future<String> signIn(String email, {@required String clientId}) async {
87 + final headers = <String, String>{'clientId': clientId};
88 + final query = <String, String>{'emailAddress': email};
89 + final uri = signInUri.replace(queryParameters: query);
90 + final response = await put(uri, headers: headers);
91 +
92 + if (response.statusCode != 200) {
93 + // throw exception
94 + return null;
95 + }
96 +
97 + final bodyJson = json.decode(response.body) as Map<String, Object>;
98 + final data = bodyJson['Data'] as Map<String, Object>;
99 + final isSuccessful = bodyJson['Successful'] as bool;
100 +
101 + if (!isSuccessful) {
102 + throw Exception(data['ErrorMessage'] as String);
103 + }
104 +
105 + return data['username'] as String;
106 + }
107 +
108 + // Get virtual card
109 +
110 + Future<IoniaVirtualCard> getCards({
111 + @required String username,
112 + @required String password,
113 + @required String clientId}) async {
114 + final headers = <String, String>{
115 + 'clientId': clientId,
116 + 'username': username,
117 + 'password': password};
118 + final response = await post(getCardsUri, headers: headers);
119 +
120 + if (response.statusCode != 200) {
121 + // throw exception
122 + return null;
123 + }
124 +
125 + final bodyJson = json.decode(response.body) as Map<String, Object>;
126 + final data = bodyJson['Data'] as Map<String, Object>;
127 + final isSuccessful = bodyJson['Successful'] as bool;
128 +
129 + if (!isSuccessful) {
130 + throw Exception(data['message'] as String);
131 + }
132 +
133 + final virtualCard = data['VirtualCard'] as Map<String, Object>;
134 + return IoniaVirtualCard.fromMap(virtualCard);
135 + }
136 +
137 + // Create virtual card
138 +
139 + Future<IoniaVirtualCard> createCard({
140 + @required String username,
141 + @required String password,
142 + @required String clientId}) async {
143 + final headers = <String, String>{
144 + 'clientId': clientId,
145 + 'username': username,
146 + 'password': password};
147 + final response = await post(createCardUri, headers: headers);
148 +
149 + if (response.statusCode != 200) {
150 + // throw exception
151 + return null;
152 + }
153 +
154 + final bodyJson = json.decode(response.body) as Map<String, Object>;
155 + final data = bodyJson['Data'] as Map<String, Object>;
156 + final isSuccessful = bodyJson['Successful'] as bool;
157 +
158 + if (!isSuccessful) {
159 + throw Exception(data['message'] as String);
160 + }
161 +
162 + return IoniaVirtualCard.fromMap(data);
163 + }
164 +
165 + // Get Merchants
166 +
167 + Future<List<IoniaMerchant>> getMerchants({
168 + @required String username,
169 + @required String password,
170 + @required String clientId}) async {
171 + final headers = <String, String>{
172 + 'clientId': clientId,
173 + 'username': username,
174 + 'password': password};
175 + final response = await post(getMerchantsUrl, headers: headers);
176 +
177 + if (response.statusCode != 200) {
178 + return [];
179 + }
180 +
181 + final decodedBody = json.decode(response.body) as Map<String, dynamic>;
182 + final isSuccessful = decodedBody['Successful'] as bool ?? false;
183 +
184 + if (!isSuccessful) {
185 + return [];
186 + }
187 +
188 + final data = decodedBody['Data'] as List<dynamic>;
189 + return data.map((dynamic e) {
190 + try {
191 + final element = e as Map<String, dynamic>;
192 + return IoniaMerchant.fromJsonMap(element);
193 + } catch(_) {
194 + return null;
195 + }
196 + }).where((e) => e != null)
197 + .toList();
198 + }
199 +
200 + // Get Merchants By Filter
201 +
202 + Future<List<IoniaMerchant>> getMerchantsByFilter({
203 + @required String username,
204 + @required String password,
205 + @required String clientId,
206 + String search,
207 + List<IoniaCategory> categories,
208 + int merchantFilterType = 0}) async {
209 + // MerchantFilterType: {All = 0, Nearby = 1, Popular = 2, Online = 3, MyFaves = 4, Search = 5}
210 +
211 + final headers = <String, String>{
212 + 'clientId': clientId,
213 + 'username': username,
214 + 'password': password,
215 + 'Content-Type': 'application/json'};
216 + final body = <String, dynamic>{'MerchantFilterType': merchantFilterType};
217 +
218 + if (search != null) {
219 + body['SearchCriteria'] = search;
220 + }
221 +
222 + if (categories != null) {
223 + body['Categories'] = categories
224 + .map((e) => e.ids)
225 + .expand((e) => e)
226 + .toList();
227 + }
228 +
229 + final response = await post(getMerchantsByFilterUrl, headers: headers, body: json.encode(body));
230 +
231 + if (response.statusCode != 200) {
232 + return [];
233 + }
234 +
235 + final decodedBody = json.decode(response.body) as Map<String, dynamic>;
236 + final isSuccessful = decodedBody['Successful'] as bool ?? false;
237 +
238 + if (!isSuccessful) {
239 + return [];
240 + }
241 +
242 + final data = decodedBody['Data'] as List<dynamic>;
243 + return data.map((dynamic e) {
244 + try {
245 + final element = e['Merchant'] as Map<String, dynamic>;
246 + return IoniaMerchant.fromJsonMap(element);
247 + } catch(_) {
248 + return null;
249 + }
250 + }).where((e) => e != null)
251 + .toList();
252 + }
253 +
254 + // Purchase Gift Card
255 +
256 + Future<IoniaOrder> purchaseGiftCard({
257 + @required String merchId,
258 + @required double amount,
259 + @required String currency,
260 + @required String username,
261 + @required String password,
262 + @required String clientId}) async {
263 + final headers = <String, String>{
264 + 'clientId': clientId,
265 + 'username': username,
266 + 'password': password,
267 + 'Content-Type': 'application/json'};
268 + final body = <String, dynamic>{
269 + 'Amount': amount,
270 + 'Currency': currency,
271 + 'MerchantId': merchId};
272 + final response = await post(getPurchaseMerchantsUrl, headers: headers, body: json.encode(body));
273 +
274 + if (response.statusCode != 200) {
275 + throw Exception('Unexpected response');
276 + }
277 +
278 + final decodedBody = json.decode(response.body) as Map<String, dynamic>;
279 + final isSuccessful = decodedBody['Successful'] as bool ?? false;
280 +
281 + if (!isSuccessful) {
282 + throw Exception(decodedBody['ErrorMessage'] as String);
283 + }
284 +
285 + final data = decodedBody['Data'] as Map<String, dynamic>;
286 + return IoniaOrder.fromMap(data);
287 + }
288 +
289 + // Get Current User Gift Card Summaries
290 +
291 + Future<List<IoniaGiftCard>> getCurrentUserGiftCardSummaries({
292 + @required String username,
293 + @required String password,
294 + @required String clientId}) async {
295 + final headers = <String, String>{
296 + 'clientId': clientId,
297 + 'username': username,
298 + 'password': password};
299 + final response = await post(getCurrentUserGiftCardSummariesUrl, headers: headers);
300 +
301 + if (response.statusCode != 200) {
302 + return [];
303 + }
304 +
305 + final decodedBody = json.decode(response.body) as Map<String, dynamic>;
306 + final isSuccessful = decodedBody['Successful'] as bool ?? false;
307 +
308 + if (!isSuccessful) {
309 + return [];
310 + }
311 +
312 + final data = decodedBody['Data'] as List<dynamic>;
313 + return data.map((dynamic e) {
314 + try {
315 + final element = e as Map<String, dynamic>;
316 + return IoniaGiftCard.fromJsonMap(element);
317 + } catch(e) {
318 + return null;
319 + }
320 + }).where((e) => e != null)
321 + .toList();
322 + }
323 +
324 + // Charge Gift Card
325 +
326 + Future<void> chargeGiftCard({
327 + @required String username,
328 + @required String password,
329 + @required String clientId,
330 + @required int giftCardId,
331 + @required double amount}) async {
332 + final headers = <String, String>{
333 + 'clientId': clientId,
334 + 'username': username,
335 + 'password': password,
336 + 'Content-Type': 'application/json'};
337 + final body = <String, dynamic>{
338 + 'Id': giftCardId,
339 + 'Amount': amount};
340 + final response = await post(
341 + changeGiftCardUrl,
342 + headers: headers,
343 + body: json.encode(body));
344 +
345 + if (response.statusCode != 200) {
346 + throw Exception('Failed to update Gift Card with ID ${giftCardId};Incorrect response status: ${response.statusCode};');
347 + }
348 +
349 + final decodedBody = json.decode(response.body) as Map<String, dynamic>;
350 + final isSuccessful = decodedBody['Successful'] as bool ?? false;
351 +
352 + if (!isSuccessful) {
353 + final data = decodedBody['Data'] as Map<String, dynamic>;
354 + final msg = data['Message'] as String ?? '';
355 +
356 + if (msg.isNotEmpty) {
357 + throw Exception(msg);
358 + }
359 +
360 + throw Exception('Failed to update Gift Card with ID ${giftCardId};');
361 + }
362 + }
363 +
364 + // Get Gift Card
365 +
366 + Future<IoniaGiftCard> getGiftCard({
367 + @required String username,
368 + @required String password,
369 + @required String clientId,
370 + @required int id}) async {
371 + final headers = <String, String>{
372 + 'clientId': clientId,
373 + 'username': username,
374 + 'password': password,
375 + 'Content-Type': 'application/json'};
376 + final body = <String, dynamic>{'Id': id};
377 + final response = await post(
378 + getGiftCardUrl,
379 + headers: headers,
380 + body: json.encode(body));
381 +
382 + if (response.statusCode != 200) {
383 + throw Exception('Failed to get Gift Card with ID ${id};Incorrect response status: ${response.statusCode};');
384 + }
385 +
386 + final decodedBody = json.decode(response.body) as Map<String, dynamic>;
387 + final isSuccessful = decodedBody['Successful'] as bool ?? false;
388 +
389 + if (!isSuccessful) {
390 + final msg = decodedBody['ErrorMessage'] as String ?? '';
391 +
392 + if (msg.isNotEmpty) {
393 + throw Exception(msg);
394 + }
395 +
396 + throw Exception('Failed to get Gift Card with ID ${id};');
397 + }
398 +
399 + final data = decodedBody['Data'] as Map<String, dynamic>;
400 + return IoniaGiftCard.fromJsonMap(data);
401 + }
402 +
403 + // Payment Status
404 +
405 + Future<int> getPaymentStatus({
406 + @required String username,
407 + @required String password,
408 + @required String clientId,
409 + @required String orderId,
410 + @required String paymentId}) async {
411 + final headers = <String, String>{
412 + 'clientId': clientId,
413 + 'username': username,
414 + 'password': password,
415 + 'Content-Type': 'application/json'};
416 + final body = <String, dynamic>{
417 + 'order_id': orderId,
418 + 'paymentId': paymentId};
419 + final response = await post(
420 + getPaymentStatusUrl,
421 + headers: headers,
422 + body: json.encode(body));
423 +
424 + if (response.statusCode != 200) {
425 + throw Exception('Failed to get Payment Status for order_id ${orderId} paymentId ${paymentId};Incorrect response status: ${response.statusCode};');
426 + }
427 +
428 + final decodedBody = json.decode(response.body) as Map<String, dynamic>;
429 + final isSuccessful = decodedBody['Successful'] as bool ?? false;
430 +
431 + if (!isSuccessful) {
432 + final msg = decodedBody['ErrorMessage'] as String ?? '';
433 +
434 + if (msg.isNotEmpty) {
435 + throw Exception(msg);
436 + }
437 +
438 + throw Exception('Failed to get Payment Status for order_id ${orderId} paymentId ${paymentId}');
439 + }
440 +
441 + final data = decodedBody['Data'] as Map<String, dynamic>;
442 + return data['gift_card_id'] as int;
443 + }
444 +}
\ No newline at end of file
lib/ionia/ionia_category.dart new
+18
@@ -0,0 +1,18 @@
1 +class IoniaCategory {
2 + const IoniaCategory({this.index, this.title, this.ids, this.iconPath});
3 +
4 + static const allCategories = <IoniaCategory>[all, apparel, onlineOnly, food, entertainment, delivery, travel];
5 + static const all = IoniaCategory(index: 0, title: 'All', ids: [], iconPath: 'assets/images/category.png');
6 + static const apparel = IoniaCategory(index: 1, title: 'Apparel', ids: [1], iconPath: 'assets/images/tshirt.png');
7 + static const onlineOnly = IoniaCategory(index: 2, title: 'Online Only', ids: [13, 43], iconPath: 'assets/images/global.png');
8 + static const food = IoniaCategory(index: 3, title: 'Food', ids: [4], iconPath: 'assets/images/food.png');
9 + static const entertainment = IoniaCategory(index: 4, title: 'Entertainment', ids: [5], iconPath: 'assets/images/gaming.png');
10 + static const delivery = IoniaCategory(index: 5, title: 'Delivery', ids: [114, 109], iconPath: 'assets/images/delivery.png');
11 + static const travel = IoniaCategory(index: 6, title: 'Travel', ids: [12], iconPath: 'assets/images/airplane.png');
12 +
13 +
14 + final int index;
15 + final String title;
16 + final List<int> ids;
17 + final String iconPath;
18 +}
lib/ionia/ionia_create_state.dart new
+58
@@ -0,0 +1,58 @@
1 +import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
2 +import 'package:flutter/material.dart';
3 +
4 +abstract class IoniaCreateAccountState {}
5 +
6 +class IoniaInitialCreateState extends IoniaCreateAccountState {}
7 +
8 +class IoniaCreateStateSuccess extends IoniaCreateAccountState {}
9 +
10 +class IoniaCreateStateLoading extends IoniaCreateAccountState {}
11 +
12 +class IoniaCreateStateFailure extends IoniaCreateAccountState {
13 + IoniaCreateStateFailure({@required this.error});
14 +
15 + final String error;
16 +}
17 +
18 +abstract class IoniaOtpState {}
19 +
20 +class IoniaOtpValidating extends IoniaOtpState {}
21 +
22 +class IoniaOtpSuccess extends IoniaOtpState {}
23 +
24 +class IoniaOtpSendDisabled extends IoniaOtpState {}
25 +
26 +class IoniaOtpSendEnabled extends IoniaOtpState {}
27 +
28 +class IoniaOtpFailure extends IoniaOtpState {
29 + IoniaOtpFailure({@required this.error});
30 +
31 + final String error;
32 +}
33 +
34 +class IoniaCreateCardState {}
35 +
36 +class IoniaCreateCardSuccess extends IoniaCreateCardState {}
37 +
38 +class IoniaCreateCardLoading extends IoniaCreateCardState {}
39 +
40 +class IoniaCreateCardFailure extends IoniaCreateCardState {
41 + IoniaCreateCardFailure({@required this.error});
42 +
43 + final String error;
44 +}
45 +
46 +class IoniaFetchCardState {}
47 +
48 +class IoniaNoCardState extends IoniaFetchCardState {}
49 +
50 +class IoniaFetchingCard extends IoniaFetchCardState {}
51 +
52 +class IoniaFetchCardFailure extends IoniaFetchCardState {}
53 +
54 +class IoniaCardSuccess extends IoniaFetchCardState {
55 + IoniaCardSuccess({@required this.card});
56 +
57 + final IoniaVirtualCard card;
58 +}
lib/ionia/ionia_gift_card.dart new
+69
@@ -0,0 +1,69 @@
1 +import 'dart:convert';
2 +import 'package:cake_wallet/ionia/ionia_gift_card_instruction.dart';
3 +import 'package:flutter/foundation.dart';
4 +
5 +class IoniaGiftCard {
6 + IoniaGiftCard({
7 + @required this.id,
8 + @required this.merchantId,
9 + @required this.legalName,
10 + @required this.systemName,
11 + @required this.barcodeUrl,
12 + @required this.cardNumber,
13 + @required this.cardPin,
14 + @required this.instructions,
15 + @required this.tip,
16 + @required this.purchaseAmount,
17 + @required this.actualAmount,
18 + @required this.totalTransactionAmount,
19 + @required this.totalDashTransactionAmount,
20 + @required this.remainingAmount,
21 + @required this.createdDateFormatted,
22 + @required this.lastTransactionDateFormatted,
23 + @required this.isActive,
24 + @required this.isEmpty,
25 + @required this.logoUrl});
26 +
27 + factory IoniaGiftCard.fromJsonMap(Map<String, dynamic> element) {
28 + return IoniaGiftCard(
29 + id: element['Id'] as int,
30 + merchantId: element['MerchantId'] as int,
31 + legalName: element['LegalName'] as String,
32 + systemName: element['SystemName'] as String,
33 + barcodeUrl: element['BarcodeUrl'] as String,
34 + cardNumber: element['CardNumber'] as String,
35 + cardPin: element['CardPin'] as String,
36 + tip: element['Tip'] as double,
37 + purchaseAmount: element['PurchaseAmount'] as double,
38 + actualAmount: element['ActualAmount'] as double,
39 + totalTransactionAmount: element['TotalTransactionAmount'] as double,
40 + totalDashTransactionAmount: element['TotalDashTransactionAmount'] as double,
41 + remainingAmount: element['RemainingAmount'] as double,
42 + isActive: element['IsActive'] as bool,
43 + isEmpty: element['IsEmpty'] as bool,
44 + logoUrl: element['LogoUrl'] as String,
45 + createdDateFormatted: element['CreatedDate'] as String,
46 + lastTransactionDateFormatted: element['LastTransactionDate'] as String,
47 + instructions: IoniaGiftCardInstruction.parseListOfInstructions(element['PaymentInstructions'] as String));
48 + }
49 +
50 + final int id;
51 + final int merchantId;
52 + final String legalName;
53 + final String systemName;
54 + final String barcodeUrl;
55 + final String cardNumber;
56 + final String cardPin;
57 + final List<IoniaGiftCardInstruction> instructions;
58 + final double tip;
59 + final double purchaseAmount;
60 + final double actualAmount;
61 + final double totalTransactionAmount;
62 + final double totalDashTransactionAmount;
63 + final double remainingAmount;
64 + final String createdDateFormatted;
65 + final String lastTransactionDateFormatted;
66 + final bool isActive;
67 + final bool isEmpty;
68 + final String logoUrl;
69 +}
\ No newline at end of file
lib/ionia/ionia_gift_card_instruction.dart new
+28
@@ -0,0 +1,28 @@
1 +import 'dart:convert';
2 +import 'package:intl/intl.dart' show toBeginningOfSentenceCase;
3 +
4 +class IoniaGiftCardInstruction {
5 + IoniaGiftCardInstruction(this.header, this.body);
6 +
7 + factory IoniaGiftCardInstruction.fromJsonMap(Map<String, dynamic> element) {
8 + return IoniaGiftCardInstruction(
9 + toBeginningOfSentenceCase(element['title'] as String ?? ''),
10 + element['description'] as String);
11 + }
12 +
13 + static List<IoniaGiftCardInstruction> parseListOfInstructions(String instructionsJSON) {
14 + List<IoniaGiftCardInstruction> instructions = <IoniaGiftCardInstruction>[];
15 +
16 + if (instructionsJSON.isNotEmpty) {
17 + final decodedInstructions = json.decode(instructionsJSON) as List<dynamic>;
18 + instructions = decodedInstructions
19 + .map((dynamic e) =>IoniaGiftCardInstruction.fromJsonMap(e as Map<String, dynamic>))
20 + .toList();
21 + }
22 +
23 + return instructions;
24 + }
25 +
26 + final String header;
27 + final String body;
28 +}
\ No newline at end of file
lib/ionia/ionia_merchant.dart new
+176
@@ -0,0 +1,176 @@
1 +import 'package:flutter/foundation.dart';
2 +import 'package:cake_wallet/ionia/ionia_gift_card_instruction.dart';
3 +
4 +class IoniaMerchant {
5 + IoniaMerchant({
6 + @required this.id,
7 + @required this.legalName,
8 + @required this.systemName,
9 + @required this.description,
10 + @required this.website,
11 + @required this.termsAndConditions,
12 + @required this.logoUrl,
13 + @required this.cardImageUrl,
14 + @required this.cardholderAgreement,
15 + @required this.purchaseFee,
16 + @required this.revenueShare,
17 + @required this.marketingFee,
18 + @required this.minimumDiscount,
19 + @required this.level1,
20 + @required this.level2,
21 + @required this.level3,
22 + @required this.level4,
23 + @required this.level5,
24 + @required this.level6,
25 + @required this.level7,
26 + @required this.isActive,
27 + @required this.isDeleted,
28 + @required this.isOnline,
29 + @required this.isPhysical,
30 + @required this.isVariablePurchase,
31 + @required this.minimumCardPurchase,
32 + @required this.maximumCardPurchase,
33 + @required this.acceptsTips,
34 + @required this.createdDateFormatted,
35 + @required this.createdBy,
36 + @required this.isRegional,
37 + @required this.modifiedDateFormatted,
38 + @required this.modifiedBy,
39 + @required this.usageInstructions,
40 + @required this.usageInstructionsBak,
41 + @required this.paymentGatewayId,
42 + @required this.giftCardGatewayId,
43 + @required this.isHtmlDescription,
44 + @required this.purchaseInstructions,
45 + @required this.balanceInstructions,
46 + @required this.amountPerCard,
47 + @required this.processingMessage,
48 + @required this.hasBarcode,
49 + @required this.hasInventory,
50 + @required this.isVoidable,
51 + @required this.receiptMessage,
52 + @required this.cssBorderCode,
53 + @required this.instructions,
54 + @required this.alderSku,
55 + @required this.ngcSku,
56 + @required this.acceptedCurrency,
57 + @required this.deepLink,
58 + @required this.isPayLater,
59 + @required this.savingsPercentage});
60 +
61 + factory IoniaMerchant.fromJsonMap(Map<String, dynamic> element) {
62 + return IoniaMerchant(
63 + id: element["Id"] as int,
64 + legalName: element["LegalName"] as String,
65 + systemName: element["SystemName"] as String,
66 + description: element["Description"] as String,
67 + website: element["Website"] as String,
68 + termsAndConditions: element["TermsAndConditions"] as String,
69 + logoUrl: element["LogoUrl"] as String,
70 + cardImageUrl: element["CardImageUrl"] as String,
71 + cardholderAgreement: element["CardholderAgreement"] as String,
72 + purchaseFee: element["PurchaseFee"] as double,
73 + revenueShare: element["RevenueShare"] as double,
74 + marketingFee: element["MarketingFee"] as double,
75 + minimumDiscount: element["MinimumDiscount"] as double,
76 + level1: element["Level1"] as double,
77 + level2: element["Level2"] as double,
78 + level3: element["Level3"] as double,
79 + level4: element["Level4"] as double,
80 + level5: element["Level5"] as double,
81 + level6: element["Level6"] as double,
82 + level7: element["Level7"] as double,
83 + isActive: element["IsActive"] as bool,
84 + isDeleted: element["IsDeleted"] as bool,
85 + isOnline: element["IsOnline"] as bool,
86 + isPhysical: element["IsPhysical"] as bool,
87 + isVariablePurchase: element["IsVariablePurchase"] as bool,
88 + minimumCardPurchase: element["MinimumCardPurchase"] as double,
89 + maximumCardPurchase: element["MaximumCardPurchase"] as double,
90 + acceptsTips: element["AcceptsTips"] as bool,
91 + createdDateFormatted: element["CreatedDate"] as String,
92 + createdBy: element["CreatedBy"] as int,
93 + isRegional: element["IsRegional"] as bool,
94 + modifiedDateFormatted: element["ModifiedDate"] as String,
95 + modifiedBy: element["ModifiedBy"] as int,
96 + usageInstructions: element["UsageInstructions"] as String,
97 + usageInstructionsBak: element["UsageInstructionsBak"] as String,
98 + paymentGatewayId: element["PaymentGatewayId"] as int,
99 + giftCardGatewayId: element["GiftCardGatewayId"] as int ,
100 + isHtmlDescription: element["IsHtmlDescription"] as bool,
101 + purchaseInstructions: element["PurchaseInstructions"] as String,
102 + balanceInstructions: element["BalanceInstructions"] as String,
103 + amountPerCard: element["AmountPerCard"] as double,
104 + processingMessage: element["ProcessingMessage"] as String,
105 + hasBarcode: element["HasBarcode"] as bool,
106 + hasInventory: element["HasInventory"] as bool,
107 + isVoidable: element["IsVoidable"] as bool,
108 + receiptMessage: element["ReceiptMessage"] as String,
109 + cssBorderCode: element["CssBorderCode"] as String,
110 + instructions: IoniaGiftCardInstruction.parseListOfInstructions(element['PaymentInstructions'] as String),
111 + alderSku: element["AlderSku"] as String,
112 + ngcSku: element["NgcSku"] as String,
113 + acceptedCurrency: element["AcceptedCurrency"] as String,
114 + deepLink: element["DeepLink"] as String,
115 + isPayLater: element["IsPayLater"] as bool,
116 + savingsPercentage: element["SavingsPercentage"] as double);
117 + }
118 +
119 + final int id;
120 + final String legalName;
121 + final String systemName;
122 + final String description;
123 + final String website;
124 + final String termsAndConditions;
125 + final String logoUrl;
126 + final String cardImageUrl;
127 + final String cardholderAgreement;
128 + final double purchaseFee;
129 + final double revenueShare;
130 + final double marketingFee;
131 + final double minimumDiscount;
132 + final double level1;
133 + final double level2;
134 + final double level3;
135 + final double level4;
136 + final double level5;
137 + final double level6;
138 + final double level7;
139 + final bool isActive;
140 + final bool isDeleted;
141 + final bool isOnline;
142 + final bool isPhysical;
143 + final bool isVariablePurchase;
144 + final double minimumCardPurchase;
145 + final double maximumCardPurchase;
146 + final bool acceptsTips;
147 + final String createdDateFormatted;
148 + final int createdBy;
149 + final bool isRegional;
150 + final String modifiedDateFormatted;
151 + final int modifiedBy;
152 + final String usageInstructions;
153 + final String usageInstructionsBak;
154 + final int paymentGatewayId;
155 + final int giftCardGatewayId;
156 + final bool isHtmlDescription;
157 + final String purchaseInstructions;
158 + final String balanceInstructions;
159 + final double amountPerCard;
160 + final String processingMessage;
161 + final bool hasBarcode;
162 + final bool hasInventory;
163 + final bool isVoidable;
164 + final String receiptMessage;
165 + final String cssBorderCode;
166 + final List<IoniaGiftCardInstruction> instructions;
167 + final String alderSku;
168 + final String ngcSku;
169 + final String acceptedCurrency;
170 + final String deepLink;
171 + final bool isPayLater;
172 + final double savingsPercentage;
173 +
174 + double get discount => savingsPercentage;
175 +
176 +}
lib/ionia/ionia_order.dart new
+23
@@ -0,0 +1,23 @@
1 +import 'package:flutter/foundation.dart';
2 +
3 +class IoniaOrder {
4 + IoniaOrder({@required this.id,
5 + @required this.uri,
6 + @required this.currency,
7 + @required this.amount,
8 + @required this.paymentId});
9 + factory IoniaOrder.fromMap(Map<String, dynamic> obj) {
10 + return IoniaOrder(
11 + id: obj['order_id'] as String,
12 + uri: obj['uri'] as String,
13 + currency: obj['currency'] as String,
14 + amount: obj['amount'] as double,
15 + paymentId: obj['paymentId'] as String);
16 + }
17 +
18 + final String id;
19 + final String uri;
20 + final String currency;
21 + final double amount;
22 + final String paymentId;
23 +}
\ No newline at end of file
lib/ionia/ionia_service.dart new
+172
@@ -0,0 +1,172 @@
1 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
2 +import 'package:cake_wallet/ionia/ionia_order.dart';
3 +import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
4 +import 'package:flutter/foundation.dart';
5 +import 'package:flutter_secure_storage/flutter_secure_storage.dart';
6 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
7 +import 'package:cake_wallet/ionia/ionia_api.dart';
8 +import 'package:cake_wallet/ionia/ionia_gift_card.dart';
9 +import 'package:cake_wallet/ionia/ionia_category.dart';
10 +
11 +class IoniaService {
12 + IoniaService(this.secureStorage, this.ioniaApi);
13 +
14 + static const ioniaEmailStorageKey = 'ionia_email';
15 + static const ioniaUsernameStorageKey = 'ionia_username';
16 + static const ioniaPasswordStorageKey = 'ionia_password';
17 +
18 + static String get clientId => secrets.ioniaClientId;
19 +
20 + final FlutterSecureStorage secureStorage;
21 + final IoniaApi ioniaApi;
22 +
23 + // Create user
24 +
25 + Future<void> createUser(String email) async {
26 + final username = await ioniaApi.createUser(email, clientId: clientId);
27 + await secureStorage.write(key: ioniaEmailStorageKey, value: email);
28 + await secureStorage.write(key: ioniaUsernameStorageKey, value: username);
29 + }
30 +
31 + // Verify email
32 +
33 + Future<void> verifyEmail(String code) async {
34 + final username = await secureStorage.read(key: ioniaUsernameStorageKey);
35 + final email = await secureStorage.read(key: ioniaEmailStorageKey);
36 + final credentials = await ioniaApi.verifyEmail(email: email, username: username, code: code, clientId: clientId);
37 + await secureStorage.write(key: ioniaPasswordStorageKey, value: credentials.password);
38 + await secureStorage.write(key: ioniaUsernameStorageKey, value: credentials.username);
39 + }
40 +
41 + // Sign In
42 +
43 + Future<void> signIn(String email) async {
44 + final username = await ioniaApi.signIn(email, clientId: clientId);
45 + await secureStorage.write(key: ioniaEmailStorageKey, value: email);
46 + await secureStorage.write(key: ioniaUsernameStorageKey, value: username);
47 + }
48 +
49 + Future<String> getUserEmail() async {
50 + return secureStorage.read(key: ioniaEmailStorageKey);
51 + }
52 +
53 + // Check is user logined
54 +
55 + Future<bool> isLogined() async {
56 + final username = await secureStorage.read(key: ioniaUsernameStorageKey) ?? '';
57 + final password = await secureStorage.read(key: ioniaPasswordStorageKey) ?? '';
58 + return username.isNotEmpty && password.isNotEmpty;
59 + }
60 +
61 + // Logout
62 +
63 + Future<void> logout() async {
64 + await secureStorage.delete(key: ioniaUsernameStorageKey);
65 + await secureStorage.delete(key: ioniaPasswordStorageKey);
66 + }
67 +
68 + // Create virtual card
69 +
70 + Future<IoniaVirtualCard> createCard() async {
71 + final username = await secureStorage.read(key: ioniaUsernameStorageKey);
72 + final password = await secureStorage.read(key: ioniaPasswordStorageKey);
73 + return ioniaApi.createCard(username: username, password: password, clientId: clientId);
74 + }
75 +
76 + // Get virtual card
77 +
78 + Future<IoniaVirtualCard> getCard() async {
79 + final username = await secureStorage.read(key: ioniaUsernameStorageKey);
80 + final password = await secureStorage.read(key: ioniaPasswordStorageKey);
81 + return ioniaApi.getCards(username: username, password: password, clientId: clientId);
82 + }
83 +
84 + // Get Merchants
85 +
86 + Future<List<IoniaMerchant>> getMerchants() async {
87 + final username = await secureStorage.read(key: ioniaUsernameStorageKey);
88 + final password = await secureStorage.read(key: ioniaPasswordStorageKey);
89 + return ioniaApi.getMerchants(username: username, password: password, clientId: clientId);
90 + }
91 +
92 + // Get Merchants By Filter
93 +
94 + Future<List<IoniaMerchant>> getMerchantsByFilter({
95 + String search,
96 + List<IoniaCategory> categories,
97 + int merchantFilterType = 0}) async {
98 + final username = await secureStorage.read(key: ioniaUsernameStorageKey);
99 + final password = await secureStorage.read(key: ioniaPasswordStorageKey);
100 + return ioniaApi.getMerchantsByFilter(
101 + username: username,
102 + password: password,
103 + clientId: clientId,
104 + search: search,
105 + categories: categories,
106 + merchantFilterType: merchantFilterType);
107 + }
108 +
109 + // Purchase Gift Card
110 +
111 + Future<IoniaOrder> purchaseGiftCard({
112 + @required String merchId,
113 + @required double amount,
114 + @required String currency}) async {
115 + final username = await secureStorage.read(key: ioniaUsernameStorageKey);
116 + final password = await secureStorage.read(key: ioniaPasswordStorageKey);
117 + return ioniaApi.purchaseGiftCard(
118 + merchId: merchId,
119 + amount: amount,
120 + currency: currency,
121 + username: username,
122 + password: password,
123 + clientId: clientId);
124 + }
125 +
126 + // Get Current User Gift Card Summaries
127 +
128 + Future<List<IoniaGiftCard>> getCurrentUserGiftCardSummaries() async {
129 + final username = await secureStorage.read(key: ioniaUsernameStorageKey);
130 + final password = await secureStorage.read(key: ioniaPasswordStorageKey);
131 + return ioniaApi.getCurrentUserGiftCardSummaries(username: username, password: password, clientId: clientId);
132 + }
133 +
134 + // Charge Gift Card
135 +
136 + Future<void> chargeGiftCard({
137 + @required int giftCardId,
138 + @required double amount}) async {
139 + final username = await secureStorage.read(key: ioniaUsernameStorageKey);
140 + final password = await secureStorage.read(key: ioniaPasswordStorageKey);
141 + await ioniaApi.chargeGiftCard(
142 + username: username,
143 + password: password,
144 + clientId: clientId,
145 + giftCardId: giftCardId,
146 + amount: amount);
147 + }
148 +
149 + // Redeem
150 +
151 + Future<void> redeem(IoniaGiftCard giftCard) async {
152 + await chargeGiftCard(giftCardId: giftCard.id, amount: giftCard.remainingAmount);
153 + }
154 +
155 + // Get Gift Card
156 +
157 + Future<IoniaGiftCard> getGiftCard({@required int id}) async {
158 + final username = await secureStorage.read(key: ioniaUsernameStorageKey);
159 + final password = await secureStorage.read(key: ioniaPasswordStorageKey);
160 + return ioniaApi.getGiftCard(username: username, password: password, clientId: clientId,id: id);
161 + }
162 +
163 + // Payment Status
164 +
165 + Future<int> getPaymentStatus({
166 + @required String orderId,
167 + @required String paymentId}) async {
168 + final username = await secureStorage.read(key: ioniaUsernameStorageKey);
169 + final password = await secureStorage.read(key: ioniaPasswordStorageKey);
170 + return ioniaApi.getPaymentStatus(username: username, password: password, clientId: clientId, orderId: orderId, paymentId: paymentId);
171 + }
172 +}
\ No newline at end of file
lib/ionia/ionia_tip.dart new
+12
@@ -0,0 +1,12 @@
1 +class IoniaTip {
2 + const IoniaTip({this.originalAmount, this.percentage});
3 + final double originalAmount;
4 + final double percentage;
5 + double get additionalAmount => double.parse((originalAmount * percentage / 100).toStringAsFixed(2));
6 +
7 + static const tipList = [
8 + IoniaTip(originalAmount: 0, percentage: 0),
9 + IoniaTip(originalAmount: 10, percentage: 10),
10 + IoniaTip(originalAmount: 20, percentage: 20)
11 + ];
12 +}
lib/ionia/ionia_token_data.dart new
+43
@@ -0,0 +1,43 @@
1 +import 'package:flutter/foundation.dart';
2 +import 'dart:convert';
3 +
4 +class IoniaTokenData {
5 + IoniaTokenData({@required this.accessToken, @required this.tokenType, @required this.expiredAt});
6 +
7 + factory IoniaTokenData.fromJson(String source) {
8 + final decoded = json.decode(source) as Map<String, dynamic>;
9 + final accessToken = decoded['access_token'] as String;
10 + final expiresIn = decoded['expires_in'] as int;
11 + final tokenType = decoded['token_type'] as String;
12 + final expiredAtInMilliseconds = decoded['expired_at'] as int;
13 + DateTime expiredAt;
14 +
15 + if (expiredAtInMilliseconds != null) {
16 + expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtInMilliseconds);
17 + } else {
18 + expiredAt = DateTime.now().add(Duration(seconds: expiresIn));
19 + }
20 +
21 + return IoniaTokenData(
22 + accessToken: accessToken,
23 + tokenType: tokenType,
24 + expiredAt: expiredAt);
25 + }
26 +
27 + final String accessToken;
28 + final String tokenType;
29 + final DateTime expiredAt;
30 +
31 + bool get isExpired => DateTime.now().isAfter(expiredAt);
32 +
33 + @override
34 + String toString() => '$tokenType $accessToken';
35 +
36 + String toJson() {
37 + return json.encode(<String, dynamic>{
38 + 'access_token': accessToken,
39 + 'token_type': tokenType,
40 + 'expired_at': expiredAt.millisecondsSinceEpoch
41 + });
42 + }
43 +}
\ No newline at end of file
lib/ionia/ionia_user_credentials.dart new
+6
@@ -0,0 +1,6 @@
1 +class IoniaUserCredentials {
2 + const IoniaUserCredentials(this.username, this.password);
3 +
4 + final String username;
5 + final String password;
6 +}
\ No newline at end of file
lib/ionia/ionia_virtual_card.dart new
+43
@@ -0,0 +1,43 @@
1 +import 'package:flutter/foundation.dart';
2 +
3 +class IoniaVirtualCard {
4 + IoniaVirtualCard({
5 + @required this.token,
6 + @required this.createdAt,
7 + @required this.lastFour,
8 + @required this.state,
9 + @required this.pan,
10 + @required this.cvv,
11 + @required this.expirationMonth,
12 + @required this.expirationYear,
13 + @required this.fundsLimit,
14 + @required this.spendLimit});
15 +
16 + factory IoniaVirtualCard.fromMap(Map<String, Object> source) {
17 + final created = source['created'] as String;
18 + final createdAt = DateTime.tryParse(created);
19 +
20 + return IoniaVirtualCard(
21 + token: source['token'] as String,
22 + createdAt: createdAt,
23 + lastFour: source['lastFour'] as String,
24 + state: source['state'] as String,
25 + pan: source['pan'] as String,
26 + cvv: source['cvv'] as String,
27 + expirationMonth: source['expirationMonth'] as String,
28 + expirationYear: source['expirationYear'] as String,
29 + fundsLimit: source['FundsLimit'] as double,
30 + spendLimit: source['spend_limit'] as double);
31 + }
32 +
33 + final String token;
34 + final String lastFour;
35 + final String state;
36 + final String pan;
37 + final String cvv;
38 + final String expirationMonth;
39 + final String expirationYear;
40 + final DateTime createdAt;
41 + final double fundsLimit;
42 + final double spendLimit;
43 +}
\ No newline at end of file
lib/main.dart
+4 -1
@@ -2,6 +2,8 @@ import 'dart:async';
2 import 'package:cake_wallet/bitcoin/bitcoin.dart';
3 import 'package:cake_wallet/entities/language_service.dart';
4 import 'package:cake_wallet/buy/order.dart';
5 +import 'package:cake_wallet/ionia/ionia_category.dart';
6 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
7 import 'package:cake_wallet/store/yat/yat_store.dart';
8 import 'package:flutter/foundation.dart';
9 import 'package:flutter/material.dart';
@@ -174,7 +176,8 @@ Future<void> initialSetup(
176 exchangeTemplates: exchangeTemplates,
177 transactionDescriptionBox: transactionDescriptions,
178 ordersSource: ordersSource,
177 - unspentCoinsInfoSource: unspentCoinsInfoSource);
179 + unspentCoinsInfoSource: unspentCoinsInfoSource,
180 + );
181 await bootstrap(navigatorKey);
182 monero?.onStartup();
183 }
lib/monero/cw_monero.dart
+47 -11
@@ -2,7 +2,7 @@ part of 'monero.dart';
2
3 class CWMoneroAccountList extends MoneroAccountList {
4 CWMoneroAccountList(this._wallet);
5 - Object _wallet;
5 + final Object _wallet;
6
7 @override
8 @computed
@@ -39,13 +39,13 @@ class CWMoneroAccountList extends MoneroAccountList {
39 @override
40 Future<void> addAccount(Object wallet, {String label}) async {
41 final moneroWallet = wallet as MoneroWallet;
42 - moneroWallet.walletAddresses.accountList.addAccount(label: label);
42 + await moneroWallet.walletAddresses.accountList.addAccount(label: label);
43 }
44
45 @override
46 Future<void> setLabelAccount(Object wallet, {int accountIndex, String label}) async {
47 final moneroWallet = wallet as MoneroWallet;
48 - moneroWallet.walletAddresses.accountList
48 + await moneroWallet.walletAddresses.accountList
49 .setLabelAccount(
50 accountIndex: accountIndex,
51 label: label);
@@ -95,7 +95,7 @@ class CWMoneroSubaddressList extends MoneroSubaddressList {
95 @override
96 Future<void> addSubaddress(Object wallet, {int accountIndex, String label}) async {
97 final moneroWallet = wallet as MoneroWallet;
98 - moneroWallet.walletAddresses.subaddressList
98 + await moneroWallet.walletAddresses.subaddressList
99 .addSubaddress(
100 accountIndex: accountIndex,
101 label: label);
@@ -105,7 +105,7 @@ class CWMoneroSubaddressList extends MoneroSubaddressList {
105 Future<void> setLabelSubaddress(Object wallet,
106 {int accountIndex, int addressIndex, String label}) async {
107 final moneroWallet = wallet as MoneroWallet;
108 - moneroWallet.walletAddresses.subaddressList
108 + await moneroWallet.walletAddresses.subaddressList
109 .setLabelSubaddress(
110 accountIndex: accountIndex,
111 addressIndex: addressIndex,
@@ -140,35 +140,43 @@ class CWMonero extends Monero {
140 return CWMoneroAccountList(wallet);
141 }
142
143 + @override
144 MoneroSubaddressList getSubaddressList(Object wallet) {
145 return CWMoneroSubaddressList(wallet);
146 }
147
148 + @override
149 TransactionHistoryBase getTransactionHistory(Object wallet) {
150 final moneroWallet = wallet as MoneroWallet;
151 return moneroWallet.transactionHistory;
152 }
153
154 + @override
155 MoneroWalletDetails getMoneroWalletDetails(Object wallet) {
156 return CWMoneroWalletDetails(wallet);
157 }
158
159 + @override
160 int getHeigthByDate({DateTime date}) {
161 return getMoneroHeigthByDate(date: date);
162 }
163
164 + @override
165 TransactionPriority getDefaultTransactionPriority() {
166 return MoneroTransactionPriority.slow;
167 }
168
169 + @override
170 TransactionPriority deserializeMoneroTransactionPriority({int raw}) {
171 return MoneroTransactionPriority.deserialize(raw: raw);
172 }
173
174 + @override
175 List<TransactionPriority> getTransactionPriorities() {
176 return MoneroTransactionPriority.all;
177 }
178
179 + @override
180 List<String> getMoneroWordList(String language) {
181 switch (language.toLowerCase()) {
182 case 'english':
@@ -196,14 +204,15 @@ class CWMonero extends Monero {
204 }
205 }
206
207 + @override
208 WalletCredentials createMoneroRestoreWalletFromKeysCredentials({
209 String name,
201 - String spendKey,
202 - String viewKey,
203 - String address,
204 - String password,
205 - String language,
206 - int height}) {
210 + String spendKey,
211 + String viewKey,
212 + String address,
213 + String password,
214 + String language,
215 + int height}) {
216 return MoneroRestoreWalletFromKeysCredentials(
217 name: name,
218 spendKey: spendKey,
@@ -214,6 +223,7 @@ class CWMonero extends Monero {
223 height: height);
224 }
225
226 + @override
227 WalletCredentials createMoneroRestoreWalletFromSeedCredentials({String name, String password, int height, String mnemonic}) {
228 return MoneroRestoreWalletFromSeedCredentials(
229 name: name,
@@ -222,6 +232,7 @@ class CWMonero extends Monero {
232 mnemonic: mnemonic);
233 }
234
235 + @override
236 WalletCredentials createMoneroNewWalletCredentials({String name, String password, String language}) {
237 return MoneroNewWalletCredentials(
238 name: name,
@@ -229,6 +240,7 @@ class CWMonero extends Monero {
240 language: language);
241 }
242
243 + @override
244 Map<String, String> getKeys(Object wallet) {
245 final moneroWallet = wallet as MoneroWallet;
246 final keys = moneroWallet.keys;
@@ -239,6 +251,7 @@ class CWMonero extends Monero {
251 'publicViewKey': keys.publicViewKey};
252 }
253
254 + @override
255 Object createMoneroTransactionCreationCredentials({List<Output> outputs, TransactionPriority priority}) {
256 return MoneroTransactionCreationCredentials(
257 outputs: outputs.map((out) => OutputInfo(
@@ -254,49 +267,72 @@ class CWMonero extends Monero {
267 priority: priority as MoneroTransactionPriority);
268 }
269
270 + @override
271 + Object createMoneroTransactionCreationCredentialsRaw({List<OutputInfo> outputs, TransactionPriority priority}) {
272 + return MoneroTransactionCreationCredentials(
273 + outputs: outputs,
274 + priority: priority as MoneroTransactionPriority);
275 + }
276 +
277 + @override
278 String formatterMoneroAmountToString({int amount}) {
279 return moneroAmountToString(amount: amount);
280 }
281
282 + @override
283 double formatterMoneroAmountToDouble({int amount}) {
284 return moneroAmountToDouble(amount: amount);
285 }
286
287 + @override
288 int formatterMoneroParseAmount({String amount}) {
289 return moneroParseAmount(amount: amount);
290 }
291
292 + @override
293 Account getCurrentAccount(Object wallet) {
294 final moneroWallet = wallet as MoneroWallet;
295 final acc = moneroWallet.walletAddresses.account;
296 return Account(id: acc.id, label: acc.label);
297 }
298
299 + @override
300 void setCurrentAccount(Object wallet, int id, String label) {
301 final moneroWallet = wallet as MoneroWallet;
302 moneroWallet.walletAddresses.account = monero_account.Account(id: id, label: label);
303 }
304
305 + @override
306 void onStartup() {
307 monero_wallet_api.onStartup();
308 }
309
310 + @override
311 int getTransactionInfoAccountId(TransactionInfo tx) {
312 final moneroTransactionInfo = tx as MoneroTransactionInfo;
313 return moneroTransactionInfo.accountIndex;
314 }
315
316 + @override
317 WalletService createMoneroWalletService(Box<WalletInfo> walletInfoSource) {
318 return MoneroWalletService(walletInfoSource);
319 }
320
321 + @override
322 String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) {
323 final moneroWallet = wallet as MoneroWallet;
324 return moneroWallet.getTransactionAddress(accountIndex, addressIndex);
325 }
326
327 + @override
328 String getSubaddressLabel(Object wallet, int accountIndex, int addressIndex) {
329 final moneroWallet = wallet as MoneroWallet;
330 return moneroWallet.getSubaddressLabel(accountIndex, addressIndex);
331 }
332 +
333 + @override
334 + Map<String, String> pendingTransactionInfo(Object transaction) {
335 + final ptx = transaction as PendingMoneroTransaction;
336 + return {'id': ptx.id, 'hex': ptx.hex, 'key': ptx.txKey};
337 + }
338 }
lib/router.dart
+60
@@ -4,6 +4,10 @@ import 'package:cake_wallet/src/screens/backup/backup_page.dart';
4 import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
5 import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
6 import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
7 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart';
8 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart';
9 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart';
10 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
11 import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
12 import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
13 import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
@@ -63,6 +67,10 @@ import 'package:flutter/services.dart';
67 import 'package:cake_wallet/wallet_types.g.dart';
68 import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart';
69 import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
70 +import 'package:cake_wallet/src/screens/ionia/ionia.dart';
71 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart';
72 +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
73 +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
74
75 RouteSettings currentRouteSettings;
76
@@ -401,6 +409,58 @@ Route<dynamic> createRoute(RouteSettings settings) {
409 param2: args['isLight'] as bool,
410 ));
411
412 + case Routes.ioniaWelcomePage:
413 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaWelcomePage>());
414 +
415 + case Routes.ioniaLoginPage:
416 + return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>());
417 +
418 + case Routes.ioniaCreateAccountPage:
419 + return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaCreateAccountPage>());
420 +
421 + case Routes.ioniaManageCardsPage:
422 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaManageCardsPage>());
423 +
424 + case Routes.ioniaBuyGiftCardPage:
425 + final args = settings.arguments as List;
426 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
427 +
428 + case Routes.ioniaBuyGiftCardDetailPage:
429 + final args = settings.arguments as List;
430 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
431 +
432 + case Routes.ioniaVerifyIoniaOtpPage:
433 + final args = settings.arguments as List;
434 + return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaVerifyIoniaOtp>(param1: args));
435 +
436 + case Routes.ioniaDebitCardPage:
437 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaDebitCardPage>());
438 +
439 + case Routes.ioniaActivateDebitCardPage:
440 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaActivateDebitCardPage>());
441 +
442 + case Routes.ioniaAccountPage:
443 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountPage>());
444 +
445 + case Routes.ioniaAccountCardsPage:
446 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountCardsPage>());
447 +
448 + case Routes.ioniaCustomTipPage:
449 + final args = settings.arguments as List;
450 + return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaCustomTipPage>(param1: args));
451 +
452 + case Routes.ioniaGiftCardDetailPage:
453 + final args = settings.arguments as List;
454 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
455 +
456 + case Routes.ioniaPaymentStatusPage:
457 + final args = settings.arguments as List;
458 + final paymentInfo = args.first as IoniaAnyPayPaymentInfo;
459 + final commitedInfo = args[1] as AnyPayPaymentCommittedInfo;
460 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaPaymentStatusPage>(
461 + param1: paymentInfo,
462 + param2: commitedInfo));
463 +
464 default:
465 return MaterialPageRoute<void>(
466 builder: (_) => Scaffold(
lib/routes.dart
+15 -1
@@ -60,4 +60,18 @@ class Routes {
60 static const moneroNewWalletFromWelcome = '/monero_new_wallet';
61 static const addressPage = '/address_page';
62 static const fullscreenQR = '/fullscreen_qr';
63 -}
\ No newline at end of file
63 + static const ioniaWelcomePage = '/cake_pay_welcome_page';
64 + static const ioniaCreateAccountPage = '/cake_pay_create_account_page';
65 + static const ioniaLoginPage = '/cake_pay_login_page';
66 + static const ioniaManageCardsPage = '/manage_cards_page';
67 + static const ioniaBuyGiftCardPage = '/buy_gift_card_page';
68 + static const ioniaBuyGiftCardDetailPage = '/buy_gift_card_detail_page';
69 + static const ioniaVerifyIoniaOtpPage = '/cake_pay_verify_otp_page';
70 + static const ioniaDebitCardPage = '/debit_card_page';
71 + static const ioniaActivateDebitCardPage = '/activate_debit_card_page';
72 + static const ioniaAccountPage = 'ionia_account_page';
73 + static const ioniaAccountCardsPage = 'ionia_account_cards_page';
74 + static const ioniaCustomTipPage = 'ionia_custom_tip_page';
75 + static const ioniaGiftCardDetailPage = '/ionia_gift_card_detail_page';
76 + static const ioniaPaymentStatusPage = '/ionia_payment_status_page';
77 +}
lib/src/screens/dashboard/dashboard_page.dart
+3 -7
@@ -1,8 +1,8 @@
1 import 'dart:async';
2 +import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart';
3 import 'package:cw_core/wallet_type.dart';
4 import 'package:cake_wallet/generated/i18n.dart';
5 import 'package:cake_wallet/routes.dart';
5 -import 'package:cake_wallet/src/screens/yat/yat_popup.dart';
6 import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
7 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
8 import 'package:cake_wallet/themes/theme_base.dart';
@@ -14,19 +14,15 @@ import 'package:cake_wallet/src/screens/base_page.dart';
14 import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
15 import 'package:cake_wallet/src/screens/dashboard/widgets/action_button.dart';
16 import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
17 -import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart';
17 import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
18 import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
19 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
20 import 'package:flutter_mobx/flutter_mobx.dart';
21 import 'package:mobx/mobx.dart';
22 import 'package:smooth_page_indicator/smooth_page_indicator.dart';
24 -import 'package:flutter_spinkit/flutter_spinkit.dart';
23 import 'package:cake_wallet/main.dart';
26 -import 'package:cake_wallet/router.dart';
24 import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
25 import 'package:url_launcher/url_launcher.dart';
29 -import 'package:cake_wallet/wallet_type_utils.dart';
26
27 class DashboardPage extends BasePage {
28 DashboardPage({
@@ -85,7 +81,7 @@ class DashboardPage extends BasePage {
81
82 final DashboardViewModel walletViewModel;
83 final WalletAddressListViewModel addressListViewModel;
88 - final controller = PageController(initialPage: 0);
84 + final controller = PageController(initialPage: 1);
85
86 var pages = <Widget>[];
87 bool _isEffectsInstalled = false;
@@ -221,7 +217,7 @@ class DashboardPage extends BasePage {
217 if (_isEffectsInstalled) {
218 return;
219 }
224 -
220 + pages.add(MarketPlacePage(dashboardViewModel: walletViewModel));
221 pages.add(balancePage);
222 pages.add(TransactionsPage(dashboardViewModel: walletViewModel));
223 _isEffectsInstalled = true;
lib/src/screens/dashboard/widgets/market_place_page.dart new
+80
@@ -0,0 +1,80 @@
1 +import 'package:cake_wallet/routes.dart';
2 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
3 +import 'package:cake_wallet/src/widgets/market_place_item.dart';
4 +import 'package:cake_wallet/utils/show_pop_up.dart';
5 +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
6 +import 'package:cw_core/wallet_type.dart';
7 +import 'package:flutter/material.dart';
8 +import 'package:cake_wallet/generated/i18n.dart';
9 +
10 +class MarketPlacePage extends StatelessWidget {
11 +
12 + MarketPlacePage({@required this.dashboardViewModel});
13 +
14 + final DashboardViewModel dashboardViewModel;
15 + final _scrollController = ScrollController();
16 +
17 + @override
18 + Widget build(BuildContext context) {
19 + return Padding(
20 + padding: const EdgeInsets.symmetric(horizontal: 10.0),
21 + child: RawScrollbar(
22 + thumbColor: Colors.white.withOpacity(0.15),
23 + radius: Radius.circular(20),
24 + isAlwaysShown: true,
25 + thickness: 2,
26 + controller: _scrollController,
27 + child: Padding(
28 + padding: const EdgeInsets.symmetric(horizontal: 10.0),
29 + child: Column(
30 + crossAxisAlignment: CrossAxisAlignment.start,
31 + children: [
32 + SizedBox(height: 50),
33 + Text(
34 + S.of(context).market_place,
35 + style: TextStyle(
36 + fontSize: 24,
37 + fontWeight: FontWeight.w500,
38 + color: Theme.of(context).accentTextTheme.display3.backgroundColor,
39 + ),
40 + ),
41 + Expanded(
42 + child: ListView(
43 + controller: _scrollController,
44 + children: <Widget>[
45 + SizedBox(height: 20),
46 + MarketPlaceItem(
47 + onTap: () =>_navigatorToGiftCardsPage(context),
48 + title: S.of(context).cake_pay_title,
49 + subTitle: S.of(context).cake_pay_subtitle,
50 + ),
51 + ],
52 + ),
53 + ),
54 + ],
55 + ),
56 + ),
57 + ),
58 + );
59 + }
60 + void _navigatorToGiftCardsPage(BuildContext context) {
61 + final walletType = dashboardViewModel.type;
62 +
63 + switch (walletType) {
64 + case WalletType.haven:
65 + showPopUp<void>(
66 + context: context,
67 + builder: (BuildContext context) {
68 + return AlertWithOneAction(
69 + alertTitle: S.of(context).error,
70 + alertContent: S.of(context).gift_cards_unavailable,
71 + buttonText: S.of(context).ok,
72 + buttonAction: () => Navigator.of(context).pop());
73 + });
74 + break;
75 + default:
76 + Navigator.of(context).pushNamed(Routes.ioniaWelcomePage);
77 + }
78 + }
79 +
80 +}
lib/src/screens/ionia/auth/ionia_create_account_page.dart new
+154
@@ -0,0 +1,154 @@
1 +import 'package:cake_wallet/core/email_validator.dart';
2 +import 'package:cake_wallet/ionia/ionia_create_state.dart';
3 +import 'package:cake_wallet/routes.dart';
4 +import 'package:cake_wallet/src/screens/base_page.dart';
5 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
6 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
7 +import 'package:cake_wallet/src/widgets/primary_button.dart';
8 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
9 +import 'package:cake_wallet/typography.dart';
10 +import 'package:cake_wallet/utils/show_pop_up.dart';
11 +import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
12 +import 'package:flutter/gestures.dart';
13 +import 'package:flutter/material.dart';
14 +import 'package:cake_wallet/generated/i18n.dart';
15 +import 'package:flutter_mobx/flutter_mobx.dart';
16 +import 'package:mobx/mobx.dart';
17 +import 'package:url_launcher/url_launcher.dart';
18 +
19 +class IoniaCreateAccountPage extends BasePage {
20 + IoniaCreateAccountPage(this._authViewModel)
21 + : _emailFocus = FocusNode(),
22 + _emailController = TextEditingController(),
23 + _formKey = GlobalKey<FormState>() {
24 + _emailController.text = _authViewModel.email;
25 + _emailController.addListener(() => _authViewModel.email = _emailController.text);
26 + }
27 +
28 + final IoniaAuthViewModel _authViewModel;
29 +
30 + final GlobalKey<FormState> _formKey;
31 +
32 + final FocusNode _emailFocus;
33 + final TextEditingController _emailController;
34 +
35 + static const privacyPolicyUrl = 'https://ionia.docsend.com/view/jaqsmbq9w7dzvnqf';
36 + static const termsAndConditionsUrl = 'https://ionia.docsend.com/view/hi9awnwxr6mqgiqj';
37 +
38 + @override
39 + Widget middle(BuildContext context) {
40 + return Text(
41 + S.current.sign_up,
42 + style: textMediumSemiBold(
43 + color: Theme.of(context).accentTextTheme.display4.backgroundColor,
44 + ),
45 + );
46 + }
47 +
48 + @override
49 + Widget body(BuildContext context) {
50 + reaction((_) => _authViewModel.createUserState, (IoniaCreateAccountState state) {
51 + if (state is IoniaCreateStateFailure) {
52 + _onCreateUserFailure(context, state.error);
53 + }
54 + if (state is IoniaCreateStateSuccess) {
55 + _onCreateSuccessful(context, _authViewModel);
56 + }
57 + });
58 +
59 + return ScrollableWithBottomSection(
60 + contentPadding: EdgeInsets.all(24),
61 + content: Form(
62 + key: _formKey,
63 + child: BaseTextFormField(
64 + hintText: S.of(context).email_address,
65 + focusNode: _emailFocus,
66 + validator: EmailValidator(),
67 + keyboardType: TextInputType.emailAddress,
68 + controller: _emailController,
69 + ),
70 + ),
71 + bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
72 + bottomSection: Column(
73 + children: [
74 + Column(
75 + mainAxisAlignment: MainAxisAlignment.end,
76 + children: <Widget>[
77 + Observer(
78 + builder: (_) => LoadingPrimaryButton(
79 + text: S.of(context).create_account,
80 + onPressed: () async {
81 + if (!_formKey.currentState.validate()) {
82 + return;
83 + }
84 + await _authViewModel.createUser(_emailController.text);
85 + },
86 + isLoading: _authViewModel.createUserState is IoniaCreateStateLoading,
87 + color: Theme.of(context).accentTextTheme.body2.color,
88 + textColor: Colors.white,
89 + ),
90 + ),
91 + SizedBox(
92 + height: 20,
93 + ),
94 + RichText(
95 + textAlign: TextAlign.center,
96 + text: TextSpan(
97 + text: S.of(context).agree_to,
98 + style: TextStyle(
99 + color: Color(0xff7A93BA),
100 + fontSize: 12,
101 + fontFamily: 'Lato',
102 + ),
103 + children: [
104 + TextSpan(
105 + text: S.of(context).settings_terms_and_conditions,
106 + style: TextStyle(
107 + color: Theme.of(context).accentTextTheme.body2.color,
108 + fontWeight: FontWeight.w700,
109 + ),
110 + recognizer: TapGestureRecognizer()
111 + ..onTap = () async {
112 + if (await canLaunch(termsAndConditionsUrl)) await launch(termsAndConditionsUrl);
113 + },
114 + ),
115 + TextSpan(text: ' ${S.of(context).and} '),
116 + TextSpan(
117 + text: S.of(context).privacy_policy,
118 + style: TextStyle(
119 + color: Theme.of(context).accentTextTheme.body2.color,
120 + fontWeight: FontWeight.w700,
121 + ),
122 + recognizer: TapGestureRecognizer()
123 + ..onTap = () async {
124 + if (await canLaunch(privacyPolicyUrl)) await launch(privacyPolicyUrl);
125 + }),
126 + TextSpan(text: ' ${S.of(context).by_cake_pay}'),
127 + ],
128 + ),
129 + ),
130 + ],
131 + ),
132 + ],
133 + ),
134 + );
135 + }
136 +
137 + void _onCreateUserFailure(BuildContext context, String error) {
138 + showPopUp<void>(
139 + context: context,
140 + builder: (BuildContext context) {
141 + return AlertWithOneAction(
142 + alertTitle: S.current.create_account,
143 + alertContent: error,
144 + buttonText: S.of(context).ok,
145 + buttonAction: () => Navigator.of(context).pop());
146 + });
147 + }
148 +
149 + void _onCreateSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed(
150 + context,
151 + Routes.ioniaVerifyIoniaOtpPage,
152 + arguments: [authViewModel.email, false],
153 + );
154 +}
lib/src/screens/ionia/auth/ionia_login_page.dart new
+112
@@ -0,0 +1,112 @@
1 +import 'package:cake_wallet/core/email_validator.dart';
2 +import 'package:cake_wallet/ionia/ionia_create_state.dart';
3 +import 'package:cake_wallet/routes.dart';
4 +import 'package:cake_wallet/src/screens/base_page.dart';
5 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
6 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
7 +import 'package:cake_wallet/src/widgets/primary_button.dart';
8 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
9 +import 'package:cake_wallet/typography.dart';
10 +import 'package:cake_wallet/utils/show_pop_up.dart';
11 +import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
12 +import 'package:flutter/material.dart';
13 +import 'package:cake_wallet/generated/i18n.dart';
14 +import 'package:flutter_mobx/flutter_mobx.dart';
15 +import 'package:mobx/mobx.dart';
16 +
17 +class IoniaLoginPage extends BasePage {
18 + IoniaLoginPage(this._authViewModel)
19 + : _formKey = GlobalKey<FormState>(),
20 + _emailController = TextEditingController() {
21 + _emailController.text = _authViewModel.email;
22 + _emailController.addListener(() => _authViewModel.email = _emailController.text);
23 + }
24 +
25 + final GlobalKey<FormState> _formKey;
26 +
27 + final IoniaAuthViewModel _authViewModel;
28 +
29 + @override
30 + Color get titleColor => Colors.black;
31 +
32 + final TextEditingController _emailController;
33 +
34 + @override
35 + Widget middle(BuildContext context) {
36 + return Text(
37 + S.current.login,
38 + style: textMediumSemiBold(
39 + color: Theme.of(context).accentTextTheme.display4.backgroundColor,
40 + ),
41 + );
42 + }
43 +
44 + @override
45 + Widget body(BuildContext context) {
46 + reaction((_) => _authViewModel.signInState, (IoniaCreateAccountState state) {
47 + if (state is IoniaCreateStateFailure) {
48 + _onLoginUserFailure(context, state.error);
49 + }
50 + if (state is IoniaCreateStateSuccess) {
51 + _onLoginSuccessful(context, _authViewModel);
52 + }
53 + });
54 + return ScrollableWithBottomSection(
55 + contentPadding: EdgeInsets.all(24),
56 + content: Form(
57 + key: _formKey,
58 + child: BaseTextFormField(
59 + hintText: S.of(context).email_address,
60 + keyboardType: TextInputType.emailAddress,
61 + validator: EmailValidator(),
62 + controller: _emailController,
63 + ),
64 + ),
65 + bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
66 + bottomSection: Column(
67 + children: [
68 + Column(
69 + mainAxisAlignment: MainAxisAlignment.end,
70 + children: <Widget>[
71 + Observer(
72 + builder: (_) => LoadingPrimaryButton(
73 + text: S.of(context).login,
74 + onPressed: () async {
75 + if (!_formKey.currentState.validate()) {
76 + return;
77 + }
78 + await _authViewModel.signIn(_emailController.text);
79 + },
80 + isLoading: _authViewModel.signInState is IoniaCreateStateLoading,
81 + color: Theme.of(context).accentTextTheme.body2.color,
82 + textColor: Colors.white,
83 + ),
84 + ),
85 + SizedBox(
86 + height: 20,
87 + ),
88 + ],
89 + ),
90 + ],
91 + ),
92 + );
93 + }
94 +
95 + void _onLoginUserFailure(BuildContext context, String error) {
96 + showPopUp<void>(
97 + context: context,
98 + builder: (BuildContext context) {
99 + return AlertWithOneAction(
100 + alertTitle: S.current.login,
101 + alertContent: error,
102 + buttonText: S.of(context).ok,
103 + buttonAction: () => Navigator.of(context).pop());
104 + });
105 + }
106 +
107 + void _onLoginSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed(
108 + context,
109 + Routes.ioniaVerifyIoniaOtpPage,
110 + arguments: [authViewModel.email, true],
111 + );
112 +}
lib/src/screens/ionia/auth/ionia_verify_otp_page.dart new
+151
@@ -0,0 +1,151 @@
1 +import 'package:cake_wallet/ionia/ionia_create_state.dart';
2 +import 'package:cake_wallet/palette.dart';
3 +import 'package:cake_wallet/routes.dart';
4 +import 'package:cake_wallet/src/screens/base_page.dart';
5 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
6 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
7 +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
8 +import 'package:cake_wallet/src/widgets/primary_button.dart';
9 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
10 +import 'package:cake_wallet/typography.dart';
11 +import 'package:cake_wallet/utils/show_pop_up.dart';
12 +import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
13 +import 'package:flutter/material.dart';
14 +import 'package:cake_wallet/generated/i18n.dart';
15 +import 'package:flutter_mobx/flutter_mobx.dart';
16 +import 'package:keyboard_actions/keyboard_actions.dart';
17 +import 'package:mobx/mobx.dart';
18 +
19 +class IoniaVerifyIoniaOtp extends BasePage {
20 + IoniaVerifyIoniaOtp(this._authViewModel, this._email, this.isSignIn)
21 + : _codeController = TextEditingController(),
22 + _codeFocus = FocusNode() {
23 + _codeController.addListener(() {
24 + final otp = _codeController.text;
25 + _authViewModel.otp = otp;
26 + if (otp.length > 3) {
27 + _authViewModel.otpState = IoniaOtpSendEnabled();
28 + } else {
29 + _authViewModel.otpState = IoniaOtpSendDisabled();
30 + }
31 + });
32 + }
33 +
34 + final IoniaAuthViewModel _authViewModel;
35 + final bool isSignIn;
36 +
37 + final String _email;
38 +
39 + @override
40 + Widget middle(BuildContext context) {
41 + return Text(
42 + S.current.verification,
43 + style: textMediumSemiBold(
44 + color: Theme.of(context).accentTextTheme.display4.backgroundColor,
45 + ),
46 + );
47 + }
48 +
49 + final TextEditingController _codeController;
50 + final FocusNode _codeFocus;
51 +
52 + @override
53 + Widget body(BuildContext context) {
54 + reaction((_) => _authViewModel.otpState, (IoniaOtpState state) {
55 + if (state is IoniaOtpFailure) {
56 + _onOtpFailure(context, state.error);
57 + }
58 + if (state is IoniaOtpSuccess) {
59 + _onOtpSuccessful(context);
60 + }
61 + });
62 + return KeyboardActions(
63 + config: KeyboardActionsConfig(
64 + keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
65 + keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor,
66 + nextFocus: false,
67 + actions: [
68 + KeyboardActionsItem(
69 + focusNode: _codeFocus,
70 + toolbarButtons: [(_) => KeyboardDoneButton()],
71 + ),
72 + ]),
73 + child: Container(
74 + height: 0,
75 + color: Theme.of(context).backgroundColor,
76 + child: ScrollableWithBottomSection(
77 + contentPadding: EdgeInsets.all(24),
78 + content: Column(
79 + children: [
80 + BaseTextFormField(
81 + hintText: S.of(context).enter_code,
82 + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
83 + focusNode: _codeFocus,
84 + controller: _codeController,
85 + ),
86 + SizedBox(height: 14),
87 + Text(
88 + S.of(context).fill_code,
89 + style: TextStyle(color: Color(0xff7A93BA), fontSize: 12),
90 + ),
91 + SizedBox(height: 34),
92 + Row(
93 + mainAxisAlignment: MainAxisAlignment.center,
94 + children: [
95 + Text(S.of(context).dont_get_code),
96 + SizedBox(width: 20),
97 + InkWell(
98 + onTap: () => isSignIn
99 + ? _authViewModel.signIn(_email)
100 + : _authViewModel.createUser(_email),
101 + child: Text(
102 + S.of(context).resend_code,
103 + style: textSmallSemiBold(color: Palette.blueCraiola),
104 + ),
105 + ),
106 + ],
107 + ),
108 + ],
109 + ),
110 + bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
111 + bottomSection: Column(
112 + children: [
113 + Column(
114 + mainAxisAlignment: MainAxisAlignment.end,
115 + children: <Widget>[
116 + Observer(
117 + builder: (_) => LoadingPrimaryButton(
118 + text: S.of(context).continue_text,
119 + onPressed: () async => await _authViewModel.verifyEmail(_codeController.text),
120 + isDisabled: _authViewModel.otpState is IoniaOtpSendDisabled,
121 + isLoading: _authViewModel.otpState is IoniaOtpValidating,
122 + color: Theme.of(context).accentTextTheme.body2.color,
123 + textColor: Colors.white,
124 + ),
125 + ),
126 + SizedBox(height: 20),
127 + ],
128 + ),
129 + ],
130 + ),
131 + ),
132 + ),
133 + );
134 + }
135 +
136 + void _onOtpFailure(BuildContext context, String error) {
137 + showPopUp<void>(
138 + context: context,
139 + builder: (BuildContext context) {
140 + return AlertWithOneAction(
141 + alertTitle: S.current.verification,
142 + alertContent: error,
143 + buttonText: S.of(context).ok,
144 + buttonAction: () => Navigator.of(context).pop());
145 + });
146 + }
147 +
148 + void _onOtpSuccessful(BuildContext context) =>
149 + Navigator.of(context)
150 + .pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst);
151 +}
lib/src/screens/ionia/auth/ionia_welcome_page.dart new
+104
@@ -0,0 +1,104 @@
1 +import 'package:cake_wallet/palette.dart';
2 +import 'package:cake_wallet/routes.dart';
3 +import 'package:cake_wallet/src/screens/base_page.dart';
4 +import 'package:cake_wallet/src/widgets/primary_button.dart';
5 +import 'package:cake_wallet/typography.dart';
6 +import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
7 +import 'package:flutter/material.dart';
8 +import 'package:flutter/src/widgets/framework.dart';
9 +import 'package:cake_wallet/generated/i18n.dart';
10 +import 'package:mobx/mobx.dart';
11 +
12 +class IoniaWelcomePage extends BasePage {
13 + IoniaWelcomePage(this._cardsListViewModel);
14 +
15 + @override
16 + Widget middle(BuildContext context) {
17 + return Text(
18 + S.current.welcome_to_cakepay,
19 + style: textMediumSemiBold(
20 + color: Theme.of(context).accentTextTheme.display4.backgroundColor,
21 + ),
22 + );
23 + }
24 +
25 + final IoniaGiftCardsListViewModel _cardsListViewModel;
26 +
27 + @override
28 + Widget body(BuildContext context) {
29 + reaction((_) => _cardsListViewModel.isLoggedIn, (bool state) {
30 + if (state) {
31 + Navigator.pushReplacementNamed(context, Routes.ioniaManageCardsPage);
32 + }
33 + });
34 + return Padding(
35 + padding: const EdgeInsets.all(24.0),
36 + child: Column(
37 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
38 + children: [
39 + Column(
40 + children: [
41 + SizedBox(height: 100),
42 + Text(
43 + S.of(context).about_cake_pay,
44 + style: TextStyle(
45 + fontSize: 18,
46 + fontWeight: FontWeight.w400,
47 + fontFamily: 'Lato',
48 + color: Theme.of(context).primaryTextTheme.title.color,
49 + ),
50 + ),
51 + SizedBox(height: 20),
52 + Text(
53 + S.of(context).cake_pay_account_note,
54 + style: TextStyle(
55 + fontSize: 18,
56 + fontWeight: FontWeight.w400,
57 + fontFamily: 'Lato',
58 + color: Theme.of(context).primaryTextTheme.title.color,
59 + ),
60 + ),
61 + ],
62 + ),
63 + Column(
64 + mainAxisAlignment: MainAxisAlignment.end,
65 + children: <Widget>[
66 + PrimaryButton(
67 + text: S.of(context).create_account,
68 + onPressed: () => Navigator.of(context).pushNamed(Routes.ioniaCreateAccountPage),
69 + color: Theme.of(context).accentTextTheme.body2.color,
70 + textColor: Colors.white,
71 + ),
72 + SizedBox(
73 + height: 16,
74 + ),
75 + Text(
76 + S.of(context).already_have_account,
77 + style: TextStyle(
78 + fontSize: 15,
79 + fontWeight: FontWeight.w500,
80 + fontFamily: 'Lato',
81 + color: Theme.of(context).primaryTextTheme.title.color,
82 + ),
83 + ),
84 + SizedBox(height: 8),
85 + InkWell(
86 + onTap: () => Navigator.of(context).pushNamed(Routes.ioniaLoginPage),
87 + child: Text(
88 + S.of(context).login,
89 + style: TextStyle(
90 + color: Palette.blueCraiola,
91 + fontSize: 18,
92 + letterSpacing: 1.5,
93 + fontWeight: FontWeight.w900,
94 + ),
95 + ),
96 + ),
97 + SizedBox(height: 20)
98 + ],
99 + )
100 + ],
101 + ),
102 + );
103 + }
104 +}
lib/src/screens/ionia/cards/ionia_account_cards_page.dart new
+181
@@ -0,0 +1,181 @@
1 +import 'dart:ffi';
2 +
3 +import 'package:cake_wallet/ionia/ionia_gift_card.dart';
4 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
5 +import 'package:cake_wallet/routes.dart';
6 +import 'package:cake_wallet/src/screens/base_page.dart';
7 +import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
8 +import 'package:cake_wallet/typography.dart';
9 +import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
10 +import 'package:flutter/material.dart';
11 +import 'package:cake_wallet/generated/i18n.dart';
12 +import 'package:flutter_mobx/flutter_mobx.dart';
13 +
14 +class IoniaAccountCardsPage extends BasePage {
15 + IoniaAccountCardsPage(this.ioniaAccountViewModel);
16 +
17 + final IoniaAccountViewModel ioniaAccountViewModel;
18 +
19 + @override
20 + Widget middle(BuildContext context) {
21 + return Text(
22 + S.of(context).cards,
23 + style: textLargeSemiBold(
24 + color: Theme.of(context).accentTextTheme.display4.backgroundColor,
25 + ),
26 + );
27 + }
28 +
29 + @override
30 + Widget body(BuildContext context) {
31 + return _IoniaCardTabs(ioniaAccountViewModel);
32 + }
33 +}
34 +
35 +class _IoniaCardTabs extends StatefulWidget {
36 + _IoniaCardTabs(this.ioniaAccountViewModel);
37 +
38 + final IoniaAccountViewModel ioniaAccountViewModel;
39 +
40 + @override
41 + _IoniaCardTabsState createState() => _IoniaCardTabsState();
42 +}
43 +
44 +class _IoniaCardTabsState extends State<_IoniaCardTabs> with SingleTickerProviderStateMixin {
45 + TabController _tabController;
46 +
47 + @override
48 + void initState() {
49 + _tabController = TabController(length: 2, vsync: this);
50 + super.initState();
51 + }
52 +
53 + @override
54 + void dispose() {
55 + super.dispose();
56 + _tabController.dispose();
57 + }
58 +
59 + @override
60 + Widget build(BuildContext context) {
61 + return Padding(
62 + padding: const EdgeInsets.all(24.0),
63 + child: Column(
64 + crossAxisAlignment: CrossAxisAlignment.start,
65 + children: [
66 + Container(
67 + height: 45,
68 + width: 230,
69 + padding: EdgeInsets.all(5),
70 + decoration: BoxDecoration(
71 + color: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
72 + borderRadius: BorderRadius.circular(
73 + 25.0,
74 + ),
75 + ),
76 + child: Theme(
77 + data: ThemeData(primaryTextTheme: TextTheme(body2: TextStyle(backgroundColor: Colors.transparent))),
78 + child: TabBar(
79 + controller: _tabController,
80 + indicator: BoxDecoration(
81 + borderRadius: BorderRadius.circular(
82 + 25.0,
83 + ),
84 + color: Theme.of(context).accentTextTheme.body2.color,
85 + ),
86 + labelColor: Theme.of(context).primaryTextTheme.display4.backgroundColor,
87 + unselectedLabelColor: Theme.of(context).primaryTextTheme.title.color,
88 + tabs: [
89 + Tab(
90 + text: S.of(context).active,
91 + ),
92 + Tab(
93 + text: S.of(context).redeemed,
94 + ),
95 + ],
96 + ),
97 + ),
98 + ),
99 + SizedBox(height: 16),
100 + Expanded(
101 + child: Observer(builder: (_) {
102 + final viewModel = widget.ioniaAccountViewModel;
103 + return TabBarView(
104 + controller: _tabController,
105 + children: [
106 + _IoniaCardListView(
107 + emptyText: S.of(context).gift_card_balance_note,
108 + merchList: viewModel.activeMechs,
109 + onTap: (giftCard) {
110 + Navigator.pushNamed(
111 + context,
112 + Routes.ioniaGiftCardDetailPage,
113 + arguments: [giftCard])
114 + .then((_) => viewModel.updateUserGiftCards());
115 + }),
116 + _IoniaCardListView(
117 + emptyText: S.of(context).gift_card_redeemed_note,
118 + merchList: viewModel.redeemedMerchs,
119 + onTap: (giftCard) {
120 + Navigator.pushNamed(
121 + context,
122 + Routes.ioniaGiftCardDetailPage,
123 + arguments: [giftCard])
124 + .then((_) => viewModel.updateUserGiftCards());
125 + }),
126 + ],
127 + );
128 + }),
129 + ),
130 + ],
131 + ),
132 + );
133 + }
134 +}
135 +
136 +class _IoniaCardListView extends StatelessWidget {
137 + _IoniaCardListView({
138 + Key key,
139 + @required this.emptyText,
140 + @required this.merchList,
141 + @required this.onTap,
142 + }) : super(key: key);
143 +
144 + final String emptyText;
145 + final List<IoniaGiftCard> merchList;
146 + final void Function(IoniaGiftCard giftCard) onTap;
147 +
148 + @override
149 + Widget build(BuildContext context) {
150 + return merchList.isEmpty
151 + ? Center(
152 + child: Text(
153 + emptyText,
154 + textAlign: TextAlign.center,
155 + style: textSmall(
156 + color: Theme.of(context).primaryTextTheme.overline.color,
157 + ),
158 + ),
159 + )
160 + : ListView.builder(
161 + itemCount: merchList.length,
162 + itemBuilder: (context, index) {
163 + final merchant = merchList[index];
164 + return Padding(
165 + padding: const EdgeInsets.only(bottom: 16),
166 + child: CardItem(
167 + onTap: () => onTap?.call(merchant),
168 + title: merchant.legalName,
169 + backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
170 + discount: 0,
171 + discountBackground: AssetImage('assets/images/red_badge_discount.png'),
172 + titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor,
173 + subtitleColor: Theme.of(context).hintColor,
174 + subTitle: '',
175 + logoUrl: merchant.logoUrl,
176 + ),
177 + );
178 + },
179 + );
180 + }
181 +}
lib/src/screens/ionia/cards/ionia_account_page.dart new
+181
@@ -0,0 +1,181 @@
1 +import 'package:cake_wallet/routes.dart';
2 +import 'package:cake_wallet/src/screens/base_page.dart';
3 +import 'package:cake_wallet/generated/i18n.dart';
4 +import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.dart';
5 +import 'package:cake_wallet/src/widgets/primary_button.dart';
6 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
7 +import 'package:cake_wallet/typography.dart';
8 +import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
9 +import 'package:flutter/material.dart';
10 +import 'package:flutter_mobx/flutter_mobx.dart';
11 +
12 +class IoniaAccountPage extends BasePage {
13 + IoniaAccountPage(this.ioniaAccountViewModel);
14 +
15 + final IoniaAccountViewModel ioniaAccountViewModel;
16 +
17 + @override
18 + Widget middle(BuildContext context) {
19 + return Text(
20 + S.current.account,
21 + style: textMediumSemiBold(
22 + color: Theme.of(context).accentTextTheme.display4.backgroundColor,
23 + ),
24 + );
25 + }
26 +
27 + @override
28 + Widget body(BuildContext context) {
29 + return ScrollableWithBottomSection(
30 + contentPadding: EdgeInsets.all(24),
31 + content: Column(
32 + children: [
33 + _GradiantContainer(
34 + content: Row(
35 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
36 + children: [
37 + Observer(
38 + builder: (_) => RichText(
39 + text: TextSpan(
40 + text: '${ioniaAccountViewModel.countOfMerch}',
41 + style: textLargeSemiBold(),
42 + children: [
43 + TextSpan(
44 + text: ' ${S.of(context).active_cards}',
45 + style: textSmall(color: Colors.white.withOpacity(0.7))),
46 + ],
47 + ),
48 + )),
49 + InkWell(
50 + onTap: () {
51 + Navigator.pushNamed(context, Routes.ioniaAccountCardsPage)
52 + .then((_) => ioniaAccountViewModel.updateUserGiftCards());
53 + },
54 + child: Padding(
55 + padding: const EdgeInsets.all(8.0),
56 + child: Text(
57 + S.of(context).view_all,
58 + style: textSmallSemiBold(),
59 + ),
60 + ),
61 + )
62 + ],
63 + ),
64 + ),
65 + SizedBox(height: 8),
66 + //Row(
67 + // mainAxisAlignment: MainAxisAlignment.spaceBetween,
68 + // children: [
69 + // _GradiantContainer(
70 + // padding: EdgeInsets.all(16),
71 + // width: deviceWidth * 0.28,
72 + // content: Column(
73 + // crossAxisAlignment: CrossAxisAlignment.start,
74 + // children: [
75 + // Text(
76 + // S.of(context).total_saving,
77 + // style: textSmall(),
78 + // ),
79 + // SizedBox(height: 8),
80 + // Text(
81 + // '\$100',
82 + // style: textMediumSemiBold(),
83 + // ),
84 + // ],
85 + // ),
86 + // ),
87 + // _GradiantContainer(
88 + // padding: EdgeInsets.all(16),
89 + // width: deviceWidth * 0.28,
90 + // content: Column(
91 + // crossAxisAlignment: CrossAxisAlignment.start,
92 + // children: [
93 + // Text(
94 + // S.of(context).last_30_days,
95 + // style: textSmall(),
96 + // ),
97 + // SizedBox(height: 8),
98 + // Text(
99 + // '\$100',
100 + // style: textMediumSemiBold(),
101 + // ),
102 + // ],
103 + // ),
104 + // ),
105 + // _GradiantContainer(
106 + // padding: EdgeInsets.all(16),
107 + // width: deviceWidth * 0.28,
108 + // content: Column(
109 + // crossAxisAlignment: CrossAxisAlignment.start,
110 + // children: [
111 + // Text(
112 + // S.of(context).avg_savings,
113 + // style: textSmall(),
114 + // ),
115 + // SizedBox(height: 8),
116 + // Text(
117 + // '10%',
118 + // style: textMediumSemiBold(),
119 + // ),
120 + // ],
121 + // ),
122 + // ),
123 + // ],
124 + //),
125 + SizedBox(height: 40),
126 + Observer(
127 + builder: (_) => IoniaTile(title: S.of(context).email_address, subTitle: ioniaAccountViewModel.email),
128 + ),
129 + Divider()
130 + ],
131 + ),
132 + bottomSectionPadding: EdgeInsets.all(30),
133 + bottomSection: Column(
134 + children: [
135 + PrimaryButton(
136 + color: Theme.of(context).accentTextTheme.body2.color,
137 + textColor: Colors.white,
138 + text: S.of(context).logout,
139 + onPressed: () {
140 + ioniaAccountViewModel.logout();
141 + Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (route) => false);
142 + },
143 + ),
144 + ],
145 + ),
146 + );
147 + }
148 +}
149 +
150 +class _GradiantContainer extends StatelessWidget {
151 + const _GradiantContainer({
152 + Key key,
153 + @required this.content,
154 + this.padding,
155 + this.width,
156 + }) : super(key: key);
157 +
158 + final Widget content;
159 + final EdgeInsets padding;
160 + final double width;
161 +
162 + @override
163 + Widget build(BuildContext context) {
164 + return Container(
165 + child: content,
166 + width: width,
167 + padding: padding ?? EdgeInsets.all(24),
168 + decoration: BoxDecoration(
169 + borderRadius: BorderRadius.circular(15),
170 + gradient: LinearGradient(
171 + colors: [
172 + Theme.of(context).scaffoldBackgroundColor,
173 + Theme.of(context).accentColor,
174 + ],
175 + begin: Alignment.topRight,
176 + end: Alignment.bottomLeft,
177 + ),
178 + ),
179 + );
180 + }
181 +}
lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart new
+114
@@ -0,0 +1,114 @@
1 +import 'package:cake_wallet/ionia/ionia_create_state.dart';
2 +import 'package:cake_wallet/routes.dart';
3 +import 'package:cake_wallet/src/screens/base_page.dart';
4 +import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart';
5 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
6 +import 'package:cake_wallet/src/widgets/primary_button.dart';
7 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
8 +import 'package:cake_wallet/typography.dart';
9 +import 'package:cake_wallet/utils/show_pop_up.dart';
10 +import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
11 +import 'package:flutter/material.dart';
12 +import 'package:cake_wallet/generated/i18n.dart';
13 +import 'package:mobx/mobx.dart';
14 +
15 +class IoniaActivateDebitCardPage extends BasePage {
16 +
17 + IoniaActivateDebitCardPage(this._cardsListViewModel);
18 +
19 + final IoniaGiftCardsListViewModel _cardsListViewModel;
20 +
21 + @override
22 + Widget middle(BuildContext context) {
23 + return Text(
24 + S.current.debit_card,
25 + style: textMediumSemiBold(
26 + color: Theme.of(context).accentTextTheme.display4.backgroundColor,
27 + ),
28 + );
29 + }
30 +
31 + @override
32 + Widget body(BuildContext context) {
33 + reaction((_) => _cardsListViewModel.createCardState, (IoniaCreateCardState state) {
34 + if (state is IoniaCreateCardFailure) {
35 + _onCreateCardFailure(context, state.error);
36 + }
37 + if (state is IoniaCreateCardSuccess) {
38 + _onCreateCardSuccess(context);
39 + }
40 + });
41 + return ScrollableWithBottomSection(
42 + contentPadding: EdgeInsets.zero,
43 + content: Padding(
44 + padding: const EdgeInsets.all(16.0),
45 + child: Column(
46 + children: [
47 + SizedBox(height: 16),
48 + Text(S.of(context).debit_card_terms),
49 + SizedBox(height: 24),
50 + Text(S.of(context).please_reference_document),
51 + SizedBox(height: 40),
52 + Padding(
53 + padding: const EdgeInsets.symmetric(horizontal: 8.0),
54 + child: Column(
55 + children: [
56 + TextIconButton(
57 + label: S.current.cardholder_agreement,
58 + onTap: () {},
59 + ),
60 + SizedBox(
61 + height: 24,
62 + ),
63 + TextIconButton(
64 + label: S.current.e_sign_consent,
65 + onTap: () {},
66 + ),
67 + ],
68 + ),
69 + ),
70 + ],
71 + ),
72 + ),
73 + bottomSection: LoadingPrimaryButton(
74 + onPressed: () {
75 + _cardsListViewModel.createCard();
76 + },
77 + isLoading: _cardsListViewModel.createCardState is IoniaCreateCardLoading,
78 + text: S.of(context).agree_and_continue,
79 + color: Theme.of(context).accentTextTheme.body2.color,
80 + textColor: Colors.white,
81 + ),
82 + );
83 + }
84 +
85 + void _onCreateCardFailure(BuildContext context, String errorMessage) {
86 + showPopUp<void>(
87 + context: context,
88 + builder: (BuildContext context) {
89 + return AlertWithOneAction(
90 + alertTitle: S.current.error,
91 + alertContent: errorMessage,
92 + buttonText: S.of(context).ok,
93 + buttonAction: () => Navigator.of(context).pop());
94 + });
95 + }
96 +
97 + void _onCreateCardSuccess(BuildContext context) {
98 + Navigator.pushNamed(
99 + context,
100 + Routes.ioniaDebitCardPage,
101 + );
102 + showPopUp<void>(
103 + context: context,
104 + builder: (BuildContext context) {
105 + return AlertWithOneAction(
106 + alertTitle: S.of(context).congratulations,
107 + alertContent: S.of(context).you_now_have_debit_card,
108 + buttonText: S.of(context).ok,
109 + buttonAction: () => Navigator.of(context).pop(),
110 + );
111 + },
112 + );
113 + }
114 +}
lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart new
+492
@@ -0,0 +1,492 @@
1 +import 'dart:ui';
2 +import 'package:cake_wallet/core/execution_state.dart';
3 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
4 +import 'package:cake_wallet/ionia/ionia_tip.dart';
5 +import 'package:cake_wallet/palette.dart';
6 +import 'package:cake_wallet/routes.dart';
7 +import 'package:cake_wallet/src/screens/ionia/widgets/confirm_modal.dart';
8 +import 'package:cake_wallet/src/screens/ionia/widgets/ionia_alert_model.dart';
9 +import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart';
10 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
11 +import 'package:cake_wallet/src/widgets/discount_badge.dart';
12 +import 'package:cake_wallet/src/widgets/primary_button.dart';
13 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
14 +import 'package:cake_wallet/typography.dart';
15 +import 'package:cake_wallet/utils/show_pop_up.dart';
16 +import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
17 +import 'package:flutter/material.dart';
18 +import 'package:cake_wallet/generated/i18n.dart';
19 +import 'package:flutter_mobx/flutter_mobx.dart';
20 +import 'package:mobx/mobx.dart';
21 +import 'package:cake_wallet/src/screens/base_page.dart';
22 +
23 +class IoniaBuyGiftCardDetailPage extends BasePage {
24 + IoniaBuyGiftCardDetailPage(this.ioniaPurchaseViewModel);
25 +
26 + final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel;
27 +
28 + @override
29 + Widget middle(BuildContext context) {
30 + return Text(
31 + ioniaPurchaseViewModel.ioniaMerchant.legalName,
32 + style: textMediumSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor),
33 + );
34 + }
35 +
36 + @override
37 + Widget trailing(BuildContext context)
38 + => ioniaPurchaseViewModel.ioniaMerchant.discount > 0
39 + ? DiscountBadge(percentage: ioniaPurchaseViewModel.ioniaMerchant.discount)
40 + : null;
41 +
42 + @override
43 + Widget body(BuildContext context) {
44 + reaction((_) => ioniaPurchaseViewModel.invoiceCreationState, (ExecutionState state) {
45 + if (state is FailureState) {
46 + WidgetsBinding.instance.addPostFrameCallback((_) {
47 + showPopUp<void>(
48 + context: context,
49 + builder: (BuildContext context) {
50 + return AlertWithOneAction(
51 + alertTitle: S.of(context).error,
52 + alertContent: state.error,
53 + buttonText: S.of(context).ok,
54 + buttonAction: () => Navigator.of(context).pop());
55 + });
56 + });
57 + }
58 + });
59 +
60 + reaction((_) => ioniaPurchaseViewModel.invoiceCommittingState, (ExecutionState state) {
61 + if (state is FailureState) {
62 + WidgetsBinding.instance.addPostFrameCallback((_) {
63 + showPopUp<void>(
64 + context: context,
65 + builder: (BuildContext context) {
66 + return AlertWithOneAction(
67 + alertTitle: S.of(context).error,
68 + alertContent: state.error,
69 + buttonText: S.of(context).ok,
70 + buttonAction: () => Navigator.of(context).pop());
71 + });
72 + });
73 + }
74 +
75 + if (state is ExecutedSuccessfullyState) {
76 + WidgetsBinding.instance.addPostFrameCallback((_) {
77 + Navigator.of(context).pushReplacementNamed(
78 + Routes.ioniaPaymentStatusPage,
79 + arguments: [
80 + ioniaPurchaseViewModel.paymentInfo,
81 + ioniaPurchaseViewModel.committedInfo]);
82 + });
83 + }
84 + });
85 +
86 + return ScrollableWithBottomSection(
87 + contentPadding: EdgeInsets.zero,
88 + content: Observer(builder: (_) {
89 + final tipAmount = ioniaPurchaseViewModel.tipAmount;
90 + return Column(
91 + children: [
92 + SizedBox(height: 36),
93 + Container(
94 + padding: EdgeInsets.symmetric(vertical: 24),
95 + margin: EdgeInsets.symmetric(horizontal: 16),
96 + decoration: BoxDecoration(
97 + borderRadius: BorderRadius.circular(20),
98 + gradient: LinearGradient(
99 + colors: [
100 + Theme.of(context).primaryTextTheme.subhead.color,
101 + Theme.of(context).primaryTextTheme.subhead.decorationColor,
102 + ],
103 + begin: Alignment.topLeft,
104 + end: Alignment.bottomRight,
105 + ),
106 + ),
107 + child: Column(
108 + children: [
109 + Text(
110 + S.of(context).gift_card_amount,
111 + style: textSmall(),
112 + ),
113 + SizedBox(height: 4),
114 + Text(
115 + '\$${ioniaPurchaseViewModel.giftCardAmount.toStringAsFixed(2)}',
116 + style: textXLargeSemiBold(),
117 + ),
118 + SizedBox(height: 24),
119 + Padding(
120 + padding: const EdgeInsets.symmetric(horizontal: 24.0),
121 + child: Row(
122 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
123 + children: [
124 + Column(
125 + crossAxisAlignment: CrossAxisAlignment.start,
126 + children: [
127 + Text(
128 + S.of(context).bill_amount,
129 + style: textSmall(),
130 + ),
131 + SizedBox(height: 4),
132 + Text(
133 + '\$${ioniaPurchaseViewModel.billAmount.toStringAsFixed(2)}',
134 + style: textLargeSemiBold(),
135 + ),
136 + ],
137 + ),
138 + Column(
139 + crossAxisAlignment: CrossAxisAlignment.end,
140 + children: [
141 + Text(
142 + S.of(context).tip,
143 + style: textSmall(),
144 + ),
145 + SizedBox(height: 4),
146 + Text(
147 + '\$${tipAmount.toStringAsFixed(2)}',
148 + style: textLargeSemiBold(),
149 + ),
150 + ],
151 + ),
152 + ],
153 + ),
154 + ),
155 + ],
156 + ),
157 + ),
158 + Padding(
159 + padding: const EdgeInsets.all(24.0),
160 + child: Column(
161 + crossAxisAlignment: CrossAxisAlignment.start,
162 + children: [
163 + Text(
164 + S.of(context).tip,
165 + style: TextStyle(
166 + color: Theme.of(context).primaryTextTheme.title.color,
167 + fontWeight: FontWeight.w700,
168 + fontSize: 14,
169 + ),
170 + ),
171 + SizedBox(height: 4),
172 + Observer(
173 + builder: (_) => TipButtonGroup(
174 + selectedTip: ioniaPurchaseViewModel.selectedTip.percentage,
175 + tipsList: ioniaPurchaseViewModel.tips,
176 + onSelect: (value) => ioniaPurchaseViewModel.addTip(value),
177 + ),
178 + )
179 + ],
180 + ),
181 + ),
182 + SizedBox(height: 20),
183 + Padding(
184 + padding: const EdgeInsets.symmetric(horizontal: 24.0),
185 + child: TextIconButton(
186 + label: S.of(context).how_to_use_card,
187 + onTap: () => _showHowToUseCard(context, ioniaPurchaseViewModel.ioniaMerchant),
188 + ),
189 + ),
190 + ],
191 + );
192 + }),
193 + bottomSection: Column(
194 + children: [
195 + Padding(
196 + padding: EdgeInsets.only(bottom: 12),
197 + child: Observer(builder: (_) {
198 + return LoadingPrimaryButton(
199 + isLoading: ioniaPurchaseViewModel.invoiceCreationState is IsExecutingState ||
200 + ioniaPurchaseViewModel.invoiceCommittingState is IsExecutingState,
201 + onPressed: () => purchaseCard(context),
202 + text: S.of(context).purchase_gift_card,
203 + color: Theme.of(context).accentTextTheme.body2.color,
204 + textColor: Colors.white,
205 + );
206 + }),
207 + ),
208 + SizedBox(height: 8),
209 + InkWell(
210 + onTap: () => _showTermsAndCondition(context),
211 + child: Text(S.of(context).settings_terms_and_conditions,
212 + style: textMediumSemiBold(
213 + color: Theme.of(context).primaryTextTheme.body1.color,
214 + ).copyWith(fontSize: 12)),
215 + ),
216 + SizedBox(height: 16)
217 + ],
218 + ),
219 + );
220 + }
221 +
222 + void _showTermsAndCondition(BuildContext context) {
223 + showPopUp<void>(
224 + context: context,
225 + builder: (BuildContext context) {
226 + return IoniaAlertModal(
227 + title: S.of(context).settings_terms_and_conditions,
228 + content: Align(
229 + alignment: Alignment.bottomLeft,
230 + child: Text(
231 + ioniaPurchaseViewModel.ioniaMerchant.termsAndConditions,
232 + style: textMedium(
233 + color: Theme.of(context).textTheme.display2.color,
234 + ),
235 + ),
236 + ),
237 + actionTitle: S.of(context).agree,
238 + showCloseButton: false,
239 + heightFactor: 0.6,
240 + );
241 + });
242 + }
243 +
244 + Future<void> purchaseCard(BuildContext context) async {
245 + await ioniaPurchaseViewModel.createInvoice();
246 +
247 + if (ioniaPurchaseViewModel.invoiceCreationState is ExecutedSuccessfullyState) {
248 + await _presentSuccessfulInvoiceCreationPopup(context);
249 + }
250 + }
251 +
252 + void _showHowToUseCard(
253 + BuildContext context,
254 + IoniaMerchant merchant,
255 + ) {
256 + showPopUp<void>(
257 + context: context,
258 + builder: (BuildContext context) {
259 + return IoniaAlertModal(
260 + title: S.of(context).how_to_use_card,
261 + content: Column(
262 + crossAxisAlignment: CrossAxisAlignment.start,
263 + children: merchant.instructions
264 + .map((instruction) {
265 + return [
266 + Padding(
267 + padding: EdgeInsets.all(10),
268 + child: Text(
269 + instruction.header,
270 + style: textLargeSemiBold(
271 + color: Theme.of(context).textTheme.display2.color,
272 + ),
273 + )),
274 + Text(
275 + instruction.body,
276 + style: textMedium(
277 + color: Theme.of(context).textTheme.display2.color,
278 + ),
279 + )
280 + ];
281 + })
282 + .expand((e) => e)
283 + .toList()),
284 + actionTitle: S.current.send_got_it,
285 + );
286 + });
287 + }
288 +
289 + Future<void> _presentSuccessfulInvoiceCreationPopup(BuildContext context) async {
290 + final amount = ioniaPurchaseViewModel.invoice.totalAmount;
291 + final addresses = ioniaPurchaseViewModel.invoice.outAddresses;
292 +
293 + await showPopUp<void>(
294 + context: context,
295 + builder: (_) {
296 + return IoniaConfirmModal(
297 + alertTitle: S.of(context).confirm_sending,
298 + alertContent: Container(
299 + height: 200,
300 + padding: EdgeInsets.all(15),
301 + child: Column(children: [
302 + Row(children: [
303 + Text(S.of(context).payment_id,
304 + textAlign: TextAlign.center,
305 + style: TextStyle(
306 + fontSize: 16,
307 + fontWeight: FontWeight.w400,
308 + color: PaletteDark.pigeonBlue,
309 + decoration: TextDecoration.none)),
310 + Text(ioniaPurchaseViewModel.invoice.paymentId,
311 + style: TextStyle(
312 + fontSize: 16,
313 + fontWeight: FontWeight.w400,
314 + color: PaletteDark.pigeonBlue,
315 + decoration: TextDecoration.none))
316 + ], mainAxisAlignment: MainAxisAlignment.spaceBetween),
317 + SizedBox(height: 10),
318 + Row(children: [
319 + Text(S.of(context).amount,
320 + textAlign: TextAlign.center,
321 + style: TextStyle(
322 + fontSize: 16,
323 + fontWeight: FontWeight.w400,
324 + color: PaletteDark.pigeonBlue,
325 + decoration: TextDecoration.none)),
326 + Text('$amount ${ioniaPurchaseViewModel.invoice.chain}',
327 + style: TextStyle(
328 + fontSize: 16,
329 + fontWeight: FontWeight.w400,
330 + color: PaletteDark.pigeonBlue,
331 + decoration: TextDecoration.none))
332 + ], mainAxisAlignment: MainAxisAlignment.spaceBetween),
333 + SizedBox(height: 25),
334 + Row(children: [
335 + Text(S.of(context).recipient_address,
336 + style: TextStyle(
337 + fontSize: 16,
338 + fontWeight: FontWeight.w400,
339 + color: PaletteDark.pigeonBlue,
340 + decoration: TextDecoration.none))
341 + ], mainAxisAlignment: MainAxisAlignment.center),
342 + Expanded(
343 + child: ListView.builder(
344 + itemBuilder: (_, int index) {
345 + return Text(addresses[index],
346 + style: TextStyle(
347 + fontSize: 14,
348 + fontWeight: FontWeight.w400,
349 + color: PaletteDark.pigeonBlue,
350 + decoration: TextDecoration.none));
351 + },
352 + itemCount: addresses.length,
353 + physics: NeverScrollableScrollPhysics()))
354 + ])),
355 + rightButtonText: S.of(context).ok,
356 + leftButtonText: S.of(context).cancel,
357 + leftActionColor: Color(0xffFF6600),
358 + rightActionColor: Theme.of(context).accentTextTheme.body2.color,
359 + actionRightButton: () async {
360 + Navigator.of(context).pop();
361 + await ioniaPurchaseViewModel.commitPaymentInvoice();
362 + },
363 + actionLeftButton: () => Navigator.of(context).pop());
364 + },
365 + );
366 + }
367 +}
368 +
369 +class TipButtonGroup extends StatelessWidget {
370 + const TipButtonGroup({
371 + Key key,
372 + @required this.selectedTip,
373 + @required this.onSelect,
374 + @required this.tipsList,
375 + }) : super(key: key);
376 +
377 + final Function(IoniaTip) onSelect;
378 + final double selectedTip;
379 + final List<IoniaTip> tipsList;
380 +
381 + bool _isSelected(double value) => selectedTip == value;
382 +
383 + @override
384 + Widget build(BuildContext context) {
385 + return Container(
386 + height: 50,
387 + child: ListView.builder(
388 + scrollDirection: Axis.horizontal,
389 + itemCount: tipsList.length,
390 + itemBuilder: (BuildContext context, int index) {
391 + final tip = tipsList[index];
392 + return Padding(
393 + padding: EdgeInsets.only(right: 5),
394 + child: TipButton(
395 + isSelected: _isSelected(tip.percentage),
396 + onTap: () => onSelect(tip),
397 + caption: '${tip.percentage}%',
398 + subTitle: '\$${tip.additionalAmount}',
399 + ));
400 + }));
401 + }
402 +}
403 +
404 +class TipButton extends StatelessWidget {
405 + const TipButton({
406 + @required this.caption,
407 + this.subTitle,
408 + @required this.onTap,
409 + this.isSelected = false,
410 + });
411 +
412 + final String caption;
413 + final String subTitle;
414 + final bool isSelected;
415 + final void Function() onTap;
416 +
417 + bool isDark(BuildContext context) => Theme.of(context).brightness == Brightness.dark;
418 +
419 + Color captionTextColor(BuildContext context) {
420 + if (isDark(context)) {
421 + return Theme.of(context).primaryTextTheme.title.color;
422 + }
423 +
424 + return isSelected
425 + ? Theme.of(context).accentTextTheme.title.color
426 + : Theme.of(context).primaryTextTheme.title.color;
427 + }
428 +
429 + Color subTitleTextColor(BuildContext context) {
430 + if (isDark(context)) {
431 + return Theme.of(context).primaryTextTheme.title.color;
432 + }
433 +
434 + return isSelected
435 + ? Theme.of(context).accentTextTheme.title.color
436 + : Theme.of(context).primaryTextTheme.overline.color;
437 + }
438 +
439 + Color backgroundColor(BuildContext context) {
440 + if (isDark(context)) {
441 + return isSelected
442 + ? null
443 + : Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.01);
444 + }
445 +
446 + return isSelected
447 + ? null
448 + : Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1);
449 + }
450 +
451 + @override
452 + Widget build(BuildContext context) {
453 + return InkWell(
454 + onTap: onTap,
455 + child: Container(
456 + height: 49,
457 + child: Column(
458 + mainAxisAlignment: MainAxisAlignment.center,
459 + children: [
460 + Text(caption,
461 + style: textSmallSemiBold(
462 + color: captionTextColor(context))),
463 + if (subTitle != null) ...[
464 + SizedBox(height: 4),
465 + Text(
466 + subTitle,
467 + style: textXxSmallSemiBold(
468 + color: subTitleTextColor(context),
469 + ),
470 + ),
471 + ]
472 + ],
473 + ),
474 + padding: EdgeInsets.symmetric(horizontal: 18, vertical: 8),
475 + decoration: BoxDecoration(
476 + borderRadius: BorderRadius.circular(10),
477 + color: backgroundColor(context),
478 + gradient: isSelected
479 + ? LinearGradient(
480 + colors: [
481 + Theme.of(context).primaryTextTheme.subhead.color,
482 + Theme.of(context).primaryTextTheme.subhead.decorationColor,
483 + ],
484 + begin: Alignment.topLeft,
485 + end: Alignment.bottomRight,
486 + )
487 + : null,
488 + ),
489 + ),
490 + );
491 + }
492 +}
lib/src/screens/ionia/cards/ionia_buy_gift_card.dart new
+185
@@ -0,0 +1,185 @@
1 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
2 +import 'package:cake_wallet/routes.dart';
3 +import 'package:cake_wallet/src/screens/base_page.dart';
4 +import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
5 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
6 +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
7 +import 'package:cake_wallet/src/widgets/primary_button.dart';
8 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
9 +import 'package:cake_wallet/themes/theme_base.dart';
10 +import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart';
11 +import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
12 +import 'package:flutter/material.dart';
13 +import 'package:flutter/services.dart';
14 +import 'package:flutter_mobx/flutter_mobx.dart';
15 +import 'package:keyboard_actions/keyboard_actions.dart';
16 +import 'package:cake_wallet/generated/i18n.dart';
17 +
18 +class IoniaBuyGiftCardPage extends BasePage {
19 + IoniaBuyGiftCardPage(
20 + this.ioniaBuyCardViewModel,
21 + ) : _amountFieldFocus = FocusNode(),
22 + _amountController = TextEditingController() {
23 + _amountController.addListener(() {
24 + ioniaBuyCardViewModel.onAmountChanged(_amountController.text);
25 + });
26 + }
27 +
28 + final IoniaBuyCardViewModel ioniaBuyCardViewModel;
29 +
30 + @override
31 + String get title => S.current.enter_amount;
32 +
33 + @override
34 + Color get titleColor => Colors.white;
35 +
36 + @override
37 + bool get extendBodyBehindAppBar => true;
38 +
39 + @override
40 + AppBarStyle get appBarStyle => AppBarStyle.transparent;
41 +
42 + Color get textColor => currentTheme.type == ThemeType.dark ? Colors.white : Color(0xff393939);
43 +
44 + final TextEditingController _amountController;
45 + final FocusNode _amountFieldFocus;
46 +
47 + @override
48 + Widget body(BuildContext context) {
49 + final _width = MediaQuery.of(context).size.width;
50 + final merchant = ioniaBuyCardViewModel.ioniaMerchant;
51 + return KeyboardActions(
52 + disableScroll: true,
53 + config: KeyboardActionsConfig(
54 + keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
55 + keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor,
56 + nextFocus: false,
57 + actions: [
58 + KeyboardActionsItem(
59 + focusNode: _amountFieldFocus,
60 + toolbarButtons: [(_) => KeyboardDoneButton()],
61 + ),
62 + ]),
63 + child: Container(
64 + color: Theme.of(context).backgroundColor,
65 + child: ScrollableWithBottomSection(
66 + contentPadding: EdgeInsets.zero,
67 + content: Column(
68 + children: [
69 + Container(
70 + padding: EdgeInsets.symmetric(horizontal: 25),
71 + decoration: BoxDecoration(
72 + borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
73 + gradient: LinearGradient(colors: [
74 + Theme.of(context).primaryTextTheme.subhead.color,
75 + Theme.of(context).primaryTextTheme.subhead.decorationColor,
76 + ], begin: Alignment.topLeft, end: Alignment.bottomRight),
77 + ),
78 + child: Column(
79 + mainAxisSize: MainAxisSize.min,
80 + crossAxisAlignment: CrossAxisAlignment.stretch,
81 + mainAxisAlignment: MainAxisAlignment.center,
82 + children: [
83 + SizedBox(height: 150),
84 + BaseTextFormField(
85 + controller: _amountController,
86 + focusNode: _amountFieldFocus,
87 + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
88 + inputFormatters: [
89 + FilteringTextInputFormatter.deny(RegExp('[\-|\ ]')),
90 + WhitelistingTextInputFormatter(RegExp(r'^\d+(\.|\,)?\d{0,2}'))],
91 + hintText: '1000',
92 + placeholderTextStyle: TextStyle(
93 + color: Theme.of(context).primaryTextTheme.headline.color,
94 + fontWeight: FontWeight.w600,
95 + fontSize: 36,
96 + ),
97 + borderColor: Theme.of(context).primaryTextTheme.headline.color,
98 + textColor: Colors.white,
99 + textStyle: TextStyle(
100 + color: Colors.white,
101 + fontSize: 36,
102 + ),
103 + suffixIcon: SizedBox(
104 + width: _width / 6,
105 + ),
106 + prefixIcon: Padding(
107 + padding: EdgeInsets.only(
108 + top: 5.0,
109 + left: _width / 4,
110 + ),
111 + child: Text(
112 + 'USD: ',
113 + style: TextStyle(
114 + color: Colors.white,
115 + fontWeight: FontWeight.w600,
116 + fontSize: 36,
117 + ),
118 + ),
119 + ),
120 + ),
121 + SizedBox(height: 8),
122 + Row(
123 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
124 + crossAxisAlignment: CrossAxisAlignment.start,
125 + children: [
126 + Text(
127 + S.of(context).min_amount(merchant.minimumCardPurchase.toStringAsFixed(2)),
128 + style: TextStyle(
129 + color: Theme.of(context).primaryTextTheme.headline.color,
130 + ),
131 + ),
132 + Text(
133 + S.of(context).max_amount(merchant.maximumCardPurchase.toStringAsFixed(2)),
134 + style: TextStyle(
135 + color: Theme.of(context).primaryTextTheme.headline.color,
136 + ),
137 + ),
138 + ],
139 + ),
140 + SizedBox(height: 24),
141 + ],
142 + ),
143 + ),
144 + Padding(
145 + padding: const EdgeInsets.all(24.0),
146 + child: CardItem(
147 + title: merchant.legalName,
148 + backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
149 + discount: merchant.discount,
150 + titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor,
151 + subtitleColor: Theme.of(context).hintColor,
152 + subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline,
153 + logoUrl: merchant.logoUrl,
154 + ),
155 + )
156 + ],
157 + ),
158 + bottomSection: Column(
159 + children: [
160 + Observer(builder: (_) {
161 + return Padding(
162 + padding: EdgeInsets.only(bottom: 12),
163 + child: PrimaryButton(
164 + onPressed: () => Navigator.of(context).pushNamed(
165 + Routes.ioniaBuyGiftCardDetailPage,
166 + arguments: [
167 + ioniaBuyCardViewModel.amount,
168 + ioniaBuyCardViewModel.ioniaMerchant,
169 + ],
170 + ),
171 + text: S.of(context).continue_text,
172 + isDisabled: !ioniaBuyCardViewModel.isEnablePurchase,
173 + color: Theme.of(context).accentTextTheme.body2.color,
174 + textColor: Colors.white,
175 + ),
176 + );
177 + }),
178 + SizedBox(height: 30),
179 + ],
180 + ),
181 + ),
182 + ),
183 + );
184 + }
185 +}
lib/src/screens/ionia/cards/ionia_custom_tip_page.dart new
+176
@@ -0,0 +1,176 @@
1 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
2 +import 'package:cake_wallet/src/screens/base_page.dart';
3 +import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
4 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
5 +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
6 +import 'package:cake_wallet/src/widgets/primary_button.dart';
7 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
8 +import 'package:cake_wallet/themes/theme_base.dart';
9 +import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
10 +import 'package:flutter/material.dart';
11 +import 'package:flutter/services.dart';
12 +import 'package:flutter_mobx/flutter_mobx.dart';
13 +import 'package:keyboard_actions/keyboard_actions.dart';
14 +import 'package:cake_wallet/generated/i18n.dart';
15 +
16 +class IoniaCustomTipPage extends BasePage {
17 + IoniaCustomTipPage(
18 + this.ioniaPurchaseViewModel,
19 + ) : _amountFieldFocus = FocusNode(),
20 + _amountController = TextEditingController() {
21 + _amountController.addListener(() {
22 + // ioniaPurchaseViewModel.onTipChanged(_amountController.text);
23 + });
24 + }
25 +
26 + final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel;
27 +
28 +
29 + @override
30 + String get title => S.current.enter_amount;
31 +
32 + @override
33 + Color get titleColor => Colors.white;
34 +
35 + @override
36 + bool get extendBodyBehindAppBar => true;
37 +
38 + @override
39 + AppBarStyle get appBarStyle => AppBarStyle.transparent;
40 +
41 + Color get textColor => currentTheme.type == ThemeType.dark ? Colors.white : Color(0xff393939);
42 +
43 + final TextEditingController _amountController;
44 + final FocusNode _amountFieldFocus;
45 +
46 + @override
47 + Widget body(BuildContext context) {
48 + final _width = MediaQuery.of(context).size.width;
49 + final merchant = ioniaPurchaseViewModel.ioniaMerchant;
50 + return KeyboardActions(
51 + disableScroll: true,
52 + config: KeyboardActionsConfig(
53 + keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
54 + keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor,
55 + nextFocus: false,
56 + actions: [
57 + KeyboardActionsItem(
58 + focusNode: _amountFieldFocus,
59 + toolbarButtons: [(_) => KeyboardDoneButton()],
60 + ),
61 + ]),
62 + child: Container(
63 + color: Theme.of(context).backgroundColor,
64 + child: ScrollableWithBottomSection(
65 + contentPadding: EdgeInsets.zero,
66 + content: Column(
67 + children: [
68 + Container(
69 + padding: EdgeInsets.symmetric(horizontal: 25),
70 + decoration: BoxDecoration(
71 + borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
72 + gradient: LinearGradient(colors: [
73 + Theme.of(context).primaryTextTheme.subhead.color,
74 + Theme.of(context).primaryTextTheme.subhead.decorationColor,
75 + ], begin: Alignment.topLeft, end: Alignment.bottomRight),
76 + ),
77 + child: Column(
78 + mainAxisSize: MainAxisSize.min,
79 + crossAxisAlignment: CrossAxisAlignment.stretch,
80 + children: [
81 + SizedBox(height: 150),
82 + BaseTextFormField(
83 + controller: _amountController,
84 + focusNode: _amountFieldFocus,
85 + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
86 + inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\-|\ ]'))],
87 + hintText: '1000',
88 + placeholderTextStyle: TextStyle(
89 + color: Theme.of(context).primaryTextTheme.headline.color,
90 + fontWeight: FontWeight.w500,
91 + fontSize: 36,
92 + ),
93 + borderColor: Theme.of(context).primaryTextTheme.headline.color,
94 + textColor: Colors.white,
95 + textStyle: TextStyle(
96 + color: Colors.white,
97 + fontSize: 36,
98 + ),
99 + suffixIcon: SizedBox(
100 + width: _width / 6,
101 + ),
102 + prefixIcon: Padding(
103 + padding: EdgeInsets.only(
104 + top: 5.0,
105 + left: _width / 4,
106 + ),
107 + child: Text(
108 + 'USD: ',
109 + style: TextStyle(
110 + color: Colors.white,
111 + fontWeight: FontWeight.w900,
112 + fontSize: 36,
113 + ),
114 + ),
115 + ),
116 + ),
117 + SizedBox(height: 8),
118 + Observer(builder: (_) {
119 + if (ioniaPurchaseViewModel.percentage == 0.0) {
120 + return SizedBox.shrink();
121 + }
122 +
123 + return RichText(
124 + textAlign: TextAlign.center,
125 + text: TextSpan(
126 + text: '\$${_amountController.text}',
127 + style: TextStyle(
128 + color: Theme.of(context).primaryTextTheme.headline.color,
129 + ),
130 + children: [
131 + TextSpan(text: ' ${S.of(context).is_percentage} '),
132 + TextSpan(text: '${ioniaPurchaseViewModel.percentage}%'),
133 + TextSpan(text: ' ${S.of(context).percentageOf(ioniaPurchaseViewModel.amount.toString())} '),
134 + ],
135 + ),
136 + );
137 + }),
138 + SizedBox(height: 24),
139 + ],
140 + ),
141 + ),
142 + Padding(
143 + padding: const EdgeInsets.all(24.0),
144 + child: CardItem(
145 + title: merchant.legalName,
146 + backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
147 + discount: 0.0,
148 + titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor,
149 + subtitleColor: Theme.of(context).hintColor,
150 + subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline,
151 + logoUrl: merchant.logoUrl,
152 + ),
153 + )
154 + ],
155 + ),
156 + bottomSection: Column(
157 + children: [
158 + Padding(
159 + padding: EdgeInsets.only(bottom: 12),
160 + child: PrimaryButton(
161 + onPressed: () {
162 + Navigator.of(context).pop(_amountController.text);
163 + },
164 + text: S.of(context).add_tip,
165 + color: Theme.of(context).accentTextTheme.body2.color,
166 + textColor: Colors.white,
167 + ),
168 + ),
169 + SizedBox(height: 30),
170 + ],
171 + ),
172 + ),
173 + ),
174 + );
175 + }
176 +}
lib/src/screens/ionia/cards/ionia_debit_card_page.dart new
+379
@@ -0,0 +1,379 @@
1 +import 'package:cake_wallet/ionia/ionia_create_state.dart';
2 +import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
3 +import 'package:cake_wallet/routes.dart';
4 +import 'package:cake_wallet/src/screens/base_page.dart';
5 +import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart';
6 +import 'package:cake_wallet/src/widgets/alert_background.dart';
7 +import 'package:cake_wallet/src/widgets/primary_button.dart';
8 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
9 +import 'package:cake_wallet/typography.dart';
10 +import 'package:cake_wallet/utils/show_pop_up.dart';
11 +import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
12 +import 'package:flutter/material.dart';
13 +import 'package:cake_wallet/generated/i18n.dart';
14 +import 'package:flutter_mobx/flutter_mobx.dart';
15 +
16 +class IoniaDebitCardPage extends BasePage {
17 + final IoniaGiftCardsListViewModel _cardsListViewModel;
18 +
19 + IoniaDebitCardPage(this._cardsListViewModel);
20 +
21 + @override
22 + Widget middle(BuildContext context) {
23 + return Text(
24 + S.current.debit_card,
25 + style: textMediumSemiBold(
26 + color: Theme.of(context).accentTextTheme.display4.backgroundColor,
27 + ),
28 + );
29 + }
30 +
31 + @override
32 + Widget body(BuildContext context) {
33 + return Observer(
34 + builder: (_) {
35 + final cardState = _cardsListViewModel.cardState;
36 + if (cardState is IoniaFetchingCard) {
37 + return Center(child: CircularProgressIndicator());
38 + }
39 + if (cardState is IoniaCardSuccess) {
40 + return ScrollableWithBottomSection(
41 + contentPadding: EdgeInsets.zero,
42 + content: Padding(
43 + padding: const EdgeInsets.all(16.0),
44 + child: _IoniaDebitCard(
45 + cardInfo: cardState.card,
46 + ),
47 + ),
48 + bottomSection: Column(
49 + children: [
50 + Padding(
51 + padding: const EdgeInsets.symmetric(horizontal: 20.0),
52 + child: Text(
53 + S.of(context).billing_address_info,
54 + style: textSmall(color: Theme.of(context).textTheme.display1.color),
55 + textAlign: TextAlign.center,
56 + ),
57 + ),
58 + SizedBox(height: 24),
59 + PrimaryButton(
60 + text: S.of(context).order_physical_card,
61 + onPressed: () {},
62 + color: Color(0xffE9F2FC),
63 + textColor: Theme.of(context).textTheme.display2.color,
64 + ),
65 + SizedBox(height: 8),
66 + PrimaryButton(
67 + text: S.of(context).add_value,
68 + onPressed: () {},
69 + color: Theme.of(context).accentTextTheme.body2.color,
70 + textColor: Colors.white,
71 + ),
72 + SizedBox(height: 16)
73 + ],
74 + ),
75 + );
76 + }
77 + return ScrollableWithBottomSection(
78 + contentPadding: EdgeInsets.zero,
79 + content: Padding(
80 + padding: const EdgeInsets.all(16.0),
81 + child: Column(
82 + children: [
83 + _IoniaDebitCard(isCardSample: true),
84 + SizedBox(height: 40),
85 + Padding(
86 + padding: const EdgeInsets.symmetric(horizontal: 8.0),
87 + child: Column(
88 + children: [
89 + TextIconButton(
90 + label: S.current.how_to_use_card,
91 + onTap: () => _showHowToUseCard(context),
92 + ),
93 + SizedBox(
94 + height: 24,
95 + ),
96 + TextIconButton(
97 + label: S.current.frequently_asked_questions,
98 + onTap: () {},
99 + ),
100 + ],
101 + ),
102 + ),
103 + SizedBox(height: 50),
104 + Container(
105 + padding: EdgeInsets.all(20),
106 + margin: EdgeInsets.all(8),
107 + width: double.infinity,
108 + decoration: BoxDecoration(
109 + color: Color.fromRGBO(233, 242, 252, 1),
110 + borderRadius: BorderRadius.circular(20),
111 + ),
112 + child: RichText(
113 + text: TextSpan(
114 + text: S.of(context).get_a,
115 + style: textMedium(color: Theme.of(context).textTheme.display2.color),
116 + children: [
117 + TextSpan(
118 + text: S.of(context).digital_and_physical_card,
119 + style: textMediumBold(color: Theme.of(context).textTheme.display2.color),
120 + ),
121 + TextSpan(
122 + text: S.of(context).get_card_note,
123 + )
124 + ],
125 + )),
126 + ),
127 + ],
128 + ),
129 + ),
130 + bottomSectionPadding: EdgeInsets.symmetric(
131 + horizontal: 16,
132 + vertical: 32,
133 + ),
134 + bottomSection: PrimaryButton(
135 + text: S.of(context).activate,
136 + onPressed: () => _showHowToUseCard(context, activate: true),
137 + color: Theme.of(context).accentTextTheme.body2.color,
138 + textColor: Colors.white,
139 + ),
140 + );
141 + },
142 + );
143 + }
144 +
145 + void _showHowToUseCard(BuildContext context, {bool activate = false}) {
146 + showPopUp<void>(
147 + context: context,
148 + builder: (BuildContext context) {
149 + return AlertBackground(
150 + child: Material(
151 + color: Colors.transparent,
152 + child: Column(
153 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
154 + children: [
155 + SizedBox(height: 10),
156 + Container(
157 + padding: EdgeInsets.only(top: 24, left: 24, right: 24),
158 + margin: EdgeInsets.all(24),
159 + decoration: BoxDecoration(
160 + color: Theme.of(context).backgroundColor,
161 + borderRadius: BorderRadius.circular(30),
162 + ),
163 + child: Column(
164 + children: [
165 + Text(
166 + S.of(context).how_to_use_card,
167 + style: textLargeSemiBold(
168 + color: Theme.of(context).textTheme.body1.color,
169 + ),
170 + ),
171 + SizedBox(height: 24),
172 + Align(
173 + alignment: Alignment.bottomLeft,
174 + child: Text(
175 + S.of(context).signup_for_card_accept_terms,
176 + style: textSmallSemiBold(
177 + color: Theme.of(context).textTheme.display2.color,
178 + ),
179 + ),
180 + ),
181 + SizedBox(height: 24),
182 + _TitleSubtitleTile(
183 + title: S.of(context).add_fund_to_card('1000'),
184 + subtitle: S.of(context).use_card_info_two,
185 + ),
186 + SizedBox(height: 21),
187 + _TitleSubtitleTile(
188 + title: S.of(context).use_card_info_three,
189 + subtitle: S.of(context).optionally_order_card,
190 + ),
191 + SizedBox(height: 35),
192 + PrimaryButton(
193 + onPressed: () => activate
194 + ? Navigator.pushNamed(context, Routes.ioniaActivateDebitCardPage)
195 + : Navigator.pop(context),
196 + text: S.of(context).send_got_it,
197 + color: Color.fromRGBO(233, 242, 252, 1),
198 + textColor: Theme.of(context).textTheme.display2.color,
199 + ),
200 + SizedBox(height: 21),
201 + ],
202 + ),
203 + ),
204 + InkWell(
205 + onTap: () => Navigator.pop(context),
206 + child: Container(
207 + margin: EdgeInsets.only(bottom: 40),
208 + child: CircleAvatar(
209 + child: Icon(
210 + Icons.close,
211 + color: Colors.black,
212 + ),
213 + backgroundColor: Colors.white,
214 + ),
215 + ),
216 + )
217 + ],
218 + ),
219 + ),
220 + );
221 + });
222 + }
223 +}
224 +
225 +class _IoniaDebitCard extends StatefulWidget {
226 + final bool isCardSample;
227 + final IoniaVirtualCard cardInfo;
228 + const _IoniaDebitCard({
229 + Key key,
230 + this.isCardSample = false,
231 + this.cardInfo,
232 + }) : super(key: key);
233 +
234 + @override
235 + _IoniaDebitCardState createState() => _IoniaDebitCardState();
236 +}
237 +
238 +class _IoniaDebitCardState extends State<_IoniaDebitCard> {
239 + bool _showDetails = false;
240 + void _toggleVisibility() {
241 + setState(() => _showDetails = !_showDetails);
242 + }
243 +
244 + String _formatPan(String pan) {
245 + if (pan == null) return '';
246 + return pan.replaceAllMapped(RegExp(r'.{4}'), (match) => '${match.group(0)} ');
247 + }
248 +
249 + String get _getLast4 => widget.isCardSample ? '0000' : widget.cardInfo.pan.substring(widget.cardInfo.pan.length - 5);
250 +
251 + String get _getSpendLimit => widget.isCardSample ? '10000' : widget.cardInfo.spendLimit.toStringAsFixed(2);
252 +
253 + @override
254 + Widget build(BuildContext context) {
255 + return Container(
256 + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 19),
257 + decoration: BoxDecoration(
258 + borderRadius: BorderRadius.circular(24),
259 + gradient: LinearGradient(
260 + colors: [
261 + Theme.of(context).primaryTextTheme.subhead.color,
262 + Theme.of(context).primaryTextTheme.subhead.decorationColor,
263 + ],
264 + begin: Alignment.topLeft,
265 + end: Alignment.bottomRight,
266 + ),
267 + ),
268 + child: Column(
269 + crossAxisAlignment: CrossAxisAlignment.start,
270 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
271 + children: [
272 + SizedBox(height: 16),
273 + Row(
274 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
275 + children: [
276 + Text(
277 + S.current.cakepay_prepaid_card,
278 + style: textSmall(),
279 + ),
280 + Image.asset(
281 + 'assets/images/mastercard.png',
282 + width: 54,
283 + ),
284 + ],
285 + ),
286 + Text(
287 + widget.isCardSample ? S.of(context).upto(_getSpendLimit) : '\$$_getSpendLimit',
288 + style: textXLargeSemiBold(),
289 + ),
290 + SizedBox(height: 16),
291 + Text(
292 + _showDetails ? _formatPan(widget.cardInfo.pan) : '**** **** **** $_getLast4',
293 + style: textMediumSemiBold(),
294 + ),
295 + SizedBox(height: 32),
296 + Row(
297 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
298 + children: [
299 + if (widget.isCardSample)
300 + Text(
301 + S.current.no_id_needed,
302 + style: textMediumBold(),
303 + )
304 + else ...[
305 + Column(
306 + children: [
307 + Text(
308 + 'CVV',
309 + style: textXSmallSemiBold(),
310 + ),
311 + SizedBox(height: 4),
312 + Text(
313 + _showDetails ? widget.cardInfo.cvv : '***',
314 + style: textMediumSemiBold(),
315 + )
316 + ],
317 + ),
318 + Column(
319 + crossAxisAlignment: CrossAxisAlignment.start,
320 + children: [
321 + Text(
322 + S.of(context).expires,
323 + style: textXSmallSemiBold(),
324 + ),
325 + SizedBox(height: 4),
326 + Text(
327 + '${widget.cardInfo.expirationMonth ?? S.of(context).mm}/${widget.cardInfo.expirationYear ?? S.of(context).yy}',
328 + style: textMediumSemiBold(),
329 + )
330 + ],
331 + ),
332 + ]
333 + ],
334 + ),
335 + if (!widget.isCardSample) ...[
336 + SizedBox(height: 8),
337 + Center(
338 + child: InkWell(
339 + onTap: () => _toggleVisibility(),
340 + child: Text(
341 + _showDetails ? S.of(context).hide_details : S.of(context).show_details,
342 + style: textSmall(),
343 + ),
344 + ),
345 + ),
346 + ],
347 + ],
348 + ),
349 + );
350 + }
351 +}
352 +
353 +class _TitleSubtitleTile extends StatelessWidget {
354 + final String title;
355 + final String subtitle;
356 + const _TitleSubtitleTile({
357 + Key key,
358 + @required this.title,
359 + @required this.subtitle,
360 + }) : super(key: key);
361 +
362 + @override
363 + Widget build(BuildContext context) {
364 + return Column(
365 + crossAxisAlignment: CrossAxisAlignment.start,
366 + children: [
367 + Text(
368 + title,
369 + style: textSmallSemiBold(color: Theme.of(context).textTheme.display2.color),
370 + ),
371 + SizedBox(height: 4),
372 + Text(
373 + subtitle,
374 + style: textSmall(color: Theme.of(context).textTheme.display2.color),
375 + ),
376 + ],
377 + );
378 + }
379 +}
lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart new
+188
@@ -0,0 +1,188 @@
1 +import 'package:cake_wallet/core/execution_state.dart';
2 +import 'package:cake_wallet/ionia/ionia_gift_card.dart';
3 +import 'package:cake_wallet/routes.dart';
4 +import 'package:cake_wallet/src/screens/base_page.dart';
5 +import 'package:cake_wallet/src/screens/ionia/widgets/ionia_alert_model.dart';
6 +import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.dart';
7 +import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart';
8 +import 'package:cake_wallet/src/widgets/alert_background.dart';
9 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
10 +import 'package:cake_wallet/src/widgets/primary_button.dart';
11 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
12 +import 'package:cake_wallet/typography.dart';
13 +import 'package:cake_wallet/utils/show_bar.dart';
14 +import 'package:cake_wallet/utils/show_pop_up.dart';
15 +import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart';
16 +import 'package:flutter/material.dart';
17 +import 'package:flutter/services.dart';
18 +import 'package:flutter/src/widgets/framework.dart';
19 +import 'package:cake_wallet/generated/i18n.dart';
20 +import 'package:flutter_mobx/flutter_mobx.dart';
21 +import 'package:mobx/mobx.dart';
22 +
23 +class IoniaGiftCardDetailPage extends BasePage {
24 + IoniaGiftCardDetailPage(this.viewModel);
25 +
26 + final IoniaGiftCardDetailsViewModel viewModel;
27 +
28 + @override
29 + Widget leading(BuildContext context) {
30 + if (ModalRoute.of(context).isFirst) {
31 + return null;
32 + }
33 +
34 + final _backButton = Icon(
35 + Icons.arrow_back_ios,
36 + color: Theme.of(context).primaryTextTheme.title.color,
37 + size: 16,
38 + );
39 + return Padding(
40 + padding: const EdgeInsets.only(left: 10.0),
41 + child: SizedBox(
42 + height: 37,
43 + width: 37,
44 + child: ButtonTheme(
45 + minWidth: double.minPositive,
46 + child: FlatButton(
47 + highlightColor: Colors.transparent,
48 + splashColor: Colors.transparent,
49 + padding: EdgeInsets.all(0),
50 + onPressed: () => onClose(context),
51 + child: _backButton),
52 + ),
53 + ),
54 + );
55 + }
56 +
57 + @override
58 + Widget middle(BuildContext context) {
59 + return Text(
60 + viewModel.giftCard.legalName,
61 + style: textMediumSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor),
62 + );
63 + }
64 +
65 + @override
66 + Widget body(BuildContext context) {
67 + reaction((_) => viewModel.redeemState, (ExecutionState state) {
68 + if (state is FailureState) {
69 + WidgetsBinding.instance.addPostFrameCallback((_) {
70 + showPopUp<void>(
71 + context: context,
72 + builder: (BuildContext context) {
73 + return AlertWithOneAction(
74 + alertTitle: S.of(context).error,
75 + alertContent: state.error,
76 + buttonText: S.of(context).ok,
77 + buttonAction: () => Navigator.of(context).pop());
78 + });
79 + });
80 + }
81 + });
82 +
83 + return ScrollableWithBottomSection(
84 + contentPadding: EdgeInsets.all(24),
85 + content: Column(
86 + children: [
87 + if (viewModel.giftCard.barcodeUrl != null && viewModel.giftCard.barcodeUrl.isNotEmpty)
88 + Padding(
89 + padding: const EdgeInsets.symmetric(
90 + horizontal: 24.0,
91 + vertical: 24,
92 + ),
93 + child: Image.network(viewModel.giftCard.barcodeUrl),
94 + ),
95 + SizedBox(height: 24),
96 + buildIoniaTile(
97 + context,
98 + title: S.of(context).gift_card_number,
99 + subTitle: viewModel.giftCard.cardNumber,
100 + ),
101 + if (viewModel.giftCard.cardPin?.isNotEmpty ?? false)
102 + ...[Divider(height: 30),
103 + buildIoniaTile(
104 + context,
105 + title: S.of(context).pin_number,
106 + subTitle: viewModel.giftCard.cardPin,
107 + )],
108 + Divider(height: 30),
109 + Observer(builder: (_) =>
110 + buildIoniaTile(
111 + context,
112 + title: S.of(context).amount,
113 + subTitle: viewModel.giftCard.remainingAmount.toStringAsFixed(2) ?? '0.00',
114 + )),
115 + Divider(height: 50),
116 + TextIconButton(
117 + label: S.of(context).how_to_use_card,
118 + onTap: () => _showHowToUseCard(context, viewModel.giftCard),
119 + ),
120 + ],
121 + ),
122 + bottomSection: Padding(
123 + padding: EdgeInsets.only(bottom: 12),
124 + child: Observer(builder: (_) {
125 + if (!viewModel.giftCard.isEmpty) {
126 + return LoadingPrimaryButton(
127 + isLoading: viewModel.redeemState is IsExecutingState,
128 + onPressed: () => viewModel.redeem().then((_){
129 + Navigator.of(context).pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst);
130 + }),
131 + text: S.of(context).mark_as_redeemed,
132 + color: Theme.of(context).accentTextTheme.body2.color,
133 + textColor: Colors.white);
134 + }
135 +
136 + return Container();
137 + })),
138 + );
139 + }
140 +
141 + Widget buildIoniaTile(BuildContext context, {@required String title, @required String subTitle}) {
142 + return IoniaTile(
143 + title: title,
144 + subTitle: subTitle,
145 + onTap: () {
146 + Clipboard.setData(ClipboardData(text: subTitle));
147 + showBar<void>(context,
148 + S.of(context).transaction_details_copied(title));
149 + });
150 + }
151 +
152 + void _showHowToUseCard(
153 + BuildContext context,
154 + IoniaGiftCard merchant,
155 + ) {
156 + showPopUp<void>(
157 + context: context,
158 + builder: (BuildContext context) {
159 + return IoniaAlertModal(
160 + title: S.of(context).how_to_use_card,
161 + content: Column(
162 + crossAxisAlignment: CrossAxisAlignment.start,
163 + children: viewModel.giftCard.instructions
164 + .map((instruction) {
165 + return [
166 + Padding(
167 + padding: EdgeInsets.all(10),
168 + child: Text(
169 + instruction.header,
170 + style: textLargeSemiBold(
171 + color: Theme.of(context).textTheme.display2.color,
172 + ),
173 + )),
174 + Text(
175 + instruction.body,
176 + style: textMedium(
177 + color: Theme.of(context).textTheme.display2.color,
178 + ),
179 + )
180 + ];
181 + })
182 + .expand((e) => e)
183 + .toList()),
184 + actionTitle: S.of(context).send_got_it,
185 + );
186 + });
187 + }
188 +}
lib/src/screens/ionia/cards/ionia_manage_cards_page.dart new
+340
@@ -0,0 +1,340 @@
1 +import 'package:cake_wallet/di.dart';
2 +import 'package:cake_wallet/ionia/ionia_category.dart';
3 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
4 +import 'package:cake_wallet/routes.dart';
5 +import 'package:cake_wallet/src/screens/base_page.dart';
6 +import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
7 +import 'package:cake_wallet/src/screens/ionia/widgets/card_menu.dart';
8 +import 'package:cake_wallet/src/screens/ionia/widgets/ionia_filter_modal.dart';
9 +import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
10 +import 'package:cake_wallet/themes/theme_base.dart';
11 +import 'package:cake_wallet/utils/debounce.dart';
12 +import 'package:cake_wallet/typography.dart';
13 +import 'package:cake_wallet/utils/show_pop_up.dart';
14 +import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
15 +import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
16 +import 'package:flutter/cupertino.dart';
17 +import 'package:flutter/material.dart';
18 +import 'package:cake_wallet/generated/i18n.dart';
19 +import 'package:flutter_mobx/flutter_mobx.dart';
20 +
21 +class IoniaManageCardsPage extends BasePage {
22 + IoniaManageCardsPage(this._cardsListViewModel) {
23 + _searchController.addListener(() {
24 + if (_searchController.text != _cardsListViewModel.searchString) {
25 + _searchDebounce.run(() {
26 + _cardsListViewModel.searchMerchant(_searchController.text);
27 + });
28 + }
29 + });
30 + }
31 + final IoniaGiftCardsListViewModel _cardsListViewModel;
32 +
33 + final _searchDebounce = Debounce(Duration(milliseconds: 500));
34 + final _searchController = TextEditingController();
35 +
36 + @override
37 + Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
38 +
39 + @override
40 + Color get backgroundDarkColor => Colors.transparent;
41 +
42 + @override
43 + Color get titleColor => currentTheme.type == ThemeType.bright ? Colors.white : Colors.black;
44 +
45 + @override
46 + Widget Function(BuildContext, Widget) get rootWrapper => (BuildContext context, Widget scaffold) => Container(
47 + decoration: BoxDecoration(
48 + gradient: LinearGradient(
49 + colors: [
50 + Theme.of(context).accentColor,
51 + Theme.of(context).scaffoldBackgroundColor,
52 + Theme.of(context).primaryColor,
53 + ],
54 + begin: Alignment.topRight,
55 + end: Alignment.bottomLeft,
56 + ),
57 + ),
58 + child: scaffold,
59 + );
60 +
61 + @override
62 + bool get resizeToAvoidBottomInset => false;
63 +
64 + @override
65 + Widget get endDrawer => CardMenu();
66 +
67 + @override
68 + Widget leading(BuildContext context) {
69 + final _backButton = Icon(
70 + Icons.arrow_back_ios,
71 + color: Theme.of(context).accentTextTheme.display3.backgroundColor,
72 + size: 16,
73 + );
74 +
75 + return SizedBox(
76 + height: 37,
77 + width: 37,
78 + child: ButtonTheme(
79 + minWidth: double.minPositive,
80 + child: FlatButton(
81 + highlightColor: Colors.transparent,
82 + splashColor: Colors.transparent,
83 + padding: EdgeInsets.all(0),
84 + onPressed: () => Navigator.pop(context),
85 + child: _backButton),
86 + ),
87 + );
88 + }
89 +
90 + @override
91 + Widget middle(BuildContext context) {
92 + return Text(
93 + S.of(context).gift_cards,
94 + style: textMediumSemiBold(
95 + color: Theme.of(context).accentTextTheme.display3.backgroundColor,
96 + ),
97 + );
98 + }
99 +
100 + @override
101 + Widget trailing(BuildContext context) {
102 + return _TrailingIcon(
103 + asset: 'assets/images/profile.png',
104 + onPressed: () => Navigator.pushNamed(context, Routes.ioniaAccountPage),
105 + );
106 + }
107 +
108 + @override
109 + Widget body(BuildContext context) {
110 + final filterIcon = InkWell(
111 + onTap: () async {
112 + final selectedFilters = await showCategoryFilter(context, _cardsListViewModel);
113 + _cardsListViewModel.setSelectedFilter(selectedFilters);
114 + },
115 + child: Image.asset(
116 + 'assets/images/filter.png',
117 + color: Theme.of(context).textTheme.caption.decorationColor,
118 + ));
119 +
120 + return Padding(
121 + padding: const EdgeInsets.all(14.0),
122 + child: Column(
123 + children: [
124 + Container(
125 + padding: EdgeInsets.only(left: 2, right: 22),
126 + height: 32,
127 + child: Row(
128 + children: [
129 + Expanded(
130 + child: _SearchWidget(
131 + controller: _searchController,
132 + )),
133 + SizedBox(width: 10),
134 + Container(
135 + width: 32,
136 + padding: EdgeInsets.all(8),
137 + decoration: BoxDecoration(
138 + color: Colors.white.withOpacity(0.15),
139 + border: Border.all(
140 + color: Colors.white.withOpacity(0.2),
141 + ),
142 + borderRadius: BorderRadius.circular(10),
143 + ),
144 + child: filterIcon,
145 + )
146 + ],
147 + ),
148 + ),
149 + SizedBox(height: 8),
150 + Expanded(
151 + child: IoniaManageCardsPageBody(
152 + cardsListViewModel: _cardsListViewModel,
153 + ),
154 + ),
155 + ],
156 + ),
157 + );
158 + }
159 +
160 + Future<List<IoniaCategory>> showCategoryFilter(
161 + BuildContext context,
162 + IoniaGiftCardsListViewModel viewModel,
163 + ) async {
164 + return await showPopUp<List<IoniaCategory>>(
165 + context: context,
166 + builder: (BuildContext context) {
167 + return IoniaFilterModal(
168 + filterViewModel: getIt.get<IoniaFilterViewModel>(),
169 + selectedCategories: viewModel.selectedFilters,
170 + );
171 + },
172 + );
173 + }
174 +}
175 +
176 +class IoniaManageCardsPageBody extends StatefulWidget {
177 + const IoniaManageCardsPageBody({
178 + Key key,
179 + @required this.cardsListViewModel,
180 + }) : super(key: key);
181 +
182 + final IoniaGiftCardsListViewModel cardsListViewModel;
183 +
184 + @override
185 + _IoniaManageCardsPageBodyState createState() => _IoniaManageCardsPageBodyState();
186 +}
187 +
188 +class _IoniaManageCardsPageBodyState extends State<IoniaManageCardsPageBody> {
189 + double get backgroundHeight => MediaQuery.of(context).size.height * 0.75;
190 + double thumbHeight = 72;
191 + bool get isAlwaysShowScrollThumb => merchantsList == null ? false : merchantsList.length > 3;
192 +
193 + List<IoniaMerchant> get merchantsList => widget.cardsListViewModel.ioniaMerchants;
194 +
195 + final _scrollController = ScrollController();
196 +
197 + @override
198 + void initState() {
199 + _scrollController.addListener(() {
200 + final scrollOffsetFromTop = _scrollController.hasClients
201 + ? (_scrollController.offset / _scrollController.position.maxScrollExtent * (backgroundHeight - thumbHeight))
202 + : 0.0;
203 + widget.cardsListViewModel.setScrollOffsetFromTop(scrollOffsetFromTop);
204 + });
205 + super.initState();
206 + }
207 +
208 + @override
209 + Widget build(BuildContext context) {
210 + return Observer(
211 + builder: (_) => Stack(children: [
212 + ListView.separated(
213 + padding: EdgeInsets.only(left: 2, right: 22),
214 + controller: _scrollController,
215 + itemCount: merchantsList.length,
216 + separatorBuilder: (_, __) => SizedBox(height: 4),
217 + itemBuilder: (_, index) {
218 + final merchant = merchantsList[index];
219 + var subTitle = '';
220 +
221 + if (merchant.isOnline) {
222 + subTitle += S.of(context).online;
223 + }
224 +
225 + if (merchant.isPhysical) {
226 + if (subTitle.isNotEmpty) {
227 + subTitle = '$subTitle & ';
228 + }
229 +
230 + subTitle = '${subTitle}${S.of(context).in_store}';
231 + }
232 +
233 + return CardItem(
234 + logoUrl: merchant.logoUrl,
235 + onTap: () {
236 + Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]);
237 + },
238 + title: merchant.legalName,
239 + subTitle: subTitle,
240 + backgroundColor: Theme.of(context).textTheme.title.backgroundColor,
241 + titleColor: Theme.of(context).accentTextTheme.display3.backgroundColor,
242 + subtitleColor: Theme.of(context).accentTextTheme.display2.backgroundColor,
243 + discount: merchant.discount,
244 + );
245 + },
246 + ),
247 + isAlwaysShowScrollThumb
248 + ? CakeScrollbar(
249 + backgroundHeight: backgroundHeight,
250 + thumbHeight: thumbHeight,
251 + rightOffset: 1,
252 + width: 3,
253 + backgroundColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.05),
254 + thumbColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.5),
255 + fromTop: widget.cardsListViewModel.scrollOffsetFromTop,
256 + )
257 + : Offstage()
258 + ]),
259 + );
260 + }
261 +}
262 +
263 +class _SearchWidget extends StatelessWidget {
264 + const _SearchWidget({
265 + Key key,
266 + @required this.controller,
267 + }) : super(key: key);
268 + final TextEditingController controller;
269 +
270 + @override
271 + Widget build(BuildContext context) {
272 + final searchIcon = Padding(
273 + padding: EdgeInsets.all(8),
274 + child: Image.asset(
275 + 'assets/images/mini_search_icon.png',
276 + color: Theme.of(context).textTheme.caption.decorationColor,
277 + ),
278 + );
279 +
280 + return TextField(
281 + style: TextStyle(color: Colors.white),
282 + controller: controller,
283 + decoration: InputDecoration(
284 + filled: true,
285 + contentPadding: EdgeInsets.only(
286 + top: 10,
287 + left: 10,
288 + ),
289 + fillColor: Colors.white.withOpacity(0.15),
290 + hintText: S.of(context).search,
291 + hintStyle: TextStyle(
292 + color: Colors.white.withOpacity(0.6),
293 + ),
294 + alignLabelWithHint: true,
295 + floatingLabelBehavior: FloatingLabelBehavior.never,
296 + suffixIcon: searchIcon,
297 + border: OutlineInputBorder(
298 + borderSide: BorderSide(
299 + color: Colors.white.withOpacity(0.2),
300 + ),
301 + borderRadius: BorderRadius.circular(10),
302 + ),
303 + enabledBorder: OutlineInputBorder(
304 + borderSide: BorderSide(
305 + color: Colors.white.withOpacity(0.2),
306 + ),
307 + borderRadius: BorderRadius.circular(10),
308 + ),
309 + focusedBorder: OutlineInputBorder(
310 + borderSide: BorderSide(color: Colors.white.withOpacity(0.2)),
311 + borderRadius: BorderRadius.circular(10),
312 + )),
313 + );
314 + }
315 +}
316 +
317 +class _TrailingIcon extends StatelessWidget {
318 + final String asset;
319 + final VoidCallback onPressed;
320 +
321 + const _TrailingIcon({this.asset, this.onPressed});
322 +
323 + @override
324 + Widget build(BuildContext context) {
325 + return Container(
326 + alignment: Alignment.centerRight,
327 + width: 25,
328 + child: FlatButton(
329 + highlightColor: Colors.transparent,
330 + splashColor: Colors.transparent,
331 + padding: EdgeInsets.all(0),
332 + onPressed: onPressed,
333 + child: Image.asset(
334 + asset,
335 + color: Theme.of(context).accentTextTheme.display3.backgroundColor,
336 + ),
337 + ),
338 + );
339 + }
340 +}
lib/src/screens/ionia/cards/ionia_payment_status_page.dart new
+217
@@ -0,0 +1,217 @@
1 +import 'package:cake_wallet/ionia/ionia_gift_card.dart';
2 +import 'package:cake_wallet/routes.dart';
3 +import 'package:cake_wallet/src/screens/base_page.dart';
4 +import 'package:cake_wallet/src/widgets/primary_button.dart';
5 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
6 +import 'package:cake_wallet/typography.dart';
7 +import 'package:cake_wallet/utils/show_bar.dart';
8 +import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart';
9 +import 'package:flutter/material.dart';
10 +import 'package:cake_wallet/generated/i18n.dart';
11 +import 'package:flutter/services.dart';
12 +import 'package:flutter_mobx/flutter_mobx.dart';
13 +import 'package:mobx/mobx.dart';
14 +
15 +class IoniaPaymentStatusPage extends BasePage {
16 + IoniaPaymentStatusPage(this.viewModel);
17 +
18 + final IoniaPaymentStatusViewModel viewModel;
19 +
20 + @override
21 + Widget middle(BuildContext context) {
22 + return Text(
23 + S.of(context).generating_gift_card,
24 + textAlign: TextAlign.center,
25 + style: textMediumSemiBold(
26 + color: Theme.of(context).accentTextTheme.display4.backgroundColor));
27 + }
28 +
29 + @override
30 + Widget body(BuildContext context) {
31 + return _IoniaPaymentStatusPageBody(viewModel);
32 + }
33 +}
34 +
35 +class _IoniaPaymentStatusPageBody extends StatefulWidget {
36 + _IoniaPaymentStatusPageBody(this.viewModel);
37 +
38 + final IoniaPaymentStatusViewModel viewModel;
39 +
40 + @override
41 + _IoniaPaymentStatusPageBodyBodyState createState() => _IoniaPaymentStatusPageBodyBodyState();
42 +}
43 +
44 +class _IoniaPaymentStatusPageBodyBodyState extends State<_IoniaPaymentStatusPageBody> {
45 + ReactionDisposer _onGiftCardReaction;
46 +
47 + @override
48 + void initState() {
49 + if (widget.viewModel.giftCard != null) {
50 + WidgetsBinding.instance.addPostFrameCallback((_) {
51 + Navigator.of(context)
52 + .pushReplacementNamed(Routes.ioniaGiftCardDetailPage, arguments: [widget.viewModel.giftCard]);
53 + });
54 + }
55 +
56 + _onGiftCardReaction = reaction((_) => widget.viewModel.giftCard, (IoniaGiftCard giftCard) {
57 + WidgetsBinding.instance.addPostFrameCallback((_) {
58 + Navigator.of(context)
59 + .pushReplacementNamed(Routes.ioniaGiftCardDetailPage, arguments: [giftCard]);
60 + });
61 + });
62 +
63 + super.initState();
64 + }
65 +
66 + @override
67 + void dispose() {
68 + _onGiftCardReaction?.reaction?.dispose();
69 + widget.viewModel.timer.cancel();
70 + super.dispose();
71 + }
72 +
73 + @override
74 + Widget build(BuildContext context) {
75 + return ScrollableWithBottomSection(
76 + contentPadding: EdgeInsets.all(24),
77 + content: Column(
78 + crossAxisAlignment: CrossAxisAlignment.start,
79 + mainAxisAlignment: MainAxisAlignment.start,
80 + children: [
81 + Row(children: [
82 + Padding(
83 + padding: EdgeInsets.only(right: 10),
84 + child: Container(
85 + decoration: BoxDecoration(
86 + borderRadius: BorderRadius.circular(10),
87 + color: Colors.green),
88 + height: 10,
89 + width: 10)),
90 + Text(
91 + S.of(context).awaiting_payment_confirmation,
92 + style: textLargeSemiBold(
93 + color: Theme.of(context).primaryTextTheme.title.color))
94 + ]),
95 + SizedBox(height: 40),
96 + Row(children: [
97 + SizedBox(width: 20),
98 + Expanded(child:
99 + Column(
100 + crossAxisAlignment: CrossAxisAlignment.start,
101 + mainAxisAlignment: MainAxisAlignment.start,
102 + children: [
103 + ...widget.viewModel
104 + .committedInfo
105 + .transactions
106 + .map((transaction) => buildDescriptionTileWithCopy(context, S.of(context).transaction_details_transaction_id, transaction.id)),
107 + Divider(height: 30),
108 + buildDescriptionTileWithCopy(context, S.of(context).order_id, widget.viewModel.paymentInfo.ioniaOrder.id),
109 + Divider(height: 30),
110 + buildDescriptionTileWithCopy(context, S.of(context).payment_id, widget.viewModel.paymentInfo.ioniaOrder.paymentId),
111 + ]))
112 + ]),
113 + SizedBox(height: 40),
114 + Observer(builder: (_) {
115 + if (widget.viewModel.giftCard != null) {
116 + return Container(
117 + padding: EdgeInsets.only(top: 40),
118 + child: Row(children: [
119 + Padding(
120 + padding: EdgeInsets.only(right: 10,),
121 + child: Container(
122 + decoration: BoxDecoration(
123 + borderRadius: BorderRadius.circular(10),
124 + color: Colors.green),
125 + height: 10,
126 + width: 10)),
127 + Text(
128 + S.of(context).gift_card_is_generated,
129 + style: textLargeSemiBold(
130 + color: Theme.of(context).primaryTextTheme.title.color))
131 + ]));
132 + }
133 +
134 + return Row(children: [
135 + Padding(
136 + padding: EdgeInsets.only(right: 10),
137 + child: Observer(builder: (_) {
138 + return Container(
139 + decoration: BoxDecoration(
140 + borderRadius: BorderRadius.circular(10),
141 + color: widget.viewModel.giftCard == null ? Colors.grey : Colors.green),
142 + height: 10,
143 + width: 10);
144 + })),
145 + Text(
146 + S.of(context).generating_gift_card,
147 + style: textLargeSemiBold(
148 + color: Theme.of(context).primaryTextTheme.title.color))]);
149 + }),
150 + ],
151 + ),
152 + bottomSection: Padding(
153 + padding: EdgeInsets.only(bottom: 12),
154 + child: Column(children: [
155 + Container(
156 + padding: EdgeInsets.only(left: 40, right: 40, bottom: 20),
157 + child: Text(
158 + S.of(context).proceed_after_one_minute,
159 + style: textMedium(
160 + color: Theme.of(context).primaryTextTheme.title.color,
161 + ).copyWith(fontWeight: FontWeight.w500),
162 + textAlign: TextAlign.center,
163 + )),
164 + Observer(builder: (_) {
165 + if (widget.viewModel.giftCard != null) {
166 + return PrimaryButton(
167 + onPressed: () => Navigator.of(context)
168 + .pushReplacementNamed(
169 + Routes.ioniaGiftCardDetailPage,
170 + arguments: [widget.viewModel.giftCard]),
171 + text: S.of(context).open_gift_card,
172 + color: Theme.of(context).accentTextTheme.body2.color,
173 + textColor: Colors.white);
174 + }
175 +
176 + return PrimaryButton(
177 + onPressed: () => Navigator.of(context).pushNamed(Routes.support),
178 + text: S.of(context).contact_support,
179 + color: Theme.of(context).accentTextTheme.caption.color,
180 + textColor: Theme.of(context).primaryTextTheme.title.color);
181 + })
182 + ])
183 + ),
184 + );
185 + }
186 +
187 + Widget buildDescriptionTile(BuildContext context, String title, String subtitle, VoidCallback onTap) {
188 + return GestureDetector(
189 + onTap: () => onTap(),
190 + child: Column(
191 + crossAxisAlignment: CrossAxisAlignment.start,
192 + children: [
193 + Text(
194 + title,
195 + style: textXSmall(
196 + color: Theme.of(context).primaryTextTheme.overline.color,
197 + ),
198 + ),
199 + SizedBox(height: 8),
200 + Text(
201 + subtitle,
202 + style: textMedium(
203 + color: Theme.of(context).primaryTextTheme.title.color,
204 + ),
205 + ),
206 + ],
207 + ));
208 + }
209 +
210 + Widget buildDescriptionTileWithCopy(BuildContext context, String title, String subtitle) {
211 + return buildDescriptionTile(context, title, subtitle, () {
212 + Clipboard.setData(ClipboardData(text: subtitle));
213 + showBar<void>(context,
214 + S.of(context).transaction_details_copied(title));
215 + });
216 + }
217 +}
\ No newline at end of file
lib/src/screens/ionia/ionia.dart new
+9
@@ -0,0 +1,9 @@
1 +export 'auth/ionia_welcome_page.dart';
2 +export 'auth/ionia_create_account_page.dart';
3 +export 'auth/ionia_login_page.dart';
4 +export 'auth/ionia_verify_otp_page.dart';
5 +export 'cards/ionia_activate_debit_card_page.dart';
6 +export 'cards/ionia_buy_card_detail_page.dart';
7 +export 'cards/ionia_manage_cards_page.dart';
8 +export 'cards/ionia_debit_card_page.dart';
9 +export 'cards/ionia_buy_gift_card.dart';
lib/src/screens/ionia/widgets/card_item.dart new
+139
@@ -0,0 +1,139 @@
1 +import 'package:cake_wallet/src/widgets/discount_badge.dart';
2 +import 'package:flutter/material.dart';
3 +
4 +class CardItem extends StatelessWidget {
5 + CardItem({
6 + @required this.title,
7 + @required this.subTitle,
8 + @required this.backgroundColor,
9 + @required this.titleColor,
10 + @required this.subtitleColor,
11 + this.discountBackground,
12 + this.onTap,
13 + this.logoUrl,
14 + this.discount,
15 + });
16 +
17 + final VoidCallback onTap;
18 + final String title;
19 + final String subTitle;
20 + final String logoUrl;
21 + final double discount;
22 + final Color backgroundColor;
23 + final Color titleColor;
24 + final Color subtitleColor;
25 + final AssetImage discountBackground;
26 +
27 + @override
28 + Widget build(BuildContext context) {
29 + return InkWell(
30 + onTap: onTap,
31 + child: Stack(
32 + children: [
33 + Container(
34 + padding: EdgeInsets.all(12),
35 + width: double.infinity,
36 + decoration: BoxDecoration(
37 + color: backgroundColor,
38 + borderRadius: BorderRadius.circular(20),
39 + border: Border.all(
40 + color: Colors.white.withOpacity(0.20),
41 + ),
42 + ),
43 + child: Row(
44 + children: [
45 + if (logoUrl != null) ...[
46 + ClipOval(
47 + child: Image.network(
48 + logoUrl,
49 + width: 40.0,
50 + height: 40.0,
51 + fit: BoxFit.cover,
52 + loadingBuilder: (BuildContext _, Widget child, ImageChunkEvent loadingProgress) {
53 + if (loadingProgress == null) {
54 + return child;
55 + } else {
56 + return _PlaceholderContainer(text: 'Logo');
57 + }
58 + },
59 + errorBuilder: (_, __, ___) => _PlaceholderContainer(text: '!'),
60 + ),
61 + ),
62 + SizedBox(width: 5),
63 + ],
64 + Column(
65 + crossAxisAlignment: (subTitle?.isEmpty ?? false)
66 + ? CrossAxisAlignment.center
67 + : CrossAxisAlignment.start,
68 + children: [
69 + SizedBox(
70 + width: 200,
71 + child: Text(
72 + title,
73 + overflow: TextOverflow.ellipsis,
74 + style: TextStyle(
75 + color: titleColor,
76 + fontSize: 20,
77 + fontWeight: FontWeight.w900,
78 + ),
79 + ),
80 + ),
81 + if (subTitle?.isNotEmpty ?? false)
82 + Padding(
83 + padding: EdgeInsets.only(top: 5),
84 + child: Text(
85 + subTitle,
86 + style: TextStyle(
87 + color: subtitleColor,
88 + fontWeight: FontWeight.w500,
89 + fontFamily: 'Lato')),
90 + )
91 + ],
92 + ),
93 + ],
94 + ),
95 + ),
96 + if (discount != 0.0)
97 + Align(
98 + alignment: Alignment.topRight,
99 + child: Padding(
100 + padding: const EdgeInsets.only(top: 20.0),
101 + child: DiscountBadge(
102 + percentage: discount,
103 + discountBackground: discountBackground,
104 + ),
105 + ),
106 + ),
107 + ],
108 + ),
109 + );
110 + }
111 +}
112 +
113 +class _PlaceholderContainer extends StatelessWidget {
114 + const _PlaceholderContainer({@required this.text});
115 +
116 + final String text;
117 +
118 + @override
119 + Widget build(BuildContext context) {
120 + return Container(
121 + height: 42,
122 + width: 42,
123 + child: Center(
124 + child: Text(
125 + text,
126 + style: TextStyle(
127 + color: Colors.black,
128 + fontSize: 12,
129 + fontWeight: FontWeight.w900,
130 + ),
131 + ),
132 + ),
133 + decoration: BoxDecoration(
134 + color: Colors.white,
135 + borderRadius: BorderRadius.circular(100),
136 + ),
137 + );
138 + }
139 +}
lib/src/screens/ionia/widgets/card_menu.dart new
+11
@@ -0,0 +1,11 @@
1 +import 'package:flutter/material.dart';
2 +
3 +class CardMenu extends StatelessWidget {
4 +
5 + @override
6 + Widget build(BuildContext context) {
7 + return Container(
8 +
9 + );
10 + }
11 +}
\ No newline at end of file
lib/src/screens/ionia/widgets/confirm_modal.dart new
+148
@@ -0,0 +1,148 @@
1 +import 'dart:ui';
2 +
3 +import 'package:cake_wallet/palette.dart';
4 +import 'package:flutter/material.dart';
5 +
6 +class IoniaConfirmModal extends StatelessWidget {
7 + IoniaConfirmModal({
8 + @required this.alertTitle,
9 + @required this.alertContent,
10 + @required this.leftButtonText,
11 + @required this.rightButtonText,
12 + @required this.actionLeftButton,
13 + @required this.actionRightButton,
14 + this.leftActionColor,
15 + this.rightActionColor,
16 + this.hideActions = false,
17 + });
18 +
19 + final String alertTitle;
20 + final Widget alertContent;
21 + final String leftButtonText;
22 + final String rightButtonText;
23 + final VoidCallback actionLeftButton;
24 + final VoidCallback actionRightButton;
25 + final Color leftActionColor;
26 + final Color rightActionColor;
27 + final bool hideActions;
28 +
29 + Widget actionButtons(BuildContext context) {
30 + return Row(
31 + mainAxisSize: MainAxisSize.max,
32 + children: <Widget>[
33 + IoniaActionButton(
34 + buttonText: leftButtonText,
35 + action: actionLeftButton,
36 + backgoundColor: leftActionColor,
37 + ),
38 + Container(
39 + width: 1,
40 + height: 52,
41 + color: Theme.of(context).dividerColor,
42 + ),
43 + IoniaActionButton(
44 + buttonText: rightButtonText,
45 + action: actionRightButton,
46 + backgoundColor: rightActionColor,
47 + ),
48 + ],
49 + );
50 + }
51 +
52 + Widget title(BuildContext context) {
53 + return Text(
54 + alertTitle,
55 + textAlign: TextAlign.center,
56 + style: TextStyle(
57 + fontSize: 20,
58 + fontFamily: 'Lato',
59 + fontWeight: FontWeight.w600,
60 + color: Theme.of(context).primaryTextTheme.title.color,
61 + decoration: TextDecoration.none,
62 + ),
63 + );
64 + }
65 +
66 + @override
67 + Widget build(BuildContext context) {
68 + return Container(
69 + color: Colors.transparent,
70 + child: BackdropFilter(
71 + filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
72 + child: Container(
73 + decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)),
74 + child: Center(
75 + child: GestureDetector(
76 + onTap: () => null,
77 + child: ClipRRect(
78 + borderRadius: BorderRadius.all(Radius.circular(30)),
79 + child: Container(
80 + width: 327,
81 + color: Theme.of(context).accentTextTheme.title.decorationColor,
82 + child: Column(
83 + mainAxisSize: MainAxisSize.min,
84 + children: [
85 + Padding(
86 + padding: EdgeInsets.fromLTRB(24, 20, 24, 0),
87 + child: title(context),
88 + ),
89 + Padding(
90 + padding: EdgeInsets.only(top: 16, bottom: 8),
91 + child: Container(
92 + height: 1,
93 + color: Theme.of(context).dividerColor,
94 + ),
95 + ),
96 + alertContent,
97 + actionButtons(context),
98 + ],
99 + ),
100 + ),
101 + ),
102 + ),
103 + ),
104 + ),
105 + ),
106 + );
107 + }
108 +}
109 +
110 +class IoniaActionButton extends StatelessWidget {
111 + const IoniaActionButton({
112 + @required this.buttonText,
113 + @required this.action,
114 + this.backgoundColor,
115 + });
116 +
117 + final String buttonText;
118 + final VoidCallback action;
119 + final Color backgoundColor;
120 +
121 + @override
122 + Widget build(BuildContext context) {
123 + return Flexible(
124 + child: Container(
125 + height: 52,
126 + padding: EdgeInsets.only(left: 6, right: 6),
127 + color: backgoundColor,
128 + child: ButtonTheme(
129 + minWidth: double.infinity,
130 + child: FlatButton(
131 + onPressed: action,
132 + highlightColor: Colors.transparent,
133 + splashColor: Colors.transparent,
134 + child: Text(
135 + buttonText,
136 + textAlign: TextAlign.center,
137 + style: TextStyle(
138 + fontSize: 15,
139 + fontFamily: 'Lato',
140 + fontWeight: FontWeight.w600,
141 + color: backgoundColor != null ? Colors.white : Theme.of(context).primaryTextTheme.body1.backgroundColor,
142 + decoration: TextDecoration.none,
143 + ),
144 + )),
145 + ),
146 + ));
147 + }
148 +}
lib/src/screens/ionia/widgets/ionia_alert_model.dart new
+86
@@ -0,0 +1,86 @@
1 +import 'package:cake_wallet/src/widgets/alert_background.dart';
2 +import 'package:cake_wallet/src/widgets/primary_button.dart';
3 +import 'package:cake_wallet/typography.dart';
4 +import 'package:flutter/material.dart';
5 +
6 +class IoniaAlertModal extends StatelessWidget {
7 + const IoniaAlertModal({
8 + Key key,
9 + @required this.title,
10 + @required this.content,
11 + @required this.actionTitle,
12 + this.heightFactor = 0.4,
13 + this.showCloseButton = true,
14 + }) : super(key: key);
15 +
16 + final String title;
17 + final Widget content;
18 + final String actionTitle;
19 + final bool showCloseButton;
20 + final double heightFactor;
21 +
22 + @override
23 + Widget build(BuildContext context) {
24 + return AlertBackground(
25 + child: Material(
26 + color: Colors.transparent,
27 + child: Column(
28 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
29 + children: [
30 + Spacer(),
31 + Container(
32 + padding: EdgeInsets.only(top: 24, left: 24, right: 24),
33 + margin: EdgeInsets.all(24),
34 + decoration: BoxDecoration(
35 + color: Theme.of(context).backgroundColor,
36 + borderRadius: BorderRadius.circular(30),
37 + ),
38 + child: Column(
39 + children: [
40 + if (title.isNotEmpty)
41 + Text(
42 + title,
43 + style: textLargeSemiBold(
44 + color: Theme.of(context).textTheme.body1.color,
45 + ),
46 + ),
47 + Container(
48 + constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * heightFactor),
49 + child: ListView(
50 + children: [
51 + content,
52 + SizedBox(height: 35),
53 + ],
54 + ),
55 + ),
56 + PrimaryButton(
57 + onPressed: () => Navigator.pop(context),
58 + text: actionTitle,
59 + color: Theme.of(context).accentTextTheme.caption.color,
60 + textColor: Theme.of(context).primaryTextTheme.title.color,
61 + ),
62 + SizedBox(height: 21),
63 + ],
64 + ),
65 + ),
66 + Spacer(),
67 + if(showCloseButton)
68 + InkWell(
69 + onTap: () => Navigator.pop(context),
70 + child: Container(
71 + margin: EdgeInsets.only(bottom: 40),
72 + child: CircleAvatar(
73 + child: Icon(
74 + Icons.close,
75 + color: Colors.black,
76 + ),
77 + backgroundColor: Colors.white,
78 + ),
79 + ),
80 + )
81 + ],
82 + ),
83 + ),
84 + );
85 + }
86 +}
\ No newline at end of file
lib/src/screens/ionia/widgets/ionia_filter_modal.dart new
+134
@@ -0,0 +1,134 @@
1 +import 'package:cake_wallet/ionia/ionia_category.dart';
2 +import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart';
3 +import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
4 +import 'package:cake_wallet/src/widgets/alert_background.dart';
5 +import 'package:cake_wallet/typography.dart';
6 +import 'package:cake_wallet/generated/i18n.dart';
7 +import 'package:flutter/material.dart';
8 +import 'package:flutter_mobx/flutter_mobx.dart';
9 +
10 +class IoniaFilterModal extends StatelessWidget {
11 + IoniaFilterModal({
12 + @required this.filterViewModel,
13 + @required this.selectedCategories,
14 + }) {
15 + filterViewModel.setSelectedCategories(this.selectedCategories);
16 + }
17 +
18 + final IoniaFilterViewModel filterViewModel;
19 + final List<IoniaCategory> selectedCategories;
20 +
21 + @override
22 + Widget build(BuildContext context) {
23 + final searchIcon = Padding(
24 + padding: EdgeInsets.all(10),
25 + child: Image.asset(
26 + 'assets/images/mini_search_icon.png',
27 + color: Theme.of(context).accentColor,
28 + ),
29 + );
30 + return Scaffold(
31 + resizeToAvoidBottomInset: false,
32 + body: AlertBackground(
33 + child: Column(
34 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
35 + children: [
36 + SizedBox(height: 10),
37 + Container(
38 + padding: EdgeInsets.only(top: 24, bottom: 20),
39 + margin: EdgeInsets.all(24),
40 + decoration: BoxDecoration(
41 + color: Theme.of(context).backgroundColor,
42 + borderRadius: BorderRadius.circular(30),
43 + ),
44 + child: Column(
45 + children: [
46 + SizedBox(
47 + height: 40,
48 + child: Padding(
49 + padding: const EdgeInsets.only(left: 24, right: 24),
50 + child: TextField(
51 + onChanged: filterViewModel.onSearchFilter,
52 + style: textMedium(
53 + color: Theme.of(context).primaryTextTheme.title.color,
54 + ),
55 + decoration: InputDecoration(
56 + filled: true,
57 + prefixIcon: searchIcon,
58 + hintText: S.of(context).search_category,
59 + contentPadding: EdgeInsets.only(bottom: 5),
60 + fillColor: Theme.of(context).textTheme.subhead.backgroundColor,
61 + border: OutlineInputBorder(
62 + borderSide: BorderSide.none,
63 + borderRadius: BorderRadius.circular(8),
64 + ),
65 + ),
66 + ),
67 + ),
68 + ),
69 + SizedBox(height: 10),
70 + Divider(thickness: 2),
71 + SizedBox(height: 24),
72 + Observer(builder: (_) {
73 + return ListView.builder(
74 + padding: EdgeInsets.zero,
75 + shrinkWrap: true,
76 + itemCount: filterViewModel.ioniaCategories.length,
77 + itemBuilder: (_, index) {
78 + final category = filterViewModel.ioniaCategories[index];
79 + return Padding(
80 + padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24),
81 + child: InkWell(
82 + onTap: () => filterViewModel.selectFilter(category),
83 + child: Row(
84 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
85 + children: [
86 + Row(
87 + mainAxisSize: MainAxisSize.min,
88 + children: [
89 + Image.asset(
90 + category.iconPath,
91 + color: Theme.of(context).primaryTextTheme.title.color,
92 + ),
93 + SizedBox(width: 10),
94 + Text(category.title,
95 + style: textSmall(
96 + color: Theme.of(context).primaryTextTheme.title.color,
97 + ).copyWith(fontWeight: FontWeight.w500)),
98 + ],
99 + ),
100 + Observer(builder: (_) {
101 + final value = filterViewModel.selectedIndices;
102 + return RoundedCheckbox(
103 + value: value.contains(category.index),
104 + );
105 + }),
106 + ],
107 + ),
108 + ),
109 + );
110 + },
111 + );
112 + }),
113 + ],
114 + ),
115 + ),
116 + InkWell(
117 + onTap: () => Navigator.pop(context, filterViewModel.selectedCategories),
118 + child: Container(
119 + margin: EdgeInsets.only(bottom: 40),
120 + child: CircleAvatar(
121 + child: Icon(
122 + Icons.close,
123 + color: Colors.black,
124 + ),
125 + backgroundColor: Colors.white,
126 + ),
127 + ),
128 + )
129 + ],
130 + ),
131 + ),
132 + );
133 + }
134 +}
lib/src/screens/ionia/widgets/ionia_tile.dart new
+44
@@ -0,0 +1,44 @@
1 +import 'package:cake_wallet/typography.dart';
2 +import 'package:flutter/material.dart';
3 +
4 +class IoniaTile extends StatelessWidget {
5 + const IoniaTile({
6 + Key key,
7 + @required this.title,
8 + @required this.subTitle,
9 + this.onTap,
10 + }) : super(key: key);
11 +
12 + final VoidCallback onTap;
13 + final String title;
14 + final String subTitle;
15 +
16 + @override
17 + Widget build(BuildContext context) {
18 + return GestureDetector(
19 + onTap: () => onTap(),
20 + child: Row(
21 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
22 + children: [
23 + Column(
24 + crossAxisAlignment: CrossAxisAlignment.start,
25 + children: [
26 + Text(
27 + title,
28 + style: textXSmall(
29 + color: Theme.of(context).primaryTextTheme.overline.color,
30 + ),
31 + ),
32 + SizedBox(height: 8),
33 + Text(
34 + subTitle,
35 + style: textMediumBold(
36 + color: Theme.of(context).primaryTextTheme.title.color,
37 + ),
38 + ),
39 + ],
40 + )
41 + ],
42 + ));
43 + }
44 +}
lib/src/screens/ionia/widgets/rounded_checkbox.dart new
+27
@@ -0,0 +1,27 @@
1 +import 'dart:ui';
2 +import 'package:flutter/cupertino.dart';
3 +import 'package:flutter/material.dart';
4 +
5 +class RoundedCheckbox extends StatelessWidget {
6 + RoundedCheckbox({Key key, @required this.value}) : super(key: key);
7 +
8 + final bool value;
9 +
10 + @override
11 + Widget build(BuildContext context) {
12 + return value
13 + ? Container(
14 + height: 20.0,
15 + width: 20.0,
16 + decoration: BoxDecoration(
17 + borderRadius: BorderRadius.all(Radius.circular(50.0)),
18 + color: Theme.of(context).accentTextTheme.body2.color,
19 + ),
20 + child: Icon(
21 + Icons.check,
22 + color: Theme.of(context).backgroundColor,
23 + size: 14.0,
24 + ))
25 + : Offstage();
26 + }
27 +}
lib/src/screens/ionia/widgets/text_icon_button.dart new
+35
@@ -0,0 +1,35 @@
1 +import 'package:cake_wallet/typography.dart';
2 +import 'package:flutter/material.dart';
3 +
4 +class TextIconButton extends StatelessWidget {
5 + final String label;
6 + final VoidCallback onTap;
7 + const TextIconButton({
8 + Key key,
9 + this.label,
10 + this.onTap,
11 + }) : super(key: key);
12 +
13 + @override
14 + Widget build(BuildContext context) {
15 + return
16 + InkWell(
17 + onTap: onTap,
18 + child: Row(
19 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
20 + children: [
21 + Text(
22 + label,
23 + style: textMediumSemiBold(
24 + color: Theme.of(context).primaryTextTheme.title.color,
25 + ),
26 + ),
27 + Icon(
28 + Icons.chevron_right_rounded,
29 + color: Theme.of(context).primaryTextTheme.title.color,
30 + ),
31 + ],
32 + ),
33 + );
34 + }
35 +}
lib/src/widgets/alert_with_two_actions.dart
+14 -2
@@ -10,7 +10,10 @@ class AlertWithTwoActions extends BaseAlertDialog {
10 @required this.rightButtonText,
11 @required this.actionLeftButton,
12 @required this.actionRightButton,
13 - this.alertBarrierDismissible = true
13 + this.alertBarrierDismissible = true,
14 + this.isDividerExist = false,
15 + this.leftActionColor,
16 + this.rightActionColor,
17 });
18
19 final String alertTitle;
@@ -20,6 +23,9 @@ class AlertWithTwoActions extends BaseAlertDialog {
23 final VoidCallback actionLeftButton;
24 final VoidCallback actionRightButton;
25 final bool alertBarrierDismissible;
26 + final Color leftActionColor;
27 + final Color rightActionColor;
28 + final bool isDividerExist;
29
30 @override
31 String get titleText => alertTitle;
@@ -35,4 +41,10 @@ class AlertWithTwoActions extends BaseAlertDialog {
41 VoidCallback get actionRight => actionRightButton;
42 @override
43 bool get barrierDismissible => alertBarrierDismissible;
38 -}
\ No newline at end of file
44 + @override
45 + Color get leftButtonColor => leftActionColor;
46 + @override
47 + Color get rightButtonColor => rightActionColor;
48 + @override
49 + bool get isDividerExists => isDividerExist;
50 +}
lib/src/widgets/base_alert_dialog.dart
+54 -60
@@ -46,30 +46,28 @@ class BaseAlertDialog extends StatelessWidget {
46 children: <Widget>[
47 Flexible(
48 child: Container(
49 - height: 52,
50 - padding: EdgeInsets.only(left: 6, right: 6),
51 - color: Theme.of(context).accentTextTheme.body2.decorationColor,
52 - child: ButtonTheme(
53 - minWidth: double.infinity,
54 - child: FlatButton(
55 - onPressed: actionLeft,
56 - highlightColor: Colors.transparent,
57 - splashColor: Colors.transparent,
58 - child: Text(
59 - leftActionButtonText,
60 - textAlign: TextAlign.center,
61 - style: TextStyle(
62 - fontSize: 15,
63 - fontFamily: 'Lato',
64 - fontWeight: FontWeight.w600,
65 - color: Theme.of(context).primaryTextTheme.body2
66 - .backgroundColor,
67 - decoration: TextDecoration.none,
68 - ),
69 - )),
70 - ),
71 - )
72 - ),
49 + height: 52,
50 + padding: EdgeInsets.only(left: 6, right: 6),
51 + color: Theme.of(context).accentTextTheme.body2.decorationColor,
52 + child: ButtonTheme(
53 + minWidth: double.infinity,
54 + child: FlatButton(
55 + onPressed: actionLeft,
56 + highlightColor: Colors.transparent,
57 + splashColor: Colors.transparent,
58 + child: Text(
59 + leftActionButtonText,
60 + textAlign: TextAlign.center,
61 + style: TextStyle(
62 + fontSize: 15,
63 + fontFamily: 'Lato',
64 + fontWeight: FontWeight.w600,
65 + color: Theme.of(context).primaryTextTheme.body2.backgroundColor,
66 + decoration: TextDecoration.none,
67 + ),
68 + )),
69 + ),
70 + )),
71 Container(
72 width: 1,
73 height: 52,
@@ -77,30 +75,28 @@ class BaseAlertDialog extends StatelessWidget {
75 ),
76 Flexible(
77 child: Container(
80 - height: 52,
81 - padding: EdgeInsets.only(left: 6, right: 6),
82 - color: Theme.of(context).accentTextTheme.body1.backgroundColor,
83 - child: ButtonTheme(
84 - minWidth: double.infinity,
85 - child: FlatButton(
86 - onPressed: actionRight,
87 - highlightColor: Colors.transparent,
88 - splashColor: Colors.transparent,
89 - child: Text(
90 - rightActionButtonText,
91 - textAlign: TextAlign.center,
92 - style: TextStyle(
93 - fontSize: 15,
94 - fontFamily: 'Lato',
95 - fontWeight: FontWeight.w600,
96 - color: Theme.of(context).primaryTextTheme.body1
97 - .backgroundColor,
98 - decoration: TextDecoration.none,
99 - ),
100 - )),
101 - ),
102 - )
103 - ),
78 + height: 52,
79 + padding: EdgeInsets.only(left: 6, right: 6),
80 + color: Theme.of(context).accentTextTheme.body1.backgroundColor,
81 + child: ButtonTheme(
82 + minWidth: double.infinity,
83 + child: FlatButton(
84 + onPressed: actionRight,
85 + highlightColor: Colors.transparent,
86 + splashColor: Colors.transparent,
87 + child: Text(
88 + rightActionButtonText,
89 + textAlign: TextAlign.center,
90 + style: TextStyle(
91 + fontSize: 15,
92 + fontFamily: 'Lato',
93 + fontWeight: FontWeight.w600,
94 + color: Theme.of(context).primaryTextTheme.body1.backgroundColor,
95 + decoration: TextDecoration.none,
96 + ),
97 + )),
98 + ),
99 + )),
100 ],
101 );
102 }
@@ -108,9 +104,7 @@ class BaseAlertDialog extends StatelessWidget {
104 @override
105 Widget build(BuildContext context) {
106 return GestureDetector(
111 - onTap: () => barrierDismissible
112 - ? Navigator.of(context).pop()
113 - : null,
107 + onTap: () => barrierDismissible ? Navigator.of(context).pop() : null,
108 child: Container(
109 color: Colors.transparent,
110 child: BackdropFilter(
@@ -136,14 +130,14 @@ class BaseAlertDialog extends StatelessWidget {
130 child: title(context),
131 ),
132 isDividerExists
139 - ? Padding(
140 - padding: EdgeInsets.only(top: 16, bottom: 8),
141 - child: Container(
142 - height: 1,
143 - color: Theme.of(context).dividerColor,
144 - ),
145 - )
146 - : Offstage(),
133 + ? Padding(
134 + padding: EdgeInsets.only(top: 16, bottom: 8),
135 + child: Container(
136 + height: 1,
137 + color: Theme.of(context).dividerColor,
138 + ),
139 + )
140 + : Offstage(),
141 Padding(
142 padding: EdgeInsets.fromLTRB(24, 8, 24, 32),
143 child: content(context),
@@ -166,4 +160,4 @@ class BaseAlertDialog extends StatelessWidget {
160 ),
161 );
162 }
169 -}
\ No newline at end of file
163 +}
lib/src/widgets/cake_scrollbar.dart
+15 -12
@@ -5,13 +5,19 @@ class CakeScrollbar extends StatelessWidget {
5 @required this.backgroundHeight,
6 @required this.thumbHeight,
7 @required this.fromTop,
8 - this.rightOffset = 6
8 + this.rightOffset = 6,
9 + this.backgroundColor,
10 + this.thumbColor,
11 + this.width = 6,
12 });
13
14 final double backgroundHeight;
15 final double thumbHeight;
16 final double fromTop;
17 + final double width;
18 final double rightOffset;
19 + final Color backgroundColor;
20 + final Color thumbColor;
21
22 @override
23 Widget build(BuildContext context) {
@@ -19,11 +25,10 @@ class CakeScrollbar extends StatelessWidget {
25 right: rightOffset,
26 child: Container(
27 height: backgroundHeight,
22 - width: 6,
28 + width: width,
29 decoration: BoxDecoration(
24 - color: Theme.of(context).textTheme.body1.decorationColor,
25 - borderRadius: BorderRadius.all(Radius.circular(3))
26 - ),
30 + color: backgroundColor ?? Theme.of(context).textTheme.body1.decorationColor,
31 + borderRadius: BorderRadius.all(Radius.circular(3))),
32 child: Stack(
33 children: <Widget>[
34 AnimatedPositioned(
@@ -31,16 +36,14 @@ class CakeScrollbar extends StatelessWidget {
36 top: fromTop,
37 child: Container(
38 height: thumbHeight,
34 - width: 6.0,
39 + width: width,
40 decoration: BoxDecoration(
36 - color: Theme.of(context).textTheme.body1.color,
37 - borderRadius: BorderRadius.all(Radius.circular(3))
38 - ),
41 + color: thumbColor ?? Theme.of(context).textTheme.body1.color,
42 + borderRadius: BorderRadius.all(Radius.circular(3))),
43 ),
44 )
45 ],
46 ),
43 - )
44 - );
47 + ));
48 }
46 -}
\ No newline at end of file
49 +}
lib/src/widgets/discount_badge.dart new
+35
@@ -0,0 +1,35 @@
1 +import 'package:flutter/material.dart';
2 +import 'package:cake_wallet/generated/i18n.dart';
3 +
4 +class DiscountBadge extends StatelessWidget {
5 + const DiscountBadge({
6 + Key key,
7 + @required this.percentage,
8 + this.discountBackground,
9 + }) : super(key: key);
10 +
11 + final double percentage;
12 + final AssetImage discountBackground;
13 +
14 + @override
15 + Widget build(BuildContext context) {
16 + return Container(
17 + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
18 + child: Text(
19 + S.of(context).discount(percentage.toStringAsFixed(2)),
20 + style: TextStyle(
21 + color: Colors.white,
22 + fontSize: 12,
23 + fontWeight: FontWeight.w500,
24 + fontFamily: 'Lato',
25 + ),
26 + ),
27 + decoration: BoxDecoration(
28 + image: DecorationImage(
29 + fit: BoxFit.fill,
30 + image: discountBackground ?? AssetImage('assets/images/badge_discount.png'),
31 + ),
32 + ),
33 + );
34 + }
35 +}
lib/src/widgets/market_place_item.dart new
+66
@@ -0,0 +1,66 @@
1 +import 'package:flutter/material.dart';
2 +
3 +class MarketPlaceItem extends StatelessWidget {
4 +
5 +
6 + MarketPlaceItem({
7 + @required this.onTap,
8 + @required this.title,
9 + @required this.subTitle,
10 + });
11 +
12 + final VoidCallback onTap;
13 + final String title;
14 + final String subTitle;
15 +
16 + @override
17 + Widget build(BuildContext context) {
18 + return InkWell(
19 + onTap: onTap,
20 + child: Stack(
21 + children: [
22 + Container(
23 + padding: EdgeInsets.all(20),
24 + width: double.infinity,
25 + decoration: BoxDecoration(
26 + color: Theme.of(context).textTheme.title.backgroundColor,
27 + borderRadius: BorderRadius.circular(20),
28 + border: Border.all(
29 + color: Colors.white.withOpacity(0.20),
30 + ),
31 + ),
32 + child:
33 + Column(
34 + crossAxisAlignment: CrossAxisAlignment.start,
35 + children: [
36 + Text(
37 + title,
38 + style: TextStyle(
39 + color: Theme.of(context)
40 + .accentTextTheme
41 + .display3
42 + .backgroundColor,
43 + fontSize: 24,
44 + fontWeight: FontWeight.w900,
45 + ),
46 + ),
47 + SizedBox(height: 5),
48 + Text(
49 + subTitle,
50 + style: TextStyle(
51 + color: Theme.of(context)
52 + .accentTextTheme
53 + .display3
54 + .backgroundColor,
55 + fontWeight: FontWeight.w500,
56 + fontFamily: 'Lato'),
57 + )
58 + ],
59 + ),
60 + ),
61 + ],
62 + ),
63 + );
64 + }
65 +}
66 +
lib/typography.dart new
+59
@@ -0,0 +1,59 @@
1 +import 'package:flutter/material.dart';
2 +
3 +const latoFont = "Lato";
4 +
5 +TextStyle textXxSmall({Color color}) => _cakeRegular(10, color);
6 +
7 +TextStyle textXxSmallSemiBold({Color color}) => _cakeSemiBold(10, color);
8 +
9 +TextStyle textXSmall({Color color}) => _cakeRegular(12, color);
10 +
11 +TextStyle textXSmallSemiBold({Color color}) => _cakeSemiBold(12, color);
12 +
13 +TextStyle textSmall({Color color}) => _cakeRegular(14, color);
14 +
15 +TextStyle textSmallSemiBold({Color color}) => _cakeSemiBold(14, color);
16 +
17 +TextStyle textMedium({Color color}) => _cakeRegular(16, color);
18 +
19 +TextStyle textMediumBold({Color color}) => _cakeBold(16, color);
20 +
21 +TextStyle textMediumSemiBold({Color color}) => _cakeSemiBold(22, color);
22 +
23 +TextStyle textLarge({Color color}) => _cakeRegular(18, color);
24 +
25 +TextStyle textLargeSemiBold({Color color}) => _cakeSemiBold(24, color);
26 +
27 +TextStyle textXLarge({Color color}) => _cakeRegular(32, color);
28 +
29 +TextStyle textXLargeSemiBold({Color color}) => _cakeSemiBold(32, color);
30 +
31 +TextStyle _cakeRegular(double size, Color color) => _textStyle(
32 + size: size,
33 + fontWeight: FontWeight.normal,
34 + color: color,
35 + );
36 +
37 +TextStyle _cakeBold(double size, Color color) => _textStyle(
38 + size: size,
39 + fontWeight: FontWeight.w900,
40 + color: color,
41 + );
42 +
43 +TextStyle _cakeSemiBold(double size, Color color) => _textStyle(
44 + size: size,
45 + fontWeight: FontWeight.w700,
46 + color: color,
47 + );
48 +
49 +TextStyle _textStyle({
50 + @required double size,
51 + @required FontWeight fontWeight,
52 + Color color,
53 +}) =>
54 + TextStyle(
55 + fontFamily: latoFont,
56 + fontSize: size,
57 + fontWeight: fontWeight,
58 + color: color ?? Colors.white,
59 + );
lib/view_model/ionia/ionia_account_view_model.dart new
+44
@@ -0,0 +1,44 @@
1 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
2 +import 'package:cake_wallet/ionia/ionia_service.dart';
3 +import 'package:mobx/mobx.dart';
4 +import 'package:cake_wallet/ionia/ionia_gift_card.dart';
5 +
6 +part 'ionia_account_view_model.g.dart';
7 +
8 +class IoniaAccountViewModel = IoniaAccountViewModelBase with _$IoniaAccountViewModel;
9 +
10 +abstract class IoniaAccountViewModelBase with Store {
11 + IoniaAccountViewModelBase({this.ioniaService}) {
12 + email = '';
13 + giftCards = [];
14 + ioniaService.getUserEmail().then((email) => this.email = email);
15 + updateUserGiftCards();
16 + }
17 +
18 + final IoniaService ioniaService;
19 +
20 + @observable
21 + String email;
22 +
23 + @observable
24 + List<IoniaGiftCard> giftCards;
25 +
26 + @computed
27 + int get countOfMerch => giftCards.where((giftCard) => !giftCard.isEmpty).length;
28 +
29 + @computed
30 + List<IoniaGiftCard> get activeMechs => giftCards.where((giftCard) => !giftCard.isEmpty).toList();
31 +
32 + @computed
33 + List<IoniaGiftCard> get redeemedMerchs => giftCards.where((giftCard) => giftCard.isEmpty).toList();
34 +
35 + @action
36 + void logout() {
37 + ioniaService.logout();
38 + }
39 +
40 + @action
41 + Future<void> updateUserGiftCards() async {
42 + giftCards = await ioniaService.getCurrentUserGiftCardSummaries();
43 + }
44 +}
lib/view_model/ionia/ionia_auth_view_model.dart new
+67
@@ -0,0 +1,67 @@
1 +import 'package:cake_wallet/ionia/ionia_create_state.dart';
2 +import 'package:cake_wallet/ionia/ionia_service.dart';
3 +import 'package:mobx/mobx.dart';
4 +
5 +part 'ionia_auth_view_model.g.dart';
6 +
7 +class IoniaAuthViewModel = IoniaAuthViewModelBase with _$IoniaAuthViewModel;
8 +
9 +abstract class IoniaAuthViewModelBase with Store {
10 +
11 + IoniaAuthViewModelBase({this.ioniaService}):
12 + createUserState = IoniaInitialCreateState(),
13 + signInState = IoniaInitialCreateState(),
14 + otpState = IoniaOtpSendDisabled();
15 +
16 + final IoniaService ioniaService;
17 +
18 + @observable
19 + IoniaCreateAccountState createUserState;
20 +
21 + @observable
22 + IoniaCreateAccountState signInState;
23 +
24 + @observable
25 + IoniaOtpState otpState;
26 +
27 + @observable
28 + String email;
29 +
30 + @observable
31 + String otp;
32 +
33 + @action
34 + Future<void> verifyEmail(String code) async {
35 + try {
36 + otpState = IoniaOtpValidating();
37 + await ioniaService.verifyEmail(code);
38 + otpState = IoniaOtpSuccess();
39 + } catch (_) {
40 + otpState = IoniaOtpFailure(error: 'Invalid OTP. Try again');
41 + }
42 + }
43 +
44 + @action
45 + Future<void> createUser(String email) async {
46 + try {
47 + createUserState = IoniaCreateStateLoading();
48 + await ioniaService.createUser(email);
49 + createUserState = IoniaCreateStateSuccess();
50 + } catch (e) {
51 + createUserState = IoniaCreateStateFailure(error: e.toString());
52 + }
53 + }
54 +
55 +
56 + @action
57 + Future<void> signIn(String email) async {
58 + try {
59 + signInState = IoniaCreateStateLoading();
60 + await ioniaService.signIn(email);
61 + signInState = IoniaCreateStateSuccess();
62 + } catch (e) {
63 + signInState = IoniaCreateStateFailure(error: e.toString());
64 + }
65 + }
66 +
67 +}
\ No newline at end of file
lib/view_model/ionia/ionia_buy_card_view_model.dart new
+31
@@ -0,0 +1,31 @@
1 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
2 +import 'package:mobx/mobx.dart';
3 +
4 +part 'ionia_buy_card_view_model.g.dart';
5 +
6 +class IoniaBuyCardViewModel = IoniaBuyCardViewModelBase with _$IoniaBuyCardViewModel;
7 +
8 +abstract class IoniaBuyCardViewModelBase with Store {
9 + IoniaBuyCardViewModelBase({this.ioniaMerchant}) {
10 + isEnablePurchase = false;
11 + amount = 0;
12 + }
13 +
14 + final IoniaMerchant ioniaMerchant;
15 +
16 + @observable
17 + double amount;
18 +
19 + @observable
20 + bool isEnablePurchase;
21 +
22 + @action
23 + void onAmountChanged(String input) {
24 + if (input.isEmpty) return;
25 + amount = double.parse(input.replaceAll(',', '.'));
26 + final min = ioniaMerchant.minimumCardPurchase;
27 + final max = ioniaMerchant.maximumCardPurchase;
28 +
29 + isEnablePurchase = amount >= min && amount <= max;
30 + }
31 +}
lib/view_model/ionia/ionia_filter_view_model.dart new
+58
@@ -0,0 +1,58 @@
1 +import 'package:cake_wallet/ionia/ionia_category.dart';
2 +import 'package:mobx/mobx.dart';
3 +
4 +part 'ionia_filter_view_model.g.dart';
5 +
6 +class IoniaFilterViewModel = IoniaFilterViewModelBase with _$IoniaFilterViewModel;
7 +
8 +abstract class IoniaFilterViewModelBase with Store {
9 + IoniaFilterViewModelBase() {
10 + selectedIndices = ObservableList<int>();
11 + ioniaCategories = IoniaCategory.allCategories;
12 + }
13 +
14 + List<IoniaCategory> get selectedCategories => ioniaCategories.where(_isSelected).toList();
15 +
16 + @observable
17 + ObservableList<int> selectedIndices;
18 +
19 + @observable
20 + List<IoniaCategory> ioniaCategories;
21 +
22 + @action
23 + void selectFilter(IoniaCategory ioniaCategory) {
24 + if (ioniaCategory == IoniaCategory.all && !selectedIndices.contains(0)) {
25 + selectedIndices.clear();
26 + selectedIndices.add(0);
27 + return;
28 + }
29 + if (selectedIndices.contains(ioniaCategory.index) && ioniaCategory.index != 0) {
30 + selectedIndices.remove(ioniaCategory.index);
31 + return;
32 + }
33 + selectedIndices.add(ioniaCategory.index);
34 + selectedIndices.remove(0);
35 + }
36 +
37 + @action
38 + void onSearchFilter(String text) {
39 + if (text.isEmpty) {
40 + ioniaCategories = IoniaCategory.allCategories;
41 + } else {
42 + ioniaCategories = IoniaCategory.allCategories
43 + .where(
44 + (e) => e.title.toLowerCase().contains(text.toLowerCase()),
45 + )
46 + .toList();
47 + }
48 + }
49 +
50 + @action
51 + void setSelectedCategories(List<IoniaCategory> selectedCategories) {
52 + selectedIndices = ObservableList.of(selectedCategories.map((e) => e.index));
53 + }
54 +
55 + bool _isSelected(IoniaCategory ioniaCategory) {
56 + return selectedIndices.contains(ioniaCategory.index);
57 + }
58 +}
lib/view_model/ionia/ionia_gift_card_details_view_model.dart new
+35
@@ -0,0 +1,35 @@
1 +import 'package:cake_wallet/core/execution_state.dart';
2 +import 'package:cake_wallet/ionia/ionia_service.dart';
3 +import 'package:cake_wallet/ionia/ionia_gift_card.dart';
4 +import 'package:mobx/mobx.dart';
5 +
6 +part 'ionia_gift_card_details_view_model.g.dart';
7 +
8 +class IoniaGiftCardDetailsViewModel = IoniaGiftCardDetailsViewModelBase with _$IoniaGiftCardDetailsViewModel;
9 +
10 +abstract class IoniaGiftCardDetailsViewModelBase with Store {
11 +
12 + IoniaGiftCardDetailsViewModelBase({this.ioniaService, this.giftCard}) {
13 + redeemState = InitialExecutionState();
14 + }
15 +
16 + final IoniaService ioniaService;
17 +
18 + @observable
19 + IoniaGiftCard giftCard;
20 +
21 + @observable
22 + ExecutionState redeemState;
23 +
24 + @action
25 + Future<void> redeem() async {
26 + try {
27 + redeemState = IsExecutingState();
28 + await ioniaService.redeem(giftCard);
29 + giftCard = await ioniaService.getGiftCard(id: giftCard.id);
30 + redeemState = ExecutedSuccessfullyState();
31 + } catch(e) {
32 + redeemState = FailureState(e.toString());
33 + }
34 + }
35 +}
\ No newline at end of file
lib/view_model/ionia/ionia_gift_cards_list_view_model.dart new
+103
@@ -0,0 +1,103 @@
1 +import 'package:cake_wallet/ionia/ionia_category.dart';
2 +import 'package:cake_wallet/ionia/ionia_service.dart';
3 +import 'package:cake_wallet/ionia/ionia_create_state.dart';
4 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
5 +import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
6 +import 'package:flutter/material.dart';
7 +import 'package:mobx/mobx.dart';
8 +part 'ionia_gift_cards_list_view_model.g.dart';
9 +
10 +class IoniaGiftCardsListViewModel = IoniaGiftCardsListViewModelBase with _$IoniaGiftCardsListViewModel;
11 +
12 +abstract class IoniaGiftCardsListViewModelBase with Store {
13 + IoniaGiftCardsListViewModelBase({
14 + @required this.ioniaService,
15 + }) :
16 + cardState = IoniaNoCardState(),
17 + ioniaMerchants = [],
18 + scrollOffsetFromTop = 0.0 {
19 + selectedFilters = [];
20 + _getAuthStatus().then((value) => isLoggedIn = value);
21 +
22 + _getMerchants();
23 + }
24 +
25 + final IoniaService ioniaService;
26 +
27 + List<IoniaMerchant> ioniaMerchantList;
28 +
29 + String searchString;
30 +
31 + List<IoniaCategory> selectedFilters;
32 +
33 + @observable
34 + double scrollOffsetFromTop;
35 +
36 + @observable
37 + IoniaCreateCardState createCardState;
38 +
39 + @observable
40 + IoniaFetchCardState cardState;
41 +
42 + @observable
43 + List<IoniaMerchant> ioniaMerchants;
44 +
45 + @observable
46 + bool isLoggedIn;
47 +
48 + Future<bool> _getAuthStatus() async {
49 + return await ioniaService.isLogined();
50 + }
51 +
52 + @action
53 + Future<IoniaVirtualCard> createCard() async {
54 + createCardState = IoniaCreateCardLoading();
55 + try {
56 + final card = await ioniaService.createCard();
57 + createCardState = IoniaCreateCardSuccess();
58 + return card;
59 + } on Exception catch (e) {
60 + createCardState = IoniaCreateCardFailure(error: e.toString());
61 + }
62 + return null;
63 + }
64 +
65 + @action
66 + void searchMerchant(String text) {
67 + if (text.isEmpty) {
68 + ioniaMerchants = ioniaMerchantList;
69 + return;
70 + }
71 + searchString = text;
72 + ioniaService.getMerchantsByFilter(search: searchString).then((value) {
73 + ioniaMerchants = value;
74 + });
75 + }
76 +
77 + Future<void> _getCard() async {
78 + cardState = IoniaFetchingCard();
79 + try {
80 + final card = await ioniaService.getCard();
81 +
82 + cardState = IoniaCardSuccess(card: card);
83 + } catch (_) {
84 + cardState = IoniaFetchCardFailure();
85 + }
86 + }
87 +
88 + void _getMerchants() {
89 + ioniaService.getMerchantsByFilter(categories: selectedFilters).then((value) {
90 + ioniaMerchants = ioniaMerchantList = value;
91 + });
92 + }
93 +
94 + @action
95 + void setSelectedFilter(List<IoniaCategory> filters) {
96 + selectedFilters = filters;
97 + _getMerchants();
98 + }
99 +
100 + void setScrollOffsetFromTop(double scrollOffset) {
101 + scrollOffsetFromTop = scrollOffset;
102 + }
103 +}
lib/view_model/ionia/ionia_payment_status_view_model.dart new
+58
@@ -0,0 +1,58 @@
1 +import 'dart:async';
2 +import 'package:mobx/mobx.dart';
3 +import 'package:flutter/foundation.dart';
4 +import 'package:cake_wallet/ionia/ionia_service.dart';
5 +import 'package:cake_wallet/ionia/ionia_gift_card.dart';
6 +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
7 +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
8 +
9 +part 'ionia_payment_status_view_model.g.dart';
10 +
11 +class IoniaPaymentStatusViewModel = IoniaPaymentStatusViewModelBase with _$IoniaPaymentStatusViewModel;
12 +
13 +abstract class IoniaPaymentStatusViewModelBase with Store {
14 + IoniaPaymentStatusViewModelBase(
15 + this.ioniaService,{
16 + @required this.paymentInfo,
17 + @required this.committedInfo}) {
18 + _timer = Timer.periodic(updateTime, (timer) async {
19 + await updatePaymentStatus();
20 +
21 + if (giftCard != null) {
22 + timer?.cancel();
23 + }
24 + });
25 + }
26 +
27 + static const updateTime = Duration(seconds: 3);
28 +
29 + final IoniaService ioniaService;
30 + final IoniaAnyPayPaymentInfo paymentInfo;
31 + final AnyPayPaymentCommittedInfo committedInfo;
32 +
33 + @observable
34 + IoniaGiftCard giftCard;
35 +
36 + @observable
37 + String error;
38 +
39 + Timer get timer => _timer;
40 +
41 + Timer _timer;
42 +
43 + @action
44 + Future<void> updatePaymentStatus() async {
45 + try {
46 + final giftCardId = await ioniaService.getPaymentStatus(
47 + orderId: paymentInfo.ioniaOrder.id,
48 + paymentId: paymentInfo.ioniaOrder.paymentId);
49 +
50 + if (giftCardId != null) {
51 + giftCard = await ioniaService.getGiftCard(id: giftCardId);
52 + }
53 +
54 + } catch (e) {
55 + error = e.toString();
56 + }
57 + }
58 +}
lib/view_model/ionia/ionia_purchase_merch_view_model.dart new
+94
@@ -0,0 +1,94 @@
1 +import 'package:flutter/foundation.dart';
2 +import 'package:mobx/mobx.dart';
3 +import 'package:cake_wallet/anypay/any_pay_payment.dart';
4 +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
5 +import 'package:cake_wallet/core/execution_state.dart';
6 +import 'package:cake_wallet/ionia/ionia_anypay.dart';
7 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
8 +import 'package:cake_wallet/ionia/ionia_tip.dart';
9 +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
10 +
11 +part 'ionia_purchase_merch_view_model.g.dart';
12 +
13 +class IoniaMerchPurchaseViewModel = IoniaMerchPurchaseViewModelBase with _$IoniaMerchPurchaseViewModel;
14 +
15 +abstract class IoniaMerchPurchaseViewModelBase with Store {
16 + IoniaMerchPurchaseViewModelBase({
17 + @required this.ioniaAnyPayService,
18 + @required this.amount,
19 + @required this.ioniaMerchant,
20 + }) {
21 + tipAmount = 0.0;
22 + percentage = 0.0;
23 + tips = <IoniaTip>[
24 + IoniaTip(percentage: 0, originalAmount: amount),
25 + IoniaTip(percentage: 15, originalAmount: amount),
26 + IoniaTip(percentage: 18, originalAmount: amount),
27 + IoniaTip(percentage: 20, originalAmount: amount),
28 + ];
29 + selectedTip = tips.first;
30 + }
31 +
32 + final double amount;
33 +
34 + List<IoniaTip> tips;
35 +
36 + @observable
37 + IoniaTip selectedTip;
38 +
39 + final IoniaMerchant ioniaMerchant;
40 +
41 + final IoniaAnyPay ioniaAnyPayService;
42 +
43 + IoniaAnyPayPaymentInfo paymentInfo;
44 +
45 + AnyPayPayment get invoice => paymentInfo?.anyPayPayment;
46 +
47 + AnyPayPaymentCommittedInfo committedInfo;
48 +
49 + @observable
50 + ExecutionState invoiceCreationState;
51 +
52 + @observable
53 + ExecutionState invoiceCommittingState;
54 +
55 + @observable
56 + double percentage;
57 +
58 + @computed
59 + double get giftCardAmount => double.parse((amount + tipAmount).toStringAsFixed(2));
60 +
61 + @computed
62 + double get billAmount => double.parse((giftCardAmount * (1 - (ioniaMerchant.discount / 100))).toStringAsFixed(2));
63 +
64 + @observable
65 + double tipAmount;
66 +
67 + @action
68 + void addTip(IoniaTip tip) {
69 + tipAmount = tip.additionalAmount;
70 + selectedTip = tip;
71 + }
72 +
73 + @action
74 + Future<void> createInvoice() async {
75 + try {
76 + invoiceCreationState = IsExecutingState();
77 + paymentInfo = await ioniaAnyPayService.purchase(merchId: ioniaMerchant.id.toString(), amount: giftCardAmount);
78 + invoiceCreationState = ExecutedSuccessfullyState();
79 + } catch (e) {
80 + invoiceCreationState = FailureState(e.toString());
81 + }
82 + }
83 +
84 + @action
85 + Future<void> commitPaymentInvoice() async {
86 + try {
87 + invoiceCommittingState = IsExecutingState();
88 + committedInfo = await ioniaAnyPayService.commitInvoice(invoice);
89 + invoiceCommittingState = ExecutedSuccessfullyState(payload: committedInfo);
90 + } catch (e) {
91 + invoiceCommittingState = FailureState(e.toString());
92 + }
93 + }
94 +}
lib/view_model/send/send_view_model.dart
+2 -2
@@ -222,11 +222,11 @@ abstract class SendViewModelBase with Store {
222 case WalletType.bitcoin:
223 final priority = _settingsStore.priority[_wallet.type];
224
225 - return bitcoin.createBitcoinTransactionCredentials(outputs, priority);
225 + return bitcoin.createBitcoinTransactionCredentials(outputs, priority: priority);
226 case WalletType.litecoin:
227 final priority = _settingsStore.priority[_wallet.type];
228
229 - return bitcoin.createBitcoinTransactionCredentials(outputs, priority);
229 + return bitcoin.createBitcoinTransactionCredentials(outputs, priority: priority);
230 case WalletType.monero:
231 final priority = _settingsStore.priority[_wallet.type];
232
res/values/strings_de.arb
+99 -1
@@ -534,5 +534,103 @@
534 "search_currency": "Währung suchen",
535 "new_template" : "neue Vorlage",
536 "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin",
537 - "wallet_name_exists": "Wallet mit diesem Namen existiert bereits"
537 + "wallet_name_exists": "Wallet mit diesem Namen existiert bereits",
538 + "market_place": "Marktplatz",
539 + "cake_pay_title": "Cake Pay-Geschenkkarten",
540 + "cake_pay_subtitle": "Geschenkkarten kaufen und sofort einlösen",
541 + "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.",
542 + "cake_pay_account_note": "Erstellen Sie ein Konto, um die verfügbaren Karten zu sehen. Einige sind sogar mit Rabatt erhältlich!",
543 + "already_have_account": "Sie haben bereits ein Konto?",
544 + "create_account": "Konto erstellen",
545 + "privacy_policy": "Datenschutzrichtlinie",
546 + "welcome_to_cakepay": "Willkommen bei Cake Pay!",
547 + "sign_up": "Anmelden",
548 + "forgot_password": "Passwort vergessen",
549 + "reset_password": "Passwort zurücksetzen",
550 + "gift_cards": "Geschenkkarten",
551 + "setup_your_debit_card": "Richten Sie Ihre Debitkarte ein",
552 + "no_id_required": "Keine ID erforderlich. Upgraden und überall ausgeben",
553 + "how_to_use_card": "Wie man diese Karte benutzt",
554 + "purchase_gift_card": "Geschenkkarte kaufen",
555 + "verification": "Verifizierung",
556 + "fill_code": "Geben Sie den Bestätigungscode ein, den Sie per E-Mail erhalten haben",
557 + "dont_get_code": "Kein Code?",
558 + "resend_code": "Bitte erneut senden",
559 + "debit_card": "Debitkarte",
560 + "cakepay_prepaid_card": "CakePay-Prepaid-Debitkarte",
561 + "no_id_needed": "Keine ID erforderlich!",
562 + "frequently_asked_questions": "Häufig gestellte Fragen",
563 + "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.",
564 + "Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.",
565 + "cardholder_agreement": "Karteninhabervertrag",
566 + "e_sign_consent": "E-Sign-Zustimmung",
567 + "agree_and_continue": "Zustimmen & fortfahren",
568 + "email_address": "E-Mail-Adresse",
569 + "agree_to": "Indem Sie ein Konto erstellen, stimmen Sie den ",
570 + "und": "und",
571 + "enter_code": "Code eingeben",
572 + "congratulations": "Glückwunsch!",
573 + "you_now_have_debit_card": "Sie haben jetzt eine Debitkarte",
574 + "min_amount": "Min: ${value}",
575 + "max_amount": "Max: ${value}",
576 + "enter_amount": "Betrag eingeben",
577 + "billing_address_info": "Wenn Sie nach einer Rechnungsadresse gefragt werden, geben Sie bitte Ihre Lieferadresse an",
578 + "order_physical_card": "Physische Karte bestellen",
579 + "add_value": "Wert hinzufügen",
580 + "activate": "aktivieren",
581 + "get_a": "Hole ein",
582 + "digital_and_physical_card": "digitale en fysieke prepaid debetkaart",
583 + "get_card_note": " die u kunt herladen met digitale valuta. Geen aanvullende informatie nodig!",
584 + "signup_for_card_accept_terms": "Meld je aan voor de kaart en accepteer de voorwaarden.",
585 + "add_fund_to_card": "Voeg prepaid tegoed toe aan de kaarten (tot ${value})",
586 + "use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.",
587 + "use_card_info_three": "Gebruik de digitale kaart online of met contactloze betaalmethoden.",
588 + "optionally_order_card": "Optioneel een fysieke kaart bestellen.",
589 + "hide_details" : "Details verbergen",
590 + "show_details" : "Toon details",
591 + "upto": "tot ${value}",
592 + "discount": "Bespaar ${value}%",
593 + "gift_card_amount": "Bedrag cadeaubon",
594 + "bill_amount": "Bill bedrag",
595 + "you_pay": "U betaalt",
596 + "tip": "Tip:",
597 + "custom": "aangepast",
598 + "by_cake_pay": "door Cake Pay",
599 + "expires": "Verloopt",
600 + "mm": "MM",
601 + "yy": "JJ",
602 + "online": "online",
603 + "offline": "Offline",
604 + "gift_card_number": "Cadeaukaartnummer",
605 + "pin_number": "PIN-nummer",
606 + "total_saving": "Totale besparingen",
607 + "last_30_days": "Laatste 30 dagen",
608 + "avg_savings": "Gem. besparingen",
609 + "view_all": "Alles bekijken",
610 + "active_cards": "Actieve kaarten",
611 + "delete_account": "Account verwijderen",
612 + "cards": "Kaarten",
613 + "active": "Actief",
614 + "redeemed": "Verzilverd",
615 + "gift_card_balance_note": "Cadeaukaarten met een resterend saldo verschijnen hier",
616 + "gift_card_redeemed_note": "Cadeaubonnen die je hebt ingewisseld, verschijnen hier",
617 + "logout": "Uitloggen",
618 + "add_tip": "Tip toevoegen",
619 + "percentageOf": "van ${amount}",
620 + "is_percentage": "is",
621 + "search_category": "Zoek categorie",
622 + "mark_as_redeemed": "Markeer als ingewisseld",
623 + "more_options": "Meer opties",
624 + "waiting_payment_confirmation": "In afwachting van betalingsbevestiging",
625 + "transaction_sent_notice": "Als het scherm na 1 minuut niet verder gaat, controleer dan een blokverkenner en je e-mail.",
626 + "agree": "mee eens",
627 + "in_store": "In winkel",
628 + "generating_gift_card": "Cadeaubon genereren",
629 + "payment_was_received": "Uw betaling is ontvangen.",
630 + "proceed_after_one_minute": "Als het scherm na 1 minuut niet verder gaat, controleer dan uw e-mail.",
631 + "order_id": "Bestell-ID",
632 + "gift_card_is_generated": "Geschenkkarte wird generiert",
633 + "open_gift_card": "Geschenkkarte öffnen",
634 + "contact_support": "Support kontaktieren",
635 + "gift_cards_unavailable": "Geschenkkarten können derzeit nur über Monero, Bitcoin und Litecoin erworben werden"
636 }
res/values/strings_en.arb
+100 -2
@@ -245,7 +245,7 @@
245 "settings_only_transactions" : "Only transactions",
246 "settings_none" : "None",
247 "settings_support" : "Support",
248 - "settings_terms_and_conditions" : "Terms and conditions",
248 + "settings_terms_and_conditions" : "Terms and Conditions",
249 "pin_is_incorrect" : "PIN is incorrect",
250
251
@@ -534,5 +534,103 @@
534 "search_currency": "Search currency",
535 "new_template" : "New Template",
536 "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work",
537 - "wallet_name_exists": "Wallet with that name has already existed"
537 + "wallet_name_exists": "Wallet with that name has already existed",
538 + "market_place": "Marketplace",
539 + "cake_pay_title": "Cake Pay Gift Cards",
540 + "cake_pay_subtitle": "Buy gift cards and redeem instantly",
541 + "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.",
542 + "cake_pay_account_note": "Make an account to see the available cards. Some are even available at a discount!",
543 + "already_have_account": "Already have an account?",
544 + "create_account": "Create Account",
545 + "privacy_policy": "Privacy Policy",
546 + "welcome_to_cakepay": "Welcome to Cake Pay!",
547 + "sign_up": "Sign Up",
548 + "forgot_password": "Forgot Password",
549 + "reset_password": "Reset Password",
550 + "gift_cards": "Gift Cards",
551 + "setup_your_debit_card": "Set up your debit card",
552 + "no_id_required": "No ID required. Top up and spend anywhere",
553 + "how_to_use_card": "How to use this card",
554 + "purchase_gift_card": "Purchase Gift Card",
555 + "verification": "Verification",
556 + "fill_code": "Please fill in the verification code provided to your email",
557 + "dont_get_code": "Don't get code?",
558 + "resend_code": "Please resend it",
559 + "debit_card": "Debit Card",
560 + "cakepay_prepaid_card": "CakePay Prepaid Debit Card",
561 + "no_id_needed": "No ID needed!",
562 + "frequently_asked_questions": "Frequently asked questions",
563 + "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.",
564 + "please_reference_document": "Please reference the documents below for more information.",
565 + "cardholder_agreement": "Cardholder Agreement",
566 + "e_sign_consent": "E-Sign Consent",
567 + "agree_and_continue": "Agree & Continue",
568 + "email_address": "Email Address",
569 + "agree_to": "By creating account you agree to the ",
570 + "and": "and",
571 + "enter_code": "Enter code",
572 + "congratulations": "Congratulations!",
573 + "you_now_have_debit_card": "You now have a debit card",
574 + "min_amount" : "Min: ${value}",
575 + "max_amount" : "Max: ${value}",
576 + "enter_amount": "Enter Amount",
577 + "billing_address_info": "If asked for a billing address, provide your shipping address",
578 + "order_physical_card": "Order Physical Card",
579 + "add_value": "Add value",
580 + "activate": "Activate",
581 + "get_a": "Get a ",
582 + "digital_and_physical_card": " digital and physical prepaid debit card",
583 + "get_card_note": " that you can reload with digital currencies. No additional information needed!",
584 + "signup_for_card_accept_terms": "Sign up for the card and accept the terms.",
585 + "add_fund_to_card": "Add prepaid funds to the cards (up to ${value})",
586 + "use_card_info_two": "Funds are converted to USD when the held in the prepaid account, not in digital currencies.",
587 + "use_card_info_three": "Use the digital card online or with contactless payment methods.",
588 + "optionally_order_card": "Optionally order a physical card.",
589 + "hide_details" : "Hide Details",
590 + "show_details" : "Show Details",
591 + "upto": "up to ${value}",
592 + "discount": "Save ${value}%",
593 + "gift_card_amount": "Gift Card Amount",
594 + "bill_amount": "Bill amount",
595 + "you_pay": "You pay",
596 + "tip": "Tip:",
597 + "custom": "custom",
598 + "by_cake_pay": "by Cake Pay",
599 + "expires": "Expires",
600 + "mm": "MM",
601 + "yy": "YY",
602 + "online": "Online",
603 + "offline": "Offline",
604 + "gift_card_number": "Gift card number",
605 + "pin_number": "PIN number",
606 + "total_saving": "Total Savings",
607 + "last_30_days": "Last 30 days",
608 + "avg_savings": "Avg. savings",
609 + "view_all": "View all",
610 + "active_cards": "Active cards",
611 + "delete_account": "Delete Account",
612 + "cards": "Cards",
613 + "active": "Active",
614 + "redeemed": "Redeemed",
615 + "gift_card_balance_note": "Gift cards with a balance remaining will appear here",
616 + "gift_card_redeemed_note": "Gift cards you’ve redeemed will appear here",
617 + "logout": "Logout",
618 + "add_tip": "Add Tip",
619 + "percentageOf": "of ${amount}",
620 + "is_percentage": "is",
621 + "search_category": "Search category",
622 + "mark_as_redeemed": "Mark As Redeemed",
623 + "more_options": "More Options",
624 + "awaiting_payment_confirmation": "Awaiting payment confirmation",
625 + "transaction_sent_notice": "If the screen doesn’t proceed after 1 minute, check a block explorer and your email.",
626 + "agree": "Agree",
627 + "in_store": "In Store",
628 + "generating_gift_card": "Generating Gift Card",
629 + "payment_was_received": "Your payment was received.",
630 + "proceed_after_one_minute": "If the screen doesn’t proceed after 1 minute, check your email.",
631 + "order_id": "Order ID",
632 + "gift_card_is_generated": "Gift Card is generated",
633 + "open_gift_card": "Open Gift Card",
634 + "contact_support": "Contact Support",
635 + "gift_cards_unavailable": "Gift cards are available to purchase only through Monero, Bitcoin, and Litecoin at this time"
636 }
res/values/strings_es.arb
+99 -1
@@ -534,5 +534,103 @@
534 "search_currency": "Moneda de búsqueda",
535 "new_template" : "Nueva plantilla",
536 "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando",
537 - "wallet_name_exists": "Wallet con ese nombre ya ha existido"
537 + "wallet_name_exists": "Wallet con ese nombre ya ha existido",
538 + "market_place": "Mercado",
539 + "cake_pay_title": "Tarjetas de regalo Cake Pay",
540 + "cake_pay_subtitle": "Compra tarjetas de regalo y canjéalas al instante",
541 + "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.",
542 + "cake_pay_account_note": "Crea una cuenta para ver las tarjetas disponibles. ¡Algunas incluso están disponibles con descuento!",
543 + "already_have_account": "¿Ya tienes una cuenta?",
544 + "create_account": "Crear Cuenta",
545 + "privacy_policy": "Política de privacidad",
546 + "welcome_to_cakepay": "¡Bienvenido a Cake Pay!",
547 + "sign_up": "Registrarse",
548 + "forgot_password": "Olvidé mi contraseña",
549 + "reset_password": "Restablecer contraseña",
550 + "gift_cards": "Tarjetas de regalo",
551 + "setup_your_debit_card": "Configura tu tarjeta de débito",
552 + "no_id_required": "No se requiere identificación. Recargue y gaste en cualquier lugar",
553 + "how_to_use_card": "Cómo usar esta tarjeta",
554 + "purchase_gift_card": "Comprar tarjeta de regalo",
555 + "verification": "Verificación",
556 + "fill_code": "Por favor complete el código de verificación proporcionado a su correo electrónico",
557 + "dont_get_code": "¿No obtienes el código?",
558 + "resend_code": "Por favor reenvíalo",
559 + "debit_card": "Tarjeta de Débito",
560 + "cakepay_prepaid_card": "Tarjeta de Débito Prepago CakePay",
561 + "no_id_needed": "¡No se necesita identificación!",
562 + "frequently_asked_questions": "Preguntas frecuentes",
563 + "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.",
564 + "please_reference_document": "Consulte los documentos a continuación para obtener más información.",
565 + "cardholder_agreement": "Acuerdo del titular de la tarjeta",
566 + "e_sign_consent": "Consentimiento de firma electrónica",
567 + "agree_and_continue": "Aceptar y continuar",
568 + "email_address": "Dirección de correo electrónico",
569 + "agree_to": "Al crear una cuenta, aceptas ",
570 + "and": "y",
571 + "enter_code": "Ingresar código",
572 + "congratulations": "Felicidades!",
573 + "you_now_have_debit_card": "Ahora tiene una tarjeta de débito",
574 + "min_amount" : "Mínimo: ${value}",
575 + "max_amount" : "Máx: ${value}",
576 + "enter_amount": "Ingrese la cantidad",
577 + "billing_address_info": "Si se le solicita una dirección de facturación, proporcione su dirección de envío",
578 + "order_physical_card": "Pedir tarjeta física",
579 + "add_value": "Añadir valor",
580 + "activate": "Activar",
581 + "get_a": "Obtener un",
582 + "digital_and_physical_card": " tarjeta de débito prepago digital y física",
583 + "get_card_note": " que puedes recargar con monedas digitales. ¡No se necesita información adicional!",
584 + "signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.",
585 + "add_fund_to_card": "Agregar fondos prepagos a las tarjetas (hasta ${value})",
586 + "use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.",
587 + "use_card_info_three": "Utilice la tarjeta digital en línea o con métodos de pago sin contacto.",
588 + "optionally_order_card": "Opcionalmente pide una tarjeta física.",
589 + "hide_details" : "Ocultar detalles",
590 + "show_details": "Mostrar detalles",
591 + "upto": "hasta ${value}",
592 + "discount": "Ahorra ${value}%",
593 + "gift_card_amount": "Cantidad de la tarjeta de regalo",
594 + "bill_amount": "Importe de la factura",
595 + "you_pay": "Tú pagas",
596 + "tip": "Consejo:",
597 + "personalizado": "personalizado",
598 + "by_cake_pay": "por Cake Pay",
599 + "expires": "Caduca",
600 + "mm": "mm",
601 + "yy": "YY",
602 + "online": "En línea",
603 + "offline": "fuera de línea",
604 + "gift_card_number": "Número de tarjeta de regalo",
605 + "pin_number": "Número PIN",
606 + "total_saving": "Ahorro Total",
607 + "last_30_days": "Últimos 30 días",
608 + "avg_savings": "Ahorro promedio",
609 + "view_all": "Ver todo",
610 + "active_cards": "Tarjetas activas",
611 + "delete_account": "Eliminar cuenta",
612 + "cards": "Cartas",
613 + "active": "Activo",
614 + "redeemed": "Redimido",
615 + "gift_card_balance_note": "Las tarjetas de regalo con saldo restante aparecerán aquí",
616 + "gift_card_redeemed_note": "Las tarjetas de regalo que hayas canjeado aparecerán aquí",
617 + "logout": "Cerrar sesión",
618 + "add_tip": "Agregar sugerencia",
619 + "percentageOf": "de ${amount}",
620 + "is_percentage": "es",
621 + "search_category": "Categoría de búsqueda",
622 + "mark_as_redeemed": "Marcar como canjeado",
623 + "more_options": "Más Opciones",
624 + "awaiting_payment_confirmation": "Esperando confirmación de pago",
625 + "transaction_sent_notice": "Si la pantalla no continúa después de 1 minuto, revisa un explorador de bloques y tu correo electrónico.",
626 + "agree": "De acuerdo",
627 + "in_store": "En la tienda",
628 + "generating_gift_card": "Generando tarjeta de regalo",
629 + "payment_was_received": "Su pago fue recibido.",
630 + "proceed_after_one_minute": "Si la pantalla no continúa después de 1 minuto, revisa tu correo electrónico.",
631 + "order_id": "Identificación del pedido",
632 + "gift_card_is_generated": "Se genera la tarjeta de regalo",
633 + "open_gift_card": "Abrir tarjeta de regalo",
634 + "contact_support": "Contactar con Soporte",
635 + "gift_cards_unavailable": "Las tarjetas de regalo están disponibles para comprar solo a través de Monero, Bitcoin y Litecoin en este momento"
636 }
res/values/strings_fr.arb
+100 -2
@@ -243,7 +243,7 @@
243 "settings_only_transactions" : "Seulement les transactions",
244 "settings_none" : "Rien",
245 "settings_support" : "Support",
246 - "settings_terms_and_conditions" : "Termes et conditions",
246 + "settings_terms_and_conditions" : "Termes et Conditions",
247 "pin_is_incorrect" : "Le code PIN est incorrect",
248
249
@@ -532,5 +532,103 @@
532 "search_currency": "Devise de recherche",
533 "new_template" : "Nouveau Modèle",
534 "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",
535 - "wallet_name_exists": "Le portefeuille portant ce nom existe déjà"
535 + "wallet_name_exists": "Le portefeuille portant ce nom existe déjà",
536 + "market_place": "Place de marché",
537 + "cake_pay_title": "Cartes cadeaux Cake Pay",
538 + "cake_pay_subtitle": "Achetez des cartes-cadeaux et échangez-les instantanément",
539 + "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.",
540 + "cake_pay_account_note": "Créez un compte pour voir les cartes disponibles. Certaines sont même disponibles à prix réduit !",
541 + "already_have_account": "Vous avez déjà un compte ?",
542 + "create_account": "Créer un compte",
543 + "privacy_policy": "Politique de confidentialité",
544 + "welcome_to_cakepay": "Bienvenue sur Cake Pay!",
545 + "sign_up": "S'inscrire",
546 + "forgot_password": "Mot de passe oublié",
547 + "reset_password": "Réinitialiser le mot de passe",
548 + "manage_cards": "Cartes cadeaux",
549 + "setup_your_debit_card": "Configurer votre carte de débit",
550 + "no_id_required": "Aucune pièce d'identité requise. Rechargez et dépensez n'importe où",
551 + "how_to_use_card": "Comment utiliser cette carte",
552 + "purchase_gift_card": "Acheter une carte-cadeau",
553 + "verification": "Vérification",
554 + "fill_code": "Veuillez remplir le code de vérification fourni sur votre e-mail",
555 + "dont_get_code": "Vous ne recevez pas le code ?",
556 + "resend_code": "Veuillez le renvoyer",
557 + "debit_card": "Carte de débit",
558 + "cakepay_prepaid_card": "Carte de débit prépayée CakePay",
559 + "no_id_needed": "Aucune pièce d'identité nécessaire !",
560 + "frequently_asked_questions": "Foire aux questions",
561 + "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.",
562 + "please_reference_document": "Veuillez vous référer aux documents ci-dessous pour plus d'informations.",
563 + "cardholder_agreement": "Contrat de titulaire de carte",
564 + "e_sign_consent": "Consentement de signature électronique",
565 + "agree_and_continue": "Accepter et continuer",
566 + "email_address": "Adresse e-mail",
567 + "agree_to": "En créant un compte, vous acceptez les ",
568 + "and": "et",
569 + "enter_code": "Entrez le code",
570 + "congratulations": "Félicitations !",
571 + "you_now_have_debit_card": "Vous avez maintenant une carte de débit",
572 + "min_amount" : "Min : ${value}",
573 + "max_amount" : "Max : ${value}",
574 + "enter_amount": "Entrez le montant",
575 + "billing_address_info": "Si une adresse de facturation vous est demandée, indiquez votre adresse de livraison",
576 + "order_physical_card": "Commander une carte physique",
577 + "add_value": "Ajouter une valeur",
578 + "activate": "Activer",
579 + "get_a": "Obtenir un ",
580 + "digital_and_physical_card": "carte de débit prépayée numérique et physique",
581 + "get_card_note": " que vous pouvez recharger avec des devises numériques. Aucune information supplémentaire n'est nécessaire !",
582 + "signup_for_card_accept_terms": "Inscrivez-vous pour la carte et acceptez les conditions.",
583 + "add_fund_to_card": "Ajouter des fonds prépayés aux cartes (jusqu'à ${value})",
584 + "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.",
585 + "use_card_info_three": "Utilisez la carte numérique en ligne ou avec des méthodes de paiement sans contact.",
586 + "optionally_order_card": "Commander éventuellement une carte physique.",
587 + "hide_details" : "Masquer les détails",
588 + "show_details" : "Afficher les détails",
589 + "upto": "jusqu'à ${value}",
590 + "discount": "Économisez ${value}%",
591 + "gift_card_amount": "Montant de la carte-cadeau",
592 + "bill_amount": "Montant de la facture",
593 + "you_pay": "Vous payez",
594 + "tip": "Astuce :",
595 + "custom": "personnalisé",
596 + "by_cake_pay": "par Cake Pay",
597 + "expire": "Expire",
598 + "mm": "MM",
599 + "yy": "AA",
600 + "online": "En ligne",
601 + "offline": "Hors ligne",
602 + "gift_card_number": "Numéro de carte cadeau",
603 + "pin_number": "Numéro PIN",
604 + "total_saving": "Économies totales",
605 + "last_30_days": "30 derniers jours",
606 + "avg_savings": "Économies moy.",
607 + "view_all": "Voir tout",
608 + "active_cards": "Cartes actives",
609 + "delete_account": "Supprimer le compte",
610 + "cards": "Cartes",
611 + "active": "Actif",
612 + "redeemed": "racheté",
613 + "gift_card_balance_note": "Les cartes-cadeaux avec un solde restant apparaîtront ici",
614 + "gift_card_redeemed_note": "Les cartes-cadeaux que vous avez utilisées apparaîtront ici",
615 + "logout": "Déconnexion",
616 + "add_tip": "Ajouter une astuce",
617 + "percentageOf": "sur ${amount}",
618 + "is_percentage": "est",
619 + "search_category": "Catégorie de recherche",
620 + "mark_as_redeemed": "Marquer comme échangé",
621 + "more_options": "Plus d'options",
622 + "awaiting_payment_confirmation": "En attente de confirmation de paiement",
623 + "transaction_sent_notice": "Si l'écran ne continue pas après 1 minute, vérifiez un explorateur de blocs et votre e-mail.",
624 + "agree": "d'accord",
625 + "in_store": "En magasin",
626 + "generating_gift_card": "Génération d'une carte-cadeau",
627 + "payment_was_received": "Votre paiement a été reçu.",
628 + "proceed_after_one_minute": "Si l'écran ne s'affiche pas après 1 minute, vérifiez vos e-mails.",
629 + "order_id": "Numéro de commande",
630 + "gift_card_is_generated": "La carte-cadeau est générée",
631 + "open_gift_card": "Ouvrir la carte-cadeau",
632 + "contact_support": "Contacter l'assistance",
633 + "gift_cards_unavailable": "Les cartes-cadeaux ne sont disponibles à l'achat que via Monero, Bitcoin et Litecoin pour le moment"
634 }
res/values/strings_hi.arb
+99 -1
@@ -534,5 +534,103 @@
534 "search_currency": "मुद्रा खोजें",
535 "new_template" : "नया टेम्पलेट",
536 "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं",
537 - "wallet_name_exists": "उस नाम वाला वॉलेट पहले से मौजूद है"
537 + "wallet_name_exists": "उस नाम वाला वॉलेट पहले से मौजूद है",
538 + "market_place": "मार्केटप्लेस",
539 + "cake_pay_title": "केक पे गिफ्ट कार्ड्स",
540 + "cake_pay_subtitle": "उपहार कार्ड खरीदें और तुरंत रिडीम करें",
541 + "about_cake_pay": "केक पे आपको वर्चुअल संपत्ति के साथ आसानी से उपहार कार्ड खरीदने की अनुमति देता है, जिसे संयुक्त राज्य में 150,000 से अधिक व्यापारियों पर तुरंत खर्च किया जा सकता है।",
542 + "cake_pay_account_note": "उपलब्ध कार्ड देखने के लिए एक खाता बनाएं। कुछ छूट पर भी उपलब्ध हैं!",
543 + "ready_have_account": "क्या आपके पास पहले से ही एक खाता है?",
544 + "create_account": "खाता बनाएं",
545 + "privacy_policy": "गोपनीयता नीति",
546 + "welcome_to_cakepay": "केकपे में आपका स्वागत है!",
547 + "sign_up": "साइन अप करें",
548 + "forgot_password": "पासवर्ड भूल गए",
549 + "reset_password": "पासवर्ड रीसेट करें",
550 + "gift_cards": "उपहार कार्ड",
551 + "setup_your_debit_card": "अपना डेबिट कार्ड सेट करें",
552 + "no_id_required": "कोई आईडी आवश्यक नहीं है। टॉप अप करें और कहीं भी खर्च करें",
553 + "how_to_use_card": "इस कार्ड का उपयोग कैसे करें",
554 + "purchase_gift_card": "गिफ्ट कार्ड खरीदें",
555 + "verification": "सत्यापन",
556 + "fill_code": "कृपया अपने ईमेल पर प्रदान किया गया सत्यापन कोड भरें",
557 + "dont_get_code": "कोड नहीं मिला?",
558 + "resend_code": "कृपया इसे फिर से भेजें",
559 + "debit_card": "डेबिट कार्ड",
560 + "cakepay_prepaid_card": "केकपे प्रीपेड डेबिट कार्ड",
561 + "no_id_needed": "कोई आईडी नहीं चाहिए!",
562 + "frequently_asked_questions": "अक्सर पूछे जाने वाले प्रश्न",
563 + "debit_card_terms": "इस डिजिटल वॉलेट में आपके भुगतान कार्ड नंबर (और आपके भुगतान कार्ड नंबर से संबंधित क्रेडेंशियल) का भंडारण और उपयोग भुगतान कार्ड जारीकर्ता के साथ लागू कार्डधारक समझौते के नियमों और शर्तों के अधीन है, जैसा कि प्रभावी है समय - समय पर।",
564 + "please_reference_document": "कृपया अधिक जानकारी के लिए नीचे दिए गए दस्तावेज़ देखें।",
565 + "cardholder_agreement": "कार्डधारक अनुबंध",
566 + "e_sign_consent": "ई-साइन सहमति",
567 + "agree_and_continue": "सहमत और जारी रखें",
568 + "email_address": "ईमेल पता",
569 + "agree_to": "खाता बनाकर आप इससे सहमत होते हैं ",
570 + "and": "और",
571 + "enter_code": "कोड दर्ज करें",
572 + "congratulations":"बधाई!",
573 + "you_now_have_debit_card": "अब आपके पास डेबिट कार्ड है",
574 + "min_amount" : "न्यूनतम: ${value}",
575 + "max_amount" : "अधिकतम: ${value}",
576 + "enter_amount": "राशि दर्ज करें",
577 + "billing_address_info": "यदि बिलिंग पता मांगा जाए, तो अपना शिपिंग पता प्रदान करें",
578 + "order_physical_card": "फिजिकल कार्ड ऑर्डर करें",
579 + "add_value": "मूल्य जोड़ें",
580 + "activate": "सक्रिय करें",
581 + "get_a": "एक प्राप्त करें",
582 + "digital_and_physical_card": "डिजिटल और भौतिक प्रीपेड डेबिट कार्ड",
583 + "get_card_note": " कि आप डिजिटल मुद्राओं के साथ पुनः लोड कर सकते हैं। कोई अतिरिक्त जानकारी की आवश्यकता नहीं है!",
584 + "signup_for_card_accept_terms": "कार्ड के लिए साइन अप करें और शर्तें स्वीकार करें।",
585 + "add_fund_to_card": "कार्ड में प्रीपेड धनराशि जोड़ें (${value} तक)",
586 + "use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।",
587 + "use_card_info_three": "डिजिटल कार्ड का ऑनलाइन या संपर्क रहित भुगतान विधियों के साथ उपयोग करें।",
588 + "optionally_order_card": "वैकल्पिक रूप से एक भौतिक कार्ड ऑर्डर करें।",
589 + "hide_details": "विवरण छुपाएं",
590 + "show_details": "विवरण दिखाएं",
591 + "upto": "${value} तक",
592 + "discount": "${value}% बचाएं",
593 + "gift_card_amount": "गिफ्ट कार्ड राशि",
594 + "bill_amount": "बिल राशि",
595 + "you_pay": "आप भुगतान करते हैं",
596 + "tip": "टिप:",
597 + "custom": "कस्टम",
598 + "by_cake_pay": "केकपे द्वारा",
599 + "expires": "समाप्त हो जाता है",
600 + "mm": "एमएम",
601 + "yy": "वाईवाई",
602 + "online": "ऑनलाइन",
603 + "offline": "ऑफ़लाइन",
604 + "gift_card_number": "गिफ्ट कार्ड नंबर",
605 + "pin_number": "पिन नंबर",
606 + "total_saving": "कुल बचत",
607 + "last_30_days": "पिछले 30 दिन",
608 + "avg_savings": "औसत बचत",
609 + "view_all": "सभी देखें",
610 + "active_cards": "सक्रिय कार्ड",
611 + "delete_account": "खाता हटाएं",
612 + "cards": "कार्ड",
613 + "active": "सक्रिय",
614 + "redeemed": "रिडीम किया गया",
615 + "gift_card_balance_note": "गिफ्ट कार्ड शेष राशि के साथ यहां दिखाई देंगे",
616 + "gift_card_redeemed_note": "आपके द्वारा भुनाए गए उपहार कार्ड यहां दिखाई देंगे",
617 + "logout": "लॉगआउट",
618 + "add_tip": "टिप जोड़ें",
619 + "percentageOf": "${amount} का",
620 + "is_percentage": "है",
621 + "search_category": "खोज श्रेणी",
622 + "mark_as_redeemed": "रिडीम किए गए के रूप में चिह्नित करें",
623 + "more_options": "और विकल्प",
624 + "awaiting_payment_confirmation": "भुगतान की पुष्टि की प्रतीक्षा में",
625 + "transaction_sent_notice": "अगर 1 मिनट के बाद भी स्क्रीन आगे नहीं बढ़ती है, तो ब्लॉक एक्सप्लोरर और अपना ईमेल देखें।",
626 + "agree": "सहमत",
627 + "in_store": "स्टोर में",
628 + "generating_gift_card": "गिफ्ट कार्ड जनरेट कर रहा है",
629 + "Payment_was_received": "आपका भुगतान प्राप्त हो गया था।",
630 + "proceed_after_one_minute": "यदि 1 मिनट के बाद भी स्क्रीन आगे नहीं बढ़ती है, तो अपना ईमेल देखें।",
631 + "order_id": "ऑर्डर आईडी",
632 + "gift_card_is_generated": "गिफ्ट कार्ड जनरेट हुआ",
633 + "open_gift_card": "गिफ्ट कार्ड खोलें",
634 + "contact_support": "सहायता से संपर्क करें",
635 + "gift_cards_unavailable": "उपहार कार्ड इस समय केवल मोनेरो, बिटकॉइन और लिटकोइन के माध्यम से खरीदने के लिए उपलब्ध हैं"
636 }
res/values/strings_hr.arb
+99 -1
@@ -534,5 +534,103 @@
534 "search_currency": "Traži valutu",
535 "new_template" : "novi predložak",
536 "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek",
537 - "wallet_name_exists": "Novčanik s tim nazivom već postoji"
537 + "wallet_name_exists": "Novčanik s tim nazivom već postoji",
538 + "market_place": "Tržnica",
539 + "cake_pay_title": "Cake Pay poklon kartice",
540 + "cake_pay_subtitle": "Kupite darovne kartice i odmah ih iskoristite",
541 + "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.",
542 + "cake_pay_account_note": "Napravite račun da vidite dostupne kartice. Neke su čak dostupne uz popust!",
543 + "already_have_account": "Već imate račun?",
544 + "create_account": "Stvori račun",
545 + "privacy_policy": "Pravila privatnosti",
546 + "welcome_to_cakepay": "Dobro došli u Cake Pay!",
547 + "sign_up": "Prijavite se",
548 + "forgot_password": "Zaboravljena lozinka",
549 + "reset_password": "Poništi lozinku",
550 + "gift_cards": "Ajándékkártya",
551 + "setup_your_debit_card": "Postavite svoju debitnu karticu",
552 + "no_id_required": "Nije potreban ID. Nadopunite i potrošite bilo gdje",
553 + "how_to_use_card": "Kako koristiti ovu karticu",
554 + "purchase_gift_card": "Kupnja darovne kartice",
555 + "verification": "Potvrda",
556 + "fill_code": "Molimo vas da ispunite kontrolni kod koji ste dobili na svojoj e-pošti",
557 + "dont_get_code": "Ne dobivate kod?",
558 + "resend_code": "Molimo da ga ponovno pošaljete",
559 + "debit_card": "Debitna kartica",
560 + "cakepay_prepaid_card": "CakePay unaprijed plaćena debitna kartica",
561 + "no_id_needed": "Nije potreban ID!",
562 + "frequently_asked_questions": "Često postavljana pitanja",
563 + "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.",
564 + "please_reference_document": "Molimo pogledajte dokumente ispod za više informacija.",
565 + "cardholder_agreement": "Ugovor s vlasnikom kartice",
566 + "e_sign_consent": "E-Sign pristanak",
567 + "agree_and_continue": "Slažem se i nastavi",
568 + "email_address": "Adresa e-pošte",
569 + "agree_to": "Stvaranjem računa pristajete na ",
570 + "and": "i",
571 + "enter_code": "Unesite kod",
572 + "congratulations": "Čestitamo!",
573 + "you_now_have_debit_card": "Sada imate debitnu karticu",
574 + "min_amount" : "Minimalno: ${value}",
575 + "max_amount" : "Maksimum: ${value}",
576 + "enter_amount": "Unesite iznos",
577 + "billing_address_info": "Ako se od vas zatraži adresa za naplatu, navedite svoju adresu za dostavu",
578 + "order_physical_card": "Naručite fizičku karticu",
579 + "add_value": "Dodaj vrijednost",
580 + "activate": "Aktiviraj",
581 + "get_a": "Nabavite ",
582 + "digital_and_physical_card": "digitalna i fizička unaprijed plaćena debitna kartica",
583 + "get_card_note": " koju možete ponovno napuniti digitalnim valutama. Nisu potrebne dodatne informacije!",
584 + "signup_for_card_accept_terms": "Prijavite se za karticu i prihvatite uvjete.",
585 + "add_fund_to_card": "Dodajte unaprijed uplaćena sredstva na kartice (do ${value})",
586 + "use_card_info_two": "Sredstva se pretvaraju u USD kada se drže na prepaid računu, a ne u digitalnim valutama.",
587 + "use_card_info_three": "Koristite digitalnu karticu online ili s beskontaktnim metodama plaćanja.",
588 + "optionally_order_card": "Opcionalno naručite fizičku karticu.",
589 + "hide_details" : "Sakrij pojedinosti",
590 + "show_details": "Prikaži pojedinosti",
591 + "upto": "do ${value}",
592 + "discount": "Uštedite ${value}%",
593 + "gift_card_amount": "Iznos darovne kartice",
594 + "bill_amount": "Iznos računa",
595 + "you_pay": "Vi plaćate",
596 + "tip": "Savjet:",
597 + "custom": "prilagođeno",
598 + "by_cake_pay": "od Cake Paya",
599 + "expires": "Ističe",
600 + "mm": "MM",
601 + "yy": "GG",
602 + "online": "Na mreži",
603 + "offline": "izvan mreže",
604 + "gift_card_number": "Broj darovne kartice",
605 + "pin_number": "PIN broj",
606 + "total_saving": "Ukupna ušteda",
607 + "last_30_days": "Zadnjih 30 dana",
608 + "avg_savings": "Prosj. ušteda",
609 + "view_all": "Prikaži sve",
610 + "active_cards": "Aktivne kartice",
611 + "delete_account": "Izbriši račun",
612 + "cards": "Kartice",
613 + "active": "Aktivno",
614 + "redeemed": "otkupljeno",
615 + "gift_card_balance_note": "Ovdje će se pojaviti darovne kartice s preostalim saldom",
616 + "gift_card_redeemed_note": "Poklon kartice koje ste iskoristili pojavit će se ovdje",
617 + "logout": "Odjava",
618 + "add_tip": "Dodaj savjet",
619 + "percentageOf": "od ${amount}",
620 + "is_percentage": "je",
621 + "search_category": "Kategorija pretraživanja",
622 + "mark_as_redeemed": "Označi kao otkupljeno",
623 + "more_options": "Više opcija",
624 + "awaiting_payment_confirmation": "Čeka se potvrda plaćanja",
625 + "transaction_sent_notice": "Ako se zaslon ne nastavi nakon 1 minute, provjerite block explorer i svoju e-poštu.",
626 + "agree": "Slažem se",
627 + "in_store": "U trgovini",
628 + "generating_gift_card": "Generiranje darovne kartice",
629 + "payment_was_received": "Vaša uplata je primljena.",
630 + "proceed_after_one_minute": "Ako se zaslon ne nastavi nakon 1 minute, provjerite svoju e-poštu.",
631 + "order_id": "ID narudžbe",
632 + "gift_card_is_generated": "Poklon kartica je generirana",
633 + "open_gift_card": "Otvori darovnu karticu",
634 + "contact_support": "Kontaktirajte podršku",
635 + "gift_cards_unavailable": "Poklon kartice trenutno su dostupne za kupnju samo putem Monera, Bitcoina i Litecoina"
636 }
res/values/strings_it.arb
+99 -1
@@ -534,5 +534,103 @@
534 "search_currency": "Cerca valuta",
535 "new_template" : "Nuovo modello",
536 "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare",
537 - "wallet_name_exists": "Il portafoglio con quel nome è già esistito"
537 + "wallet_name_exists": "Il portafoglio con quel nome è già esistito",
538 + "market_place": "Mercato",
539 + "cake_pay_title": "Carte regalo Cake Pay",
540 + "cake_pay_subtitle": "Acquista carte regalo e riscattale all'istante",
541 + "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.",
542 + "cake_pay_account_note": "Crea un account per vedere le carte disponibili. Alcune sono anche disponibili con uno sconto!",
543 + "already_have_account": "Hai già un account?",
544 + "create_account": "Crea account",
545 + "privacy_policy": "Informativa sulla privacy",
546 + "welcome_to_cakepay": "Benvenuto in Cake Pay!",
547 + "sign_up": "Registrati",
548 + "forgot_password": "Password dimenticata",
549 + "reset_password": "Reimposta password",
550 + "gift_cards": "Carte regalo",
551 + "setup_your_debit_card": "Configura la tua carta di debito",
552 + "no_id_required": "Nessun ID richiesto. Ricarica e spendi ovunque",
553 + "how_to_use_card": "Come usare questa carta",
554 + "purchase_gift_card": "Acquista carta regalo",
555 + "verification": "Verifica",
556 + "fill_code": "Compila il codice di verifica fornito alla tua email",
557 + "dont_get_code": "Non ricevi il codice?",
558 + "resend_code": "Per favore, invialo nuovamente",
559 + "debit_card": "Carta di debito",
560 + "cakepay_prepaid_card": "Carta di debito prepagata CakePay",
561 + "no_id_needed": "Nessun ID necessario!",
562 + "frequently_asked_questions": "Domande frequenti",
563 + "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.",
564 + "please_reference_document": "Si prega di fare riferimento ai documenti di seguito per ulteriori informazioni.",
565 + "cardholder_agreement": "Contratto del titolare della carta",
566 + "e_sign_consent": "Consenso alla firma elettronica",
567 + "agree_and_continue": "Accetta e continua",
568 + "email_address": "Indirizzo e-mail",
569 + "agree_to": "Creando un account accetti il ​​",
570 + "and": "e",
571 + "enter_code": "Inserisci codice",
572 + "congratulation": "Congratulazioni!",
573 + "you_now_have_debit_card": "Ora hai una carta di debito",
574 + "min_amount" : "Min: ${value}",
575 + "max_amount" : "Max: ${value}",
576 + "enter_amount": "Inserisci importo",
577 + "billing_address_info": "Se ti viene richiesto un indirizzo di fatturazione, fornisci il tuo indirizzo di spedizione",
578 + "order_physical_card": "Ordine carta fisica",
579 + "add_value": "Aggiungi valore",
580 + "activate": "Attiva",
581 + "get_a": "Prendi un ",
582 + "digital_and_physical_card": "carta di debito prepagata digitale e fisica",
583 + "get_card_note": "che puoi ricaricare con le valute digitali. Non sono necessarie informazioni aggiuntive!",
584 + "signup_for_card_accept_terms": "Registrati per la carta e accetta i termini.",
585 + "add_fund_to_card": "Aggiungi fondi prepagati alle carte (fino a ${value})",
586 + "use_card_info_two": "I fondi vengono convertiti in USD quando sono detenuti nel conto prepagato, non in valute digitali.",
587 + "use_card_info_three": "Utilizza la carta digitale online o con metodi di pagamento contactless.",
588 + "optional_order_card": "Ordina facoltativamente una carta fisica.",
589 + "hide_details" : "Nascondi dettagli",
590 + "show_details": "Mostra dettagli",
591 + "upto": "fino a ${value}",
592 + "discount": "Risparmia ${value}%",
593 + "gift_card_amount": "Importo del buono regalo",
594 + "bill_amount": "Importo della fattura",
595 + "you_pay": "Tu paghi",
596 + "tip": "Suggerimento:",
597 + "custom": "personalizzato",
598 + "by_cake_pay": "da Cake Pay",
599 + "expires": "Scade",
600 + "mm": "mm",
601 + "yy": "YY",
602 + "online": "in linea",
603 + "offline": "Offline",
604 + "gift_card_number": "Numero del buono regalo",
605 + "pin_number": "Numero PIN",
606 + "total_saving": "Risparmio totale",
607 + "last_30_days": "Ultimi 30 giorni",
608 + "avg_savings": "Risparmio medio",
609 + "view_all": "Visualizza tutto",
610 + "active_cards": "Carte attive",
611 + "delete_account": "Elimina account",
612 + "cards": "Carte",
613 + "active": "Attivo",
614 + "redeemed": "Redento",
615 + "gift_card_balance_note": "Le carte regalo con un saldo residuo appariranno qui",
616 + "gift_card_redeemed_note": "Le carte regalo che hai riscattato appariranno qui",
617 + "logout": "Logout",
618 + "add_tip": "Aggiungi suggerimento",
619 + "percentageOf": "di ${amount}",
620 + "is_percentage": "è",
621 + "search_category": "Categoria di ricerca",
622 + "mark_as_redeemed": "Segna come riscattato",
623 + "more_options": "Altre opzioni",
624 + "waiting_payment_confirmation": "In attesa di conferma del pagamento",
625 + "transaction_sent_notice": "Se lo schermo non procede dopo 1 minuto, controlla un block explorer e la tua email.",
626 + "agree": "d'accordo",
627 + "in_store": "In negozio",
628 + "generating_gift_card": "Generazione carta regalo",
629 + "payment_was_received": "Il tuo pagamento è stato ricevuto.",
630 + "proceed_after_one_minute": "Se lo schermo non procede dopo 1 minuto, controlla la tua email.",
631 + "order_id": "ID ordine",
632 + "gift_card_is_generated": "Il buono regalo è stato generato",
633 + "open_gift_card": "Apri carta regalo",
634 + "contact_support": "Contatta l'assistenza",
635 + "gift_cards_unavailable": "Le carte regalo sono disponibili per l'acquisto solo tramite Monero, Bitcoin e Litecoin in questo momento"
636 }
res/values/strings_ja.arb
+99 -1
@@ -534,5 +534,103 @@
534 "search_currency": "検索通貨",
535 "new_template" : "新しいテンプレート",
536 "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します",
537 - "wallet_name_exists": "その名前のウォレットはすでに存在しています"
537 + "wallet_name_exists": "その名前のウォレットはすでに存在しています",
538 + "market_place": "Marketplace",
539 + "cake_pay_title": "ケーキペイギフトカード",
540 + "cake_pay_subtitle": "ギフトカードを購入してすぐに利用できます",
541 + "about_cake_pay": "Cake Payを使用すると、仮想資産を含むギフトカードを簡単に購入でき、米国内の150,000を超える加盟店ですぐに利用できます。",
542 + "cake_pay_account_note": "アカウントを作成して、利用可能なカードを確認してください。割引価格で利用できるカードもあります!",
543 + "already_have_account": "すでにアカウントをお持ちですか?",
544 + "create_account": "アカウントの作成",
545 + "privacy_policy": "プライバシーポリシー",
546 + "welcome_to_cakepay": "Cake Payへようこそ!",
547 + "sign_up": "サインアップ",
548 + "forgot_password": "パスワードを忘れた",
549 + "reset_password": "パスワードのリセット",
550 + "gift_cards": "ギフトカード",
551 + "setup_your_debit_card": "デビットカードを設定してください",
552 + "no_id_required": "IDは必要ありません。どこにでも補充して使用できます",
553 + "how_to_use_card": "このカードの使用方法",
554 + "purchase_gift_card": "ギフトカードを購入",
555 + "verification" : "検証",
556 + "fill_code": "メールアドレスに記載されている確認コードを入力してください",
557 + "dont_get_code": "コードを取得しませんか?",
558 + "resend_code": "再送してください",
559 + "debit_card": "デビットカード",
560 + "cakepay_prepaid_card": "CakePayプリペイドデビットカード",
561 + "no_id_needed": "IDは必要ありません!",
562 + "frequently_asked_questions": "よくある質問",
563 + "debit_card_terms": "このデジタルウォレットでの支払いカード番号(および支払いカード番号に対応する資格情報)の保存と使用には、支払いカード発行者との該当するカード所有者契約の利用規約が適用されます。時々。",
564 + "please_reference_document": "詳細については、以下のドキュメントを参照してください。",
565 + "cardholder_agreement": "カード所有者契約",
566 + "e_sign_consent": "電子署名の同意",
567 + "agree_and_continue": "同意して続行",
568 + "email_address": "メールアドレス",
569 + "agree_to": "アカウントを作成することにより、",
570 + "and": "と",
571 + "enter_code": "コードを入力",
572 + "congratulations": "おめでとうございます!",
573 + "you_now_have_debit_card": "デビットカードができました",
574 + "min_amount": "最小: ${value}",
575 + "max_amount": "最大: ${value}",
576 + "enter_amount": "金額を入力",
577 + "billing_address_info": "請求先住所を尋ねられた場合は、配送先住所を入力してください",
578 + "order_physical_card": "物理カードの注文",
579 + "add_value": "付加価値",
580 + "activate": "アクティブ化",
581 + "get_a": "Get a",
582 + "digital_and_physical_card": "デジタルおよび物理プリペイドデビットカード",
583 + "get_card_note": "デジタル通貨でリロードできます。追加情報は必要ありません!",
584 + "signup_for_card_accept_terms": "カードにサインアップして、利用規約に同意してください。",
585 + "add_fund_to_card": "プリペイド資金をカードに追加します(最大 ${value})",
586 + "use_card_info_two": "デジタル通貨ではなく、プリペイドアカウントで保持されている場合、資金は米ドルに変換されます。",
587 + "use_card_info_three": "デジタルカードをオンラインまたは非接触型決済方法で使用してください。",
588 + "optionally_order_card": "オプションで物理カードを注文します。",
589 + "hide_details": "詳細を非表示",
590 + "show_details": "詳細を表示",
591 + "upto": "up up ${value}",
592 + "discount": "${value}%を節約",
593 + "gift_card_amount": "ギフトカードの金額",
594 + "bill_amount": "請求額",
595 + "you_pay": "あなたが支払う",
596 + "tip": "ヒント: ",
597 + "custom": "カスタム",
598 + "by_cake_pay": "by Cake Pay",
599 + "expires": "Expires",
600 + "mm": "んん",
601 + "yy": "YY",
602 + "online": "オンライン",
603 + "offline": "オフライン",
604 + "gift_card_number": "ギフトカード番号",
605 + "pin_number": "PIN番号",
606 + "total_saving": "合計節約額",
607 + "last_30_days": "過去30日",
608 + "avg_savings": "平均節約額",
609 + "view_all": "すべて表示",
610 + "active_cards": "アクティブカード",
611 + "delete_account": "アカウントの削除",
612 + "cards": "カード",
613 + "active": "アクティブ",
614 + "redeemed": "償還",
615 + "gift_card_balance_note": "残高が残っているギフトカードがここに表示されます",
616 + "gift_card_redeemed_note": "利用したギフトカードがここに表示されます",
617 + "logout": "ログアウト",
618 + "add_tip": "ヒントを追加",
619 + "percentageOf": "of ${amount}",
620 + "is_percentage": "is",
621 + "search_category": "検索カテゴリ",
622 + "mark_as_redeemed": "償還済みとしてマーク",
623 + "more_options": "その他のオプション",
624 + "awaiting_payment_confirmation": "支払い確認を待っています",
625 + "transaction_sent_notice": "1分経っても画面が進まない場合は、ブロックエクスプローラーとメールアドレスを確認してください。",
626 + "agree": "同意する",
627 + "in_store": "インストア",
628 + "generated_gift_card": "ギフトカードの生成",
629 + "payment_was_received": "お支払いを受け取りました。",
630 + "proceed_after_one_minute": "1分経っても画面が進まない場合は、メールを確認してください。",
631 + "order_id": "注文ID",
632 + "gift_card_is_generated": "ギフトカードが生成されます",
633 + "open_gift_card": "オープンギフトカード",
634 + "contact_support": "サポートに連絡する",
635 + "gift_cards_unavailable": "現時点では、ギフトカードはMonero、Bitcoin、Litecoinからのみ購入できます。"
636 }
res/values/strings_ko.arb
+99 -1
@@ -534,5 +534,103 @@
534 "search_currency": "통화 검색",
535 "new_template" : "새 템플릿",
536 "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다.",
537 - "wallet_name_exists": "해당 이름의 지갑이 이미 존재합니다."
537 + "wallet_name_exists": "해당 이름의 지갑이 이미 존재합니다.",
538 + "market_place": "마켓플레이스",
539 + "cake_pay_title": "케이크 페이 기프트 카드",
540 + "cake_pay_subtitle": "기프트 카드를 구매하고 즉시 사용",
541 + "about_cake_pay": "Cake Pay를 사용하면 미국 내 150,000개 이상의 가맹점에서 즉시 사용할 수 있는 가상 자산이 포함된 기프트 카드를 쉽게 구입할 수 있습니다.",
542 + "cake_pay_account_note": "사용 가능한 카드를 보려면 계정을 만드십시오. 일부는 할인된 가격으로 사용 가능합니다!",
543 + "already_have_account": "이미 계정이 있습니까?",
544 + "create_account": "계정 만들기",
545 + "privacy_policy": "개인 정보 보호 정책",
546 + "welcome_to_cakepay": "Cake Pay에 오신 것을 환영합니다!",
547 + "sign_up": "가입",
548 + "forgot_password": "비밀번호 찾기",
549 + "reset_password": "비밀번호 재설정",
550 + "gift_cards": "기프트 카드",
551 + "setup_your_debit_card": "직불카드 설정",
552 + "no_id_required": "신분증이 필요하지 않습니다. 충전하고 어디에서나 사용하세요",
553 + "how_to_use_card": "이 카드를 사용하는 방법",
554 + "purchase_gift_card": "기프트 카드 구매",
555 + "verification": "검증",
556 + "fill_code": "이메일에 제공된 인증 코드를 입력하세요.",
557 + "dont_get_code": "코드를 받지 못하셨습니까?",
558 + "resend_code": "다시 보내주세요",
559 + "debit_card": "직불 카드",
560 + "cakepay_prepaid_card": "CakePay 선불 직불 카드",
561 + "no_id_needed": "ID가 필요하지 않습니다!",
562 + "frequently_asked_questions": "자주 묻는 질문",
563 + "debit_card_terms": "이 디지털 지갑에 있는 귀하의 지불 카드 번호(및 귀하의 지불 카드 번호에 해당하는 자격 증명)의 저장 및 사용은 부터 발효되는 지불 카드 발행자와의 해당 카드 소지자 계약의 이용 약관을 따릅니다. 수시로.",
564 + "Please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
565 + "cardholder_agreement": "카드 소유자 계약",
566 + "e_sign_consent": "전자 서명 동의",
567 + "agree_and_continue": "동의 및 계속",
568 + "email_address": "이메일 주소",
569 + "agree_to": "계정을 생성하면 ",
570 + "and": "그리고",
571 + "enter_code": "코드 입력",
572 + "congratulations": "축하합니다!",
573 + "you_now_have_debit_card": "이제 직불카드가 있습니다.",
574 + "min_amount" : "최소: ${value}",
575 + "max_amount" : "최대: ${value}",
576 + "enter_amount": "금액 입력",
577 + "billing_address_info": "청구서 수신 주소를 묻는 메시지가 표시되면 배송 주소를 입력하세요.",
578 + "order_physical_card": "물리적 카드 주문",
579 + "add_value": "값 추가",
580 + "activate": "활성화",
581 + "get_a": "가져오기",
582 + "digital_and_physical_card": " 디지털 및 실제 선불 직불 카드",
583 + "get_card_note": " 디지털 통화로 충전할 수 있습니다. 추가 정보가 필요하지 않습니다!",
584 + "signup_for_card_accept_terms": "카드에 가입하고 약관에 동의합니다.",
585 + "add_fund_to_card": "카드에 선불 금액 추가(최대 ${value})",
586 + "use_card_info_two": "디지털 화폐가 아닌 선불 계정에 보유하면 자금이 USD로 변환됩니다.",
587 + "use_card_info_three": "디지털 카드를 온라인 또는 비접촉식 결제 수단으로 사용하십시오.",
588 + "optionally_order_card": "선택적으로 실제 카드를 주문하십시오.",
589 + "hide_details" : "세부 정보 숨기기",
590 + "show_details" : "세부정보 표시",
591 + "upto": "최대 ${value}",
592 + "discount": "${value}% 절약",
593 + "gift_card_amount": "기프트 카드 금액",
594 + "bill_amount": "청구 금액",
595 + "you_pay": "당신이 지불합니다",
596 + "tip": "팁:",
597 + "custom": "커스텀",
598 + "by_cake_pay": "Cake Pay로",
599 + "expires": "만료",
600 + "mm": "mm",
601 + "YY": "YY",
602 + "online": "온라인",
603 + "offline": "오프라인",
604 + "gift_card_number": "기프트 카드 번호",
605 + "pin_number": "PIN 번호",
606 + "total_saving": "총 절감액",
607 + "last_30_days": "지난 30일",
608 + "avg_savings": "평균 절감액",
609 + "view_all": "모두 보기",
610 + "active_cards": "활성 카드",
611 + "delete_account": "계정 삭제",
612 + "cards": "카드",
613 + "active": "활성",
614 + "redeemed": "구함",
615 + "gift_card_balance_note": "잔액이 남아 있는 기프트 카드가 여기에 표시됩니다.",
616 + "gift_card_redeemed_note": "사용한 기프트 카드가 여기에 표시됩니다.",
617 + "logout": "로그아웃",
618 + "add_tip": "팁 추가",
619 + "percentageOf": "${amount} 중",
620 + "is_percentage": "이다",
621 + "search_category": "검색 카테고리",
622 + "mark_as_redeemed": "사용한 것으로 표시",
623 + "more_options": "추가 옵션",
624 + "awaiting_payment_confirmation": "결제 확인 대기 중",
625 + "transaction_sent_notice": "1분 후에도 화면이 진행되지 않으면 블록 익스플로러와 이메일을 확인하세요.",
626 + "agree": "동의하다",
627 + "in_store": "매장 내",
628 + "generating_gift_card": "기프트 카드 생성 중",
629 + "payment_was_received": "결제가 접수되었습니다.",
630 + "proceed_after_one_minute": "1분 후에도 화면이 진행되지 않으면 이메일을 확인하세요.",
631 + "order_id": "주문 ID",
632 + "gift_card_is_generated": "기프트 카드가 생성되었습니다",
633 + "open_gift_card": "기프트 카드 열기",
634 + "contact_support": "지원팀에 문의",
635 + "gift_cards_unavailable": "기프트 카드는 현재 Monero, Bitcoin 및 Litecoin을 통해서만 구매할 수 있습니다."
636 }
res/values/strings_nl.arb
+99 -1
@@ -534,5 +534,103 @@
534 "search_currency": "Zoek valuta",
535 "new_template" : "Nieuwe sjabloon",
536 "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work",
537 - "wallet_name_exists": "Portemonnee met die naam bestaat al"
537 + "wallet_name_exists": "Portemonnee met die naam bestaat al",
538 + "market_place": "Marktplaats",
539 + "cake_pay_title": "Cake Pay-cadeaubonnen",
540 + "cake_pay_subtitle": "Koop cadeaubonnen en wissel ze direct in",
541 + "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.",
542 + "cake_pay_account_note": "Maak een account aan om de beschikbare kaarten te zien. Sommige zijn zelfs met korting verkrijgbaar!",
543 + "already_have_account": "Heb je al een account?",
544 + "create_account": "Account aanmaken",
545 + "privacy_policy": "Privacybeleid",
546 + "welcome_to_cakepay": "Welkom bij Cake Pay!",
547 + "sign_up": "Aanmelden",
548 + "forgot_password": "Wachtwoord vergeten",
549 + "reset_password": "Wachtwoord resetten",
550 + "gift_cards": "Cadeaubonnen",
551 + "setup_your_debit_card": "Stel uw debetkaart in",
552 + "no_id_required": "Geen ID vereist. Opwaarderen en overal uitgeven",
553 + "how_to_use_card": "Hoe deze kaart te gebruiken",
554 + "purchase_gift_card": "Cadeaubon kopen",
555 + "verification": "Verificatie",
556 + "fill_code": "Vul de verificatiecode in die u in uw e-mail hebt ontvangen",
557 + "dont_get_code": "Geen code?",
558 + "resend_code": "Stuur het alstublieft opnieuw",
559 + "debit_card": "Debetkaart",
560 + "cakepay_prepaid_card": "CakePay Prepaid Debetkaart",
561 + "no_id_needed": "Geen ID nodig!",
562 + "frequently_asked_questions": "Veelgestelde vragen",
563 + "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.",
564 + "please_reference_document": "Raadpleeg de onderstaande documenten voor meer informatie.",
565 + "cardholder_agreement": "Kaarthouderovereenkomst",
566 + "e_sign_consent": "Toestemming e-ondertekenen",
567 + "agree_and_continue": "Akkoord & doorgaan",
568 + "email_address": "E-mailadres",
569 + "agree_to": "Door een account aan te maken gaat u akkoord met de ",
570 + "and": "en",
571 + "enter_code": "Voer code in",
572 + "congratulations": "gefeliciteerd!",
573 + "you_now_have_debit_card": "Je hebt nu een debetkaart",
574 + "min_amount" : "Min: ${value}",
575 + "max_amount" : "Max: ${value}",
576 + "enter_amount": "Voer Bedrag in",
577 + "billing_address_info": "Als u om een ​​factuuradres wordt gevraagd, geef dan uw verzendadres op",
578 + "order_physical_card": "Fysieke kaart bestellen",
579 + "add_value": "Waarde toevoegen",
580 + "activate": "Activeren",
581 + "get_a": "Krijg een ",
582 + "digital_and_physical_card": "digitale und physische Prepaid-Debitkarte",
583 + "get_card_note": " die Sie mit digitaler Währung aufladen können. Keine zusätzlichen Informationen erforderlich!",
584 + "signup_for_card_accept_terms": "Melden Sie sich für die Karte an und akzeptieren Sie die Bedingungen.",
585 + "add_fund_to_card": "Prepaid-Guthaben zu den Karten hinzufügen (bis zu ${value})",
586 + "use_card_info_two": "Guthaben werden auf dem Prepaid-Konto in USD umgerechnet, nicht in digitale Währung.",
587 + "use_card_info_three": "Verwenden Sie die digitale Karte online oder mit kontaktlosen Zahlungsmethoden.",
588 + "optional_order_card": "Optional eine physische Karte bestellen.",
589 + "hide_details": "Details ausblenden",
590 + "show_details": "Details anzeigen",
591 + "upto": "bis zu ${value}",
592 + "discount": "${value} % sparen",
593 + "gift_card_amount": "Gutscheinbetrag",
594 + "bill_amount": "Rechnungsbetrag",
595 + "you_pay": "Sie bezahlen",
596 + "tip": "Hinweis:",
597 + "custom": "benutzerdefiniert",
598 + "by_cake_pay": "von Cake Pay",
599 + "expires": "Läuft ab",
600 + "mm": "MM",
601 + "yy": "YY",
602 + "online": "online",
603 + "offline": "offline",
604 + "gift_card_number": "Geschenkkartennummer",
605 + "pin_number": "PIN-Nummer",
606 + "total_saving": "Gesamteinsparungen",
607 + "last_30_days": "Letzte 30 Tage",
608 + "avg_savings": "Durchschn. Einsparungen",
609 + "view_all": "Alle anzeigen",
610 + "active_cards": "Aktive Karten",
611 + "delete_account": "Konto löschen",
612 + "cards": "Karten",
613 + "active": "Aktiv",
614 + "redeemed": "Versilbert",
615 + "gift_card_balance_note": "Geschenkkarten mit Restguthaben erscheinen hier",
616 + "gift_card_redeemed_note": "Gutscheine, die Sie eingelöst haben, werden hier angezeigt",
617 + "abmelden": "Abmelden",
618 + "add_tip": "Tipp hinzufügen",
619 + "percentageOf": "von ${amount}",
620 + "is_percentage": "ist",
621 + "search_category": "Suchkategorie",
622 + "mark_as_redeemed": "Als eingelöst markieren",
623 + "more_options": "Weitere Optionen",
624 + "waiting_payment_confirmation": "Warte auf Zahlungsbestätigung",
625 + "transaction_sent_notice": "Wenn der Bildschirm nach 1 Minute nicht weitergeht, überprüfen Sie einen Block-Explorer und Ihre E-Mail.",
626 + "agree": "stimme zu",
627 + "in_store": "Im Geschäft",
628 + "generating_gift_card": "Geschenkkarte wird erstellt",
629 + "payment_was_received": "Ihre Zahlung ist eingegangen.",
630 + "proceed_after_one_minute": "Wenn der Bildschirm nach 1 Minute nicht weitergeht, überprüfen Sie bitte Ihre E-Mail.",
631 + "order_id": "Order-ID",
632 + "gift_card_is_generated": "Cadeaukaart is gegenereerd",
633 + "open_gift_card": "Geschenkkaart openen",
634 + "contact_support": "Contact opnemen met ondersteuning",
635 + "gift_cards_unavailable": "Cadeaubonnen kunnen momenteel alleen worden gekocht via Monero, Bitcoin en Litecoin"
636 }
res/values/strings_pl.arb
+99 -1
@@ -534,5 +534,103 @@
534 "search_currency": "Wyszukaj walutę",
535 "new_template" : "Nowy szablon",
536 "electrum_address_disclaimer": "Za każdym razem, gdy korzystasz z jednego z nich, generujemy nowe adresy, ale poprzednie adresy nadal działają",
537 - "wallet_name_exists": "Portfel o tej nazwie już istnieje"
537 + "wallet_name_exists": "Portfel o tej nazwie już istnieje",
538 + "market_place": "Rynek",
539 + "cake_pay_title": "Karty podarunkowe Cake Pay",
540 + "cake_pay_subtitle": "Kup karty podarunkowe i wykorzystaj je natychmiast",
541 + "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.",
542 + "cake_pay_account_note": "Załóż konto, aby zobaczyć dostępne karty. Niektóre są nawet dostępne ze zniżką!",
543 + "already_have_account": "Masz już konto?",
544 + "create_account": "Utwórz konto",
545 + "privacy_policy": "Polityka prywatności",
546 + "welcome_to_cakepay": "Witamy w Cake Pay!",
547 + "sign_up": "Zarejestruj się",
548 + "forgot_password": "Zapomniałem hasła",
549 + "reset_password": "Zresetuj hasło",
550 + "gift_cards": "Karty podarunkowe",
551 + "setup_your_debit_card": "Skonfiguruj swoją kartę debetową",
552 + "no_id_required": "Nie wymagamy ID. Doładuj i wydawaj gdziekolwiek",
553 + "how_to_use_card": "Jak korzystać z tej karty",
554 + "purchase_gift_card": "Kup kartę podarunkową",
555 + "verification": "Weryfikacja",
556 + "fill_code": "Proszę wpisać kod weryfikacyjny podany w wiadomości e-mail",
557 + "dont_get_code": "Nie odbierasz kodu?",
558 + "resend_code": "Wyślij go ponownie",
559 + "debit_card": "Karta debetowa",
560 + "cakepay_prepaid_card": "Przedpłacona karta debetowa CakePay",
561 + "no_id_needed": "Nie potrzeba ID!",
562 + "frequently_asked_questions": "Często zadawane pytania",
563 + "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.",
564 + "please_reference_document": "Proszę odwołać się do poniższych dokumentów, aby uzyskać więcej informacji.",
565 + "cardholder_agreement": "Umowa posiadacza karty",
566 + "e_sign_consent": "Zgoda na podpis elektroniczny",
567 + "agree_and_continue": "Zgadzam się i kontynuuj",
568 + "email_address": "Adres e-mail",
569 + "agree_to": "Tworząc konto wyrażasz zgodę na ",
570 + "and": "i",
571 + "enter_code": "Wprowadź kod",
572 + "congratulations": "gratulacje!",
573 + "you_now_have_debit_card": "Masz teraz kartę debetową",
574 + "min_amount" : "Min: ${value}",
575 + "max_amount" : "Max: ${value}",
576 + "enter_amount": "Wprowadź kwotę",
577 + "billing_address_info": "Jeśli zostaniesz poproszony o podanie adresu rozliczeniowego, podaj swój adres wysyłki",
578 + "order_physical_card": "Zamów kartę fizyczną",
579 + "add_value": "Dodaj wartość",
580 + "activate": "Aktywuj",
581 + "get_a": "Zdobądź ",
582 + "digital_and_physical_card": " cyfrowa i fizyczna przedpłacona karta debetowa",
583 + "get_card_note": " które możesz doładować walutami cyfrowymi. Nie są potrzebne żadne dodatkowe informacje!",
584 + "signup_for_card_accept_terms": "Zarejestruj się, aby otrzymać kartę i zaakceptuj warunki.",
585 + "add_fund_to_card": "Dodaj przedpłacone środki do kart (do ${value})",
586 + "use_card_info_two": "Środki są przeliczane na USD, gdy są przechowywane na koncie przedpłaconym, a nie w walutach cyfrowych.",
587 + "use_card_info_three": "Użyj cyfrowej karty online lub za pomocą zbliżeniowych metod płatności.",
588 + "optionally_order_card": "Opcjonalnie zamów kartę fizyczną.",
589 + "hide_details" : "Ukryj szczegóły",
590 + "show_details" : "Pokaż szczegóły",
591 + "upto": "do ${value}",
592 + "discount": "Zaoszczędź ${value}%",
593 + "gift_card_amount": "Kwota karty podarunkowej",
594 + "bill_amount": "Kwota rachunku",
595 + "you_pay": "Płacisz",
596 + "tip": "wskazówka:",
597 + "custom": "niestandardowy",
598 + "by_cake_pay": "przez Cake Pay",
599 + "expires": "Wygasa",
600 + "mm": "MM",
601 + "yy": "RR",
602 + "online": "online",
603 + "offline": "Offline",
604 + "gift_card_number": "Numer karty podarunkowej",
605 + "pin_number": "Numer PIN",
606 + "total_saving": "Całkowite oszczędności",
607 + "last_30_days": "Ostatnie 30 dni",
608 + "avg_savings": "Śr. oszczędności",
609 + "view_all": "Wyświetl wszystko",
610 + "active_cards": "Aktywne karty",
611 + "delete_account": "Usuń konto",
612 + "cards": "Karty",
613 + "active": "Aktywny",
614 + "redeemed": "wykupione",
615 + "gift_card_balance_note": "Tutaj pojawią się karty podarunkowe z pozostałym saldem",
616 + "gift_card_redeemed_note": "Karty podarunkowe, które wykorzystałeś, pojawią się tutaj",
617 + "logout": "Wyloguj",
618 + "add_tip": "Dodaj wskazówkę",
619 + "percentageOf": "z ${amount}",
620 + "is_percentage": "jest",
621 + "search_category": "Kategoria wyszukiwania",
622 + "mark_as_redeemed": "Oznacz jako wykorzystany",
623 + "more_options": "Więcej opcji",
624 + "awaiting_payment_confirmation": "Oczekiwanie na potwierdzenie płatności",
625 + "transaction_sent_notice": "Jeśli ekran nie pojawi się po 1 minucie, sprawdź eksplorator bloków i swój e-mail.",
626 + "agree": "Zgadzam się",
627 + "in_store": "W Sklepie",
628 + "generating_gift_card": "Generowanie karty podarunkowej",
629 + "payment_was_received": "Twoja płatność została otrzymana.",
630 + "proceed_after_one_minute": "Jeśli ekran nie przejdzie dalej po 1 minucie, sprawdź pocztę.",
631 + "order_id": "Identyfikator zamówienia",
632 + "gift_card_is_generated": "Karta podarunkowa jest generowana",
633 + "open_gift_card": "Otwórz kartę podarunkową",
634 + "contact_support": "Skontaktuj się z pomocą techniczną",
635 + "gift_cards_unavailable": "Karty podarunkowe można obecnie kupić tylko za pośrednictwem Monero, Bitcoin i Litecoin"
636 }
res/values/strings_pt.arb
+99 -1
@@ -534,5 +534,103 @@
534 "search_currency": "Pesquisar moeda",
535 "new_template" : "Novo modelo",
536 "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando",
537 - "wallet_name_exists": "A carteira com esse nome já existe"
537 + "wallet_name_exists": "A carteira com esse nome já existe",
538 + "market_place": "Mercado",
539 + "cake_pay_title": "Cartões de presente de pagamento de bolo",
540 + "cake_pay_subtitle": "Compre vales-presente e resgate instantaneamente",
541 + "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.",
542 + "cake_pay_account_note": "Faça uma conta para ver os cartões disponíveis. Alguns estão até com desconto!",
543 + "already_have_account": "Já tem uma conta?",
544 + "create_account": "Criar conta",
545 + "privacy_policy": "Política de privacidade",
546 + "welcome_to_cakepay": "Bem-vindo ao Cake Pay!",
547 + "create_account": "Registar-se",
548 + "forgot_password": "Esqueci a senha",
549 + "reset_password": "Redefinir senha",
550 + "gift_cards": "Cartões de presente",
551 + "setup_your_debit_card": "Configure seu cartão de débito",
552 + "no_id_required": "Não é necessário ID. Recarregue e gaste em qualquer lugar",
553 + "how_to_use_card": "Como usar este cartão",
554 + "purchase_gift_card": "Comprar vale-presente",
555 + "verification": "Verificação",
556 + "fill_code": "Por favor, preencha o código de verificação fornecido ao seu e-mail",
557 + "dont_get_code": "Não recebeu o código?",
558 + "resend_code": "Por favor, reenvie",
559 + "debit_card": "Cartão de débito",
560 + "cakepay_prepaid_card": "Cartão de débito pré-pago CakePay",
561 + "no_id_needed": "Nenhum ID necessário!",
562 + "frequently_asked_questions": "Perguntas frequentes",
563 + "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.",
564 + "please_reference_document": "Por favor, consulte os documentos abaixo para mais informações.",
565 + "cardholder_agreement": "Acordo do titular do cartão",
566 + "e_sign_consent": "Consentimento de assinatura eletrônica",
567 + "agree_and_continue": "Concordar e continuar",
568 + "email_address": "Endereço de e-mail",
569 + "agree_to": "Ao criar conta você concorda com ",
570 + "and": "e",
571 + "enter_code": "Digite o código",
572 + "congratulations": "Parabéns!",
573 + "you_now_have_debit_card": "Agora você tem um cartão de débito",
574 + "min_amount" : "Mínimo: ${valor}",
575 + "max_amount" : "Máx.: ${valor}",
576 + "enter_amount": "Digite o valor",
577 + "billing_address_info": "Se for solicitado um endereço de cobrança, forneça seu endereço de entrega",
578 + "order_physical_card": "Pedir Cartão Físico",
579 + "add_value": "Adicionar valor",
580 + "activate": "Ativar",
581 + "get_a": "Obter um ",
582 + "digital_and_physical_card": "cartão de débito pré-pago digital e físico",
583 + "get_card_note": " que você pode recarregar com moedas digitais. Nenhuma informação adicional é necessária!",
584 + "signup_for_card_accept_terms": "Cadastre-se no cartão e aceite os termos.",
585 + "add_fund_to_card": "Adicionar fundos pré-pagos aos cartões (até ${value})",
586 + "use_card_info_two": "Os fundos são convertidos para USD quando mantidos na conta pré-paga, não em moedas digitais.",
587 + "use_card_info_three": "Use o cartão digital online ou com métodos de pagamento sem contato.",
588 + "opcionalmente_order_card": "Opcionalmente, peça um cartão físico.",
589 + "hide_details" : "Ocultar detalhes",
590 + "show_details" : "Mostrar detalhes",
591 + "upto": "até ${value}",
592 + "discount": "Economize ${value}%",
593 + "gift_card_amount": "Valor do Cartão Presente",
594 + "bill_amount": "Valor da conta",
595 + "you_pay": "Você paga",
596 + "tip": "Dica:",
597 + "custom": "personalizado",
598 + "by_cake_pay": "por Cake Pay",
599 + "expires": "Expira",
600 + "mm": "MM",
601 + "yy": "aa",
602 + "online": "Online",
603 + "offline": "offline",
604 + "gift_card_number": "Número do cartão-presente",
605 + "pin_number": "Número PIN",
606 + "total_saving": "Economia total",
607 + "last_30_days": "Últimos 30 dias",
608 + "avg_savings": "Poupança média",
609 + "view_all": "Ver todos",
610 + "active_cards": "Cartões ativos",
611 + "delete_account": "Excluir conta",
612 + "cards": "Cartões",
613 + "active": "Ativo",
614 + "redeemed": "Resgatado",
615 + "gift_card_balance_note": "Os cartões-presente com saldo restante aparecerão aqui",
616 + "gift_card_redeemed_note": "Os cartões-presente que você resgatou aparecerão aqui",
617 + "logout": "Logout",
618 + "add_tip": "Adicionar Dica",
619 + "percentageOf": "de ${amount}",
620 + "is_percentage": "é",
621 + "search_category": "Categoria de pesquisa",
622 + "mark_as_redemed": "Marcar como resgatado",
623 + "more_options": "Mais opções",
624 + "waiting_payment_confirmation": "Aguardando confirmação de pagamento",
625 + "transaction_sent_notice": "Se a tela não prosseguir após 1 minuto, verifique um explorador de blocos e seu e-mail.",
626 + "agree": "Concordo",
627 + "in_store": "Na loja",
628 + "generating_gift_card": "Gerando Cartão Presente",
629 + "payment_was_received": "Seu pagamento foi recebido.",
630 + "proceed_after_one_minute": "Se a tela não prosseguir após 1 minuto, verifique seu e-mail.",
631 + "order_id": "ID do pedido",
632 + "gift_card_is_generated": "Cartão presente é gerado",
633 + "open_gift_card": "Abrir vale-presente",
634 + "contact_support": "Contatar Suporte",
635 + "gift_cards_unavailable": "Os cartões-presente estão disponíveis para compra apenas através do Monero, Bitcoin e Litecoin no momento"
636 }
res/values/strings_ru.arb
+99 -1
@@ -534,5 +534,103 @@
534 "search_currency": "Валюта поиска",
535 "new_template" : "Новый шаблон",
536 "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать.",
537 - "wallet_name_exists": "Кошелек с таким именем уже существует"
537 + "wallet_name_exists": "Кошелек с таким именем уже существует",
538 + "market_place": "Торговая площадка",
539 + "cake_pay_title": "Подарочные карты Cake Pay",
540 + "cake_pay_subtitle": "Купите подарочные карты и моментально погасите их",
541 + "about_cake_pay": "Cake Pay позволяет вам легко покупать подарочные карты с виртуальными активами, которые можно мгновенно потратить в более чем 150 000 продавцов в Соединенных Штатах.",
542 + "cake_pay_account_note": "Создайте учетную запись, чтобы увидеть доступные карты. Некоторые даже доступны со скидкой!",
543 + "already_have_account": "У вас уже есть аккаунт?",
544 + "create_account": "Создать аккаунт",
545 + "privacy_policy": "Политика конфиденциальности",
546 + "welcome_to_cakepay": "Добро пожаловать в Cake Pay!",
547 + "sign_up": "Зарегистрироваться",
548 + "forgot_password": "Забыли пароль",
549 + "reset_password": "Сбросить пароль",
550 + "gift_cards": "Подарочные карты",
551 + "setup_your_debit_card": "Настройте свою дебетовую карту",
552 + "no_id_required": "Идентификатор не требуется. Пополняйте и тратьте где угодно",
553 + "how_to_use_card": "Как использовать эту карту",
554 + "purchase_gift_card": "Купить подарочную карту",
555 + "verification": "Проверка",
556 + "fill_code": "Пожалуйста, введите код подтверждения, отправленный на вашу электронную почту",
557 + "dont_get_code": "Не получить код?",
558 + "resend_code": "Пожалуйста, отправьте еще раз",
559 + "debit_card": "Дебетовая карта",
560 + "cakepay_prepaid_card": "Предоплаченная дебетовая карта CakePay",
561 + "no_id_needed": "Идентификатор не нужен!",
562 + "frequently_asked_questions": "Часто задаваемые вопросы",
563 + "debit_card_terms": "Хранение и использование номера вашей платежной карты (и учетных данных, соответствующих номеру вашей платежной карты) в этом цифровом кошельке регулируются положениями и условиями применимого соглашения держателя карты с эмитентом платежной карты, действующим с время от времени.",
564 + "please_reference_document": "Пожалуйста, обратитесь к документам ниже для получения дополнительной информации.",
565 + "cardholder_agreement": "Соглашение с держателем карты",
566 + "e_sign_consent": "Согласие электронной подписи",
567 + "agree_and_continue": "Согласиться и продолжить",
568 + "email_address": "Адрес электронной почты",
569 + "agree_to": "Создавая аккаунт, вы соглашаетесь с ",
570 + "and" :"и",
571 + "enter_code": "Введите код",
572 + "congratulations": "Поздравляем!",
573 + "you_now_have_debit_card": "Теперь у вас есть дебетовая карта",
574 + "min_amount": "Минимум: ${value}",
575 + "max_amount": "Макс.: ${value}",
576 + "enter_amount": "Введите сумму",
577 + "billing_address_info": "Если вас попросят указать платежный адрес, укажите адрес доставки",
578 + "order_physical_card": "Заказать физическую карту",
579 + "add_value": "Добавить значение",
580 + "activate": "Активировать",
581 + "get_a": "Получить ",
582 + "digital_and_physical_card": "цифровая и физическая предоплаченная дебетовая карта",
583 + "get_card_note": " которую вы можете пополнить цифровой валютой. Дополнительная информация не требуется!",
584 + "signup_for_card_accept_terms": "Подпишитесь на карту и примите условия.",
585 + "add_fund_to_card": "Добавить предоплаченные средства на карты (до ${value})",
586 + "use_card_info_two": "Средства конвертируются в доллары США, когда они хранятся на предоплаченном счете, а не в цифровых валютах.",
587 + "use_card_info_three": "Используйте цифровую карту онлайн или с помощью бесконтактных способов оплаты.",
588 + "optionly_order_card": "При желании закажите физическую карту.",
589 + "hide_details": "Скрыть детали",
590 + "show_details": "Показать детали",
591 + "upto": "до ${value}",
592 + "discount": "Сэкономьте ${value}%",
593 + "gift_card_amount": "Сумма подарочной карты",
594 + "bill_amount": "Сумма счета",
595 + "you_pay": "Вы платите",
596 + "tip": "Совет:",
597 + "custom": "обычай",
598 + "by_cake_pay": "от Cake Pay",
599 + "expires": "Истекает",
600 + "mm": "ММ",
601 + "yy": "ГГ",
602 + "online": "Онлайн",
603 + "offline": "Не в сети",
604 + "gift_card_number": "Номер подарочной карты",
605 + "pin_number": "ПИН-код",
606 + "total_saving": "Общая экономия",
607 + "last_30_days": "Последние 30 дней",
608 + "avg_savings": "Средняя экономия",
609 + "view_all": "Просмотреть все",
610 + "active_cards": "Активные карты",
611 + "delete_account": "Удалить аккаунт",
612 + "cards": "Карты",
613 + "active": "Активный",
614 + "redeemed": "искуплен",
615 + "gift_card_balance_note": "Здесь будут отображаться подарочные карты с остатком на балансе",
616 + "gift_card_redeemed_note": "Здесь будут отображаться использованные вами подарочные карты",
617 + "logout": "Выйти",
618 + "add_tip": "Добавить подсказку",
619 + "percentageOf": "из ${amount}",
620 + "is_percentage": "есть",
621 + "search_category": "Категория поиска",
622 + "mark_as_redeemed": "Отметить как погашенный",
623 + "more_options": "Дополнительные параметры",
624 + "awaiting_payment_confirmation": "Ожидается подтверждения платежа",
625 + "transaction_sent_notice": "Если экран не отображается через 1 минуту, проверьте обозреватель блоков и свою электронную почту.",
626 + "agree": "согласен",
627 + "in_store": "В магазине",
628 + "generating_gift_card": "Создание подарочной карты",
629 + "payment_was_received": "Ваш платеж получен.",
630 + "proceed_after_one_minute": "Если через 1 минуту экран не отображается, проверьте свою электронную почту.",
631 + "order_id": "Идентификатор заказа",
632 + "gift_card_is_generated": "Подарочная карта сгенерирована",
633 + "open_gift_card": "Открыть подарочную карту",
634 + "contact_support": "Связаться со службой поддержки",
635 + "gift_cards_unavailable": "В настоящее время подарочные карты можно приобрести только через Monero, Bitcoin и Litecoin."
636 }
res/values/strings_uk.arb
+99 -1
@@ -533,5 +533,103 @@
533 "search_currency": "Шукати валюту",
534 "new_template" : "Новий шаблон",
535 "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати",
536 - "wallet_name_exists": "Гаманець з такою назвою вже існує"
536 + "wallet_name_exists": "Гаманець з такою назвою вже існує",
537 + "market_place": "Ринок",
538 + "cake_pay_title": "Подарункові картки Cake Pay",
539 + "cake_pay_subtitle": "Купуйте подарункові картки та використовуйте їх миттєво",
540 + "about_cake_pay": "Cake Pay дозволяє вам легко купувати подарункові картки з віртуальними активами, які можна миттєво витратити в понад 150 000 продавців у Сполучених Штатах.",
541 + "cake_pay_account_note": "Створіть обліковий запис, щоб побачити доступні картки. Деякі навіть доступні зі знижкою!",
542 + "already_have_account": "Вже є обліковий запис?",
543 + "create_account": "Створити обліковий запис",
544 + "privacy_policy": "Політика конфіденційності",
545 + "welcome_to_cakepay": "Ласкаво просимо до Cake Pay!",
546 + "sign_up": "Зареєструватися",
547 + "forgot_password": "Забули пароль",
548 + "reset_password": "Скинути пароль",
549 + "gift_cards": "Подарункові карти",
550 + "setup_your_debit_card": "Налаштуйте свою дебетову картку",
551 + "no_id_required": "Ідентифікатор не потрібен. Поповнюйте та витрачайте будь-де",
552 + "how_to_use_card": "Як використовувати цю картку",
553 + "purchase_gift_card": "Придбати подарункову картку",
554 + "verification": "Перевірка",
555 + "fill_code": "Будь ласка, введіть код підтвердження, надісланий на вашу електронну адресу",
556 + "dont_get_code": "Не отримуєте код?",
557 + "resend_code": "Будь ласка, надішліть його повторно",
558 + "debit_card": "Дебетова картка",
559 + "cakepay_prepaid_card": "Передплачена дебетова картка CakePay",
560 + "no_id_needed": "Ідентифікатор не потрібен!",
561 + "frequently_asked_questions": "Часті запитання",
562 + "debit_card_terms": "Зберігання та використання номера вашої платіжної картки (та облікових даних, які відповідають номеру вашої платіжної картки) у цьому цифровому гаманці регулюються Умовами відповідної угоди власника картки з емітентом платіжної картки, що діє з час від часу.",
563 + "please_reference_document": "Для отримання додаткової інформації зверніться до документів нижче.",
564 + "cardholder_agreement": "Угода власника картки",
565 + "e_sign_consent": "Згода електронного підпису",
566 + "agree_and_continue": "Погодитися та продовжити",
567 + "email_address": "Адреса електронної пошти",
568 + "agree_to": "Створюючи обліковий запис, ви погоджуєтеся з ",
569 + "and": "і",
570 + "enter_code": "Введіть код",
571 + "congratulations": "Вітаємо!",
572 + "you_now_have_debit_card": "Тепер у вас є дебетова картка",
573 + "min_amount": "Мінімум: ${value}",
574 + "max_amount": "Макс: ${value}",
575 + "enter_amount": "Введіть суму",
576 + "billing_address_info": "Якщо буде запропоновано платіжну адресу, вкажіть свою адресу доставки",
577 + "order_physical_card": "Замовити фізичну картку",
578 + "add_value": "Додати значення",
579 + "activate": "Активувати",
580 + "get_a": "Отримати ",
581 + "digital_and_physical_card": " цифрова та фізична передплачена дебетова картка",
582 + "get_card_note": " яку можна перезавантажувати цифровими валютами. Додаткова інформація не потрібна!",
583 + "signup_for_card_accept_terms": "Зареєструйтеся на картку та прийміть умови.",
584 + "add_fund_to_card": "Додайте передплачені кошти на картки (до ${value})",
585 + "use_card_info_two": "Кошти конвертуються в долари США, якщо вони зберігаються на передплаченому рахунку, а не в цифрових валютах.",
586 + "use_card_info_three": "Використовуйте цифрову картку онлайн або за допомогою безконтактних методів оплати.",
587 + "optionally_order_card": "Необов'язково замовте фізичну картку.",
588 + "hide_details": "Приховати деталі",
589 + "show_details": "Показати деталі",
590 + "upto": "до ${value}",
591 + "discount": "Зекономте ${value}%",
592 + "gift_card_amount": "Сума подарункової картки",
593 + "bill_amount": "Сума рахунку",
594 + "you_pay": "Ви платите",
595 + "tip": "Порада:",
596 + "custom": "на замовлення",
597 + "by_cake_pay": "від Cake Pay",
598 + "expires": "Закінчується",
599 + "mm": "MM",
600 + "yy": "YY",
601 + "online": "Онлайн",
602 + "offline": "Офлайн",
603 + "gift_card_number": "Номер подарункової картки",
604 + "pin_number": "PIN-код",
605 + "total_saving": "Загальна економія",
606 + "last_30_days": "Останні 30 днів",
607 + "avg_savings": "Середня економія",
608 + "view_all": "Переглянути все",
609 + "active_cards": "Активні картки",
610 + "delete_account": "Видалити обліковий запис",
611 + "cards": "Картки",
612 + "active": "Активний",
613 + "redeeded": "Викуплено",
614 + "gift_card_balance_note": "Тут з'являться подарункові картки із залишком на балансі",
615 + "gift_card_redeemed_note": "Подарункові картки, які ви активували, відображатимуться тут",
616 + "logout": "Вийти",
617 + "add_tip": "Додати підказку",
618 + "percentageOf": "${amount}",
619 + "is_percentage": "є",
620 + "search_category": "Категорія пошуку",
621 + "mark_as_redeemed": "Позначити як погашене",
622 + "more_options": "Більше параметрів",
623 + "awaiting_payment_confirmation": "Очікується підтвердження платежу",
624 + "transaction_sent_notice": "Якщо екран не відображається через 1 хвилину, перевірте провідник блоків і свою електронну пошту.",
625 + "agree": "Згоден",
626 + "in_store": "У магазині",
627 + "generating_gift_card": "Створення подарункової картки",
628 + "payment_was_received": "Ваш платіж отримано.",
629 + "proceed_after_one_minute": "Якщо екран не продовжується через 1 хвилину, перевірте свою електронну пошту.",
630 + "order_id": "Ідентифікатор замовлення",
631 + "gift_card_is_generated": "Подарункова картка створена",
632 + "open_gift_card": "Відкрити подарункову картку",
633 + "contact_support": "Звернутися до служби підтримки",
634 + "gift_cards_unavailable": "Наразі подарункові картки можна придбати лише через Monero, Bitcoin і Litecoin"
635 }
res/values/strings_zh.arb
+99 -1
@@ -532,5 +532,103 @@
532 "search_currency": "搜索货币",
533 "new_template" : "新模板",
534 "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效",
535 - "wallet_name_exists": "同名的钱包已经存在"
535 + "wallet_name_exists": "同名的钱包已经存在",
536 + "market_place": "市场",
537 + "cake_pay_title": "Cake Pay 礼品卡",
538 + "cake_pay_subtitle": "购买礼品卡并立即兑换",
539 + "about_cake_pay": "Cake Pay 让您可以轻松购买带有虚拟资产的礼品卡,可立即在美国超过 150,000 家商家消费。",
540 + "cake_pay_account_note": "注册一个账户来查看可用的卡片。有些甚至可以打折!",
541 + "already_have_account": "已经有账号了?",
542 + "create_account": "创建账户",
543 + "privacy_policy": "隐私政策",
544 + "welcome_to_cakepay": "欢迎来到 Cake Pay!",
545 + "sign_up": "注册",
546 + "forgot_password": "忘记密码",
547 + "reset_password": "重置密码",
548 + "gift_cards": "礼品卡",
549 + "setup_your_debit_card": "设置你的借记卡",
550 + "no_id_required": "不需要身份证。充值并在任何地方消费",
551 + "how_to_use_card": "如何使用这张卡",
552 + "purchase_gift_card": "购买礼品卡",
553 + "verification": "验证",
554 + "fill_code": "请填写提供给您邮箱的验证码",
555 + "dont_get_code": "没有获取代码?",
556 + "resend_code": "请重新发送",
557 + "debit_card": "借记卡",
558 + "cakepay_prepaid_card": "CakePay 预付借记卡",
559 + "no_id_needed": "不需要 ID!",
560 + "frequently_asked_questions": "常见问题",
561 + "debit_card_terms": "您的支付卡号(以及与您的支付卡号对应的凭证)在此数字钱包中的存储和使用受适用的持卡人与支付卡发卡机构签订的协议的条款和条件的约束,自时不时。",
562 + "please_reference_document": "请参考以下文档以获取更多信息。",
563 + "cardholder_agreement": "持卡人协议",
564 + "e_sign_consent": "电子签名同意",
565 + "agree_and_continue": "同意并继续",
566 + "email_address": "电子邮件地址",
567 + "agree_to": "创建账户即表示您同意 ",
568 + "and": "和",
569 + "enter_code": "输入代码",
570 + "congratulations": "恭喜!",
571 + "you_now_have_debit_card": "你现在有一张借记卡",
572 + "min_amount" : "最小值: ${value}",
573 + "max_amount" : "最大值: ${value}",
574 + "enter_amount": "输入金额",
575 + "billing_address_info": "如果要求提供帐单地址,请提供您的送货地址",
576 + "order_physical_card": "订购实体卡",
577 + "add_value": "增加价值",
578 + "activate": "激活",
579 + "get_a": "得到一个",
580 + "digital_and_physical_card": "数字和物理预付借记卡",
581 + "get_card_note": "你可以用数字货币重新加载。不需要额外的信息!",
582 + "signup_for_card_accept_terms": "注册卡并接受条款。",
583 + "add_fund_to_card": "向卡中添加预付资金(最多 ${value})",
584 + "use_card_info_two": "预付账户中的资金转换为美元,不是数字货币。",
585 + "use_card_info_three": "在线使用电子卡或使用非接触式支付方式。",
586 + "optionally_order_card": "可选择订购实体卡。",
587 + "hide_details": "隐藏细节",
588 + "show_details": "显示详细信息",
589 + "upto": "最高 ${value}",
590 + "discount": "节省 ${value}%",
591 + "gift_card_amount": "礼品卡金额",
592 + "bill_amount": "账单金额",
593 + "you_pay": "你付钱",
594 + "tip": "提示:",
595 + "custom": "自定义",
596 + "by_cake_pay": "通过 Cake Pay",
597 + "expires": "过期",
598 + "mm": "毫米",
599 + "yy": "YY",
600 + "online": "在线",
601 + "offline": "离线",
602 + "gift_card_number": "礼品卡号",
603 + "pin_number": "PIN 码",
604 + "total_saving": "总储蓄",
605 + "last_30_days": "过去 30 天",
606 + "avg_savings": "平均储蓄",
607 + "view_all": "查看全部",
608 + "active_cards": "活动卡",
609 + "delete_account": "删除账户",
610 + "cards": "卡片",
611 + "active": "活跃",
612 + "redeemed": "赎回",
613 + "gift_card_balance_note": "有余额的礼品卡会出现在这里",
614 + "gift_card_redeemed_note": "您兑换的礼品卡会出现在这里",
615 + "logout": "注销",
616 + "add_tip": "添加提示",
617 + "percentageOf": "${amount}",
618 + "is_percentage": "是",
619 + "search_category": "搜索类别",
620 + "mark_as_redeemed": "标记为已赎回",
621 + "more_options": "更多选项",
622 + "awaiting_payment_confirmation": "等待付款确认",
623 + "transaction_sent_notice": "如果屏幕在 1 分钟后没有继续,请检查区块浏览器和您的电子邮件。",
624 + "agree": "同意",
625 + "in_store": "店内",
626 + "generating_gift_card": "生成礼品卡",
627 + "payment_was_received": "您的付款已收到。",
628 + "proceed_after_one_minute": "如果屏幕在 1 分钟后没有继续,请检查您的电子邮件。",
629 + "order_id": "订单编号",
630 + "gift_card_is_generated": "礼品卡生成",
631 + "open_gift_card": "打开礼品卡",
632 + "contact_support": "联系支持",
633 + "gift_cards_unavailable": "目前只能通过门罗币、比特币和莱特币购买礼品卡"
634 }
scripts/android/app_env.sh
+2 -2
@@ -20,8 +20,8 @@ MONERO_COM_BUNDLE_ID="com.monero.app"
20 MONERO_COM_PACKAGE="com.monero.app"
21
22 CAKEWALLET_NAME="Cake Wallet"
23 -CAKEWALLET_VERSION="4.4.3"
24 -CAKEWALLET_BUILD_NUMBER=105
23 +CAKEWALLET_VERSION="4.4.4"
24 +CAKEWALLET_BUILD_NUMBER=108
25 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
26 CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
27
scripts/android/build_monero.sh
+1 -1
@@ -1,7 +1,7 @@
1 #!/bin/sh
2
3 . ./config.sh
4 -MONERO_BRANCH=v0.17.3.0-android
4 +MONERO_BRANCH=release-v0.17.3.2-android
5 MONERO_SRC_DIR=${WORKDIR}/monero
6
7 git clone https://github.com/cake-tech/monero.git ${MONERO_SRC_DIR} --branch ${MONERO_BRANCH}
scripts/ios/app_env.sh
+2 -2
@@ -18,8 +18,8 @@ MONERO_COM_BUILD_NUMBER=17
18 MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
19
20 CAKEWALLET_NAME="Cake Wallet"
21 -CAKEWALLET_VERSION="4.4.3"
22 -CAKEWALLET_BUILD_NUMBER=104
21 +CAKEWALLET_VERSION="4.4.4"
22 +CAKEWALLET_BUILD_NUMBER=109
23 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
24
25 HAVEN_NAME="Haven"
scripts/ios/build_monero.sh
+2 -2
@@ -2,9 +2,9 @@
2
3 . ./config.sh
4
5 -MONERO_URL="https://github.com/monero-project/monero.git"
5 +MONERO_URL="https://github.com/cake-tech/monero.git"
6 MONERO_DIR_PATH="${EXTERNAL_IOS_SOURCE_DIR}/monero"
7 -MONERO_VERSION=tags/v0.17.3.0
7 +MONERO_VERSION=release-v0.17.3.2
8 BUILD_TYPE=release
9 PREFIX=${EXTERNAL_IOS_DIR}
10 DEST_LIB_DIR=${EXTERNAL_IOS_LIB_DIR}/monero
tool/configure.dart
+5 -1
@@ -77,7 +77,8 @@ abstract class Bitcoin {
77 TransactionPriority deserializeBitcoinTransactionPriority(int raw);
78 int getFeeRate(Object wallet, TransactionPriority priority);
79 Future<void> generateNewAddress(Object wallet);
80 - Object createBitcoinTransactionCredentials(List<Output> outputs, TransactionPriority priority);
80 + Object createBitcoinTransactionCredentials(List<Output> outputs, {TransactionPriority priority, int feeRate});
81 + Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs, {TransactionPriority priority, int feeRate});
82
83 List<String> getAddresses(Object wallet);
84 String getAddress(Object wallet);
@@ -146,6 +147,7 @@ import 'package:cw_monero/mnemonics/spanish.dart';
147 import 'package:cw_monero/mnemonics/portuguese.dart';
148 import 'package:cw_monero/mnemonics/french.dart';
149 import 'package:cw_monero/mnemonics/italian.dart';
150 +import 'package:cw_monero/pending_monero_transaction.dart';
151 """;
152 const moneroCwPart = "part 'cw_monero.dart';";
153 const moneroContent = """
@@ -229,6 +231,7 @@ abstract class Monero {
231 WalletCredentials createMoneroNewWalletCredentials({String name, String password, String language});
232 Map<String, String> getKeys(Object wallet);
233 Object createMoneroTransactionCreationCredentials({List<Output> outputs, TransactionPriority priority});
234 + Object createMoneroTransactionCreationCredentialsRaw({List<OutputInfo> outputs, TransactionPriority priority});
235 String formatterMoneroAmountToString({int amount});
236 double formatterMoneroAmountToDouble({int amount});
237 int formatterMoneroParseAmount({String amount});
@@ -237,6 +240,7 @@ abstract class Monero {
240 void onStartup();
241 int getTransactionInfoAccountId(TransactionInfo tx);
242 WalletService createMoneroWalletService(Box<WalletInfo> walletInfoSource);
243 + Map<String, String> pendingTransactionInfo(Object transaction);
244 }
245
246 abstract class MoneroSubaddressList {