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>
BIN
assets/images/airplane.png
Normal file
After Width: | Height: | Size: 960 B |
BIN
assets/images/badge_discount.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/card.png
Normal file
After Width: | Height: | Size: 556 B |
BIN
assets/images/category.png
Normal file
After Width: | Height: | Size: 454 B |
BIN
assets/images/copy.png
Normal file
After Width: | Height: | Size: 556 B |
BIN
assets/images/delivery.png
Normal file
After Width: | Height: | Size: 728 B |
BIN
assets/images/filter.png
Normal file
After Width: | Height: | Size: 504 B |
BIN
assets/images/food.png
Normal file
After Width: | Height: | Size: 710 B |
BIN
assets/images/gaming.png
Normal file
After Width: | Height: | Size: 705 B |
BIN
assets/images/global.png
Normal file
After Width: | Height: | Size: 997 B |
BIN
assets/images/mastercard.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/images/mini_search_icon.png
Normal file
After Width: | Height: | Size: 536 B |
BIN
assets/images/profile.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
assets/images/red_badge_discount.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/images/tshirt.png
Normal file
After Width: | Height: | Size: 583 B |
BIN
assets/images/wifi.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
|
@ -2,8 +2,9 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
|||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class BitcoinTransactionCredentials {
|
||||
BitcoinTransactionCredentials(this.outputs, this.priority);
|
||||
BitcoinTransactionCredentials(this.outputs, {this.priority, this.feeRate});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
BitcoinTransactionPriority priority;
|
||||
final BitcoinTransactionPriority priority;
|
||||
final int feeRate;
|
||||
}
|
||||
|
|
|
@ -208,8 +208,14 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
}
|
||||
|
||||
amount = credentialsAmount;
|
||||
fee = calculateEstimatedFee(transactionCredentials.priority, amount,
|
||||
outputsCount: outputs.length + 1);
|
||||
|
||||
if (transactionCredentials.feeRate != null) {
|
||||
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate, amount,
|
||||
outputsCount: outputs.length + 1);
|
||||
} else {
|
||||
fee = calculateEstimatedFee(transactionCredentials.priority, amount,
|
||||
outputsCount: outputs.length + 1);
|
||||
}
|
||||
} else {
|
||||
final output = outputs.first;
|
||||
credentialsAmount = !output.sendAll
|
||||
|
@ -223,9 +229,14 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
amount = output.sendAll || allAmount - credentialsAmount < minAmount
|
||||
? allAmount
|
||||
: credentialsAmount;
|
||||
fee = output.sendAll || amount == allAmount
|
||||
? allAmountFee
|
||||
: calculateEstimatedFee(transactionCredentials.priority, amount);
|
||||
|
||||
if (output.sendAll || amount == allAmount) {
|
||||
fee = allAmountFee;
|
||||
} else if (transactionCredentials.feeRate != null) {
|
||||
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate, amount);
|
||||
} else {
|
||||
fee = calculateEstimatedFee(transactionCredentials.priority, amount);
|
||||
}
|
||||
}
|
||||
|
||||
if (fee == 0) {
|
||||
|
@ -296,7 +307,14 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
|
||||
final estimatedSize =
|
||||
estimatedTransactionSize(inputs.length, outputs.length + 1);
|
||||
final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
|
||||
var feeAmount = 0;
|
||||
|
||||
if (transactionCredentials.feeRate != null) {
|
||||
feeAmount = transactionCredentials.feeRate * estimatedSize;
|
||||
} else {
|
||||
feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
|
||||
}
|
||||
|
||||
final changeValue = totalInputAmount - amount - feeAmount;
|
||||
|
||||
if (changeValue > minAmount) {
|
||||
|
@ -346,45 +364,57 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
int outputsCount) =>
|
||||
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
|
||||
|
||||
int feeAmountWithFeeRate(int feeRate, int inputsCount,
|
||||
int outputsCount) =>
|
||||
feeRate * estimatedTransactionSize(inputsCount, outputsCount);
|
||||
|
||||
@override
|
||||
int calculateEstimatedFee(TransactionPriority priority, int amount,
|
||||
{int outputsCount}) {
|
||||
if (priority is BitcoinTransactionPriority) {
|
||||
int inputsCount = 0;
|
||||
|
||||
if (amount != null) {
|
||||
int totalValue = 0;
|
||||
|
||||
for (final input in unspentCoins) {
|
||||
if (totalValue >= amount) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (input.isSending) {
|
||||
totalValue += input.value;
|
||||
inputsCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalValue < amount) return 0;
|
||||
} else {
|
||||
for (final input in unspentCoins) {
|
||||
if (input.isSending) {
|
||||
inputsCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If send all, then we have no change value
|
||||
final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
|
||||
|
||||
return feeAmountForPriority(
|
||||
priority, inputsCount, _outputsCount);
|
||||
return calculateEstimatedFeeWithFeeRate(
|
||||
feeRate(priority),
|
||||
amount,
|
||||
outputsCount: outputsCount);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int calculateEstimatedFeeWithFeeRate(int feeRate, int amount,
|
||||
{int outputsCount}) {
|
||||
int inputsCount = 0;
|
||||
|
||||
if (amount != null) {
|
||||
int totalValue = 0;
|
||||
|
||||
for (final input in unspentCoins) {
|
||||
if (totalValue >= amount) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (input.isSending) {
|
||||
totalValue += input.value;
|
||||
inputsCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalValue < amount) return 0;
|
||||
} else {
|
||||
for (final input in unspentCoins) {
|
||||
if (input.isSending) {
|
||||
inputsCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If send all, then we have no change value
|
||||
final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
|
||||
|
||||
return feeAmountWithFeeRate(
|
||||
feeRate, inputsCount, _outputsCount);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
final path = await makePath();
|
||||
|
@ -525,10 +555,6 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
final addressHashes = <String, BitcoinAddressRecord>{};
|
||||
final normalizedHistories = <Map<String, dynamic>>[];
|
||||
walletAddresses.addresses.forEach((addressRecord) {
|
||||
if (addressRecord.isHidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
final sh = scriptHash(addressRecord.address, networkType: networkType);
|
||||
addressHashes[sh] = addressRecord;
|
||||
});
|
||||
|
|
|
@ -24,6 +24,9 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
@override
|
||||
String get id => _tx.getId();
|
||||
|
||||
@override
|
||||
String get hex => _tx.toHex();
|
||||
|
||||
@override
|
||||
String get amountFormatted => bitcoinAmountToString(amount: amount);
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ mixin PendingTransaction {
|
|||
String get id;
|
||||
String get amountFormatted;
|
||||
String get feeFormatted;
|
||||
String get hex;
|
||||
|
||||
Future<void> commit();
|
||||
}
|
|
@ -22,6 +22,9 @@ class PendingHavenTransaction with PendingTransaction {
|
|||
@override
|
||||
String get id => pendingTransactionDescription.hash;
|
||||
|
||||
@override
|
||||
String get hex => '';
|
||||
|
||||
@override
|
||||
String get amountFormatted => AmountConverter.amountIntToString(
|
||||
cryptoCurrency, pendingTransactionDescription.amount);
|
||||
|
|
|
@ -169,13 +169,6 @@ packages:
|
|||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
cw_monero:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../cw_monero"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -166,6 +166,8 @@ extern "C"
|
|||
uint64_t amount;
|
||||
uint64_t fee;
|
||||
char *hash;
|
||||
char *hex;
|
||||
char *txKey;
|
||||
Monero::PendingTransaction *transaction;
|
||||
|
||||
PendingTransactionRaw(Monero::PendingTransaction *_transaction)
|
||||
|
@ -174,6 +176,8 @@ extern "C"
|
|||
amount = _transaction->amount();
|
||||
fee = _transaction->fee();
|
||||
hash = strdup(_transaction->txid()[0].c_str());
|
||||
hex = strdup(_transaction->hex()[0].c_str());
|
||||
txKey = strdup(_transaction->txKey()[0].c_str());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -228,8 +232,6 @@ extern "C"
|
|||
|
||||
bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error)
|
||||
{
|
||||
Monero::WalletManagerFactory::setLogLevel(4);
|
||||
|
||||
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
|
||||
Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager();
|
||||
Monero::Wallet *wallet = walletManager->createWallet(path, password, language, _networkType);
|
||||
|
|
|
@ -10,14 +10,30 @@ class PendingTransactionRaw extends Struct {
|
|||
|
||||
Pointer<Utf8> hash;
|
||||
|
||||
Pointer<Utf8> hex;
|
||||
|
||||
Pointer<Utf8> txKey;
|
||||
|
||||
String getHash() => Utf8.fromUtf8(hash);
|
||||
|
||||
String getHex() => Utf8.fromUtf8(hex);
|
||||
|
||||
String getKey() => Utf8.fromUtf8(txKey);
|
||||
}
|
||||
|
||||
class PendingTransactionDescription {
|
||||
PendingTransactionDescription({this.amount, this.fee, this.hash, this.pointerAddress});
|
||||
PendingTransactionDescription({
|
||||
this.amount,
|
||||
this.fee,
|
||||
this.hash,
|
||||
this.hex,
|
||||
this.txKey,
|
||||
this.pointerAddress});
|
||||
|
||||
final int amount;
|
||||
final int fee;
|
||||
final String hash;
|
||||
final String hex;
|
||||
final String txKey;
|
||||
final int pointerAddress;
|
||||
}
|
|
@ -104,6 +104,8 @@ PendingTransactionDescription createTransactionSync(
|
|||
amount: pendingTransactionRawPointer.ref.amount,
|
||||
fee: pendingTransactionRawPointer.ref.fee,
|
||||
hash: pendingTransactionRawPointer.ref.getHash(),
|
||||
hex: pendingTransactionRawPointer.ref.getHex(),
|
||||
txKey: pendingTransactionRawPointer.ref.getKey(),
|
||||
pointerAddress: pendingTransactionRawPointer.address);
|
||||
}
|
||||
|
||||
|
@ -157,6 +159,8 @@ PendingTransactionDescription createTransactionMultDestSync(
|
|||
amount: pendingTransactionRawPointer.ref.amount,
|
||||
fee: pendingTransactionRawPointer.ref.fee,
|
||||
hash: pendingTransactionRawPointer.ref.getHash(),
|
||||
hex: pendingTransactionRawPointer.ref.getHex(),
|
||||
txKey: pendingTransactionRawPointer.ref.getKey(),
|
||||
pointerAddress: pendingTransactionRawPointer.address);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,11 @@ class PendingMoneroTransaction with PendingTransaction {
|
|||
@override
|
||||
String get id => pendingTransactionDescription.hash;
|
||||
|
||||
@override
|
||||
String get hex => pendingTransactionDescription.hex;
|
||||
|
||||
String get txKey => pendingTransactionDescription.txKey;
|
||||
|
||||
@override
|
||||
String get amountFormatted => AmountConverter.amountIntToString(
|
||||
CryptoCurrency.xmr, pendingTransactionDescription.amount);
|
||||
|
|
|
@ -57,6 +57,8 @@ PODS:
|
|||
- Flutter
|
||||
- cw_shared_external/Sodium (0.0.1):
|
||||
- Flutter
|
||||
- device_display_brightness (0.0.1):
|
||||
- Flutter
|
||||
- devicelocale (0.0.1):
|
||||
- Flutter
|
||||
- DKImagePickerController/Core (4.3.2):
|
||||
|
@ -134,6 +136,7 @@ DEPENDENCIES:
|
|||
- cw_haven (from `.symlinks/plugins/cw_haven/ios`)
|
||||
- cw_monero (from `.symlinks/plugins/cw_monero/ios`)
|
||||
- cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
|
||||
- device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
|
||||
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
|
||||
- esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
|
@ -174,6 +177,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/cw_monero/ios"
|
||||
cw_shared_external:
|
||||
:path: ".symlinks/plugins/cw_shared_external/ios"
|
||||
device_display_brightness:
|
||||
:path: ".symlinks/plugins/device_display_brightness/ios"
|
||||
devicelocale:
|
||||
:path: ".symlinks/plugins/devicelocale/ios"
|
||||
esys_flutter_share:
|
||||
|
@ -211,6 +216,7 @@ SPEC CHECKSUMS:
|
|||
cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
|
||||
cw_monero: 88c5e7aa596c6848330750f5f8bcf05fb9c66375
|
||||
cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
|
||||
device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
|
||||
devicelocale: b22617f40038496deffba44747101255cee005b0
|
||||
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
|
|
5
lib/anypay/any_pay_chain.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
class AnyPayChain {
|
||||
static const xmr = 'XMR';
|
||||
static const btc = 'BTC';
|
||||
static const ltc = 'LTC';
|
||||
}
|
64
lib/anypay/any_pay_payment.dart
Normal file
|
@ -0,0 +1,64 @@
|
|||
import 'package:cake_wallet/anypay/any_pay_chain.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cw_core/monero_amount_format.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment_instruction.dart';
|
||||
|
||||
class AnyPayPayment {
|
||||
AnyPayPayment({
|
||||
@required this.time,
|
||||
@required this.expires,
|
||||
@required this.memo,
|
||||
@required this.paymentUrl,
|
||||
@required this.paymentId,
|
||||
@required this.chain,
|
||||
@required this.network,
|
||||
@required this.instructions});
|
||||
|
||||
factory AnyPayPayment.fromMap(Map<String, dynamic> obj) {
|
||||
final instructions = (obj['instructions'] as List<dynamic>)
|
||||
.map((dynamic instruction) => AnyPayPaymentInstruction.fromMap(instruction as Map<String, dynamic>))
|
||||
.toList();
|
||||
return AnyPayPayment(
|
||||
time: DateTime.parse(obj['time'] as String),
|
||||
expires: DateTime.parse(obj['expires'] as String),
|
||||
memo: obj['memo'] as String,
|
||||
paymentUrl: obj['paymentUrl'] as String,
|
||||
paymentId: obj['paymentId'] as String,
|
||||
chain: obj['chain'] as String,
|
||||
network: obj['network'] as String,
|
||||
instructions: instructions);
|
||||
}
|
||||
|
||||
final DateTime time;
|
||||
final DateTime expires;
|
||||
final String memo;
|
||||
final String paymentUrl;
|
||||
final String paymentId;
|
||||
final String chain;
|
||||
final String network;
|
||||
final List<AnyPayPaymentInstruction> instructions;
|
||||
|
||||
String get totalAmount {
|
||||
final total = instructions
|
||||
.fold<int>(0, (int acc, instruction) => acc + instruction.outputs
|
||||
.fold<int>(0, (int outAcc, out) => outAcc + out.amount));
|
||||
switch (chain) {
|
||||
case AnyPayChain.xmr:
|
||||
return moneroAmountToString(amount: total);
|
||||
case AnyPayChain.btc:
|
||||
return bitcoinAmountToString(amount: total);
|
||||
case AnyPayChain.ltc:
|
||||
return bitcoinAmountToString(amount: total);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> get outAddresses {
|
||||
return instructions
|
||||
.map((instuction) => instuction.outputs.map((out) => out.address))
|
||||
.expand((e) => e)
|
||||
.toList();
|
||||
}
|
||||
}
|
17
lib/anypay/any_pay_payment_committed_info.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_trasnaction.dart';
|
||||
|
||||
class AnyPayPaymentCommittedInfo {
|
||||
const AnyPayPaymentCommittedInfo({
|
||||
@required this.uri,
|
||||
@required this.currency,
|
||||
@required this.chain,
|
||||
@required this.transactions,
|
||||
@required this.memo});
|
||||
|
||||
final String uri;
|
||||
final String currency;
|
||||
final String chain;
|
||||
final List<AnyPayTransaction> transactions;
|
||||
final String memo;
|
||||
}
|
32
lib/anypay/any_pay_payment_instruction.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment_instruction_output.dart';
|
||||
|
||||
class AnyPayPaymentInstruction {
|
||||
AnyPayPaymentInstruction({
|
||||
@required this.type,
|
||||
@required this.requiredFeeRate,
|
||||
@required this.txKey,
|
||||
@required this.txHash,
|
||||
@required this.outputs});
|
||||
|
||||
factory AnyPayPaymentInstruction.fromMap(Map<String, dynamic> obj) {
|
||||
final outputs = (obj['outputs'] as List<dynamic>)
|
||||
.map((dynamic out) =>
|
||||
AnyPayPaymentInstructionOutput.fromMap(out as Map<String, dynamic>))
|
||||
.toList();
|
||||
return AnyPayPaymentInstruction(
|
||||
type: obj['type'] as String,
|
||||
requiredFeeRate: obj['requiredFeeRate'] as int,
|
||||
txKey: obj['tx_key'] as bool,
|
||||
txHash: obj['tx_hash'] as bool,
|
||||
outputs: outputs);
|
||||
}
|
||||
|
||||
static const transactionType = 'transaction';
|
||||
|
||||
final String type;
|
||||
final int requiredFeeRate;
|
||||
final bool txKey;
|
||||
final bool txHash;
|
||||
final List<AnyPayPaymentInstructionOutput> outputs;
|
||||
}
|
10
lib/anypay/any_pay_payment_instruction_output.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
class AnyPayPaymentInstructionOutput {
|
||||
const AnyPayPaymentInstructionOutput(this.address, this.amount);
|
||||
|
||||
factory AnyPayPaymentInstructionOutput.fromMap(Map<String, dynamic> obj) {
|
||||
return AnyPayPaymentInstructionOutput(obj['address'] as String, obj['amount'] as int);
|
||||
}
|
||||
|
||||
final String address;
|
||||
final int amount;
|
||||
}
|
9
lib/anypay/any_pay_trasnaction.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class AnyPayTransaction {
|
||||
const AnyPayTransaction(this.tx, {@required this.id, @required this.key});
|
||||
|
||||
final String tx;
|
||||
final String id;
|
||||
final String key;
|
||||
}
|
92
lib/anypay/anypay_api.dart
Normal file
|
@ -0,0 +1,92 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_trasnaction.dart';
|
||||
|
||||
class AnyPayApi {
|
||||
static const contentTypePaymentRequest = 'application/payment-request';
|
||||
static const contentTypePayment = 'application/payment';
|
||||
static const xPayproVersion = '2';
|
||||
|
||||
static String chainByScheme(String scheme) {
|
||||
switch (scheme.toLowerCase()) {
|
||||
case 'monero':
|
||||
return CryptoCurrency.xmr.title;
|
||||
case 'bitcoin':
|
||||
return CryptoCurrency.btc.title;
|
||||
case 'litecoin':
|
||||
return CryptoCurrency.ltc.title;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
static CryptoCurrency currencyByScheme(String scheme) {
|
||||
switch (scheme.toLowerCase()) {
|
||||
case 'monero':
|
||||
return CryptoCurrency.xmr;
|
||||
case 'bitcoin':
|
||||
return CryptoCurrency.btc;
|
||||
case 'litecoin':
|
||||
return CryptoCurrency.ltc;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<AnyPayPayment> paymentRequest(String uri) async {
|
||||
final fragments = uri.split(':?r=');
|
||||
final scheme = fragments.first;
|
||||
final url = fragments[1];
|
||||
final headers = <String, String>{
|
||||
'Content-Type': contentTypePaymentRequest,
|
||||
'X-Paypro-Version': xPayproVersion,
|
||||
'Accept': '*/*',};
|
||||
final body = <String, dynamic>{
|
||||
'chain': chainByScheme(scheme),
|
||||
'currency': currencyByScheme(scheme).title};
|
||||
final response = await post(url, headers: headers, body: utf8.encode(json.encode(body)));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final decodedBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
return AnyPayPayment.fromMap(decodedBody);
|
||||
}
|
||||
|
||||
Future<AnyPayPaymentCommittedInfo> payment(
|
||||
String uri,
|
||||
{@required String chain,
|
||||
@required String currency,
|
||||
@required List<AnyPayTransaction> transactions}) async {
|
||||
final headers = <String, String>{
|
||||
'Content-Type': contentTypePayment,
|
||||
'X-Paypro-Version': xPayproVersion,
|
||||
'Accept': '*/*',};
|
||||
final body = <String, dynamic>{
|
||||
'chain': chain,
|
||||
'currency': currency,
|
||||
'transactions': transactions.map((tx) => {'tx': tx.tx, 'tx_hash': tx.id, 'tx_key': tx.key}).toList()};
|
||||
final response = await post(uri, headers: headers, body: utf8.encode(json.encode(body)));
|
||||
if (response.statusCode == 400) {
|
||||
final decodedBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
throw Exception(decodedBody['message'] as String);
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Unexpected response');
|
||||
}
|
||||
|
||||
final decodedBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
return AnyPayPaymentCommittedInfo(
|
||||
uri: uri,
|
||||
currency: currency,
|
||||
chain: chain,
|
||||
transactions: transactions,
|
||||
memo: decodedBody['memo'] as String);
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ class CWBitcoin extends Bitcoin {
|
|||
}
|
||||
|
||||
@override
|
||||
Object createBitcoinTransactionCredentials(List<Output> outputs, TransactionPriority priority)
|
||||
Object createBitcoinTransactionCredentials(List<Output> outputs, {TransactionPriority priority, int feeRate})
|
||||
=> BitcoinTransactionCredentials(
|
||||
outputs.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
|
@ -67,7 +67,15 @@ class CWBitcoin extends Bitcoin {
|
|||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||
.toList(),
|
||||
priority as BitcoinTransactionPriority);
|
||||
priority: priority != null ? priority as BitcoinTransactionPriority : null,
|
||||
feeRate: feeRate);
|
||||
|
||||
@override
|
||||
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs, {TransactionPriority priority, int feeRate})
|
||||
=> BitcoinTransactionCredentials(
|
||||
outputs,
|
||||
priority: priority != null ? priority as BitcoinTransactionPriority : null,
|
||||
feeRate: feeRate);
|
||||
|
||||
@override
|
||||
List<String> getAddresses(Object wallet) {
|
||||
|
|
11
lib/core/email_validator.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class EmailValidator extends TextValidator {
|
||||
EmailValidator()
|
||||
: super(
|
||||
errorMessage: 'Invalid email address',
|
||||
pattern:
|
||||
'^[^@]+@[^@]+\.[^@]+',
|
||||
);
|
||||
}
|
130
lib/di.dart
|
@ -1,10 +1,27 @@
|
|||
import 'package:cake_wallet/core/yat_service.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/entities/wake_lock.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_anypay.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_category.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_api.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/haven/haven.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/ionia.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/core/backup_service.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
|
@ -100,6 +117,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
|||
import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
@ -123,6 +141,12 @@ import 'package:cake_wallet/entities/template.dart';
|
|||
import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart';
|
||||
import 'package:cake_wallet/anypay/anypay_api.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
|
||||
import 'package:cake_wallet/core/wallet_loading_service.dart';
|
||||
|
||||
|
@ -261,7 +285,6 @@ Future setup(
|
|||
fiatConvertationStore: getIt.get<FiatConversionStore>()));
|
||||
|
||||
getIt.registerFactory(() => DashboardViewModel(
|
||||
|
||||
balanceViewModel: getIt.get<BalanceViewModel>(),
|
||||
appStore: getIt.get<AppStore>(),
|
||||
tradesStore: getIt.get<TradesStore>(),
|
||||
|
@ -560,10 +583,6 @@ Future setup(
|
|||
|
||||
getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => EditBackupPasswordViewModel(
|
||||
getIt.get<FlutterSecureStorage>(), getIt.get<SecretStore>())
|
||||
..init());
|
||||
|
||||
getIt.registerFactory(
|
||||
() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
|
||||
|
||||
|
@ -596,10 +615,7 @@ Future setup(
|
|||
final url = args.first as String;
|
||||
final buyViewModel = args[1] as BuyViewModel;
|
||||
|
||||
return BuyWebViewPage(
|
||||
buyViewModel: buyViewModel,
|
||||
ordersStore: getIt.get<OrdersStore>(),
|
||||
url: url);
|
||||
return BuyWebViewPage(buyViewModel: buyViewModel, ordersStore: getIt.get<OrdersStore>(), url: url);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<OrderDetailsViewModel, Order, void>((order, _) {
|
||||
|
@ -649,6 +665,102 @@ Future setup(
|
|||
|
||||
getIt.registerFactoryParam<FullscreenQRPage, String, bool>(
|
||||
(String qrData, bool isLight) => FullscreenQRPage(qrData: qrData, isLight: isLight,));
|
||||
|
||||
getIt.registerFactory(() => IoniaApi());
|
||||
|
||||
getIt.registerFactory(() => AnyPayApi());
|
||||
|
||||
getIt.registerFactory<IoniaService>(
|
||||
() => IoniaService(getIt.get<FlutterSecureStorage>(), getIt.get<IoniaApi>()));
|
||||
|
||||
getIt.registerFactory<IoniaAnyPay>(
|
||||
() => IoniaAnyPay(
|
||||
getIt.get<IoniaService>(),
|
||||
getIt.get<AnyPayApi>(),
|
||||
getIt.get<AppStore>().wallet));
|
||||
|
||||
getIt.registerFactory<IoniaFilterViewModel>(() => IoniaFilterViewModel());
|
||||
|
||||
getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));
|
||||
|
||||
getIt.registerFactoryParam<IoniaMerchPurchaseViewModel, double, IoniaMerchant>((double amount, merchant) {
|
||||
return IoniaMerchPurchaseViewModel(
|
||||
ioniaAnyPayService: getIt.get<IoniaAnyPay>(),
|
||||
amount: amount,
|
||||
ioniaMerchant: merchant,
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaBuyCardViewModel, IoniaMerchant, void>((IoniaMerchant merchant, _) {
|
||||
return IoniaBuyCardViewModel(ioniaMerchant: merchant);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => IoniaAccountViewModel(ioniaService: getIt.get<IoniaService>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaCreateAccountPage(getIt.get<IoniaAuthViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaLoginPage(getIt.get<IoniaAuthViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<IoniaVerifyIoniaOtp, List, void>((List args, _) {
|
||||
final email = args.first as String;
|
||||
final isSignIn = args[1] as bool;
|
||||
|
||||
return IoniaVerifyIoniaOtp(getIt.get<IoniaAuthViewModel>(), email, isSignIn);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => IoniaWelcomePage(getIt.get<IoniaGiftCardsListViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<IoniaBuyGiftCardPage, List, void>((List args, _) {
|
||||
final merchant = args.first as IoniaMerchant;
|
||||
|
||||
return IoniaBuyGiftCardPage(getIt.get<IoniaBuyCardViewModel>(param1: merchant));
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaBuyGiftCardDetailPage, List, void>((List args, _) {
|
||||
final amount = args.first as double;
|
||||
final merchant = args.last as IoniaMerchant;
|
||||
return IoniaBuyGiftCardDetailPage(getIt.get<IoniaMerchPurchaseViewModel>(param1: amount, param2: merchant));
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaGiftCardDetailsViewModel, IoniaGiftCard, void>((IoniaGiftCard giftCard, _) {
|
||||
return IoniaGiftCardDetailsViewModel(
|
||||
ioniaService: getIt.get<IoniaService>(),
|
||||
giftCard: giftCard);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaGiftCardDetailPage, IoniaGiftCard, void>((IoniaGiftCard giftCard, _) {
|
||||
return IoniaGiftCardDetailPage(getIt.get<IoniaGiftCardDetailsViewModel>(param1: giftCard));
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaCustomTipPage, List, void>((List args, _) {
|
||||
final amount = args.first as String;
|
||||
final merchant = args.last as IoniaMerchant;
|
||||
|
||||
return IoniaCustomTipPage(getIt.get<IoniaMerchPurchaseViewModel>(param1: amount, param2: merchant));
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => IoniaManageCardsPage(getIt.get<IoniaGiftCardsListViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaDebitCardPage(getIt.get<IoniaGiftCardsListViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaActivateDebitCardPage(getIt.get<IoniaGiftCardsListViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaAccountPage(getIt.get<IoniaAccountViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get<IoniaAccountViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<IoniaPaymentStatusViewModel, IoniaAnyPayPaymentInfo, AnyPayPaymentCommittedInfo>(
|
||||
(IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo)
|
||||
=> IoniaPaymentStatusViewModel(
|
||||
getIt.get<IoniaService>(),
|
||||
paymentInfo: paymentInfo,
|
||||
committedInfo: committedInfo));
|
||||
|
||||
getIt.registerFactoryParam<IoniaPaymentStatusPage, IoniaAnyPayPaymentInfo, AnyPayPaymentCommittedInfo>(
|
||||
(IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo)
|
||||
=> IoniaPaymentStatusPage(getIt.get<IoniaPaymentStatusViewModel>(param1: paymentInfo, param2: committedInfo)));
|
||||
|
||||
_isSetupFinished = true;
|
||||
}
|
||||
|
|
9
lib/ionia/ionia_any_pay_payment_info.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
import 'package:cake_wallet/anypay/any_pay_payment.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_order.dart';
|
||||
|
||||
class IoniaAnyPayPaymentInfo {
|
||||
const IoniaAnyPayPaymentInfo(this.ioniaOrder, this.anyPayPayment);
|
||||
|
||||
final IoniaOrder ioniaOrder;
|
||||
final AnyPayPayment anyPayPayment;
|
||||
}
|
92
lib/ionia/ionia_anypay.dart
Normal file
|
@ -0,0 +1,92 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cw_core/monero_amount_format.dart';
|
||||
import 'package:cw_core/monero_transaction_priority.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment_instruction.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
import 'package:cake_wallet/anypay/anypay_api.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_chain.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_trasnaction.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_order.dart';
|
||||
|
||||
class IoniaAnyPay {
|
||||
IoniaAnyPay(this.ioniaService, this.anyPayApi, this.wallet);
|
||||
|
||||
final IoniaService ioniaService;
|
||||
final AnyPayApi anyPayApi;
|
||||
final WalletBase wallet;
|
||||
|
||||
Future<IoniaAnyPayPaymentInfo> purchase({
|
||||
@required String merchId,
|
||||
@required double amount}) async {
|
||||
final invoice = await ioniaService.purchaseGiftCard(
|
||||
merchId: merchId,
|
||||
amount: amount,
|
||||
currency: wallet.currency.title.toUpperCase());
|
||||
final anypayPayment = await anyPayApi.paymentRequest(invoice.uri);
|
||||
return IoniaAnyPayPaymentInfo(invoice, anypayPayment);
|
||||
}
|
||||
|
||||
Future<AnyPayPaymentCommittedInfo> commitInvoice(AnyPayPayment payment) async {
|
||||
final transactionCredentials = payment.instructions
|
||||
.where((instruction) => instruction.type == AnyPayPaymentInstruction.transactionType)
|
||||
.map((AnyPayPaymentInstruction instruction) {
|
||||
switch(payment.chain.toUpperCase()) {
|
||||
case AnyPayChain.xmr:
|
||||
return monero.createMoneroTransactionCreationCredentialsRaw(
|
||||
outputs: instruction.outputs.map((out) =>
|
||||
OutputInfo(
|
||||
isParsedAddress: false,
|
||||
address: out.address,
|
||||
cryptoAmount: moneroAmountToString(amount: out.amount),
|
||||
sendAll: false)).toList(),
|
||||
priority: MoneroTransactionPriority.medium); // FIXME: HARDCODED PRIORITY
|
||||
case AnyPayChain.btc:
|
||||
return bitcoin.createBitcoinTransactionCredentialsRaw(
|
||||
instruction.outputs.map((out) =>
|
||||
OutputInfo(
|
||||
isParsedAddress: false,
|
||||
address: out.address,
|
||||
formattedCryptoAmount: out.amount,
|
||||
sendAll: false)).toList(),
|
||||
feeRate: instruction.requiredFeeRate);
|
||||
case AnyPayChain.ltc:
|
||||
return bitcoin.createBitcoinTransactionCredentialsRaw(
|
||||
instruction.outputs.map((out) =>
|
||||
OutputInfo(
|
||||
isParsedAddress: false,
|
||||
address: out.address,
|
||||
formattedCryptoAmount: out.amount,
|
||||
sendAll: false)).toList(),
|
||||
feeRate: instruction.requiredFeeRate);
|
||||
default:
|
||||
throw Exception('Incorrect transaction chain: ${payment.chain.toUpperCase()}');
|
||||
}
|
||||
});
|
||||
final transactions = (await Future.wait(transactionCredentials
|
||||
.map((Object credentials) async => await wallet.createTransaction(credentials))))
|
||||
.map((PendingTransaction pendingTransaction) {
|
||||
switch (payment.chain.toUpperCase()){
|
||||
case AnyPayChain.xmr:
|
||||
final ptx = monero.pendingTransactionInfo(pendingTransaction);
|
||||
return AnyPayTransaction(ptx['hex'], id: ptx['id'], key: ptx['key']);
|
||||
default:
|
||||
return AnyPayTransaction(pendingTransaction.hex, id: pendingTransaction.id, key: null);
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
|
||||
return await anyPayApi.payment(
|
||||
payment.paymentUrl,
|
||||
chain: payment.chain,
|
||||
currency: payment.chain,
|
||||
transactions: transactions);
|
||||
}
|
||||
}
|
444
lib/ionia/ionia_api.dart
Normal file
|
@ -0,0 +1,444 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_order.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_user_credentials.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_category.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
|
||||
class IoniaApi {
|
||||
static const baseUri = 'apistaging.ionia.io';
|
||||
static const pathPrefix = 'cake';
|
||||
static final createUserUri = Uri.https(baseUri, '/$pathPrefix/CreateUser');
|
||||
static final verifyEmailUri = Uri.https(baseUri, '/$pathPrefix/VerifyEmail');
|
||||
static final signInUri = Uri.https(baseUri, '/$pathPrefix/SignIn');
|
||||
static final createCardUri = Uri.https(baseUri, '/$pathPrefix/CreateCard');
|
||||
static final getCardsUri = Uri.https(baseUri, '/$pathPrefix/GetCards');
|
||||
static final getMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/GetMerchants');
|
||||
static final getMerchantsByFilterUrl = Uri.https(baseUri, '/$pathPrefix/GetMerchantsByFilter');
|
||||
static final getPurchaseMerchantsUrl = Uri.https(baseUri, '/$pathPrefix/PurchaseGiftCard');
|
||||
static final getCurrentUserGiftCardSummariesUrl = Uri.https(baseUri, '/$pathPrefix/GetCurrentUserGiftCardSummaries');
|
||||
static final changeGiftCardUrl = Uri.https(baseUri, '/$pathPrefix/ChargeGiftCard');
|
||||
static final getGiftCardUrl = Uri.https(baseUri, '/$pathPrefix/GetGiftCard');
|
||||
static final getPaymentStatusUrl = Uri.https(baseUri, '/$pathPrefix/PaymentStatus');
|
||||
|
||||
// Create user
|
||||
|
||||
Future<String> createUser(String email, {@required String clientId}) async {
|
||||
final headers = <String, String>{'clientId': clientId};
|
||||
final query = <String, String>{'emailAddress': email};
|
||||
final uri = createUserUri.replace(queryParameters: query);
|
||||
final response = await put(uri, headers: headers);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
// throw exception
|
||||
return null;
|
||||
}
|
||||
|
||||
final bodyJson = json.decode(response.body) as Map<String, Object>;
|
||||
final data = bodyJson['Data'] as Map<String, Object>;
|
||||
final isSuccessful = bodyJson['Successful'] as bool;
|
||||
|
||||
if (!isSuccessful) {
|
||||
throw Exception(data['ErrorMessage'] as String);
|
||||
}
|
||||
|
||||
return data['username'] as String;
|
||||
}
|
||||
|
||||
// Verify email
|
||||
|
||||
Future<IoniaUserCredentials> verifyEmail({
|
||||
@required String username,
|
||||
@required String email,
|
||||
@required String code,
|
||||
@required String clientId}) async {
|
||||
final headers = <String, String>{
|
||||
'clientId': clientId,
|
||||
'username': username,
|
||||
'EmailAddress': email};
|
||||
final query = <String, String>{'verificationCode': code};
|
||||
final uri = verifyEmailUri.replace(queryParameters: query);
|
||||
final response = await put(uri, headers: headers);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
// throw exception
|
||||
return null;
|
||||
}
|
||||
|
||||
final bodyJson = json.decode(response.body) as Map<String, Object>;
|
||||
final data = bodyJson['Data'] as Map<String, Object>;
|
||||
final isSuccessful = bodyJson['Successful'] as bool;
|
||||
|
||||
if (!isSuccessful) {
|
||||
throw Exception(bodyJson['ErrorMessage'] as String);
|
||||
}
|
||||
|
||||
final password = data['password'] as String;
|
||||
username = data['username'] as String;
|
||||
return IoniaUserCredentials(username, password);
|
||||
}
|
||||
|
||||
// Sign In
|
||||
|
||||
Future<String> signIn(String email, {@required String clientId}) async {
|
||||
final headers = <String, String>{'clientId': clientId};
|
||||
final query = <String, String>{'emailAddress': email};
|
||||
final uri = signInUri.replace(queryParameters: query);
|
||||
final response = await put(uri, headers: headers);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
// throw exception
|
||||
return null;
|
||||
}
|
||||
|
||||
final bodyJson = json.decode(response.body) as Map<String, Object>;
|
||||
final data = bodyJson['Data'] as Map<String, Object>;
|
||||
final isSuccessful = bodyJson['Successful'] as bool;
|
||||
|
||||
if (!isSuccessful) {
|
||||
throw Exception(data['ErrorMessage'] as String);
|
||||
}
|
||||
|
||||
return data['username'] as String;
|
||||
}
|
||||
|
||||
// Get virtual card
|
||||
|
||||
Future<IoniaVirtualCard> getCards({
|
||||
@required String username,
|
||||
@required String password,
|
||||
@required String clientId}) async {
|
||||
final headers = <String, String>{
|
||||
'clientId': clientId,
|
||||
'username': username,
|
||||
'password': password};
|
||||
final response = await post(getCardsUri, headers: headers);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
// throw exception
|
||||
return null;
|
||||
}
|
||||
|
||||
final bodyJson = json.decode(response.body) as Map<String, Object>;
|
||||
final data = bodyJson['Data'] as Map<String, Object>;
|
||||
final isSuccessful = bodyJson['Successful'] as bool;
|
||||
|
||||
if (!isSuccessful) {
|
||||
throw Exception(data['message'] as String);
|
||||
}
|
||||
|
||||
final virtualCard = data['VirtualCard'] as Map<String, Object>;
|
||||
return IoniaVirtualCard.fromMap(virtualCard);
|
||||
}
|
||||
|
||||
// Create virtual card
|
||||
|
||||
Future<IoniaVirtualCard> createCard({
|
||||
@required String username,
|
||||
@required String password,
|
||||
@required String clientId}) async {
|
||||
final headers = <String, String>{
|
||||
'clientId': clientId,
|
||||
'username': username,
|
||||
'password': password};
|
||||
final response = await post(createCardUri, headers: headers);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
// throw exception
|
||||
return null;
|
||||
}
|
||||
|
||||
final bodyJson = json.decode(response.body) as Map<String, Object>;
|
||||
final data = bodyJson['Data'] as Map<String, Object>;
|
||||
final isSuccessful = bodyJson['Successful'] as bool;
|
||||
|
||||
if (!isSuccessful) {
|
||||
throw Exception(data['message'] as String);
|
||||
}
|
||||
|
||||
return IoniaVirtualCard.fromMap(data);
|
||||
}
|
||||
|
||||
// Get Merchants
|
||||
|
||||
Future<List<IoniaMerchant>> getMerchants({
|
||||
@required String username,
|
||||
@required String password,
|
||||
@required String clientId}) async {
|
||||
final headers = <String, String>{
|
||||
'clientId': clientId,
|
||||
'username': username,
|
||||
'password': password};
|
||||
final response = await post(getMerchantsUrl, headers: headers);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final decodedBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
final isSuccessful = decodedBody['Successful'] as bool ?? false;
|
||||
|
||||
if (!isSuccessful) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final data = decodedBody['Data'] as List<dynamic>;
|
||||
return data.map((dynamic e) {
|
||||
try {
|
||||
final element = e as Map<String, dynamic>;
|
||||
return IoniaMerchant.fromJsonMap(element);
|
||||
} catch(_) {
|
||||
return null;
|
||||
}
|
||||
}).where((e) => e != null)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Get Merchants By Filter
|
||||
|
||||
Future<List<IoniaMerchant>> getMerchantsByFilter({
|
||||
@required String username,
|
||||
@required String password,
|
||||
@required String clientId,
|
||||
String search,
|
||||
List<IoniaCategory> categories,
|
||||
int merchantFilterType = 0}) async {
|
||||
// MerchantFilterType: {All = 0, Nearby = 1, Popular = 2, Online = 3, MyFaves = 4, Search = 5}
|
||||
|
||||
final headers = <String, String>{
|
||||
'clientId': clientId,
|
||||
'username': username,
|
||||
'password': password,
|
||||
'Content-Type': 'application/json'};
|
||||
final body = <String, dynamic>{'MerchantFilterType': merchantFilterType};
|
||||
|
||||
if (search != null) {
|
||||
body['SearchCriteria'] = search;
|
||||
}
|
||||
|
||||
if (categories != null) {
|
||||
body['Categories'] = categories
|
||||
.map((e) => e.ids)
|
||||
.expand((e) => e)
|
||||
.toList();
|
||||
}
|
||||
|
||||
final response = await post(getMerchantsByFilterUrl, headers: headers, body: json.encode(body));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final decodedBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
final isSuccessful = decodedBody['Successful'] as bool ?? false;
|
||||
|
||||
if (!isSuccessful) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final data = decodedBody['Data'] as List<dynamic>;
|
||||
return data.map((dynamic e) {
|
||||
try {
|
||||
final element = e['Merchant'] as Map<String, dynamic>;
|
||||
return IoniaMerchant.fromJsonMap(element);
|
||||
} catch(_) {
|
||||
return null;
|
||||
}
|
||||
}).where((e) => e != null)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Purchase Gift Card
|
||||
|
||||
Future<IoniaOrder> purchaseGiftCard({
|
||||
@required String merchId,
|
||||
@required double amount,
|
||||
@required String currency,
|
||||
@required String username,
|
||||
@required String password,
|
||||
@required String clientId}) async {
|
||||
final headers = <String, String>{
|
||||
'clientId': clientId,
|
||||
'username': username,
|
||||
'password': password,
|
||||
'Content-Type': 'application/json'};
|
||||
final body = <String, dynamic>{
|
||||
'Amount': amount,
|
||||
'Currency': currency,
|
||||
'MerchantId': merchId};
|
||||
final response = await post(getPurchaseMerchantsUrl, headers: headers, body: json.encode(body));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Unexpected response');
|
||||
}
|
||||
|
||||
final decodedBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
final isSuccessful = decodedBody['Successful'] as bool ?? false;
|
||||
|
||||
if (!isSuccessful) {
|
||||
throw Exception(decodedBody['ErrorMessage'] as String);
|
||||
}
|
||||
|
||||
final data = decodedBody['Data'] as Map<String, dynamic>;
|
||||
return IoniaOrder.fromMap(data);
|
||||
}
|
||||
|
||||
// Get Current User Gift Card Summaries
|
||||
|
||||
Future<List<IoniaGiftCard>> getCurrentUserGiftCardSummaries({
|
||||
@required String username,
|
||||
@required String password,
|
||||
@required String clientId}) async {
|
||||
final headers = <String, String>{
|
||||
'clientId': clientId,
|
||||
'username': username,
|
||||
'password': password};
|
||||
final response = await post(getCurrentUserGiftCardSummariesUrl, headers: headers);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final decodedBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
final isSuccessful = decodedBody['Successful'] as bool ?? false;
|
||||
|
||||
if (!isSuccessful) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final data = decodedBody['Data'] as List<dynamic>;
|
||||
return data.map((dynamic e) {
|
||||
try {
|
||||
final element = e as Map<String, dynamic>;
|
||||
return IoniaGiftCard.fromJsonMap(element);
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
}).where((e) => e != null)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Charge Gift Card
|
||||
|
||||
Future<void> chargeGiftCard({
|
||||
@required String username,
|
||||
@required String password,
|
||||
@required String clientId,
|
||||
@required int giftCardId,
|
||||
@required double amount}) async {
|
||||
final headers = <String, String>{
|
||||
'clientId': clientId,
|
||||
'username': username,
|
||||
'password': password,
|
||||
'Content-Type': 'application/json'};
|
||||
final body = <String, dynamic>{
|
||||
'Id': giftCardId,
|
||||
'Amount': amount};
|
||||
final response = await post(
|
||||
changeGiftCardUrl,
|
||||
headers: headers,
|
||||
body: json.encode(body));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to update Gift Card with ID ${giftCardId};Incorrect response status: ${response.statusCode};');
|
||||
}
|
||||
|
||||
final decodedBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
final isSuccessful = decodedBody['Successful'] as bool ?? false;
|
||||
|
||||
if (!isSuccessful) {
|
||||
final data = decodedBody['Data'] as Map<String, dynamic>;
|
||||
final msg = data['Message'] as String ?? '';
|
||||
|
||||
if (msg.isNotEmpty) {
|
||||
throw Exception(msg);
|
||||
}
|
||||
|
||||
throw Exception('Failed to update Gift Card with ID ${giftCardId};');
|
||||
}
|
||||
}
|
||||
|
||||
// Get Gift Card
|
||||
|
||||
Future<IoniaGiftCard> getGiftCard({
|
||||
@required String username,
|
||||
@required String password,
|
||||
@required String clientId,
|
||||
@required int id}) async {
|
||||
final headers = <String, String>{
|
||||
'clientId': clientId,
|
||||
'username': username,
|
||||
'password': password,
|
||||
'Content-Type': 'application/json'};
|
||||
final body = <String, dynamic>{'Id': id};
|
||||
final response = await post(
|
||||
getGiftCardUrl,
|
||||
headers: headers,
|
||||
body: json.encode(body));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to get Gift Card with ID ${id};Incorrect response status: ${response.statusCode};');
|
||||
}
|
||||
|
||||
final decodedBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
final isSuccessful = decodedBody['Successful'] as bool ?? false;
|
||||
|
||||
if (!isSuccessful) {
|
||||
final msg = decodedBody['ErrorMessage'] as String ?? '';
|
||||
|
||||
if (msg.isNotEmpty) {
|
||||
throw Exception(msg);
|
||||
}
|
||||
|
||||
throw Exception('Failed to get Gift Card with ID ${id};');
|
||||
}
|
||||
|
||||
final data = decodedBody['Data'] as Map<String, dynamic>;
|
||||
return IoniaGiftCard.fromJsonMap(data);
|
||||
}
|
||||
|
||||
// Payment Status
|
||||
|
||||
Future<int> getPaymentStatus({
|
||||
@required String username,
|
||||
@required String password,
|
||||
@required String clientId,
|
||||
@required String orderId,
|
||||
@required String paymentId}) async {
|
||||
final headers = <String, String>{
|
||||
'clientId': clientId,
|
||||
'username': username,
|
||||
'password': password,
|
||||
'Content-Type': 'application/json'};
|
||||
final body = <String, dynamic>{
|
||||
'order_id': orderId,
|
||||
'paymentId': paymentId};
|
||||
final response = await post(
|
||||
getPaymentStatusUrl,
|
||||
headers: headers,
|
||||
body: json.encode(body));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to get Payment Status for order_id ${orderId} paymentId ${paymentId};Incorrect response status: ${response.statusCode};');
|
||||
}
|
||||
|
||||
final decodedBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
final isSuccessful = decodedBody['Successful'] as bool ?? false;
|
||||
|
||||
if (!isSuccessful) {
|
||||
final msg = decodedBody['ErrorMessage'] as String ?? '';
|
||||
|
||||
if (msg.isNotEmpty) {
|
||||
throw Exception(msg);
|
||||
}
|
||||
|
||||
throw Exception('Failed to get Payment Status for order_id ${orderId} paymentId ${paymentId}');
|
||||
}
|
||||
|
||||
final data = decodedBody['Data'] as Map<String, dynamic>;
|
||||
return data['gift_card_id'] as int;
|
||||
}
|
||||
}
|
18
lib/ionia/ionia_category.dart
Normal file
|
@ -0,0 +1,18 @@
|
|||
class IoniaCategory {
|
||||
const IoniaCategory({this.index, this.title, this.ids, this.iconPath});
|
||||
|
||||
static const allCategories = <IoniaCategory>[all, apparel, onlineOnly, food, entertainment, delivery, travel];
|
||||
static const all = IoniaCategory(index: 0, title: 'All', ids: [], iconPath: 'assets/images/category.png');
|
||||
static const apparel = IoniaCategory(index: 1, title: 'Apparel', ids: [1], iconPath: 'assets/images/tshirt.png');
|
||||
static const onlineOnly = IoniaCategory(index: 2, title: 'Online Only', ids: [13, 43], iconPath: 'assets/images/global.png');
|
||||
static const food = IoniaCategory(index: 3, title: 'Food', ids: [4], iconPath: 'assets/images/food.png');
|
||||
static const entertainment = IoniaCategory(index: 4, title: 'Entertainment', ids: [5], iconPath: 'assets/images/gaming.png');
|
||||
static const delivery = IoniaCategory(index: 5, title: 'Delivery', ids: [114, 109], iconPath: 'assets/images/delivery.png');
|
||||
static const travel = IoniaCategory(index: 6, title: 'Travel', ids: [12], iconPath: 'assets/images/airplane.png');
|
||||
|
||||
|
||||
final int index;
|
||||
final String title;
|
||||
final List<int> ids;
|
||||
final String iconPath;
|
||||
}
|
58
lib/ionia/ionia_create_state.dart
Normal file
|
@ -0,0 +1,58 @@
|
|||
import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class IoniaCreateAccountState {}
|
||||
|
||||
class IoniaInitialCreateState extends IoniaCreateAccountState {}
|
||||
|
||||
class IoniaCreateStateSuccess extends IoniaCreateAccountState {}
|
||||
|
||||
class IoniaCreateStateLoading extends IoniaCreateAccountState {}
|
||||
|
||||
class IoniaCreateStateFailure extends IoniaCreateAccountState {
|
||||
IoniaCreateStateFailure({@required this.error});
|
||||
|
||||
final String error;
|
||||
}
|
||||
|
||||
abstract class IoniaOtpState {}
|
||||
|
||||
class IoniaOtpValidating extends IoniaOtpState {}
|
||||
|
||||
class IoniaOtpSuccess extends IoniaOtpState {}
|
||||
|
||||
class IoniaOtpSendDisabled extends IoniaOtpState {}
|
||||
|
||||
class IoniaOtpSendEnabled extends IoniaOtpState {}
|
||||
|
||||
class IoniaOtpFailure extends IoniaOtpState {
|
||||
IoniaOtpFailure({@required this.error});
|
||||
|
||||
final String error;
|
||||
}
|
||||
|
||||
class IoniaCreateCardState {}
|
||||
|
||||
class IoniaCreateCardSuccess extends IoniaCreateCardState {}
|
||||
|
||||
class IoniaCreateCardLoading extends IoniaCreateCardState {}
|
||||
|
||||
class IoniaCreateCardFailure extends IoniaCreateCardState {
|
||||
IoniaCreateCardFailure({@required this.error});
|
||||
|
||||
final String error;
|
||||
}
|
||||
|
||||
class IoniaFetchCardState {}
|
||||
|
||||
class IoniaNoCardState extends IoniaFetchCardState {}
|
||||
|
||||
class IoniaFetchingCard extends IoniaFetchCardState {}
|
||||
|
||||
class IoniaFetchCardFailure extends IoniaFetchCardState {}
|
||||
|
||||
class IoniaCardSuccess extends IoniaFetchCardState {
|
||||
IoniaCardSuccess({@required this.card});
|
||||
|
||||
final IoniaVirtualCard card;
|
||||
}
|
69
lib/ionia/ionia_gift_card.dart
Normal file
|
@ -0,0 +1,69 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card_instruction.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class IoniaGiftCard {
|
||||
IoniaGiftCard({
|
||||
@required this.id,
|
||||
@required this.merchantId,
|
||||
@required this.legalName,
|
||||
@required this.systemName,
|
||||
@required this.barcodeUrl,
|
||||
@required this.cardNumber,
|
||||
@required this.cardPin,
|
||||
@required this.instructions,
|
||||
@required this.tip,
|
||||
@required this.purchaseAmount,
|
||||
@required this.actualAmount,
|
||||
@required this.totalTransactionAmount,
|
||||
@required this.totalDashTransactionAmount,
|
||||
@required this.remainingAmount,
|
||||
@required this.createdDateFormatted,
|
||||
@required this.lastTransactionDateFormatted,
|
||||
@required this.isActive,
|
||||
@required this.isEmpty,
|
||||
@required this.logoUrl});
|
||||
|
||||
factory IoniaGiftCard.fromJsonMap(Map<String, dynamic> element) {
|
||||
return IoniaGiftCard(
|
||||
id: element['Id'] as int,
|
||||
merchantId: element['MerchantId'] as int,
|
||||
legalName: element['LegalName'] as String,
|
||||
systemName: element['SystemName'] as String,
|
||||
barcodeUrl: element['BarcodeUrl'] as String,
|
||||
cardNumber: element['CardNumber'] as String,
|
||||
cardPin: element['CardPin'] as String,
|
||||
tip: element['Tip'] as double,
|
||||
purchaseAmount: element['PurchaseAmount'] as double,
|
||||
actualAmount: element['ActualAmount'] as double,
|
||||
totalTransactionAmount: element['TotalTransactionAmount'] as double,
|
||||
totalDashTransactionAmount: element['TotalDashTransactionAmount'] as double,
|
||||
remainingAmount: element['RemainingAmount'] as double,
|
||||
isActive: element['IsActive'] as bool,
|
||||
isEmpty: element['IsEmpty'] as bool,
|
||||
logoUrl: element['LogoUrl'] as String,
|
||||
createdDateFormatted: element['CreatedDate'] as String,
|
||||
lastTransactionDateFormatted: element['LastTransactionDate'] as String,
|
||||
instructions: IoniaGiftCardInstruction.parseListOfInstructions(element['PaymentInstructions'] as String));
|
||||
}
|
||||
|
||||
final int id;
|
||||
final int merchantId;
|
||||
final String legalName;
|
||||
final String systemName;
|
||||
final String barcodeUrl;
|
||||
final String cardNumber;
|
||||
final String cardPin;
|
||||
final List<IoniaGiftCardInstruction> instructions;
|
||||
final double tip;
|
||||
final double purchaseAmount;
|
||||
final double actualAmount;
|
||||
final double totalTransactionAmount;
|
||||
final double totalDashTransactionAmount;
|
||||
final double remainingAmount;
|
||||
final String createdDateFormatted;
|
||||
final String lastTransactionDateFormatted;
|
||||
final bool isActive;
|
||||
final bool isEmpty;
|
||||
final String logoUrl;
|
||||
}
|
28
lib/ionia/ionia_gift_card_instruction.dart
Normal file
|
@ -0,0 +1,28 @@
|
|||
import 'dart:convert';
|
||||
import 'package:intl/intl.dart' show toBeginningOfSentenceCase;
|
||||
|
||||
class IoniaGiftCardInstruction {
|
||||
IoniaGiftCardInstruction(this.header, this.body);
|
||||
|
||||
factory IoniaGiftCardInstruction.fromJsonMap(Map<String, dynamic> element) {
|
||||
return IoniaGiftCardInstruction(
|
||||
toBeginningOfSentenceCase(element['title'] as String ?? ''),
|
||||
element['description'] as String);
|
||||
}
|
||||
|
||||
static List<IoniaGiftCardInstruction> parseListOfInstructions(String instructionsJSON) {
|
||||
List<IoniaGiftCardInstruction> instructions = <IoniaGiftCardInstruction>[];
|
||||
|
||||
if (instructionsJSON.isNotEmpty) {
|
||||
final decodedInstructions = json.decode(instructionsJSON) as List<dynamic>;
|
||||
instructions = decodedInstructions
|
||||
.map((dynamic e) =>IoniaGiftCardInstruction.fromJsonMap(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
final String header;
|
||||
final String body;
|
||||
}
|
176
lib/ionia/ionia_merchant.dart
Normal file
|
@ -0,0 +1,176 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card_instruction.dart';
|
||||
|
||||
class IoniaMerchant {
|
||||
IoniaMerchant({
|
||||
@required this.id,
|
||||
@required this.legalName,
|
||||
@required this.systemName,
|
||||
@required this.description,
|
||||
@required this.website,
|
||||
@required this.termsAndConditions,
|
||||
@required this.logoUrl,
|
||||
@required this.cardImageUrl,
|
||||
@required this.cardholderAgreement,
|
||||
@required this.purchaseFee,
|
||||
@required this.revenueShare,
|
||||
@required this.marketingFee,
|
||||
@required this.minimumDiscount,
|
||||
@required this.level1,
|
||||
@required this.level2,
|
||||
@required this.level3,
|
||||
@required this.level4,
|
||||
@required this.level5,
|
||||
@required this.level6,
|
||||
@required this.level7,
|
||||
@required this.isActive,
|
||||
@required this.isDeleted,
|
||||
@required this.isOnline,
|
||||
@required this.isPhysical,
|
||||
@required this.isVariablePurchase,
|
||||
@required this.minimumCardPurchase,
|
||||
@required this.maximumCardPurchase,
|
||||
@required this.acceptsTips,
|
||||
@required this.createdDateFormatted,
|
||||
@required this.createdBy,
|
||||
@required this.isRegional,
|
||||
@required this.modifiedDateFormatted,
|
||||
@required this.modifiedBy,
|
||||
@required this.usageInstructions,
|
||||
@required this.usageInstructionsBak,
|
||||
@required this.paymentGatewayId,
|
||||
@required this.giftCardGatewayId,
|
||||
@required this.isHtmlDescription,
|
||||
@required this.purchaseInstructions,
|
||||
@required this.balanceInstructions,
|
||||
@required this.amountPerCard,
|
||||
@required this.processingMessage,
|
||||
@required this.hasBarcode,
|
||||
@required this.hasInventory,
|
||||
@required this.isVoidable,
|
||||
@required this.receiptMessage,
|
||||
@required this.cssBorderCode,
|
||||
@required this.instructions,
|
||||
@required this.alderSku,
|
||||
@required this.ngcSku,
|
||||
@required this.acceptedCurrency,
|
||||
@required this.deepLink,
|
||||
@required this.isPayLater,
|
||||
@required this.savingsPercentage});
|
||||
|
||||
factory IoniaMerchant.fromJsonMap(Map<String, dynamic> element) {
|
||||
return IoniaMerchant(
|
||||
id: element["Id"] as int,
|
||||
legalName: element["LegalName"] as String,
|
||||
systemName: element["SystemName"] as String,
|
||||
description: element["Description"] as String,
|
||||
website: element["Website"] as String,
|
||||
termsAndConditions: element["TermsAndConditions"] as String,
|
||||
logoUrl: element["LogoUrl"] as String,
|
||||
cardImageUrl: element["CardImageUrl"] as String,
|
||||
cardholderAgreement: element["CardholderAgreement"] as String,
|
||||
purchaseFee: element["PurchaseFee"] as double,
|
||||
revenueShare: element["RevenueShare"] as double,
|
||||
marketingFee: element["MarketingFee"] as double,
|
||||
minimumDiscount: element["MinimumDiscount"] as double,
|
||||
level1: element["Level1"] as double,
|
||||
level2: element["Level2"] as double,
|
||||
level3: element["Level3"] as double,
|
||||
level4: element["Level4"] as double,
|
||||
level5: element["Level5"] as double,
|
||||
level6: element["Level6"] as double,
|
||||
level7: element["Level7"] as double,
|
||||
isActive: element["IsActive"] as bool,
|
||||
isDeleted: element["IsDeleted"] as bool,
|
||||
isOnline: element["IsOnline"] as bool,
|
||||
isPhysical: element["IsPhysical"] as bool,
|
||||
isVariablePurchase: element["IsVariablePurchase"] as bool,
|
||||
minimumCardPurchase: element["MinimumCardPurchase"] as double,
|
||||
maximumCardPurchase: element["MaximumCardPurchase"] as double,
|
||||
acceptsTips: element["AcceptsTips"] as bool,
|
||||
createdDateFormatted: element["CreatedDate"] as String,
|
||||
createdBy: element["CreatedBy"] as int,
|
||||
isRegional: element["IsRegional"] as bool,
|
||||
modifiedDateFormatted: element["ModifiedDate"] as String,
|
||||
modifiedBy: element["ModifiedBy"] as int,
|
||||
usageInstructions: element["UsageInstructions"] as String,
|
||||
usageInstructionsBak: element["UsageInstructionsBak"] as String,
|
||||
paymentGatewayId: element["PaymentGatewayId"] as int,
|
||||
giftCardGatewayId: element["GiftCardGatewayId"] as int ,
|
||||
isHtmlDescription: element["IsHtmlDescription"] as bool,
|
||||
purchaseInstructions: element["PurchaseInstructions"] as String,
|
||||
balanceInstructions: element["BalanceInstructions"] as String,
|
||||
amountPerCard: element["AmountPerCard"] as double,
|
||||
processingMessage: element["ProcessingMessage"] as String,
|
||||
hasBarcode: element["HasBarcode"] as bool,
|
||||
hasInventory: element["HasInventory"] as bool,
|
||||
isVoidable: element["IsVoidable"] as bool,
|
||||
receiptMessage: element["ReceiptMessage"] as String,
|
||||
cssBorderCode: element["CssBorderCode"] as String,
|
||||
instructions: IoniaGiftCardInstruction.parseListOfInstructions(element['PaymentInstructions'] as String),
|
||||
alderSku: element["AlderSku"] as String,
|
||||
ngcSku: element["NgcSku"] as String,
|
||||
acceptedCurrency: element["AcceptedCurrency"] as String,
|
||||
deepLink: element["DeepLink"] as String,
|
||||
isPayLater: element["IsPayLater"] as bool,
|
||||
savingsPercentage: element["SavingsPercentage"] as double);
|
||||
}
|
||||
|
||||
final int id;
|
||||
final String legalName;
|
||||
final String systemName;
|
||||
final String description;
|
||||
final String website;
|
||||
final String termsAndConditions;
|
||||
final String logoUrl;
|
||||
final String cardImageUrl;
|
||||
final String cardholderAgreement;
|
||||
final double purchaseFee;
|
||||
final double revenueShare;
|
||||
final double marketingFee;
|
||||
final double minimumDiscount;
|
||||
final double level1;
|
||||
final double level2;
|
||||
final double level3;
|
||||
final double level4;
|
||||
final double level5;
|
||||
final double level6;
|
||||
final double level7;
|
||||
final bool isActive;
|
||||
final bool isDeleted;
|
||||
final bool isOnline;
|
||||
final bool isPhysical;
|
||||
final bool isVariablePurchase;
|
||||
final double minimumCardPurchase;
|
||||
final double maximumCardPurchase;
|
||||
final bool acceptsTips;
|
||||
final String createdDateFormatted;
|
||||
final int createdBy;
|
||||
final bool isRegional;
|
||||
final String modifiedDateFormatted;
|
||||
final int modifiedBy;
|
||||
final String usageInstructions;
|
||||
final String usageInstructionsBak;
|
||||
final int paymentGatewayId;
|
||||
final int giftCardGatewayId;
|
||||
final bool isHtmlDescription;
|
||||
final String purchaseInstructions;
|
||||
final String balanceInstructions;
|
||||
final double amountPerCard;
|
||||
final String processingMessage;
|
||||
final bool hasBarcode;
|
||||
final bool hasInventory;
|
||||
final bool isVoidable;
|
||||
final String receiptMessage;
|
||||
final String cssBorderCode;
|
||||
final List<IoniaGiftCardInstruction> instructions;
|
||||
final String alderSku;
|
||||
final String ngcSku;
|
||||
final String acceptedCurrency;
|
||||
final String deepLink;
|
||||
final bool isPayLater;
|
||||
final double savingsPercentage;
|
||||
|
||||
double get discount => savingsPercentage;
|
||||
|
||||
}
|
23
lib/ionia/ionia_order.dart
Normal file
|
@ -0,0 +1,23 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class IoniaOrder {
|
||||
IoniaOrder({@required this.id,
|
||||
@required this.uri,
|
||||
@required this.currency,
|
||||
@required this.amount,
|
||||
@required this.paymentId});
|
||||
factory IoniaOrder.fromMap(Map<String, dynamic> obj) {
|
||||
return IoniaOrder(
|
||||
id: obj['order_id'] as String,
|
||||
uri: obj['uri'] as String,
|
||||
currency: obj['currency'] as String,
|
||||
amount: obj['amount'] as double,
|
||||
paymentId: obj['paymentId'] as String);
|
||||
}
|
||||
|
||||
final String id;
|
||||
final String uri;
|
||||
final String currency;
|
||||
final double amount;
|
||||
final String paymentId;
|
||||
}
|
172
lib/ionia/ionia_service.dart
Normal file
|
@ -0,0 +1,172 @@
|
|||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_order.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
import 'package:cake_wallet/ionia/ionia_api.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_category.dart';
|
||||
|
||||
class IoniaService {
|
||||
IoniaService(this.secureStorage, this.ioniaApi);
|
||||
|
||||
static const ioniaEmailStorageKey = 'ionia_email';
|
||||
static const ioniaUsernameStorageKey = 'ionia_username';
|
||||
static const ioniaPasswordStorageKey = 'ionia_password';
|
||||
|
||||
static String get clientId => secrets.ioniaClientId;
|
||||
|
||||
final FlutterSecureStorage secureStorage;
|
||||
final IoniaApi ioniaApi;
|
||||
|
||||
// Create user
|
||||
|
||||
Future<void> createUser(String email) async {
|
||||
final username = await ioniaApi.createUser(email, clientId: clientId);
|
||||
await secureStorage.write(key: ioniaEmailStorageKey, value: email);
|
||||
await secureStorage.write(key: ioniaUsernameStorageKey, value: username);
|
||||
}
|
||||
|
||||
// Verify email
|
||||
|
||||
Future<void> verifyEmail(String code) async {
|
||||
final username = await secureStorage.read(key: ioniaUsernameStorageKey);
|
||||
final email = await secureStorage.read(key: ioniaEmailStorageKey);
|
||||
final credentials = await ioniaApi.verifyEmail(email: email, username: username, code: code, clientId: clientId);
|
||||
await secureStorage.write(key: ioniaPasswordStorageKey, value: credentials.password);
|
||||
await secureStorage.write(key: ioniaUsernameStorageKey, value: credentials.username);
|
||||
}
|
||||
|
||||
// Sign In
|
||||
|
||||
Future<void> signIn(String email) async {
|
||||
final username = await ioniaApi.signIn(email, clientId: clientId);
|
||||
await secureStorage.write(key: ioniaEmailStorageKey, value: email);
|
||||
await secureStorage.write(key: ioniaUsernameStorageKey, value: username);
|
||||
}
|
||||
|
||||
Future<String> getUserEmail() async {
|
||||
return secureStorage.read(key: ioniaEmailStorageKey);
|
||||
}
|
||||
|
||||
// Check is user logined
|
||||
|
||||
Future<bool> isLogined() async {
|
||||
final username = await secureStorage.read(key: ioniaUsernameStorageKey) ?? '';
|
||||
final password = await secureStorage.read(key: ioniaPasswordStorageKey) ?? '';
|
||||
return username.isNotEmpty && password.isNotEmpty;
|
||||
}
|
||||
|
||||
// Logout
|
||||
|
||||
Future<void> logout() async {
|
||||
await secureStorage.delete(key: ioniaUsernameStorageKey);
|
||||
await secureStorage.delete(key: ioniaPasswordStorageKey);
|
||||
}
|
||||
|
||||
// Create virtual card
|
||||
|
||||
Future<IoniaVirtualCard> createCard() async {
|
||||
final username = await secureStorage.read(key: ioniaUsernameStorageKey);
|
||||
final password = await secureStorage.read(key: ioniaPasswordStorageKey);
|
||||
return ioniaApi.createCard(username: username, password: password, clientId: clientId);
|
||||
}
|
||||
|
||||
// Get virtual card
|
||||
|
||||
Future<IoniaVirtualCard> getCard() async {
|
||||
final username = await secureStorage.read(key: ioniaUsernameStorageKey);
|
||||
final password = await secureStorage.read(key: ioniaPasswordStorageKey);
|
||||
return ioniaApi.getCards(username: username, password: password, clientId: clientId);
|
||||
}
|
||||
|
||||
// Get Merchants
|
||||
|
||||
Future<List<IoniaMerchant>> getMerchants() async {
|
||||
final username = await secureStorage.read(key: ioniaUsernameStorageKey);
|
||||
final password = await secureStorage.read(key: ioniaPasswordStorageKey);
|
||||
return ioniaApi.getMerchants(username: username, password: password, clientId: clientId);
|
||||
}
|
||||
|
||||
// Get Merchants By Filter
|
||||
|
||||
Future<List<IoniaMerchant>> getMerchantsByFilter({
|
||||
String search,
|
||||
List<IoniaCategory> categories,
|
||||
int merchantFilterType = 0}) async {
|
||||
final username = await secureStorage.read(key: ioniaUsernameStorageKey);
|
||||
final password = await secureStorage.read(key: ioniaPasswordStorageKey);
|
||||
return ioniaApi.getMerchantsByFilter(
|
||||
username: username,
|
||||
password: password,
|
||||
clientId: clientId,
|
||||
search: search,
|
||||
categories: categories,
|
||||
merchantFilterType: merchantFilterType);
|
||||
}
|
||||
|
||||
// Purchase Gift Card
|
||||
|
||||
Future<IoniaOrder> purchaseGiftCard({
|
||||
@required String merchId,
|
||||
@required double amount,
|
||||
@required String currency}) async {
|
||||
final username = await secureStorage.read(key: ioniaUsernameStorageKey);
|
||||
final password = await secureStorage.read(key: ioniaPasswordStorageKey);
|
||||
return ioniaApi.purchaseGiftCard(
|
||||
merchId: merchId,
|
||||
amount: amount,
|
||||
currency: currency,
|
||||
username: username,
|
||||
password: password,
|
||||
clientId: clientId);
|
||||
}
|
||||
|
||||
// Get Current User Gift Card Summaries
|
||||
|
||||
Future<List<IoniaGiftCard>> getCurrentUserGiftCardSummaries() async {
|
||||
final username = await secureStorage.read(key: ioniaUsernameStorageKey);
|
||||
final password = await secureStorage.read(key: ioniaPasswordStorageKey);
|
||||
return ioniaApi.getCurrentUserGiftCardSummaries(username: username, password: password, clientId: clientId);
|
||||
}
|
||||
|
||||
// Charge Gift Card
|
||||
|
||||
Future<void> chargeGiftCard({
|
||||
@required int giftCardId,
|
||||
@required double amount}) async {
|
||||
final username = await secureStorage.read(key: ioniaUsernameStorageKey);
|
||||
final password = await secureStorage.read(key: ioniaPasswordStorageKey);
|
||||
await ioniaApi.chargeGiftCard(
|
||||
username: username,
|
||||
password: password,
|
||||
clientId: clientId,
|
||||
giftCardId: giftCardId,
|
||||
amount: amount);
|
||||
}
|
||||
|
||||
// Redeem
|
||||
|
||||
Future<void> redeem(IoniaGiftCard giftCard) async {
|
||||
await chargeGiftCard(giftCardId: giftCard.id, amount: giftCard.remainingAmount);
|
||||
}
|
||||
|
||||
// Get Gift Card
|
||||
|
||||
Future<IoniaGiftCard> getGiftCard({@required int id}) async {
|
||||
final username = await secureStorage.read(key: ioniaUsernameStorageKey);
|
||||
final password = await secureStorage.read(key: ioniaPasswordStorageKey);
|
||||
return ioniaApi.getGiftCard(username: username, password: password, clientId: clientId,id: id);
|
||||
}
|
||||
|
||||
// Payment Status
|
||||
|
||||
Future<int> getPaymentStatus({
|
||||
@required String orderId,
|
||||
@required String paymentId}) async {
|
||||
final username = await secureStorage.read(key: ioniaUsernameStorageKey);
|
||||
final password = await secureStorage.read(key: ioniaPasswordStorageKey);
|
||||
return ioniaApi.getPaymentStatus(username: username, password: password, clientId: clientId, orderId: orderId, paymentId: paymentId);
|
||||
}
|
||||
}
|
12
lib/ionia/ionia_tip.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
class IoniaTip {
|
||||
const IoniaTip({this.originalAmount, this.percentage});
|
||||
final double originalAmount;
|
||||
final double percentage;
|
||||
double get additionalAmount => double.parse((originalAmount * percentage / 100).toStringAsFixed(2));
|
||||
|
||||
static const tipList = [
|
||||
IoniaTip(originalAmount: 0, percentage: 0),
|
||||
IoniaTip(originalAmount: 10, percentage: 10),
|
||||
IoniaTip(originalAmount: 20, percentage: 20)
|
||||
];
|
||||
}
|
43
lib/ionia/ionia_token_data.dart
Normal file
|
@ -0,0 +1,43 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class IoniaTokenData {
|
||||
IoniaTokenData({@required this.accessToken, @required this.tokenType, @required this.expiredAt});
|
||||
|
||||
factory IoniaTokenData.fromJson(String source) {
|
||||
final decoded = json.decode(source) as Map<String, dynamic>;
|
||||
final accessToken = decoded['access_token'] as String;
|
||||
final expiresIn = decoded['expires_in'] as int;
|
||||
final tokenType = decoded['token_type'] as String;
|
||||
final expiredAtInMilliseconds = decoded['expired_at'] as int;
|
||||
DateTime expiredAt;
|
||||
|
||||
if (expiredAtInMilliseconds != null) {
|
||||
expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtInMilliseconds);
|
||||
} else {
|
||||
expiredAt = DateTime.now().add(Duration(seconds: expiresIn));
|
||||
}
|
||||
|
||||
return IoniaTokenData(
|
||||
accessToken: accessToken,
|
||||
tokenType: tokenType,
|
||||
expiredAt: expiredAt);
|
||||
}
|
||||
|
||||
final String accessToken;
|
||||
final String tokenType;
|
||||
final DateTime expiredAt;
|
||||
|
||||
bool get isExpired => DateTime.now().isAfter(expiredAt);
|
||||
|
||||
@override
|
||||
String toString() => '$tokenType $accessToken';
|
||||
|
||||
String toJson() {
|
||||
return json.encode(<String, dynamic>{
|
||||
'access_token': accessToken,
|
||||
'token_type': tokenType,
|
||||
'expired_at': expiredAt.millisecondsSinceEpoch
|
||||
});
|
||||
}
|
||||
}
|
6
lib/ionia/ionia_user_credentials.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
class IoniaUserCredentials {
|
||||
const IoniaUserCredentials(this.username, this.password);
|
||||
|
||||
final String username;
|
||||
final String password;
|
||||
}
|
43
lib/ionia/ionia_virtual_card.dart
Normal file
|
@ -0,0 +1,43 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class IoniaVirtualCard {
|
||||
IoniaVirtualCard({
|
||||
@required this.token,
|
||||
@required this.createdAt,
|
||||
@required this.lastFour,
|
||||
@required this.state,
|
||||
@required this.pan,
|
||||
@required this.cvv,
|
||||
@required this.expirationMonth,
|
||||
@required this.expirationYear,
|
||||
@required this.fundsLimit,
|
||||
@required this.spendLimit});
|
||||
|
||||
factory IoniaVirtualCard.fromMap(Map<String, Object> source) {
|
||||
final created = source['created'] as String;
|
||||
final createdAt = DateTime.tryParse(created);
|
||||
|
||||
return IoniaVirtualCard(
|
||||
token: source['token'] as String,
|
||||
createdAt: createdAt,
|
||||
lastFour: source['lastFour'] as String,
|
||||
state: source['state'] as String,
|
||||
pan: source['pan'] as String,
|
||||
cvv: source['cvv'] as String,
|
||||
expirationMonth: source['expirationMonth'] as String,
|
||||
expirationYear: source['expirationYear'] as String,
|
||||
fundsLimit: source['FundsLimit'] as double,
|
||||
spendLimit: source['spend_limit'] as double);
|
||||
}
|
||||
|
||||
final String token;
|
||||
final String lastFour;
|
||||
final String state;
|
||||
final String pan;
|
||||
final String cvv;
|
||||
final String expirationMonth;
|
||||
final String expirationYear;
|
||||
final DateTime createdAt;
|
||||
final double fundsLimit;
|
||||
final double spendLimit;
|
||||
}
|
|
@ -2,6 +2,8 @@ import 'dart:async';
|
|||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/language_service.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_category.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -174,7 +176,8 @@ Future<void> initialSetup(
|
|||
exchangeTemplates: exchangeTemplates,
|
||||
transactionDescriptionBox: transactionDescriptions,
|
||||
ordersSource: ordersSource,
|
||||
unspentCoinsInfoSource: unspentCoinsInfoSource);
|
||||
unspentCoinsInfoSource: unspentCoinsInfoSource,
|
||||
);
|
||||
await bootstrap(navigatorKey);
|
||||
monero?.onStartup();
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ part of 'monero.dart';
|
|||
|
||||
class CWMoneroAccountList extends MoneroAccountList {
|
||||
CWMoneroAccountList(this._wallet);
|
||||
Object _wallet;
|
||||
final Object _wallet;
|
||||
|
||||
@override
|
||||
@computed
|
||||
|
@ -39,13 +39,13 @@ class CWMoneroAccountList extends MoneroAccountList {
|
|||
@override
|
||||
Future<void> addAccount(Object wallet, {String label}) async {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
moneroWallet.walletAddresses.accountList.addAccount(label: label);
|
||||
await moneroWallet.walletAddresses.accountList.addAccount(label: label);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setLabelAccount(Object wallet, {int accountIndex, String label}) async {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
moneroWallet.walletAddresses.accountList
|
||||
await moneroWallet.walletAddresses.accountList
|
||||
.setLabelAccount(
|
||||
accountIndex: accountIndex,
|
||||
label: label);
|
||||
|
@ -95,7 +95,7 @@ class CWMoneroSubaddressList extends MoneroSubaddressList {
|
|||
@override
|
||||
Future<void> addSubaddress(Object wallet, {int accountIndex, String label}) async {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
moneroWallet.walletAddresses.subaddressList
|
||||
await moneroWallet.walletAddresses.subaddressList
|
||||
.addSubaddress(
|
||||
accountIndex: accountIndex,
|
||||
label: label);
|
||||
|
@ -105,7 +105,7 @@ class CWMoneroSubaddressList extends MoneroSubaddressList {
|
|||
Future<void> setLabelSubaddress(Object wallet,
|
||||
{int accountIndex, int addressIndex, String label}) async {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
moneroWallet.walletAddresses.subaddressList
|
||||
await moneroWallet.walletAddresses.subaddressList
|
||||
.setLabelSubaddress(
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndex,
|
||||
|
@ -140,35 +140,43 @@ class CWMonero extends Monero {
|
|||
return CWMoneroAccountList(wallet);
|
||||
}
|
||||
|
||||
@override
|
||||
MoneroSubaddressList getSubaddressList(Object wallet) {
|
||||
return CWMoneroSubaddressList(wallet);
|
||||
}
|
||||
|
||||
@override
|
||||
TransactionHistoryBase getTransactionHistory(Object wallet) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
return moneroWallet.transactionHistory;
|
||||
}
|
||||
|
||||
@override
|
||||
MoneroWalletDetails getMoneroWalletDetails(Object wallet) {
|
||||
return CWMoneroWalletDetails(wallet);
|
||||
}
|
||||
|
||||
@override
|
||||
int getHeigthByDate({DateTime date}) {
|
||||
return getMoneroHeigthByDate(date: date);
|
||||
}
|
||||
|
||||
@override
|
||||
TransactionPriority getDefaultTransactionPriority() {
|
||||
return MoneroTransactionPriority.slow;
|
||||
}
|
||||
|
||||
@override
|
||||
TransactionPriority deserializeMoneroTransactionPriority({int raw}) {
|
||||
return MoneroTransactionPriority.deserialize(raw: raw);
|
||||
}
|
||||
|
||||
@override
|
||||
List<TransactionPriority> getTransactionPriorities() {
|
||||
return MoneroTransactionPriority.all;
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> getMoneroWordList(String language) {
|
||||
switch (language.toLowerCase()) {
|
||||
case 'english':
|
||||
|
@ -196,14 +204,15 @@ class CWMonero extends Monero {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
WalletCredentials createMoneroRestoreWalletFromKeysCredentials({
|
||||
String name,
|
||||
String spendKey,
|
||||
String viewKey,
|
||||
String address,
|
||||
String password,
|
||||
String language,
|
||||
int height}) {
|
||||
String spendKey,
|
||||
String viewKey,
|
||||
String address,
|
||||
String password,
|
||||
String language,
|
||||
int height}) {
|
||||
return MoneroRestoreWalletFromKeysCredentials(
|
||||
name: name,
|
||||
spendKey: spendKey,
|
||||
|
@ -214,6 +223,7 @@ class CWMonero extends Monero {
|
|||
height: height);
|
||||
}
|
||||
|
||||
@override
|
||||
WalletCredentials createMoneroRestoreWalletFromSeedCredentials({String name, String password, int height, String mnemonic}) {
|
||||
return MoneroRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
|
@ -222,6 +232,7 @@ class CWMonero extends Monero {
|
|||
mnemonic: mnemonic);
|
||||
}
|
||||
|
||||
@override
|
||||
WalletCredentials createMoneroNewWalletCredentials({String name, String password, String language}) {
|
||||
return MoneroNewWalletCredentials(
|
||||
name: name,
|
||||
|
@ -229,6 +240,7 @@ class CWMonero extends Monero {
|
|||
language: language);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> getKeys(Object wallet) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
final keys = moneroWallet.keys;
|
||||
|
@ -239,6 +251,7 @@ class CWMonero extends Monero {
|
|||
'publicViewKey': keys.publicViewKey};
|
||||
}
|
||||
|
||||
@override
|
||||
Object createMoneroTransactionCreationCredentials({List<Output> outputs, TransactionPriority priority}) {
|
||||
return MoneroTransactionCreationCredentials(
|
||||
outputs: outputs.map((out) => OutputInfo(
|
||||
|
@ -254,49 +267,72 @@ class CWMonero extends Monero {
|
|||
priority: priority as MoneroTransactionPriority);
|
||||
}
|
||||
|
||||
@override
|
||||
Object createMoneroTransactionCreationCredentialsRaw({List<OutputInfo> outputs, TransactionPriority priority}) {
|
||||
return MoneroTransactionCreationCredentials(
|
||||
outputs: outputs,
|
||||
priority: priority as MoneroTransactionPriority);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatterMoneroAmountToString({int amount}) {
|
||||
return moneroAmountToString(amount: amount);
|
||||
}
|
||||
|
||||
@override
|
||||
double formatterMoneroAmountToDouble({int amount}) {
|
||||
return moneroAmountToDouble(amount: amount);
|
||||
}
|
||||
|
||||
@override
|
||||
int formatterMoneroParseAmount({String amount}) {
|
||||
return moneroParseAmount(amount: amount);
|
||||
}
|
||||
|
||||
@override
|
||||
Account getCurrentAccount(Object wallet) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
final acc = moneroWallet.walletAddresses.account;
|
||||
return Account(id: acc.id, label: acc.label);
|
||||
}
|
||||
|
||||
@override
|
||||
void setCurrentAccount(Object wallet, int id, String label) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
moneroWallet.walletAddresses.account = monero_account.Account(id: id, label: label);
|
||||
}
|
||||
|
||||
@override
|
||||
void onStartup() {
|
||||
monero_wallet_api.onStartup();
|
||||
}
|
||||
|
||||
@override
|
||||
int getTransactionInfoAccountId(TransactionInfo tx) {
|
||||
final moneroTransactionInfo = tx as MoneroTransactionInfo;
|
||||
return moneroTransactionInfo.accountIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
WalletService createMoneroWalletService(Box<WalletInfo> walletInfoSource) {
|
||||
return MoneroWalletService(walletInfoSource);
|
||||
}
|
||||
|
||||
@override
|
||||
String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
return moneroWallet.getTransactionAddress(accountIndex, addressIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
String getSubaddressLabel(Object wallet, int accountIndex, int addressIndex) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
return moneroWallet.getSubaddressLabel(accountIndex, addressIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> pendingTransactionInfo(Object transaction) {
|
||||
final ptx = transaction as PendingMoneroTransaction;
|
||||
return {'id': ptx.id, 'hex': ptx.hex, 'key': ptx.txKey};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,10 @@ import 'package:cake_wallet/src/screens/backup/backup_page.dart';
|
|||
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
|
||||
import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
|
||||
|
@ -63,6 +67,10 @@ import 'package:flutter/services.dart';
|
|||
import 'package:cake_wallet/wallet_types.g.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/ionia.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
|
||||
|
||||
RouteSettings currentRouteSettings;
|
||||
|
||||
|
@ -401,6 +409,58 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
param2: args['isLight'] as bool,
|
||||
));
|
||||
|
||||
case Routes.ioniaWelcomePage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaWelcomePage>());
|
||||
|
||||
case Routes.ioniaLoginPage:
|
||||
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>());
|
||||
|
||||
case Routes.ioniaCreateAccountPage:
|
||||
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaCreateAccountPage>());
|
||||
|
||||
case Routes.ioniaManageCardsPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaManageCardsPage>());
|
||||
|
||||
case Routes.ioniaBuyGiftCardPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
|
||||
|
||||
case Routes.ioniaBuyGiftCardDetailPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
|
||||
|
||||
case Routes.ioniaVerifyIoniaOtpPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaVerifyIoniaOtp>(param1: args));
|
||||
|
||||
case Routes.ioniaDebitCardPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaDebitCardPage>());
|
||||
|
||||
case Routes.ioniaActivateDebitCardPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaActivateDebitCardPage>());
|
||||
|
||||
case Routes.ioniaAccountPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountPage>());
|
||||
|
||||
case Routes.ioniaAccountCardsPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountCardsPage>());
|
||||
|
||||
case Routes.ioniaCustomTipPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaCustomTipPage>(param1: args));
|
||||
|
||||
case Routes.ioniaGiftCardDetailPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
|
||||
|
||||
case Routes.ioniaPaymentStatusPage:
|
||||
final args = settings.arguments as List;
|
||||
final paymentInfo = args.first as IoniaAnyPayPaymentInfo;
|
||||
final commitedInfo = args[1] as AnyPayPaymentCommittedInfo;
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaPaymentStatusPage>(
|
||||
param1: paymentInfo,
|
||||
param2: commitedInfo));
|
||||
|
||||
default:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => Scaffold(
|
||||
|
|
|
@ -60,4 +60,18 @@ class Routes {
|
|||
static const moneroNewWalletFromWelcome = '/monero_new_wallet';
|
||||
static const addressPage = '/address_page';
|
||||
static const fullscreenQR = '/fullscreen_qr';
|
||||
}
|
||||
static const ioniaWelcomePage = '/cake_pay_welcome_page';
|
||||
static const ioniaCreateAccountPage = '/cake_pay_create_account_page';
|
||||
static const ioniaLoginPage = '/cake_pay_login_page';
|
||||
static const ioniaManageCardsPage = '/manage_cards_page';
|
||||
static const ioniaBuyGiftCardPage = '/buy_gift_card_page';
|
||||
static const ioniaBuyGiftCardDetailPage = '/buy_gift_card_detail_page';
|
||||
static const ioniaVerifyIoniaOtpPage = '/cake_pay_verify_otp_page';
|
||||
static const ioniaDebitCardPage = '/debit_card_page';
|
||||
static const ioniaActivateDebitCardPage = '/activate_debit_card_page';
|
||||
static const ioniaAccountPage = 'ionia_account_page';
|
||||
static const ioniaAccountCardsPage = 'ionia_account_cards_page';
|
||||
static const ioniaCustomTipPage = 'ionia_custom_tip_page';
|
||||
static const ioniaGiftCardDetailPage = '/ionia_gift_card_detail_page';
|
||||
static const ioniaPaymentStatusPage = '/ionia_payment_status_page';
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'dart:async';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/yat/yat_popup.dart';
|
||||
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
|
@ -14,19 +14,15 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
|||
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/action_button.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
import 'package:cake_wallet/router.dart';
|
||||
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
|
||||
class DashboardPage extends BasePage {
|
||||
DashboardPage({
|
||||
|
@ -85,7 +81,7 @@ class DashboardPage extends BasePage {
|
|||
|
||||
final DashboardViewModel walletViewModel;
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
final controller = PageController(initialPage: 0);
|
||||
final controller = PageController(initialPage: 1);
|
||||
|
||||
var pages = <Widget>[];
|
||||
bool _isEffectsInstalled = false;
|
||||
|
@ -221,7 +217,7 @@ class DashboardPage extends BasePage {
|
|||
if (_isEffectsInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
pages.add(MarketPlacePage(dashboardViewModel: walletViewModel));
|
||||
pages.add(balancePage);
|
||||
pages.add(TransactionsPage(dashboardViewModel: walletViewModel));
|
||||
_isEffectsInstalled = true;
|
||||
|
|
80
lib/src/screens/dashboard/widgets/market_place_page.dart
Normal file
|
@ -0,0 +1,80 @@
|
|||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/market_place_item.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class MarketPlacePage extends StatelessWidget {
|
||||
|
||||
MarketPlacePage({@required this.dashboardViewModel});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: RawScrollbar(
|
||||
thumbColor: Colors.white.withOpacity(0.15),
|
||||
radius: Radius.circular(20),
|
||||
isAlwaysShown: true,
|
||||
thickness: 2,
|
||||
controller: _scrollController,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 50),
|
||||
Text(
|
||||
S.of(context).market_place,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).accentTextTheme.display3.backgroundColor,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
children: <Widget>[
|
||||
SizedBox(height: 20),
|
||||
MarketPlaceItem(
|
||||
onTap: () =>_navigatorToGiftCardsPage(context),
|
||||
title: S.of(context).cake_pay_title,
|
||||
subTitle: S.of(context).cake_pay_subtitle,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
void _navigatorToGiftCardsPage(BuildContext context) {
|
||||
final walletType = dashboardViewModel.type;
|
||||
|
||||
switch (walletType) {
|
||||
case WalletType.haven:
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: S.of(context).gift_cards_unavailable,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
break;
|
||||
default:
|
||||
Navigator.of(context).pushNamed(Routes.ioniaWelcomePage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
154
lib/src/screens/ionia/auth/ionia_create_account_page.dart
Normal file
|
@ -0,0 +1,154 @@
|
|||
import 'package:cake_wallet/core/email_validator.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class IoniaCreateAccountPage extends BasePage {
|
||||
IoniaCreateAccountPage(this._authViewModel)
|
||||
: _emailFocus = FocusNode(),
|
||||
_emailController = TextEditingController(),
|
||||
_formKey = GlobalKey<FormState>() {
|
||||
_emailController.text = _authViewModel.email;
|
||||
_emailController.addListener(() => _authViewModel.email = _emailController.text);
|
||||
}
|
||||
|
||||
final IoniaAuthViewModel _authViewModel;
|
||||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
|
||||
final FocusNode _emailFocus;
|
||||
final TextEditingController _emailController;
|
||||
|
||||
static const privacyPolicyUrl = 'https://ionia.docsend.com/view/jaqsmbq9w7dzvnqf';
|
||||
static const termsAndConditionsUrl = 'https://ionia.docsend.com/view/hi9awnwxr6mqgiqj';
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.sign_up,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _authViewModel.createUserState, (IoniaCreateAccountState state) {
|
||||
if (state is IoniaCreateStateFailure) {
|
||||
_onCreateUserFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaCreateStateSuccess) {
|
||||
_onCreateSuccessful(context, _authViewModel);
|
||||
}
|
||||
});
|
||||
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.all(24),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: BaseTextFormField(
|
||||
hintText: S.of(context).email_address,
|
||||
focusNode: _emailFocus,
|
||||
validator: EmailValidator(),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
controller: _emailController,
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
text: S.of(context).create_account,
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
await _authViewModel.createUser(_emailController.text);
|
||||
},
|
||||
isLoading: _authViewModel.createUserState is IoniaCreateStateLoading,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text: S.of(context).agree_to,
|
||||
style: TextStyle(
|
||||
color: Color(0xff7A93BA),
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: S.of(context).settings_terms_and_conditions,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () async {
|
||||
if (await canLaunch(termsAndConditionsUrl)) await launch(termsAndConditionsUrl);
|
||||
},
|
||||
),
|
||||
TextSpan(text: ' ${S.of(context).and} '),
|
||||
TextSpan(
|
||||
text: S.of(context).privacy_policy,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () async {
|
||||
if (await canLaunch(privacyPolicyUrl)) await launch(privacyPolicyUrl);
|
||||
}),
|
||||
TextSpan(text: ' ${S.of(context).by_cake_pay}'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onCreateUserFailure(BuildContext context, String error) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.create_account,
|
||||
alertContent: error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
|
||||
void _onCreateSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed(
|
||||
context,
|
||||
Routes.ioniaVerifyIoniaOtpPage,
|
||||
arguments: [authViewModel.email, false],
|
||||
);
|
||||
}
|
112
lib/src/screens/ionia/auth/ionia_login_page.dart
Normal file
|
@ -0,0 +1,112 @@
|
|||
import 'package:cake_wallet/core/email_validator.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class IoniaLoginPage extends BasePage {
|
||||
IoniaLoginPage(this._authViewModel)
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
_emailController = TextEditingController() {
|
||||
_emailController.text = _authViewModel.email;
|
||||
_emailController.addListener(() => _authViewModel.email = _emailController.text);
|
||||
}
|
||||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
|
||||
final IoniaAuthViewModel _authViewModel;
|
||||
|
||||
@override
|
||||
Color get titleColor => Colors.black;
|
||||
|
||||
final TextEditingController _emailController;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.login,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _authViewModel.signInState, (IoniaCreateAccountState state) {
|
||||
if (state is IoniaCreateStateFailure) {
|
||||
_onLoginUserFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaCreateStateSuccess) {
|
||||
_onLoginSuccessful(context, _authViewModel);
|
||||
}
|
||||
});
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.all(24),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: BaseTextFormField(
|
||||
hintText: S.of(context).email_address,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: EmailValidator(),
|
||||
controller: _emailController,
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
text: S.of(context).login,
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
await _authViewModel.signIn(_emailController.text);
|
||||
},
|
||||
isLoading: _authViewModel.signInState is IoniaCreateStateLoading,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onLoginUserFailure(BuildContext context, String error) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.login,
|
||||
alertContent: error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
|
||||
void _onLoginSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed(
|
||||
context,
|
||||
Routes.ioniaVerifyIoniaOtpPage,
|
||||
arguments: [authViewModel.email, true],
|
||||
);
|
||||
}
|
151
lib/src/screens/ionia/auth/ionia_verify_otp_page.dart
Normal file
|
@ -0,0 +1,151 @@
|
|||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class IoniaVerifyIoniaOtp extends BasePage {
|
||||
IoniaVerifyIoniaOtp(this._authViewModel, this._email, this.isSignIn)
|
||||
: _codeController = TextEditingController(),
|
||||
_codeFocus = FocusNode() {
|
||||
_codeController.addListener(() {
|
||||
final otp = _codeController.text;
|
||||
_authViewModel.otp = otp;
|
||||
if (otp.length > 3) {
|
||||
_authViewModel.otpState = IoniaOtpSendEnabled();
|
||||
} else {
|
||||
_authViewModel.otpState = IoniaOtpSendDisabled();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final IoniaAuthViewModel _authViewModel;
|
||||
final bool isSignIn;
|
||||
|
||||
final String _email;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.verification,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final TextEditingController _codeController;
|
||||
final FocusNode _codeFocus;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _authViewModel.otpState, (IoniaOtpState state) {
|
||||
if (state is IoniaOtpFailure) {
|
||||
_onOtpFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaOtpSuccess) {
|
||||
_onOtpSuccessful(context);
|
||||
}
|
||||
});
|
||||
return KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _codeFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
]),
|
||||
child: Container(
|
||||
height: 0,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.all(24),
|
||||
content: Column(
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
hintText: S.of(context).enter_code,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
focusNode: _codeFocus,
|
||||
controller: _codeController,
|
||||
),
|
||||
SizedBox(height: 14),
|
||||
Text(
|
||||
S.of(context).fill_code,
|
||||
style: TextStyle(color: Color(0xff7A93BA), fontSize: 12),
|
||||
),
|
||||
SizedBox(height: 34),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(S.of(context).dont_get_code),
|
||||
SizedBox(width: 20),
|
||||
InkWell(
|
||||
onTap: () => isSignIn
|
||||
? _authViewModel.signIn(_email)
|
||||
: _authViewModel.createUser(_email),
|
||||
child: Text(
|
||||
S.of(context).resend_code,
|
||||
style: textSmallSemiBold(color: Palette.blueCraiola),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
text: S.of(context).continue_text,
|
||||
onPressed: () async => await _authViewModel.verifyEmail(_codeController.text),
|
||||
isDisabled: _authViewModel.otpState is IoniaOtpSendDisabled,
|
||||
isLoading: _authViewModel.otpState is IoniaOtpValidating,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onOtpFailure(BuildContext context, String error) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.verification,
|
||||
alertContent: error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
|
||||
void _onOtpSuccessful(BuildContext context) =>
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst);
|
||||
}
|
104
lib/src/screens/ionia/auth/ionia_welcome_page.dart
Normal file
|
@ -0,0 +1,104 @@
|
|||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class IoniaWelcomePage extends BasePage {
|
||||
IoniaWelcomePage(this._cardsListViewModel);
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.welcome_to_cakepay,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final IoniaGiftCardsListViewModel _cardsListViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _cardsListViewModel.isLoggedIn, (bool state) {
|
||||
if (state) {
|
||||
Navigator.pushReplacementNamed(context, Routes.ioniaManageCardsPage);
|
||||
}
|
||||
});
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SizedBox(height: 100),
|
||||
Text(
|
||||
S.of(context).about_cake_pay,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
S.of(context).cake_pay_account_note,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
PrimaryButton(
|
||||
text: S.of(context).create_account,
|
||||
onPressed: () => Navigator.of(context).pushNamed(Routes.ioniaCreateAccountPage),
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
S.of(context).already_have_account,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.ioniaLoginPage),
|
||||
child: Text(
|
||||
S.of(context).login,
|
||||
style: TextStyle(
|
||||
color: Palette.blueCraiola,
|
||||
fontSize: 18,
|
||||
letterSpacing: 1.5,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
181
lib/src/screens/ionia/cards/ionia_account_cards_page.dart
Normal file
|
@ -0,0 +1,181 @@
|
|||
import 'dart:ffi';
|
||||
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class IoniaAccountCardsPage extends BasePage {
|
||||
IoniaAccountCardsPage(this.ioniaAccountViewModel);
|
||||
|
||||
final IoniaAccountViewModel ioniaAccountViewModel;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.of(context).cards,
|
||||
style: textLargeSemiBold(
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return _IoniaCardTabs(ioniaAccountViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
class _IoniaCardTabs extends StatefulWidget {
|
||||
_IoniaCardTabs(this.ioniaAccountViewModel);
|
||||
|
||||
final IoniaAccountViewModel ioniaAccountViewModel;
|
||||
|
||||
@override
|
||||
_IoniaCardTabsState createState() => _IoniaCardTabsState();
|
||||
}
|
||||
|
||||
class _IoniaCardTabsState extends State<_IoniaCardTabs> with SingleTickerProviderStateMixin {
|
||||
TabController _tabController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_tabController.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
width: 230,
|
||||
padding: EdgeInsets.all(5),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(
|
||||
25.0,
|
||||
),
|
||||
),
|
||||
child: Theme(
|
||||
data: ThemeData(primaryTextTheme: TextTheme(body2: TextStyle(backgroundColor: Colors.transparent))),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
indicator: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
25.0,
|
||||
),
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
),
|
||||
labelColor: Theme.of(context).primaryTextTheme.display4.backgroundColor,
|
||||
unselectedLabelColor: Theme.of(context).primaryTextTheme.title.color,
|
||||
tabs: [
|
||||
Tab(
|
||||
text: S.of(context).active,
|
||||
),
|
||||
Tab(
|
||||
text: S.of(context).redeemed,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: Observer(builder: (_) {
|
||||
final viewModel = widget.ioniaAccountViewModel;
|
||||
return TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_IoniaCardListView(
|
||||
emptyText: S.of(context).gift_card_balance_note,
|
||||
merchList: viewModel.activeMechs,
|
||||
onTap: (giftCard) {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.ioniaGiftCardDetailPage,
|
||||
arguments: [giftCard])
|
||||
.then((_) => viewModel.updateUserGiftCards());
|
||||
}),
|
||||
_IoniaCardListView(
|
||||
emptyText: S.of(context).gift_card_redeemed_note,
|
||||
merchList: viewModel.redeemedMerchs,
|
||||
onTap: (giftCard) {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.ioniaGiftCardDetailPage,
|
||||
arguments: [giftCard])
|
||||
.then((_) => viewModel.updateUserGiftCards());
|
||||
}),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _IoniaCardListView extends StatelessWidget {
|
||||
_IoniaCardListView({
|
||||
Key key,
|
||||
@required this.emptyText,
|
||||
@required this.merchList,
|
||||
@required this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
final String emptyText;
|
||||
final List<IoniaGiftCard> merchList;
|
||||
final void Function(IoniaGiftCard giftCard) onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return merchList.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
emptyText,
|
||||
textAlign: TextAlign.center,
|
||||
style: textSmall(
|
||||
color: Theme.of(context).primaryTextTheme.overline.color,
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: merchList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final merchant = merchList[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: CardItem(
|
||||
onTap: () => onTap?.call(merchant),
|
||||
title: merchant.legalName,
|
||||
backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
|
||||
discount: 0,
|
||||
discountBackground: AssetImage('assets/images/red_badge_discount.png'),
|
||||
titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
subtitleColor: Theme.of(context).hintColor,
|
||||
subTitle: '',
|
||||
logoUrl: merchant.logoUrl,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
181
lib/src/screens/ionia/cards/ionia_account_page.dart
Normal file
|
@ -0,0 +1,181 @@
|
|||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class IoniaAccountPage extends BasePage {
|
||||
IoniaAccountPage(this.ioniaAccountViewModel);
|
||||
|
||||
final IoniaAccountViewModel ioniaAccountViewModel;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.account,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.all(24),
|
||||
content: Column(
|
||||
children: [
|
||||
_GradiantContainer(
|
||||
content: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => RichText(
|
||||
text: TextSpan(
|
||||
text: '${ioniaAccountViewModel.countOfMerch}',
|
||||
style: textLargeSemiBold(),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: ' ${S.of(context).active_cards}',
|
||||
style: textSmall(color: Colors.white.withOpacity(0.7))),
|
||||
],
|
||||
),
|
||||
)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.pushNamed(context, Routes.ioniaAccountCardsPage)
|
||||
.then((_) => ioniaAccountViewModel.updateUserGiftCards());
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
S.of(context).view_all,
|
||||
style: textSmallSemiBold(),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
//Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// _GradiantContainer(
|
||||
// padding: EdgeInsets.all(16),
|
||||
// width: deviceWidth * 0.28,
|
||||
// content: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Text(
|
||||
// S.of(context).total_saving,
|
||||
// style: textSmall(),
|
||||
// ),
|
||||
// SizedBox(height: 8),
|
||||
// Text(
|
||||
// '\$100',
|
||||
// style: textMediumSemiBold(),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// _GradiantContainer(
|
||||
// padding: EdgeInsets.all(16),
|
||||
// width: deviceWidth * 0.28,
|
||||
// content: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Text(
|
||||
// S.of(context).last_30_days,
|
||||
// style: textSmall(),
|
||||
// ),
|
||||
// SizedBox(height: 8),
|
||||
// Text(
|
||||
// '\$100',
|
||||
// style: textMediumSemiBold(),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// _GradiantContainer(
|
||||
// padding: EdgeInsets.all(16),
|
||||
// width: deviceWidth * 0.28,
|
||||
// content: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Text(
|
||||
// S.of(context).avg_savings,
|
||||
// style: textSmall(),
|
||||
// ),
|
||||
// SizedBox(height: 8),
|
||||
// Text(
|
||||
// '10%',
|
||||
// style: textMediumSemiBold(),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
//),
|
||||
SizedBox(height: 40),
|
||||
Observer(
|
||||
builder: (_) => IoniaTile(title: S.of(context).email_address, subTitle: ioniaAccountViewModel.email),
|
||||
),
|
||||
Divider()
|
||||
],
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.all(30),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
PrimaryButton(
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
text: S.of(context).logout,
|
||||
onPressed: () {
|
||||
ioniaAccountViewModel.logout();
|
||||
Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (route) => false);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GradiantContainer extends StatelessWidget {
|
||||
const _GradiantContainer({
|
||||
Key key,
|
||||
@required this.content,
|
||||
this.padding,
|
||||
this.width,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget content;
|
||||
final EdgeInsets padding;
|
||||
final double width;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: content,
|
||||
width: width,
|
||||
padding: padding ?? EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
Theme.of(context).accentColor,
|
||||
],
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
114
lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart
Normal file
|
@ -0,0 +1,114 @@
|
|||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class IoniaActivateDebitCardPage extends BasePage {
|
||||
|
||||
IoniaActivateDebitCardPage(this._cardsListViewModel);
|
||||
|
||||
final IoniaGiftCardsListViewModel _cardsListViewModel;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.debit_card,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _cardsListViewModel.createCardState, (IoniaCreateCardState state) {
|
||||
if (state is IoniaCreateCardFailure) {
|
||||
_onCreateCardFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaCreateCardSuccess) {
|
||||
_onCreateCardSuccess(context);
|
||||
}
|
||||
});
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
Text(S.of(context).debit_card_terms),
|
||||
SizedBox(height: 24),
|
||||
Text(S.of(context).please_reference_document),
|
||||
SizedBox(height: 40),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextIconButton(
|
||||
label: S.current.cardholder_agreement,
|
||||
onTap: () {},
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
TextIconButton(
|
||||
label: S.current.e_sign_consent,
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomSection: LoadingPrimaryButton(
|
||||
onPressed: () {
|
||||
_cardsListViewModel.createCard();
|
||||
},
|
||||
isLoading: _cardsListViewModel.createCardState is IoniaCreateCardLoading,
|
||||
text: S.of(context).agree_and_continue,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onCreateCardFailure(BuildContext context, String errorMessage) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.error,
|
||||
alertContent: errorMessage,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
|
||||
void _onCreateCardSuccess(BuildContext context) {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.ioniaDebitCardPage,
|
||||
);
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).congratulations,
|
||||
alertContent: S.of(context).you_now_have_debit_card,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
492
lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart
Normal file
|
@ -0,0 +1,492 @@
|
|||
import 'dart:ui';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_tip.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/confirm_modal.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_alert_model.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/discount_badge.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
|
||||
class IoniaBuyGiftCardDetailPage extends BasePage {
|
||||
IoniaBuyGiftCardDetailPage(this.ioniaPurchaseViewModel);
|
||||
|
||||
final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
ioniaPurchaseViewModel.ioniaMerchant.legalName,
|
||||
style: textMediumSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context)
|
||||
=> ioniaPurchaseViewModel.ioniaMerchant.discount > 0
|
||||
? DiscountBadge(percentage: ioniaPurchaseViewModel.ioniaMerchant.discount)
|
||||
: null;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => ioniaPurchaseViewModel.invoiceCreationState, (ExecutionState state) {
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => ioniaPurchaseViewModel.invoiceCommittingState, (ExecutionState state) {
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Navigator.of(context).pushReplacementNamed(
|
||||
Routes.ioniaPaymentStatusPage,
|
||||
arguments: [
|
||||
ioniaPurchaseViewModel.paymentInfo,
|
||||
ioniaPurchaseViewModel.committedInfo]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Observer(builder: (_) {
|
||||
final tipAmount = ioniaPurchaseViewModel.tipAmount;
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(height: 36),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 24),
|
||||
margin: EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).primaryTextTheme.subhead.color,
|
||||
Theme.of(context).primaryTextTheme.subhead.decorationColor,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).gift_card_amount,
|
||||
style: textSmall(),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'\$${ioniaPurchaseViewModel.giftCardAmount.toStringAsFixed(2)}',
|
||||
style: textXLargeSemiBold(),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).bill_amount,
|
||||
style: textSmall(),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'\$${ioniaPurchaseViewModel.billAmount.toStringAsFixed(2)}',
|
||||
style: textLargeSemiBold(),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).tip,
|
||||
style: textSmall(),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'\$${tipAmount.toStringAsFixed(2)}',
|
||||
style: textLargeSemiBold(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).tip,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Observer(
|
||||
builder: (_) => TipButtonGroup(
|
||||
selectedTip: ioniaPurchaseViewModel.selectedTip.percentage,
|
||||
tipsList: ioniaPurchaseViewModel.tips,
|
||||
onSelect: (value) => ioniaPurchaseViewModel.addTip(value),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: TextIconButton(
|
||||
label: S.of(context).how_to_use_card,
|
||||
onTap: () => _showHowToUseCard(context, ioniaPurchaseViewModel.ioniaMerchant),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: Observer(builder: (_) {
|
||||
return LoadingPrimaryButton(
|
||||
isLoading: ioniaPurchaseViewModel.invoiceCreationState is IsExecutingState ||
|
||||
ioniaPurchaseViewModel.invoiceCommittingState is IsExecutingState,
|
||||
onPressed: () => purchaseCard(context),
|
||||
text: S.of(context).purchase_gift_card,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
);
|
||||
}),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: () => _showTermsAndCondition(context),
|
||||
child: Text(S.of(context).settings_terms_and_conditions,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).primaryTextTheme.body1.color,
|
||||
).copyWith(fontSize: 12)),
|
||||
),
|
||||
SizedBox(height: 16)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showTermsAndCondition(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return IoniaAlertModal(
|
||||
title: S.of(context).settings_terms_and_conditions,
|
||||
content: Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text(
|
||||
ioniaPurchaseViewModel.ioniaMerchant.termsAndConditions,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).textTheme.display2.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
actionTitle: S.of(context).agree,
|
||||
showCloseButton: false,
|
||||
heightFactor: 0.6,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> purchaseCard(BuildContext context) async {
|
||||
await ioniaPurchaseViewModel.createInvoice();
|
||||
|
||||
if (ioniaPurchaseViewModel.invoiceCreationState is ExecutedSuccessfullyState) {
|
||||
await _presentSuccessfulInvoiceCreationPopup(context);
|
||||
}
|
||||
}
|
||||
|
||||
void _showHowToUseCard(
|
||||
BuildContext context,
|
||||
IoniaMerchant merchant,
|
||||
) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return IoniaAlertModal(
|
||||
title: S.of(context).how_to_use_card,
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: merchant.instructions
|
||||
.map((instruction) {
|
||||
return [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Text(
|
||||
instruction.header,
|
||||
style: textLargeSemiBold(
|
||||
color: Theme.of(context).textTheme.display2.color,
|
||||
),
|
||||
)),
|
||||
Text(
|
||||
instruction.body,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).textTheme.display2.color,
|
||||
),
|
||||
)
|
||||
];
|
||||
})
|
||||
.expand((e) => e)
|
||||
.toList()),
|
||||
actionTitle: S.current.send_got_it,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _presentSuccessfulInvoiceCreationPopup(BuildContext context) async {
|
||||
final amount = ioniaPurchaseViewModel.invoice.totalAmount;
|
||||
final addresses = ioniaPurchaseViewModel.invoice.outAddresses;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return IoniaConfirmModal(
|
||||
alertTitle: S.of(context).confirm_sending,
|
||||
alertContent: Container(
|
||||
height: 200,
|
||||
padding: EdgeInsets.all(15),
|
||||
child: Column(children: [
|
||||
Row(children: [
|
||||
Text(S.of(context).payment_id,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none)),
|
||||
Text(ioniaPurchaseViewModel.invoice.paymentId,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none))
|
||||
], mainAxisAlignment: MainAxisAlignment.spaceBetween),
|
||||
SizedBox(height: 10),
|
||||
Row(children: [
|
||||
Text(S.of(context).amount,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none)),
|
||||
Text('$amount ${ioniaPurchaseViewModel.invoice.chain}',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none))
|
||||
], mainAxisAlignment: MainAxisAlignment.spaceBetween),
|
||||
SizedBox(height: 25),
|
||||
Row(children: [
|
||||
Text(S.of(context).recipient_address,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none))
|
||||
], mainAxisAlignment: MainAxisAlignment.center),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemBuilder: (_, int index) {
|
||||
return Text(addresses[index],
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none));
|
||||
},
|
||||
itemCount: addresses.length,
|
||||
physics: NeverScrollableScrollPhysics()))
|
||||
])),
|
||||
rightButtonText: S.of(context).ok,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
leftActionColor: Color(0xffFF6600),
|
||||
rightActionColor: Theme.of(context).accentTextTheme.body2.color,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(context).pop();
|
||||
await ioniaPurchaseViewModel.commitPaymentInvoice();
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(context).pop());
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TipButtonGroup extends StatelessWidget {
|
||||
const TipButtonGroup({
|
||||
Key key,
|
||||
@required this.selectedTip,
|
||||
@required this.onSelect,
|
||||
@required this.tipsList,
|
||||
}) : super(key: key);
|
||||
|
||||
final Function(IoniaTip) onSelect;
|
||||
final double selectedTip;
|
||||
final List<IoniaTip> tipsList;
|
||||
|
||||
bool _isSelected(double value) => selectedTip == value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 50,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: tipsList.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final tip = tipsList[index];
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: TipButton(
|
||||
isSelected: _isSelected(tip.percentage),
|
||||
onTap: () => onSelect(tip),
|
||||
caption: '${tip.percentage}%',
|
||||
subTitle: '\$${tip.additionalAmount}',
|
||||
));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class TipButton extends StatelessWidget {
|
||||
const TipButton({
|
||||
@required this.caption,
|
||||
this.subTitle,
|
||||
@required this.onTap,
|
||||
this.isSelected = false,
|
||||
});
|
||||
|
||||
final String caption;
|
||||
final String subTitle;
|
||||
final bool isSelected;
|
||||
final void Function() onTap;
|
||||
|
||||
bool isDark(BuildContext context) => Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
Color captionTextColor(BuildContext context) {
|
||||
if (isDark(context)) {
|
||||
return Theme.of(context).primaryTextTheme.title.color;
|
||||
}
|
||||
|
||||
return isSelected
|
||||
? Theme.of(context).accentTextTheme.title.color
|
||||
: Theme.of(context).primaryTextTheme.title.color;
|
||||
}
|
||||
|
||||
Color subTitleTextColor(BuildContext context) {
|
||||
if (isDark(context)) {
|
||||
return Theme.of(context).primaryTextTheme.title.color;
|
||||
}
|
||||
|
||||
return isSelected
|
||||
? Theme.of(context).accentTextTheme.title.color
|
||||
: Theme.of(context).primaryTextTheme.overline.color;
|
||||
}
|
||||
|
||||
Color backgroundColor(BuildContext context) {
|
||||
if (isDark(context)) {
|
||||
return isSelected
|
||||
? null
|
||||
: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.01);
|
||||
}
|
||||
|
||||
return isSelected
|
||||
? null
|
||||
: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 49,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(caption,
|
||||
style: textSmallSemiBold(
|
||||
color: captionTextColor(context))),
|
||||
if (subTitle != null) ...[
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
subTitle,
|
||||
style: textXxSmallSemiBold(
|
||||
color: subTitleTextColor(context),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 18, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: backgroundColor(context),
|
||||
gradient: isSelected
|
||||
? LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).primaryTextTheme.subhead.color,
|
||||
Theme.of(context).primaryTextTheme.subhead.decorationColor,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
185
lib/src/screens/ionia/cards/ionia_buy_gift_card.dart
Normal file
|
@ -0,0 +1,185 @@
|
|||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class IoniaBuyGiftCardPage extends BasePage {
|
||||
IoniaBuyGiftCardPage(
|
||||
this.ioniaBuyCardViewModel,
|
||||
) : _amountFieldFocus = FocusNode(),
|
||||
_amountController = TextEditingController() {
|
||||
_amountController.addListener(() {
|
||||
ioniaBuyCardViewModel.onAmountChanged(_amountController.text);
|
||||
});
|
||||
}
|
||||
|
||||
final IoniaBuyCardViewModel ioniaBuyCardViewModel;
|
||||
|
||||
@override
|
||||
String get title => S.current.enter_amount;
|
||||
|
||||
@override
|
||||
Color get titleColor => Colors.white;
|
||||
|
||||
@override
|
||||
bool get extendBodyBehindAppBar => true;
|
||||
|
||||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
Color get textColor => currentTheme.type == ThemeType.dark ? Colors.white : Color(0xff393939);
|
||||
|
||||
final TextEditingController _amountController;
|
||||
final FocusNode _amountFieldFocus;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final _width = MediaQuery.of(context).size.width;
|
||||
final merchant = ioniaBuyCardViewModel.ioniaMerchant;
|
||||
return KeyboardActions(
|
||||
disableScroll: true,
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _amountFieldFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
]),
|
||||
child: Container(
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 25),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).primaryTextTheme.subhead.color,
|
||||
Theme.of(context).primaryTextTheme.subhead.decorationColor,
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(height: 150),
|
||||
BaseTextFormField(
|
||||
controller: _amountController,
|
||||
focusNode: _amountFieldFocus,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\-|\ ]')),
|
||||
WhitelistingTextInputFormatter(RegExp(r'^\d+(\.|\,)?\d{0,2}'))],
|
||||
hintText: '1000',
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline.color,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 36,
|
||||
),
|
||||
borderColor: Theme.of(context).primaryTextTheme.headline.color,
|
||||
textColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 36,
|
||||
),
|
||||
suffixIcon: SizedBox(
|
||||
width: _width / 6,
|
||||
),
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 5.0,
|
||||
left: _width / 4,
|
||||
),
|
||||
child: Text(
|
||||
'USD: ',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 36,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).min_amount(merchant.minimumCardPurchase.toStringAsFixed(2)),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline.color,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
S.of(context).max_amount(merchant.maximumCardPurchase.toStringAsFixed(2)),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: CardItem(
|
||||
title: merchant.legalName,
|
||||
backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
|
||||
discount: merchant.discount,
|
||||
titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
subtitleColor: Theme.of(context).hintColor,
|
||||
subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline,
|
||||
logoUrl: merchant.logoUrl,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Observer(builder: (_) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: PrimaryButton(
|
||||
onPressed: () => Navigator.of(context).pushNamed(
|
||||
Routes.ioniaBuyGiftCardDetailPage,
|
||||
arguments: [
|
||||
ioniaBuyCardViewModel.amount,
|
||||
ioniaBuyCardViewModel.ioniaMerchant,
|
||||
],
|
||||
),
|
||||
text: S.of(context).continue_text,
|
||||
isDisabled: !ioniaBuyCardViewModel.isEnablePurchase,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
176
lib/src/screens/ionia/cards/ionia_custom_tip_page.dart
Normal file
|
@ -0,0 +1,176 @@
|
|||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class IoniaCustomTipPage extends BasePage {
|
||||
IoniaCustomTipPage(
|
||||
this.ioniaPurchaseViewModel,
|
||||
) : _amountFieldFocus = FocusNode(),
|
||||
_amountController = TextEditingController() {
|
||||
_amountController.addListener(() {
|
||||
// ioniaPurchaseViewModel.onTipChanged(_amountController.text);
|
||||
});
|
||||
}
|
||||
|
||||
final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel;
|
||||
|
||||
|
||||
@override
|
||||
String get title => S.current.enter_amount;
|
||||
|
||||
@override
|
||||
Color get titleColor => Colors.white;
|
||||
|
||||
@override
|
||||
bool get extendBodyBehindAppBar => true;
|
||||
|
||||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
Color get textColor => currentTheme.type == ThemeType.dark ? Colors.white : Color(0xff393939);
|
||||
|
||||
final TextEditingController _amountController;
|
||||
final FocusNode _amountFieldFocus;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final _width = MediaQuery.of(context).size.width;
|
||||
final merchant = ioniaPurchaseViewModel.ioniaMerchant;
|
||||
return KeyboardActions(
|
||||
disableScroll: true,
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _amountFieldFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
]),
|
||||
child: Container(
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 25),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).primaryTextTheme.subhead.color,
|
||||
Theme.of(context).primaryTextTheme.subhead.decorationColor,
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 150),
|
||||
BaseTextFormField(
|
||||
controller: _amountController,
|
||||
focusNode: _amountFieldFocus,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\-|\ ]'))],
|
||||
hintText: '1000',
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline.color,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 36,
|
||||
),
|
||||
borderColor: Theme.of(context).primaryTextTheme.headline.color,
|
||||
textColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 36,
|
||||
),
|
||||
suffixIcon: SizedBox(
|
||||
width: _width / 6,
|
||||
),
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 5.0,
|
||||
left: _width / 4,
|
||||
),
|
||||
child: Text(
|
||||
'USD: ',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 36,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Observer(builder: (_) {
|
||||
if (ioniaPurchaseViewModel.percentage == 0.0) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
|
||||
return RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text: '\$${_amountController.text}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline.color,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: ' ${S.of(context).is_percentage} '),
|
||||
TextSpan(text: '${ioniaPurchaseViewModel.percentage}%'),
|
||||
TextSpan(text: ' ${S.of(context).percentageOf(ioniaPurchaseViewModel.amount.toString())} '),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: CardItem(
|
||||
title: merchant.legalName,
|
||||
backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
|
||||
discount: 0.0,
|
||||
titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
subtitleColor: Theme.of(context).hintColor,
|
||||
subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline,
|
||||
logoUrl: merchant.logoUrl,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: PrimaryButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(_amountController.text);
|
||||
},
|
||||
text: S.of(context).add_tip,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
379
lib/src/screens/ionia/cards/ionia_debit_card_page.dart
Normal file
|
@ -0,0 +1,379 @@
|
|||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class IoniaDebitCardPage extends BasePage {
|
||||
final IoniaGiftCardsListViewModel _cardsListViewModel;
|
||||
|
||||
IoniaDebitCardPage(this._cardsListViewModel);
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.debit_card,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
final cardState = _cardsListViewModel.cardState;
|
||||
if (cardState is IoniaFetchingCard) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (cardState is IoniaCardSuccess) {
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: _IoniaDebitCard(
|
||||
cardInfo: cardState.card,
|
||||
),
|
||||
),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: Text(
|
||||
S.of(context).billing_address_info,
|
||||
style: textSmall(color: Theme.of(context).textTheme.display1.color),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
PrimaryButton(
|
||||
text: S.of(context).order_physical_card,
|
||||
onPressed: () {},
|
||||
color: Color(0xffE9F2FC),
|
||||
textColor: Theme.of(context).textTheme.display2.color,
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
PrimaryButton(
|
||||
text: S.of(context).add_value,
|
||||
onPressed: () {},
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
SizedBox(height: 16)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_IoniaDebitCard(isCardSample: true),
|
||||
SizedBox(height: 40),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextIconButton(
|
||||
label: S.current.how_to_use_card,
|
||||
onTap: () => _showHowToUseCard(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
TextIconButton(
|
||||
label: S.current.frequently_asked_questions,
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 50),
|
||||
Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
margin: EdgeInsets.all(8),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Color.fromRGBO(233, 242, 252, 1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: S.of(context).get_a,
|
||||
style: textMedium(color: Theme.of(context).textTheme.display2.color),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: S.of(context).digital_and_physical_card,
|
||||
style: textMediumBold(color: Theme.of(context).textTheme.display2.color),
|
||||
),
|
||||
TextSpan(
|
||||
text: S.of(context).get_card_note,
|
||||
)
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 32,
|
||||
),
|
||||
bottomSection: PrimaryButton(
|
||||
text: S.of(context).activate,
|
||||
onPressed: () => _showHowToUseCard(context, activate: true),
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showHowToUseCard(BuildContext context, {bool activate = false}) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertBackground(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(height: 10),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 24, left: 24, right: 24),
|
||||
margin: EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).backgroundColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).how_to_use_card,
|
||||
style: textLargeSemiBold(
|
||||
color: Theme.of(context).textTheme.body1.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text(
|
||||
S.of(context).signup_for_card_accept_terms,
|
||||
style: textSmallSemiBold(
|
||||
color: Theme.of(context).textTheme.display2.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
_TitleSubtitleTile(
|
||||
title: S.of(context).add_fund_to_card('1000'),
|
||||
subtitle: S.of(context).use_card_info_two,
|
||||
),
|
||||
SizedBox(height: 21),
|
||||
_TitleSubtitleTile(
|
||||
title: S.of(context).use_card_info_three,
|
||||
subtitle: S.of(context).optionally_order_card,
|
||||
),
|
||||
SizedBox(height: 35),
|
||||
PrimaryButton(
|
||||
onPressed: () => activate
|
||||
? Navigator.pushNamed(context, Routes.ioniaActivateDebitCardPage)
|
||||
: Navigator.pop(context),
|
||||
text: S.of(context).send_got_it,
|
||||
color: Color.fromRGBO(233, 242, 252, 1),
|
||||
textColor: Theme.of(context).textTheme.display2.color,
|
||||
),
|
||||
SizedBox(height: 21),
|
||||
],
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(bottom: 40),
|
||||
child: CircleAvatar(
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
color: Colors.black,
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _IoniaDebitCard extends StatefulWidget {
|
||||
final bool isCardSample;
|
||||
final IoniaVirtualCard cardInfo;
|
||||
const _IoniaDebitCard({
|
||||
Key key,
|
||||
this.isCardSample = false,
|
||||
this.cardInfo,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_IoniaDebitCardState createState() => _IoniaDebitCardState();
|
||||
}
|
||||
|
||||
class _IoniaDebitCardState extends State<_IoniaDebitCard> {
|
||||
bool _showDetails = false;
|
||||
void _toggleVisibility() {
|
||||
setState(() => _showDetails = !_showDetails);
|
||||
}
|
||||
|
||||
String _formatPan(String pan) {
|
||||
if (pan == null) return '';
|
||||
return pan.replaceAllMapped(RegExp(r'.{4}'), (match) => '${match.group(0)} ');
|
||||
}
|
||||
|
||||
String get _getLast4 => widget.isCardSample ? '0000' : widget.cardInfo.pan.substring(widget.cardInfo.pan.length - 5);
|
||||
|
||||
String get _getSpendLimit => widget.isCardSample ? '10000' : widget.cardInfo.spendLimit.toStringAsFixed(2);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 19),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).primaryTextTheme.subhead.color,
|
||||
Theme.of(context).primaryTextTheme.subhead.decorationColor,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.current.cakepay_prepaid_card,
|
||||
style: textSmall(),
|
||||
),
|
||||
Image.asset(
|
||||
'assets/images/mastercard.png',
|
||||
width: 54,
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
widget.isCardSample ? S.of(context).upto(_getSpendLimit) : '\$$_getSpendLimit',
|
||||
style: textXLargeSemiBold(),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
_showDetails ? _formatPan(widget.cardInfo.pan) : '**** **** **** $_getLast4',
|
||||
style: textMediumSemiBold(),
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (widget.isCardSample)
|
||||
Text(
|
||||
S.current.no_id_needed,
|
||||
style: textMediumBold(),
|
||||
)
|
||||
else ...[
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
'CVV',
|
||||
style: textXSmallSemiBold(),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
_showDetails ? widget.cardInfo.cvv : '***',
|
||||
style: textMediumSemiBold(),
|
||||
)
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).expires,
|
||||
style: textXSmallSemiBold(),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'${widget.cardInfo.expirationMonth ?? S.of(context).mm}/${widget.cardInfo.expirationYear ?? S.of(context).yy}',
|
||||
style: textMediumSemiBold(),
|
||||
)
|
||||
],
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
if (!widget.isCardSample) ...[
|
||||
SizedBox(height: 8),
|
||||
Center(
|
||||
child: InkWell(
|
||||
onTap: () => _toggleVisibility(),
|
||||
child: Text(
|
||||
_showDetails ? S.of(context).hide_details : S.of(context).show_details,
|
||||
style: textSmall(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TitleSubtitleTile extends StatelessWidget {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
const _TitleSubtitleTile({
|
||||
Key key,
|
||||
@required this.title,
|
||||
@required this.subtitle,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: textSmallSemiBold(color: Theme.of(context).textTheme.display2.color),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: textSmall(color: Theme.of(context).textTheme.display2.color),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
188
lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart
Normal file
|
@ -0,0 +1,188 @@
|
|||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_alert_model.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class IoniaGiftCardDetailPage extends BasePage {
|
||||
IoniaGiftCardDetailPage(this.viewModel);
|
||||
|
||||
final IoniaGiftCardDetailsViewModel viewModel;
|
||||
|
||||
@override
|
||||
Widget leading(BuildContext context) {
|
||||
if (ModalRoute.of(context).isFirst) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final _backButton = Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
size: 16,
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
child: SizedBox(
|
||||
height: 37,
|
||||
width: 37,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => onClose(context),
|
||||
child: _backButton),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
viewModel.giftCard.legalName,
|
||||
style: textMediumSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => viewModel.redeemState, (ExecutionState state) {
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.all(24),
|
||||
content: Column(
|
||||
children: [
|
||||
if (viewModel.giftCard.barcodeUrl != null && viewModel.giftCard.barcodeUrl.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0,
|
||||
vertical: 24,
|
||||
),
|
||||
child: Image.network(viewModel.giftCard.barcodeUrl),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
buildIoniaTile(
|
||||
context,
|
||||
title: S.of(context).gift_card_number,
|
||||
subTitle: viewModel.giftCard.cardNumber,
|
||||
),
|
||||
if (viewModel.giftCard.cardPin?.isNotEmpty ?? false)
|
||||
...[Divider(height: 30),
|
||||
buildIoniaTile(
|
||||
context,
|
||||
title: S.of(context).pin_number,
|
||||
subTitle: viewModel.giftCard.cardPin,
|
||||
)],
|
||||
Divider(height: 30),
|
||||
Observer(builder: (_) =>
|
||||
buildIoniaTile(
|
||||
context,
|
||||
title: S.of(context).amount,
|
||||
subTitle: viewModel.giftCard.remainingAmount.toStringAsFixed(2) ?? '0.00',
|
||||
)),
|
||||
Divider(height: 50),
|
||||
TextIconButton(
|
||||
label: S.of(context).how_to_use_card,
|
||||
onTap: () => _showHowToUseCard(context, viewModel.giftCard),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomSection: Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: Observer(builder: (_) {
|
||||
if (!viewModel.giftCard.isEmpty) {
|
||||
return LoadingPrimaryButton(
|
||||
isLoading: viewModel.redeemState is IsExecutingState,
|
||||
onPressed: () => viewModel.redeem().then((_){
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst);
|
||||
}),
|
||||
text: S.of(context).mark_as_redeemed,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white);
|
||||
}
|
||||
|
||||
return Container();
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildIoniaTile(BuildContext context, {@required String title, @required String subTitle}) {
|
||||
return IoniaTile(
|
||||
title: title,
|
||||
subTitle: subTitle,
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: subTitle));
|
||||
showBar<void>(context,
|
||||
S.of(context).transaction_details_copied(title));
|
||||
});
|
||||
}
|
||||
|
||||
void _showHowToUseCard(
|
||||
BuildContext context,
|
||||
IoniaGiftCard merchant,
|
||||
) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return IoniaAlertModal(
|
||||
title: S.of(context).how_to_use_card,
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: viewModel.giftCard.instructions
|
||||
.map((instruction) {
|
||||
return [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Text(
|
||||
instruction.header,
|
||||
style: textLargeSemiBold(
|
||||
color: Theme.of(context).textTheme.display2.color,
|
||||
),
|
||||
)),
|
||||
Text(
|
||||
instruction.body,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).textTheme.display2.color,
|
||||
),
|
||||
)
|
||||
];
|
||||
})
|
||||
.expand((e) => e)
|
||||
.toList()),
|
||||
actionTitle: S.of(context).send_got_it,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
340
lib/src/screens/ionia/cards/ionia_manage_cards_page.dart
Normal file
|
@ -0,0 +1,340 @@
|
|||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_category.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/card_menu.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_filter_modal.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/debounce.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class IoniaManageCardsPage extends BasePage {
|
||||
IoniaManageCardsPage(this._cardsListViewModel) {
|
||||
_searchController.addListener(() {
|
||||
if (_searchController.text != _cardsListViewModel.searchString) {
|
||||
_searchDebounce.run(() {
|
||||
_cardsListViewModel.searchMerchant(_searchController.text);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
final IoniaGiftCardsListViewModel _cardsListViewModel;
|
||||
|
||||
final _searchDebounce = Debounce(Duration(milliseconds: 500));
|
||||
final _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
|
||||
|
||||
@override
|
||||
Color get backgroundDarkColor => Colors.transparent;
|
||||
|
||||
@override
|
||||
Color get titleColor => currentTheme.type == ThemeType.bright ? Colors.white : Colors.black;
|
||||
|
||||
@override
|
||||
Widget Function(BuildContext, Widget) get rootWrapper => (BuildContext context, Widget scaffold) => Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).accentColor,
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
Theme.of(context).primaryColor,
|
||||
],
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
),
|
||||
),
|
||||
child: scaffold,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get resizeToAvoidBottomInset => false;
|
||||
|
||||
@override
|
||||
Widget get endDrawer => CardMenu();
|
||||
|
||||
@override
|
||||
Widget leading(BuildContext context) {
|
||||
final _backButton = Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: Theme.of(context).accentTextTheme.display3.backgroundColor,
|
||||
size: 16,
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
height: 37,
|
||||
width: 37,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: _backButton),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.of(context).gift_cards,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).accentTextTheme.display3.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
return _TrailingIcon(
|
||||
asset: 'assets/images/profile.png',
|
||||
onPressed: () => Navigator.pushNamed(context, Routes.ioniaAccountPage),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final filterIcon = InkWell(
|
||||
onTap: () async {
|
||||
final selectedFilters = await showCategoryFilter(context, _cardsListViewModel);
|
||||
_cardsListViewModel.setSelectedFilter(selectedFilters);
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/images/filter.png',
|
||||
color: Theme.of(context).textTheme.caption.decorationColor,
|
||||
));
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(14.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 2, right: 22),
|
||||
height: 32,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _SearchWidget(
|
||||
controller: _searchController,
|
||||
)),
|
||||
SizedBox(width: 10),
|
||||
Container(
|
||||
width: 32,
|
||||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.15),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: filterIcon,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: IoniaManageCardsPageBody(
|
||||
cardsListViewModel: _cardsListViewModel,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<IoniaCategory>> showCategoryFilter(
|
||||
BuildContext context,
|
||||
IoniaGiftCardsListViewModel viewModel,
|
||||
) async {
|
||||
return await showPopUp<List<IoniaCategory>>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return IoniaFilterModal(
|
||||
filterViewModel: getIt.get<IoniaFilterViewModel>(),
|
||||
selectedCategories: viewModel.selectedFilters,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IoniaManageCardsPageBody extends StatefulWidget {
|
||||
const IoniaManageCardsPageBody({
|
||||
Key key,
|
||||
@required this.cardsListViewModel,
|
||||
}) : super(key: key);
|
||||
|
||||
final IoniaGiftCardsListViewModel cardsListViewModel;
|
||||
|
||||
@override
|
||||
_IoniaManageCardsPageBodyState createState() => _IoniaManageCardsPageBodyState();
|
||||
}
|
||||
|
||||
class _IoniaManageCardsPageBodyState extends State<IoniaManageCardsPageBody> {
|
||||
double get backgroundHeight => MediaQuery.of(context).size.height * 0.75;
|
||||
double thumbHeight = 72;
|
||||
bool get isAlwaysShowScrollThumb => merchantsList == null ? false : merchantsList.length > 3;
|
||||
|
||||
List<IoniaMerchant> get merchantsList => widget.cardsListViewModel.ioniaMerchants;
|
||||
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_scrollController.addListener(() {
|
||||
final scrollOffsetFromTop = _scrollController.hasClients
|
||||
? (_scrollController.offset / _scrollController.position.maxScrollExtent * (backgroundHeight - thumbHeight))
|
||||
: 0.0;
|
||||
widget.cardsListViewModel.setScrollOffsetFromTop(scrollOffsetFromTop);
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) => Stack(children: [
|
||||
ListView.separated(
|
||||
padding: EdgeInsets.only(left: 2, right: 22),
|
||||
controller: _scrollController,
|
||||
itemCount: merchantsList.length,
|
||||
separatorBuilder: (_, __) => SizedBox(height: 4),
|
||||
itemBuilder: (_, index) {
|
||||
final merchant = merchantsList[index];
|
||||
var subTitle = '';
|
||||
|
||||
if (merchant.isOnline) {
|
||||
subTitle += S.of(context).online;
|
||||
}
|
||||
|
||||
if (merchant.isPhysical) {
|
||||
if (subTitle.isNotEmpty) {
|
||||
subTitle = '$subTitle & ';
|
||||
}
|
||||
|
||||
subTitle = '${subTitle}${S.of(context).in_store}';
|
||||
}
|
||||
|
||||
return CardItem(
|
||||
logoUrl: merchant.logoUrl,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]);
|
||||
},
|
||||
title: merchant.legalName,
|
||||
subTitle: subTitle,
|
||||
backgroundColor: Theme.of(context).textTheme.title.backgroundColor,
|
||||
titleColor: Theme.of(context).accentTextTheme.display3.backgroundColor,
|
||||
subtitleColor: Theme.of(context).accentTextTheme.display2.backgroundColor,
|
||||
discount: merchant.discount,
|
||||
);
|
||||
},
|
||||
),
|
||||
isAlwaysShowScrollThumb
|
||||
? CakeScrollbar(
|
||||
backgroundHeight: backgroundHeight,
|
||||
thumbHeight: thumbHeight,
|
||||
rightOffset: 1,
|
||||
width: 3,
|
||||
backgroundColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.05),
|
||||
thumbColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.5),
|
||||
fromTop: widget.cardsListViewModel.scrollOffsetFromTop,
|
||||
)
|
||||
: Offstage()
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchWidget extends StatelessWidget {
|
||||
const _SearchWidget({
|
||||
Key key,
|
||||
@required this.controller,
|
||||
}) : super(key: key);
|
||||
final TextEditingController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final searchIcon = Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Image.asset(
|
||||
'assets/images/mini_search_icon.png',
|
||||
color: Theme.of(context).textTheme.caption.decorationColor,
|
||||
),
|
||||
);
|
||||
|
||||
return TextField(
|
||||
style: TextStyle(color: Colors.white),
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
contentPadding: EdgeInsets.only(
|
||||
top: 10,
|
||||
left: 10,
|
||||
),
|
||||
fillColor: Colors.white.withOpacity(0.15),
|
||||
hintText: S.of(context).search,
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white.withOpacity(0.6),
|
||||
),
|
||||
alignLabelWithHint: true,
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
suffixIcon: searchIcon,
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.white.withOpacity(0.2)),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TrailingIcon extends StatelessWidget {
|
||||
final String asset;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const _TrailingIcon({this.asset, this.onPressed});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
alignment: Alignment.centerRight,
|
||||
width: 25,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: onPressed,
|
||||
child: Image.asset(
|
||||
asset,
|
||||
color: Theme.of(context).accentTextTheme.display3.backgroundColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
217
lib/src/screens/ionia/cards/ionia_payment_status_page.dart
Normal file
|
@ -0,0 +1,217 @@
|
|||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class IoniaPaymentStatusPage extends BasePage {
|
||||
IoniaPaymentStatusPage(this.viewModel);
|
||||
|
||||
final IoniaPaymentStatusViewModel viewModel;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.of(context).generating_gift_card,
|
||||
textAlign: TextAlign.center,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return _IoniaPaymentStatusPageBody(viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
class _IoniaPaymentStatusPageBody extends StatefulWidget {
|
||||
_IoniaPaymentStatusPageBody(this.viewModel);
|
||||
|
||||
final IoniaPaymentStatusViewModel viewModel;
|
||||
|
||||
@override
|
||||
_IoniaPaymentStatusPageBodyBodyState createState() => _IoniaPaymentStatusPageBodyBodyState();
|
||||
}
|
||||
|
||||
class _IoniaPaymentStatusPageBodyBodyState extends State<_IoniaPaymentStatusPageBody> {
|
||||
ReactionDisposer _onGiftCardReaction;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (widget.viewModel.giftCard != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Navigator.of(context)
|
||||
.pushReplacementNamed(Routes.ioniaGiftCardDetailPage, arguments: [widget.viewModel.giftCard]);
|
||||
});
|
||||
}
|
||||
|
||||
_onGiftCardReaction = reaction((_) => widget.viewModel.giftCard, (IoniaGiftCard giftCard) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Navigator.of(context)
|
||||
.pushReplacementNamed(Routes.ioniaGiftCardDetailPage, arguments: [giftCard]);
|
||||
});
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_onGiftCardReaction?.reaction?.dispose();
|
||||
widget.viewModel.timer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.all(24),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.green),
|
||||
height: 10,
|
||||
width: 10)),
|
||||
Text(
|
||||
S.of(context).awaiting_payment_confirmation,
|
||||
style: textLargeSemiBold(
|
||||
color: Theme.of(context).primaryTextTheme.title.color))
|
||||
]),
|
||||
SizedBox(height: 40),
|
||||
Row(children: [
|
||||
SizedBox(width: 20),
|
||||
Expanded(child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
...widget.viewModel
|
||||
.committedInfo
|
||||
.transactions
|
||||
.map((transaction) => buildDescriptionTileWithCopy(context, S.of(context).transaction_details_transaction_id, transaction.id)),
|
||||
Divider(height: 30),
|
||||
buildDescriptionTileWithCopy(context, S.of(context).order_id, widget.viewModel.paymentInfo.ioniaOrder.id),
|
||||
Divider(height: 30),
|
||||
buildDescriptionTileWithCopy(context, S.of(context).payment_id, widget.viewModel.paymentInfo.ioniaOrder.paymentId),
|
||||
]))
|
||||
]),
|
||||
SizedBox(height: 40),
|
||||
Observer(builder: (_) {
|
||||
if (widget.viewModel.giftCard != null) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 40),
|
||||
child: Row(children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 10,),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.green),
|
||||
height: 10,
|
||||
width: 10)),
|
||||
Text(
|
||||
S.of(context).gift_card_is_generated,
|
||||
style: textLargeSemiBold(
|
||||
color: Theme.of(context).primaryTextTheme.title.color))
|
||||
]));
|
||||
}
|
||||
|
||||
return Row(children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
child: Observer(builder: (_) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: widget.viewModel.giftCard == null ? Colors.grey : Colors.green),
|
||||
height: 10,
|
||||
width: 10);
|
||||
})),
|
||||
Text(
|
||||
S.of(context).generating_gift_card,
|
||||
style: textLargeSemiBold(
|
||||
color: Theme.of(context).primaryTextTheme.title.color))]);
|
||||
}),
|
||||
],
|
||||
),
|
||||
bottomSection: Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: Column(children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 40, right: 40, bottom: 20),
|
||||
child: Text(
|
||||
S.of(context).proceed_after_one_minute,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
).copyWith(fontWeight: FontWeight.w500),
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
Observer(builder: (_) {
|
||||
if (widget.viewModel.giftCard != null) {
|
||||
return PrimaryButton(
|
||||
onPressed: () => Navigator.of(context)
|
||||
.pushReplacementNamed(
|
||||
Routes.ioniaGiftCardDetailPage,
|
||||
arguments: [widget.viewModel.giftCard]),
|
||||
text: S.of(context).open_gift_card,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white);
|
||||
}
|
||||
|
||||
return PrimaryButton(
|
||||
onPressed: () => Navigator.of(context).pushNamed(Routes.support),
|
||||
text: S.of(context).contact_support,
|
||||
color: Theme.of(context).accentTextTheme.caption.color,
|
||||
textColor: Theme.of(context).primaryTextTheme.title.color);
|
||||
})
|
||||
])
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildDescriptionTile(BuildContext context, String title, String subtitle, VoidCallback onTap) {
|
||||
return GestureDetector(
|
||||
onTap: () => onTap(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: textXSmall(
|
||||
color: Theme.of(context).primaryTextTheme.overline.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
subtitle,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Widget buildDescriptionTileWithCopy(BuildContext context, String title, String subtitle) {
|
||||
return buildDescriptionTile(context, title, subtitle, () {
|
||||
Clipboard.setData(ClipboardData(text: subtitle));
|
||||
showBar<void>(context,
|
||||
S.of(context).transaction_details_copied(title));
|
||||
});
|
||||
}
|
||||
}
|
9
lib/src/screens/ionia/ionia.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
export 'auth/ionia_welcome_page.dart';
|
||||
export 'auth/ionia_create_account_page.dart';
|
||||
export 'auth/ionia_login_page.dart';
|
||||
export 'auth/ionia_verify_otp_page.dart';
|
||||
export 'cards/ionia_activate_debit_card_page.dart';
|
||||
export 'cards/ionia_buy_card_detail_page.dart';
|
||||
export 'cards/ionia_manage_cards_page.dart';
|
||||
export 'cards/ionia_debit_card_page.dart';
|
||||
export 'cards/ionia_buy_gift_card.dart';
|
139
lib/src/screens/ionia/widgets/card_item.dart
Normal file
|
@ -0,0 +1,139 @@
|
|||
import 'package:cake_wallet/src/widgets/discount_badge.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CardItem extends StatelessWidget {
|
||||
CardItem({
|
||||
@required this.title,
|
||||
@required this.subTitle,
|
||||
@required this.backgroundColor,
|
||||
@required this.titleColor,
|
||||
@required this.subtitleColor,
|
||||
this.discountBackground,
|
||||
this.onTap,
|
||||
this.logoUrl,
|
||||
this.discount,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
final String logoUrl;
|
||||
final double discount;
|
||||
final Color backgroundColor;
|
||||
final Color titleColor;
|
||||
final Color subtitleColor;
|
||||
final AssetImage discountBackground;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.20),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (logoUrl != null) ...[
|
||||
ClipOval(
|
||||
child: Image.network(
|
||||
logoUrl,
|
||||
width: 40.0,
|
||||
height: 40.0,
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder: (BuildContext _, Widget child, ImageChunkEvent loadingProgress) {
|
||||
if (loadingProgress == null) {
|
||||
return child;
|
||||
} else {
|
||||
return _PlaceholderContainer(text: 'Logo');
|
||||
}
|
||||
},
|
||||
errorBuilder: (_, __, ___) => _PlaceholderContainer(text: '!'),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
],
|
||||
Column(
|
||||
crossAxisAlignment: (subTitle?.isEmpty ?? false)
|
||||
? CrossAxisAlignment.center
|
||||
: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: Text(
|
||||
title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: titleColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (subTitle?.isNotEmpty ?? false)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
subTitle,
|
||||
style: TextStyle(
|
||||
color: subtitleColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato')),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (discount != 0.0)
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: DiscountBadge(
|
||||
percentage: discount,
|
||||
discountBackground: discountBackground,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlaceholderContainer extends StatelessWidget {
|
||||
const _PlaceholderContainer({@required this.text});
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 42,
|
||||
width: 42,
|
||||
child: Center(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
11
lib/src/screens/ionia/widgets/card_menu.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class CardMenu extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
|
||||
);
|
||||
}
|
||||
}
|
148
lib/src/screens/ionia/widgets/confirm_modal.dart
Normal file
|
@ -0,0 +1,148 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IoniaConfirmModal extends StatelessWidget {
|
||||
IoniaConfirmModal({
|
||||
@required this.alertTitle,
|
||||
@required this.alertContent,
|
||||
@required this.leftButtonText,
|
||||
@required this.rightButtonText,
|
||||
@required this.actionLeftButton,
|
||||
@required this.actionRightButton,
|
||||
this.leftActionColor,
|
||||
this.rightActionColor,
|
||||
this.hideActions = false,
|
||||
});
|
||||
|
||||
final String alertTitle;
|
||||
final Widget alertContent;
|
||||
final String leftButtonText;
|
||||
final String rightButtonText;
|
||||
final VoidCallback actionLeftButton;
|
||||
final VoidCallback actionRightButton;
|
||||
final Color leftActionColor;
|
||||
final Color rightActionColor;
|
||||
final bool hideActions;
|
||||
|
||||
Widget actionButtons(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
IoniaActionButton(
|
||||
buttonText: leftButtonText,
|
||||
action: actionLeftButton,
|
||||
backgoundColor: leftActionColor,
|
||||
),
|
||||
Container(
|
||||
width: 1,
|
||||
height: 52,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
IoniaActionButton(
|
||||
buttonText: rightButtonText,
|
||||
action: actionRightButton,
|
||||
backgoundColor: rightActionColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget title(BuildContext context) {
|
||||
return Text(
|
||||
alertTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.transparent,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)),
|
||||
child: Center(
|
||||
child: GestureDetector(
|
||||
onTap: () => null,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
width: 327,
|
||||
color: Theme.of(context).accentTextTheme.title.decorationColor,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 20, 24, 0),
|
||||
child: title(context),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 16, bottom: 8),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
alertContent,
|
||||
actionButtons(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IoniaActionButton extends StatelessWidget {
|
||||
const IoniaActionButton({
|
||||
@required this.buttonText,
|
||||
@required this.action,
|
||||
this.backgoundColor,
|
||||
});
|
||||
|
||||
final String buttonText;
|
||||
final VoidCallback action;
|
||||
final Color backgoundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Flexible(
|
||||
child: Container(
|
||||
height: 52,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
color: backgoundColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.infinity,
|
||||
child: FlatButton(
|
||||
onPressed: action,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: Text(
|
||||
buttonText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: backgoundColor != null ? Colors.white : Theme.of(context).primaryTextTheme.body1.backgroundColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
86
lib/src/screens/ionia/widgets/ionia_alert_model.dart
Normal file
|
@ -0,0 +1,86 @@
|
|||
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IoniaAlertModal extends StatelessWidget {
|
||||
const IoniaAlertModal({
|
||||
Key key,
|
||||
@required this.title,
|
||||
@required this.content,
|
||||
@required this.actionTitle,
|
||||
this.heightFactor = 0.4,
|
||||
this.showCloseButton = true,
|
||||
}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
final Widget content;
|
||||
final String actionTitle;
|
||||
final bool showCloseButton;
|
||||
final double heightFactor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertBackground(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Spacer(),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 24, left: 24, right: 24),
|
||||
margin: EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).backgroundColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
if (title.isNotEmpty)
|
||||
Text(
|
||||
title,
|
||||
style: textLargeSemiBold(
|
||||
color: Theme.of(context).textTheme.body1.color,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * heightFactor),
|
||||
child: ListView(
|
||||
children: [
|
||||
content,
|
||||
SizedBox(height: 35),
|
||||
],
|
||||
),
|
||||
),
|
||||
PrimaryButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
text: actionTitle,
|
||||
color: Theme.of(context).accentTextTheme.caption.color,
|
||||
textColor: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
SizedBox(height: 21),
|
||||
],
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
if(showCloseButton)
|
||||
InkWell(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(bottom: 40),
|
||||
child: CircleAvatar(
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
color: Colors.black,
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
134
lib/src/screens/ionia/widgets/ionia_filter_modal.dart
Normal file
|
@ -0,0 +1,134 @@
|
|||
import 'package:cake_wallet/ionia/ionia_category.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class IoniaFilterModal extends StatelessWidget {
|
||||
IoniaFilterModal({
|
||||
@required this.filterViewModel,
|
||||
@required this.selectedCategories,
|
||||
}) {
|
||||
filterViewModel.setSelectedCategories(this.selectedCategories);
|
||||
}
|
||||
|
||||
final IoniaFilterViewModel filterViewModel;
|
||||
final List<IoniaCategory> selectedCategories;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final searchIcon = Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Image.asset(
|
||||
'assets/images/mini_search_icon.png',
|
||||
color: Theme.of(context).accentColor,
|
||||
),
|
||||
);
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: AlertBackground(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(height: 10),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 24, bottom: 20),
|
||||
margin: EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).backgroundColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 40,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
child: TextField(
|
||||
onChanged: filterViewModel.onSearchFilter,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
prefixIcon: searchIcon,
|
||||
hintText: S.of(context).search_category,
|
||||
contentPadding: EdgeInsets.only(bottom: 5),
|
||||
fillColor: Theme.of(context).textTheme.subhead.backgroundColor,
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Divider(thickness: 2),
|
||||
SizedBox(height: 24),
|
||||
Observer(builder: (_) {
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: filterViewModel.ioniaCategories.length,
|
||||
itemBuilder: (_, index) {
|
||||
final category = filterViewModel.ioniaCategories[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
child: InkWell(
|
||||
onTap: () => filterViewModel.selectFilter(category),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset(
|
||||
category.iconPath,
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(category.title,
|
||||
style: textSmall(
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
).copyWith(fontWeight: FontWeight.w500)),
|
||||
],
|
||||
),
|
||||
Observer(builder: (_) {
|
||||
final value = filterViewModel.selectedIndices;
|
||||
return RoundedCheckbox(
|
||||
value: value.contains(category.index),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => Navigator.pop(context, filterViewModel.selectedCategories),
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(bottom: 40),
|
||||
child: CircleAvatar(
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
color: Colors.black,
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
44
lib/src/screens/ionia/widgets/ionia_tile.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IoniaTile extends StatelessWidget {
|
||||
const IoniaTile({
|
||||
Key key,
|
||||
@required this.title,
|
||||
@required this.subTitle,
|
||||
this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
final VoidCallback onTap;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onTap(),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: textXSmall(
|
||||
color: Theme.of(context).primaryTextTheme.overline.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
subTitle,
|
||||
style: textMediumBold(
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
27
lib/src/screens/ionia/widgets/rounded_checkbox.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
import 'dart:ui';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RoundedCheckbox extends StatelessWidget {
|
||||
RoundedCheckbox({Key key, @required this.value}) : super(key: key);
|
||||
|
||||
final bool value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return value
|
||||
? Container(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(50.0)),
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
size: 14.0,
|
||||
))
|
||||
: Offstage();
|
||||
}
|
||||
}
|
35
lib/src/screens/ionia/widgets/text_icon_button.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextIconButton extends StatelessWidget {
|
||||
final String label;
|
||||
final VoidCallback onTap;
|
||||
const TextIconButton({
|
||||
Key key,
|
||||
this.label,
|
||||
this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return
|
||||
InkWell(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,7 +10,10 @@ class AlertWithTwoActions extends BaseAlertDialog {
|
|||
@required this.rightButtonText,
|
||||
@required this.actionLeftButton,
|
||||
@required this.actionRightButton,
|
||||
this.alertBarrierDismissible = true
|
||||
this.alertBarrierDismissible = true,
|
||||
this.isDividerExist = false,
|
||||
this.leftActionColor,
|
||||
this.rightActionColor,
|
||||
});
|
||||
|
||||
final String alertTitle;
|
||||
|
@ -20,6 +23,9 @@ class AlertWithTwoActions extends BaseAlertDialog {
|
|||
final VoidCallback actionLeftButton;
|
||||
final VoidCallback actionRightButton;
|
||||
final bool alertBarrierDismissible;
|
||||
final Color leftActionColor;
|
||||
final Color rightActionColor;
|
||||
final bool isDividerExist;
|
||||
|
||||
@override
|
||||
String get titleText => alertTitle;
|
||||
|
@ -35,4 +41,10 @@ class AlertWithTwoActions extends BaseAlertDialog {
|
|||
VoidCallback get actionRight => actionRightButton;
|
||||
@override
|
||||
bool get barrierDismissible => alertBarrierDismissible;
|
||||
}
|
||||
@override
|
||||
Color get leftButtonColor => leftActionColor;
|
||||
@override
|
||||
Color get rightButtonColor => rightActionColor;
|
||||
@override
|
||||
bool get isDividerExists => isDividerExist;
|
||||
}
|
||||
|
|
|
@ -46,30 +46,28 @@ class BaseAlertDialog extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Container(
|
||||
height: 52,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
color: Theme.of(context).accentTextTheme.body2.decorationColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.infinity,
|
||||
child: FlatButton(
|
||||
onPressed: actionLeft,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: Text(
|
||||
leftActionButtonText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.body2
|
||||
.backgroundColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
),
|
||||
)
|
||||
),
|
||||
height: 52,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
color: Theme.of(context).accentTextTheme.body2.decorationColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.infinity,
|
||||
child: FlatButton(
|
||||
onPressed: actionLeft,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: Text(
|
||||
leftActionButtonText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.body2.backgroundColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
),
|
||||
)),
|
||||
Container(
|
||||
width: 1,
|
||||
height: 52,
|
||||
|
@ -77,30 +75,28 @@ class BaseAlertDialog extends StatelessWidget {
|
|||
),
|
||||
Flexible(
|
||||
child: Container(
|
||||
height: 52,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
color: Theme.of(context).accentTextTheme.body1.backgroundColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.infinity,
|
||||
child: FlatButton(
|
||||
onPressed: actionRight,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: Text(
|
||||
rightActionButtonText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.body1
|
||||
.backgroundColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
),
|
||||
)
|
||||
),
|
||||
height: 52,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
color: Theme.of(context).accentTextTheme.body1.backgroundColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.infinity,
|
||||
child: FlatButton(
|
||||
onPressed: actionRight,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: Text(
|
||||
rightActionButtonText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.body1.backgroundColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -108,9 +104,7 @@ class BaseAlertDialog extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => barrierDismissible
|
||||
? Navigator.of(context).pop()
|
||||
: null,
|
||||
onTap: () => barrierDismissible ? Navigator.of(context).pop() : null,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: BackdropFilter(
|
||||
|
@ -136,14 +130,14 @@ class BaseAlertDialog extends StatelessWidget {
|
|||
child: title(context),
|
||||
),
|
||||
isDividerExists
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(top: 16, bottom: 8),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
)
|
||||
: Offstage(),
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(top: 16, bottom: 8),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
)
|
||||
: Offstage(),
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 8, 24, 32),
|
||||
child: content(context),
|
||||
|
@ -166,4 +160,4 @@ class BaseAlertDialog extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,19 @@ class CakeScrollbar extends StatelessWidget {
|
|||
@required this.backgroundHeight,
|
||||
@required this.thumbHeight,
|
||||
@required this.fromTop,
|
||||
this.rightOffset = 6
|
||||
this.rightOffset = 6,
|
||||
this.backgroundColor,
|
||||
this.thumbColor,
|
||||
this.width = 6,
|
||||
});
|
||||
|
||||
final double backgroundHeight;
|
||||
final double thumbHeight;
|
||||
final double fromTop;
|
||||
final double width;
|
||||
final double rightOffset;
|
||||
final Color backgroundColor;
|
||||
final Color thumbColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -19,11 +25,10 @@ class CakeScrollbar extends StatelessWidget {
|
|||
right: rightOffset,
|
||||
child: Container(
|
||||
height: backgroundHeight,
|
||||
width: 6,
|
||||
width: width,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).textTheme.body1.decorationColor,
|
||||
borderRadius: BorderRadius.all(Radius.circular(3))
|
||||
),
|
||||
color: backgroundColor ?? Theme.of(context).textTheme.body1.decorationColor,
|
||||
borderRadius: BorderRadius.all(Radius.circular(3))),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
AnimatedPositioned(
|
||||
|
@ -31,16 +36,14 @@ class CakeScrollbar extends StatelessWidget {
|
|||
top: fromTop,
|
||||
child: Container(
|
||||
height: thumbHeight,
|
||||
width: 6.0,
|
||||
width: width,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).textTheme.body1.color,
|
||||
borderRadius: BorderRadius.all(Radius.circular(3))
|
||||
),
|
||||
color: thumbColor ?? Theme.of(context).textTheme.body1.color,
|
||||
borderRadius: BorderRadius.all(Radius.circular(3))),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
35
lib/src/widgets/discount_badge.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class DiscountBadge extends StatelessWidget {
|
||||
const DiscountBadge({
|
||||
Key key,
|
||||
@required this.percentage,
|
||||
this.discountBackground,
|
||||
}) : super(key: key);
|
||||
|
||||
final double percentage;
|
||||
final AssetImage discountBackground;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Text(
|
||||
S.of(context).discount(percentage.toStringAsFixed(2)),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato',
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.fill,
|
||||
image: discountBackground ?? AssetImage('assets/images/badge_discount.png'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
66
lib/src/widgets/market_place_item.dart
Normal file
|
@ -0,0 +1,66 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class MarketPlaceItem extends StatelessWidget {
|
||||
|
||||
|
||||
MarketPlaceItem({
|
||||
@required this.onTap,
|
||||
@required this.title,
|
||||
@required this.subTitle,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).textTheme.title.backgroundColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.20),
|
||||
),
|
||||
),
|
||||
child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.display3
|
||||
.backgroundColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
subTitle,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.display3
|
||||
.backgroundColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
59
lib/typography.dart
Normal file
|
@ -0,0 +1,59 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
const latoFont = "Lato";
|
||||
|
||||
TextStyle textXxSmall({Color color}) => _cakeRegular(10, color);
|
||||
|
||||
TextStyle textXxSmallSemiBold({Color color}) => _cakeSemiBold(10, color);
|
||||
|
||||
TextStyle textXSmall({Color color}) => _cakeRegular(12, color);
|
||||
|
||||
TextStyle textXSmallSemiBold({Color color}) => _cakeSemiBold(12, color);
|
||||
|
||||
TextStyle textSmall({Color color}) => _cakeRegular(14, color);
|
||||
|
||||
TextStyle textSmallSemiBold({Color color}) => _cakeSemiBold(14, color);
|
||||
|
||||
TextStyle textMedium({Color color}) => _cakeRegular(16, color);
|
||||
|
||||
TextStyle textMediumBold({Color color}) => _cakeBold(16, color);
|
||||
|
||||
TextStyle textMediumSemiBold({Color color}) => _cakeSemiBold(22, color);
|
||||
|
||||
TextStyle textLarge({Color color}) => _cakeRegular(18, color);
|
||||
|
||||
TextStyle textLargeSemiBold({Color color}) => _cakeSemiBold(24, color);
|
||||
|
||||
TextStyle textXLarge({Color color}) => _cakeRegular(32, color);
|
||||
|
||||
TextStyle textXLargeSemiBold({Color color}) => _cakeSemiBold(32, color);
|
||||
|
||||
TextStyle _cakeRegular(double size, Color color) => _textStyle(
|
||||
size: size,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: color,
|
||||
);
|
||||
|
||||
TextStyle _cakeBold(double size, Color color) => _textStyle(
|
||||
size: size,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: color,
|
||||
);
|
||||
|
||||
TextStyle _cakeSemiBold(double size, Color color) => _textStyle(
|
||||
size: size,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: color,
|
||||
);
|
||||
|
||||
TextStyle _textStyle({
|
||||
@required double size,
|
||||
@required FontWeight fontWeight,
|
||||
Color color,
|
||||
}) =>
|
||||
TextStyle(
|
||||
fontFamily: latoFont,
|
||||
fontSize: size,
|
||||
fontWeight: fontWeight,
|
||||
color: color ?? Colors.white,
|
||||
);
|
44
lib/view_model/ionia/ionia_account_view_model.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
|
||||
part 'ionia_account_view_model.g.dart';
|
||||
|
||||
class IoniaAccountViewModel = IoniaAccountViewModelBase with _$IoniaAccountViewModel;
|
||||
|
||||
abstract class IoniaAccountViewModelBase with Store {
|
||||
IoniaAccountViewModelBase({this.ioniaService}) {
|
||||
email = '';
|
||||
giftCards = [];
|
||||
ioniaService.getUserEmail().then((email) => this.email = email);
|
||||
updateUserGiftCards();
|
||||
}
|
||||
|
||||
final IoniaService ioniaService;
|
||||
|
||||
@observable
|
||||
String email;
|
||||
|
||||
@observable
|
||||
List<IoniaGiftCard> giftCards;
|
||||
|
||||
@computed
|
||||
int get countOfMerch => giftCards.where((giftCard) => !giftCard.isEmpty).length;
|
||||
|
||||
@computed
|
||||
List<IoniaGiftCard> get activeMechs => giftCards.where((giftCard) => !giftCard.isEmpty).toList();
|
||||
|
||||
@computed
|
||||
List<IoniaGiftCard> get redeemedMerchs => giftCards.where((giftCard) => giftCard.isEmpty).toList();
|
||||
|
||||
@action
|
||||
void logout() {
|
||||
ioniaService.logout();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> updateUserGiftCards() async {
|
||||
giftCards = await ioniaService.getCurrentUserGiftCardSummaries();
|
||||
}
|
||||
}
|
67
lib/view_model/ionia/ionia_auth_view_model.dart
Normal file
|
@ -0,0 +1,67 @@
|
|||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'ionia_auth_view_model.g.dart';
|
||||
|
||||
class IoniaAuthViewModel = IoniaAuthViewModelBase with _$IoniaAuthViewModel;
|
||||
|
||||
abstract class IoniaAuthViewModelBase with Store {
|
||||
|
||||
IoniaAuthViewModelBase({this.ioniaService}):
|
||||
createUserState = IoniaInitialCreateState(),
|
||||
signInState = IoniaInitialCreateState(),
|
||||
otpState = IoniaOtpSendDisabled();
|
||||
|
||||
final IoniaService ioniaService;
|
||||
|
||||
@observable
|
||||
IoniaCreateAccountState createUserState;
|
||||
|
||||
@observable
|
||||
IoniaCreateAccountState signInState;
|
||||
|
||||
@observable
|
||||
IoniaOtpState otpState;
|
||||
|
||||
@observable
|
||||
String email;
|
||||
|
||||
@observable
|
||||
String otp;
|
||||
|
||||
@action
|
||||
Future<void> verifyEmail(String code) async {
|
||||
try {
|
||||
otpState = IoniaOtpValidating();
|
||||
await ioniaService.verifyEmail(code);
|
||||
otpState = IoniaOtpSuccess();
|
||||
} catch (_) {
|
||||
otpState = IoniaOtpFailure(error: 'Invalid OTP. Try again');
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> createUser(String email) async {
|
||||
try {
|
||||
createUserState = IoniaCreateStateLoading();
|
||||
await ioniaService.createUser(email);
|
||||
createUserState = IoniaCreateStateSuccess();
|
||||
} catch (e) {
|
||||
createUserState = IoniaCreateStateFailure(error: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@action
|
||||
Future<void> signIn(String email) async {
|
||||
try {
|
||||
signInState = IoniaCreateStateLoading();
|
||||
await ioniaService.signIn(email);
|
||||
signInState = IoniaCreateStateSuccess();
|
||||
} catch (e) {
|
||||
signInState = IoniaCreateStateFailure(error: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
31
lib/view_model/ionia/ionia_buy_card_view_model.dart
Normal file
|
@ -0,0 +1,31 @@
|
|||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'ionia_buy_card_view_model.g.dart';
|
||||
|
||||
class IoniaBuyCardViewModel = IoniaBuyCardViewModelBase with _$IoniaBuyCardViewModel;
|
||||
|
||||
abstract class IoniaBuyCardViewModelBase with Store {
|
||||
IoniaBuyCardViewModelBase({this.ioniaMerchant}) {
|
||||
isEnablePurchase = false;
|
||||
amount = 0;
|
||||
}
|
||||
|
||||
final IoniaMerchant ioniaMerchant;
|
||||
|
||||
@observable
|
||||
double amount;
|
||||
|
||||
@observable
|
||||
bool isEnablePurchase;
|
||||
|
||||
@action
|
||||
void onAmountChanged(String input) {
|
||||
if (input.isEmpty) return;
|
||||
amount = double.parse(input.replaceAll(',', '.'));
|
||||
final min = ioniaMerchant.minimumCardPurchase;
|
||||
final max = ioniaMerchant.maximumCardPurchase;
|
||||
|
||||
isEnablePurchase = amount >= min && amount <= max;
|
||||
}
|
||||
}
|
58
lib/view_model/ionia/ionia_filter_view_model.dart
Normal file
|
@ -0,0 +1,58 @@
|
|||
import 'package:cake_wallet/ionia/ionia_category.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'ionia_filter_view_model.g.dart';
|
||||
|
||||
class IoniaFilterViewModel = IoniaFilterViewModelBase with _$IoniaFilterViewModel;
|
||||
|
||||
abstract class IoniaFilterViewModelBase with Store {
|
||||
IoniaFilterViewModelBase() {
|
||||
selectedIndices = ObservableList<int>();
|
||||
ioniaCategories = IoniaCategory.allCategories;
|
||||
}
|
||||
|
||||
List<IoniaCategory> get selectedCategories => ioniaCategories.where(_isSelected).toList();
|
||||
|
||||
@observable
|
||||
ObservableList<int> selectedIndices;
|
||||
|
||||
@observable
|
||||
List<IoniaCategory> ioniaCategories;
|
||||
|
||||
@action
|
||||
void selectFilter(IoniaCategory ioniaCategory) {
|
||||
if (ioniaCategory == IoniaCategory.all && !selectedIndices.contains(0)) {
|
||||
selectedIndices.clear();
|
||||
selectedIndices.add(0);
|
||||
return;
|
||||
}
|
||||
if (selectedIndices.contains(ioniaCategory.index) && ioniaCategory.index != 0) {
|
||||
selectedIndices.remove(ioniaCategory.index);
|
||||
return;
|
||||
}
|
||||
selectedIndices.add(ioniaCategory.index);
|
||||
selectedIndices.remove(0);
|
||||
}
|
||||
|
||||
@action
|
||||
void onSearchFilter(String text) {
|
||||
if (text.isEmpty) {
|
||||
ioniaCategories = IoniaCategory.allCategories;
|
||||
} else {
|
||||
ioniaCategories = IoniaCategory.allCategories
|
||||
.where(
|
||||
(e) => e.title.toLowerCase().contains(text.toLowerCase()),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void setSelectedCategories(List<IoniaCategory> selectedCategories) {
|
||||
selectedIndices = ObservableList.of(selectedCategories.map((e) => e.index));
|
||||
}
|
||||
|
||||
bool _isSelected(IoniaCategory ioniaCategory) {
|
||||
return selectedIndices.contains(ioniaCategory.index);
|
||||
}
|
||||
}
|
35
lib/view_model/ionia/ionia_gift_card_details_view_model.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'ionia_gift_card_details_view_model.g.dart';
|
||||
|
||||
class IoniaGiftCardDetailsViewModel = IoniaGiftCardDetailsViewModelBase with _$IoniaGiftCardDetailsViewModel;
|
||||
|
||||
abstract class IoniaGiftCardDetailsViewModelBase with Store {
|
||||
|
||||
IoniaGiftCardDetailsViewModelBase({this.ioniaService, this.giftCard}) {
|
||||
redeemState = InitialExecutionState();
|
||||
}
|
||||
|
||||
final IoniaService ioniaService;
|
||||
|
||||
@observable
|
||||
IoniaGiftCard giftCard;
|
||||
|
||||
@observable
|
||||
ExecutionState redeemState;
|
||||
|
||||
@action
|
||||
Future<void> redeem() async {
|
||||
try {
|
||||
redeemState = IsExecutingState();
|
||||
await ioniaService.redeem(giftCard);
|
||||
giftCard = await ioniaService.getGiftCard(id: giftCard.id);
|
||||
redeemState = ExecutedSuccessfullyState();
|
||||
} catch(e) {
|
||||
redeemState = FailureState(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
103
lib/view_model/ionia/ionia_gift_cards_list_view_model.dart
Normal file
|
@ -0,0 +1,103 @@
|
|||
import 'package:cake_wallet/ionia/ionia_category.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
part 'ionia_gift_cards_list_view_model.g.dart';
|
||||
|
||||
class IoniaGiftCardsListViewModel = IoniaGiftCardsListViewModelBase with _$IoniaGiftCardsListViewModel;
|
||||
|
||||
abstract class IoniaGiftCardsListViewModelBase with Store {
|
||||
IoniaGiftCardsListViewModelBase({
|
||||
@required this.ioniaService,
|
||||
}) :
|
||||
cardState = IoniaNoCardState(),
|
||||
ioniaMerchants = [],
|
||||
scrollOffsetFromTop = 0.0 {
|
||||
selectedFilters = [];
|
||||
_getAuthStatus().then((value) => isLoggedIn = value);
|
||||
|
||||
_getMerchants();
|
||||
}
|
||||
|
||||
final IoniaService ioniaService;
|
||||
|
||||
List<IoniaMerchant> ioniaMerchantList;
|
||||
|
||||
String searchString;
|
||||
|
||||
List<IoniaCategory> selectedFilters;
|
||||
|
||||
@observable
|
||||
double scrollOffsetFromTop;
|
||||
|
||||
@observable
|
||||
IoniaCreateCardState createCardState;
|
||||
|
||||
@observable
|
||||
IoniaFetchCardState cardState;
|
||||
|
||||
@observable
|
||||
List<IoniaMerchant> ioniaMerchants;
|
||||
|
||||
@observable
|
||||
bool isLoggedIn;
|
||||
|
||||
Future<bool> _getAuthStatus() async {
|
||||
return await ioniaService.isLogined();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<IoniaVirtualCard> createCard() async {
|
||||
createCardState = IoniaCreateCardLoading();
|
||||
try {
|
||||
final card = await ioniaService.createCard();
|
||||
createCardState = IoniaCreateCardSuccess();
|
||||
return card;
|
||||
} on Exception catch (e) {
|
||||
createCardState = IoniaCreateCardFailure(error: e.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@action
|
||||
void searchMerchant(String text) {
|
||||
if (text.isEmpty) {
|
||||
ioniaMerchants = ioniaMerchantList;
|
||||
return;
|
||||
}
|
||||
searchString = text;
|
||||
ioniaService.getMerchantsByFilter(search: searchString).then((value) {
|
||||
ioniaMerchants = value;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _getCard() async {
|
||||
cardState = IoniaFetchingCard();
|
||||
try {
|
||||
final card = await ioniaService.getCard();
|
||||
|
||||
cardState = IoniaCardSuccess(card: card);
|
||||
} catch (_) {
|
||||
cardState = IoniaFetchCardFailure();
|
||||
}
|
||||
}
|
||||
|
||||
void _getMerchants() {
|
||||
ioniaService.getMerchantsByFilter(categories: selectedFilters).then((value) {
|
||||
ioniaMerchants = ioniaMerchantList = value;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
void setSelectedFilter(List<IoniaCategory> filters) {
|
||||
selectedFilters = filters;
|
||||
_getMerchants();
|
||||
}
|
||||
|
||||
void setScrollOffsetFromTop(double scrollOffset) {
|
||||
scrollOffsetFromTop = scrollOffset;
|
||||
}
|
||||
}
|
58
lib/view_model/ionia/ionia_payment_status_view_model.dart
Normal file
|
@ -0,0 +1,58 @@
|
|||
import 'dart:async';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
|
||||
|
||||
part 'ionia_payment_status_view_model.g.dart';
|
||||
|
||||
class IoniaPaymentStatusViewModel = IoniaPaymentStatusViewModelBase with _$IoniaPaymentStatusViewModel;
|
||||
|
||||
abstract class IoniaPaymentStatusViewModelBase with Store {
|
||||
IoniaPaymentStatusViewModelBase(
|
||||
this.ioniaService,{
|
||||
@required this.paymentInfo,
|
||||
@required this.committedInfo}) {
|
||||
_timer = Timer.periodic(updateTime, (timer) async {
|
||||
await updatePaymentStatus();
|
||||
|
||||
if (giftCard != null) {
|
||||
timer?.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static const updateTime = Duration(seconds: 3);
|
||||
|
||||
final IoniaService ioniaService;
|
||||
final IoniaAnyPayPaymentInfo paymentInfo;
|
||||
final AnyPayPaymentCommittedInfo committedInfo;
|
||||
|
||||
@observable
|
||||
IoniaGiftCard giftCard;
|
||||
|
||||
@observable
|
||||
String error;
|
||||
|
||||
Timer get timer => _timer;
|
||||
|
||||
Timer _timer;
|
||||
|
||||
@action
|
||||
Future<void> updatePaymentStatus() async {
|
||||
try {
|
||||
final giftCardId = await ioniaService.getPaymentStatus(
|
||||
orderId: paymentInfo.ioniaOrder.id,
|
||||
paymentId: paymentInfo.ioniaOrder.paymentId);
|
||||
|
||||
if (giftCardId != null) {
|
||||
giftCard = await ioniaService.getGiftCard(id: giftCardId);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
error = e.toString();
|
||||
}
|
||||
}
|
||||
}
|
94
lib/view_model/ionia/ionia_purchase_merch_view_model.dart
Normal file
|
@ -0,0 +1,94 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment.dart';
|
||||
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_anypay.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_tip.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
|
||||
|
||||
part 'ionia_purchase_merch_view_model.g.dart';
|
||||
|
||||
class IoniaMerchPurchaseViewModel = IoniaMerchPurchaseViewModelBase with _$IoniaMerchPurchaseViewModel;
|
||||
|
||||
abstract class IoniaMerchPurchaseViewModelBase with Store {
|
||||
IoniaMerchPurchaseViewModelBase({
|
||||
@required this.ioniaAnyPayService,
|
||||
@required this.amount,
|
||||
@required this.ioniaMerchant,
|
||||
}) {
|
||||
tipAmount = 0.0;
|
||||
percentage = 0.0;
|
||||
tips = <IoniaTip>[
|
||||
IoniaTip(percentage: 0, originalAmount: amount),
|
||||
IoniaTip(percentage: 15, originalAmount: amount),
|
||||
IoniaTip(percentage: 18, originalAmount: amount),
|
||||
IoniaTip(percentage: 20, originalAmount: amount),
|
||||
];
|
||||
selectedTip = tips.first;
|
||||
}
|
||||
|
||||
final double amount;
|
||||
|
||||
List<IoniaTip> tips;
|
||||
|
||||
@observable
|
||||
IoniaTip selectedTip;
|
||||
|
||||
final IoniaMerchant ioniaMerchant;
|
||||
|
||||
final IoniaAnyPay ioniaAnyPayService;
|
||||
|
||||
IoniaAnyPayPaymentInfo paymentInfo;
|
||||
|
||||
AnyPayPayment get invoice => paymentInfo?.anyPayPayment;
|
||||
|
||||
AnyPayPaymentCommittedInfo committedInfo;
|
||||
|
||||
@observable
|
||||
ExecutionState invoiceCreationState;
|
||||
|
||||
@observable
|
||||
ExecutionState invoiceCommittingState;
|
||||
|
||||
@observable
|
||||
double percentage;
|
||||
|
||||
@computed
|
||||
double get giftCardAmount => double.parse((amount + tipAmount).toStringAsFixed(2));
|
||||
|
||||
@computed
|
||||
double get billAmount => double.parse((giftCardAmount * (1 - (ioniaMerchant.discount / 100))).toStringAsFixed(2));
|
||||
|
||||
@observable
|
||||
double tipAmount;
|
||||
|
||||
@action
|
||||
void addTip(IoniaTip tip) {
|
||||
tipAmount = tip.additionalAmount;
|
||||
selectedTip = tip;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> createInvoice() async {
|
||||
try {
|
||||
invoiceCreationState = IsExecutingState();
|
||||
paymentInfo = await ioniaAnyPayService.purchase(merchId: ioniaMerchant.id.toString(), amount: giftCardAmount);
|
||||
invoiceCreationState = ExecutedSuccessfullyState();
|
||||
} catch (e) {
|
||||
invoiceCreationState = FailureState(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> commitPaymentInvoice() async {
|
||||
try {
|
||||
invoiceCommittingState = IsExecutingState();
|
||||
committedInfo = await ioniaAnyPayService.commitInvoice(invoice);
|
||||
invoiceCommittingState = ExecutedSuccessfullyState(payload: committedInfo);
|
||||
} catch (e) {
|
||||
invoiceCommittingState = FailureState(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -222,11 +222,11 @@ abstract class SendViewModelBase with Store {
|
|||
case WalletType.bitcoin:
|
||||
final priority = _settingsStore.priority[_wallet.type];
|
||||
|
||||
return bitcoin.createBitcoinTransactionCredentials(outputs, priority);
|
||||
return bitcoin.createBitcoinTransactionCredentials(outputs, priority: priority);
|
||||
case WalletType.litecoin:
|
||||
final priority = _settingsStore.priority[_wallet.type];
|
||||
|
||||
return bitcoin.createBitcoinTransactionCredentials(outputs, priority);
|
||||
return bitcoin.createBitcoinTransactionCredentials(outputs, priority: priority);
|
||||
case WalletType.monero:
|
||||
final priority = _settingsStore.priority[_wallet.type];
|
||||
|
||||
|
|
|
@ -534,5 +534,103 @@
|
|||
"search_currency": "Währung suchen",
|
||||
"new_template" : "neue Vorlage",
|
||||
"electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin",
|
||||
"wallet_name_exists": "Wallet mit diesem Namen existiert bereits"
|
||||
"wallet_name_exists": "Wallet mit diesem Namen existiert bereits",
|
||||
"market_place": "Marktplatz",
|
||||
"cake_pay_title": "Cake Pay-Geschenkkarten",
|
||||
"cake_pay_subtitle": "Geschenkkarten kaufen und sofort einlösen",
|
||||
"about_cake_pay": "Mit Cake Pay können Sie ganz einfach Geschenkkarten mit virtuellen Vermögenswerten kaufen, die Sie sofort bei über 150.000 Händlern in den Vereinigten Staaten ausgeben können.",
|
||||
"cake_pay_account_note": "Erstellen Sie ein Konto, um die verfügbaren Karten zu sehen. Einige sind sogar mit Rabatt erhältlich!",
|
||||
"already_have_account": "Sie haben bereits ein Konto?",
|
||||
"create_account": "Konto erstellen",
|
||||
"privacy_policy": "Datenschutzrichtlinie",
|
||||
"welcome_to_cakepay": "Willkommen bei Cake Pay!",
|
||||
"sign_up": "Anmelden",
|
||||
"forgot_password": "Passwort vergessen",
|
||||
"reset_password": "Passwort zurücksetzen",
|
||||
"gift_cards": "Geschenkkarten",
|
||||
"setup_your_debit_card": "Richten Sie Ihre Debitkarte ein",
|
||||
"no_id_required": "Keine ID erforderlich. Upgraden und überall ausgeben",
|
||||
"how_to_use_card": "Wie man diese Karte benutzt",
|
||||
"purchase_gift_card": "Geschenkkarte kaufen",
|
||||
"verification": "Verifizierung",
|
||||
"fill_code": "Geben Sie den Bestätigungscode ein, den Sie per E-Mail erhalten haben",
|
||||
"dont_get_code": "Kein Code?",
|
||||
"resend_code": "Bitte erneut senden",
|
||||
"debit_card": "Debitkarte",
|
||||
"cakepay_prepaid_card": "CakePay-Prepaid-Debitkarte",
|
||||
"no_id_needed": "Keine ID erforderlich!",
|
||||
"frequently_asked_questions": "Häufig gestellte Fragen",
|
||||
"debit_card_terms": "Die Speicherung und Nutzung Ihrer Zahlungskartennummer (und Ihrer Zahlungskartennummer entsprechenden Anmeldeinformationen) in dieser digitalen Geldbörse unterliegt den Allgemeinen Geschäftsbedingungen des geltenden Karteninhabervertrags mit dem Zahlungskartenaussteller, gültig ab von Zeit zu Zeit.",
|
||||
"Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.",
|
||||
"cardholder_agreement": "Karteninhabervertrag",
|
||||
"e_sign_consent": "E-Sign-Zustimmung",
|
||||
"agree_and_continue": "Zustimmen & fortfahren",
|
||||
"email_address": "E-Mail-Adresse",
|
||||
"agree_to": "Indem Sie ein Konto erstellen, stimmen Sie den ",
|
||||
"und": "und",
|
||||
"enter_code": "Code eingeben",
|
||||
"congratulations": "Glückwunsch!",
|
||||
"you_now_have_debit_card": "Sie haben jetzt eine Debitkarte",
|
||||
"min_amount": "Min: ${value}",
|
||||
"max_amount": "Max: ${value}",
|
||||
"enter_amount": "Betrag eingeben",
|
||||
"billing_address_info": "Wenn Sie nach einer Rechnungsadresse gefragt werden, geben Sie bitte Ihre Lieferadresse an",
|
||||
"order_physical_card": "Physische Karte bestellen",
|
||||
"add_value": "Wert hinzufügen",
|
||||
"activate": "aktivieren",
|
||||
"get_a": "Hole ein",
|
||||
"digital_and_physical_card": "digitale en fysieke prepaid debetkaart",
|
||||
"get_card_note": " die u kunt herladen met digitale valuta. Geen aanvullende informatie nodig!",
|
||||
"signup_for_card_accept_terms": "Meld je aan voor de kaart en accepteer de voorwaarden.",
|
||||
"add_fund_to_card": "Voeg prepaid tegoed toe aan de kaarten (tot ${value})",
|
||||
"use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.",
|
||||
"use_card_info_three": "Gebruik de digitale kaart online of met contactloze betaalmethoden.",
|
||||
"optionally_order_card": "Optioneel een fysieke kaart bestellen.",
|
||||
"hide_details" : "Details verbergen",
|
||||
"show_details" : "Toon details",
|
||||
"upto": "tot ${value}",
|
||||
"discount": "Bespaar ${value}%",
|
||||
"gift_card_amount": "Bedrag cadeaubon",
|
||||
"bill_amount": "Bill bedrag",
|
||||
"you_pay": "U betaalt",
|
||||
"tip": "Tip:",
|
||||
"custom": "aangepast",
|
||||
"by_cake_pay": "door Cake Pay",
|
||||
"expires": "Verloopt",
|
||||
"mm": "MM",
|
||||
"yy": "JJ",
|
||||
"online": "online",
|
||||
"offline": "Offline",
|
||||
"gift_card_number": "Cadeaukaartnummer",
|
||||
"pin_number": "PIN-nummer",
|
||||
"total_saving": "Totale besparingen",
|
||||
"last_30_days": "Laatste 30 dagen",
|
||||
"avg_savings": "Gem. besparingen",
|
||||
"view_all": "Alles bekijken",
|
||||
"active_cards": "Actieve kaarten",
|
||||
"delete_account": "Account verwijderen",
|
||||
"cards": "Kaarten",
|
||||
"active": "Actief",
|
||||
"redeemed": "Verzilverd",
|
||||
"gift_card_balance_note": "Cadeaukaarten met een resterend saldo verschijnen hier",
|
||||
"gift_card_redeemed_note": "Cadeaubonnen die je hebt ingewisseld, verschijnen hier",
|
||||
"logout": "Uitloggen",
|
||||
"add_tip": "Tip toevoegen",
|
||||
"percentageOf": "van ${amount}",
|
||||
"is_percentage": "is",
|
||||
"search_category": "Zoek categorie",
|
||||
"mark_as_redeemed": "Markeer als ingewisseld",
|
||||
"more_options": "Meer opties",
|
||||
"waiting_payment_confirmation": "In afwachting van betalingsbevestiging",
|
||||
"transaction_sent_notice": "Als het scherm na 1 minuut niet verder gaat, controleer dan een blokverkenner en je e-mail.",
|
||||
"agree": "mee eens",
|
||||
"in_store": "In winkel",
|
||||
"generating_gift_card": "Cadeaubon genereren",
|
||||
"payment_was_received": "Uw betaling is ontvangen.",
|
||||
"proceed_after_one_minute": "Als het scherm na 1 minuut niet verder gaat, controleer dan uw e-mail.",
|
||||
"order_id": "Bestell-ID",
|
||||
"gift_card_is_generated": "Geschenkkarte wird generiert",
|
||||
"open_gift_card": "Geschenkkarte öffnen",
|
||||
"contact_support": "Support kontaktieren",
|
||||
"gift_cards_unavailable": "Geschenkkarten können derzeit nur über Monero, Bitcoin und Litecoin erworben werden"
|
||||
}
|
||||
|
|
|
@ -245,7 +245,7 @@
|
|||
"settings_only_transactions" : "Only transactions",
|
||||
"settings_none" : "None",
|
||||
"settings_support" : "Support",
|
||||
"settings_terms_and_conditions" : "Terms and conditions",
|
||||
"settings_terms_and_conditions" : "Terms and Conditions",
|
||||
"pin_is_incorrect" : "PIN is incorrect",
|
||||
|
||||
|
||||
|
@ -534,5 +534,103 @@
|
|||
"search_currency": "Search currency",
|
||||
"new_template" : "New Template",
|
||||
"electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work",
|
||||
"wallet_name_exists": "Wallet with that name has already existed"
|
||||
"wallet_name_exists": "Wallet with that name has already existed",
|
||||
"market_place": "Marketplace",
|
||||
"cake_pay_title": "Cake Pay Gift Cards",
|
||||
"cake_pay_subtitle": "Buy gift cards and redeem instantly",
|
||||
"about_cake_pay": "Cake Pay allows you to easily buy gift cards with virtual assets, spendable instantly at over 150,000 merchants in the United States.",
|
||||
"cake_pay_account_note": "Make an account to see the available cards. Some are even available at a discount!",
|
||||
"already_have_account": "Already have an account?",
|
||||
"create_account": "Create Account",
|
||||
"privacy_policy": "Privacy Policy",
|
||||
"welcome_to_cakepay": "Welcome to Cake Pay!",
|
||||
"sign_up": "Sign Up",
|
||||
"forgot_password": "Forgot Password",
|
||||
"reset_password": "Reset Password",
|
||||
"gift_cards": "Gift Cards",
|
||||
"setup_your_debit_card": "Set up your debit card",
|
||||
"no_id_required": "No ID required. Top up and spend anywhere",
|
||||
"how_to_use_card": "How to use this card",
|
||||
"purchase_gift_card": "Purchase Gift Card",
|
||||
"verification": "Verification",
|
||||
"fill_code": "Please fill in the verification code provided to your email",
|
||||
"dont_get_code": "Don't get code?",
|
||||
"resend_code": "Please resend it",
|
||||
"debit_card": "Debit Card",
|
||||
"cakepay_prepaid_card": "CakePay Prepaid Debit Card",
|
||||
"no_id_needed": "No ID needed!",
|
||||
"frequently_asked_questions": "Frequently asked questions",
|
||||
"debit_card_terms": "The storage and usage of your payment card number (and credentials corresponding to your payment card number) in this digital wallet are subject to the Terms and Conditions of the applicable cardholder agreement with the payment card issuer, as in effect from time to time.",
|
||||
"please_reference_document": "Please reference the documents below for more information.",
|
||||
"cardholder_agreement": "Cardholder Agreement",
|
||||
"e_sign_consent": "E-Sign Consent",
|
||||
"agree_and_continue": "Agree & Continue",
|
||||
"email_address": "Email Address",
|
||||
"agree_to": "By creating account you agree to the ",
|
||||
"and": "and",
|
||||
"enter_code": "Enter code",
|
||||
"congratulations": "Congratulations!",
|
||||
"you_now_have_debit_card": "You now have a debit card",
|
||||
"min_amount" : "Min: ${value}",
|
||||
"max_amount" : "Max: ${value}",
|
||||
"enter_amount": "Enter Amount",
|
||||
"billing_address_info": "If asked for a billing address, provide your shipping address",
|
||||
"order_physical_card": "Order Physical Card",
|
||||
"add_value": "Add value",
|
||||
"activate": "Activate",
|
||||
"get_a": "Get a ",
|
||||
"digital_and_physical_card": " digital and physical prepaid debit card",
|
||||
"get_card_note": " that you can reload with digital currencies. No additional information needed!",
|
||||
"signup_for_card_accept_terms": "Sign up for the card and accept the terms.",
|
||||
"add_fund_to_card": "Add prepaid funds to the cards (up to ${value})",
|
||||
"use_card_info_two": "Funds are converted to USD when the held in the prepaid account, not in digital currencies.",
|
||||
"use_card_info_three": "Use the digital card online or with contactless payment methods.",
|
||||
"optionally_order_card": "Optionally order a physical card.",
|
||||
"hide_details" : "Hide Details",
|
||||
"show_details" : "Show Details",
|
||||
"upto": "up to ${value}",
|
||||
"discount": "Save ${value}%",
|
||||
"gift_card_amount": "Gift Card Amount",
|
||||
"bill_amount": "Bill amount",
|
||||
"you_pay": "You pay",
|
||||
"tip": "Tip:",
|
||||
"custom": "custom",
|
||||
"by_cake_pay": "by Cake Pay",
|
||||
"expires": "Expires",
|
||||
"mm": "MM",
|
||||
"yy": "YY",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"gift_card_number": "Gift card number",
|
||||
"pin_number": "PIN number",
|
||||
"total_saving": "Total Savings",
|
||||
"last_30_days": "Last 30 days",
|
||||
"avg_savings": "Avg. savings",
|
||||
"view_all": "View all",
|
||||
"active_cards": "Active cards",
|
||||
"delete_account": "Delete Account",
|
||||
"cards": "Cards",
|
||||
"active": "Active",
|
||||
"redeemed": "Redeemed",
|
||||
"gift_card_balance_note": "Gift cards with a balance remaining will appear here",
|
||||
"gift_card_redeemed_note": "Gift cards you’ve redeemed will appear here",
|
||||
"logout": "Logout",
|
||||
"add_tip": "Add Tip",
|
||||
"percentageOf": "of ${amount}",
|
||||
"is_percentage": "is",
|
||||
"search_category": "Search category",
|
||||
"mark_as_redeemed": "Mark As Redeemed",
|
||||
"more_options": "More Options",
|
||||
"awaiting_payment_confirmation": "Awaiting payment confirmation",
|
||||
"transaction_sent_notice": "If the screen doesn’t proceed after 1 minute, check a block explorer and your email.",
|
||||
"agree": "Agree",
|
||||
"in_store": "In Store",
|
||||
"generating_gift_card": "Generating Gift Card",
|
||||
"payment_was_received": "Your payment was received.",
|
||||
"proceed_after_one_minute": "If the screen doesn’t proceed after 1 minute, check your email.",
|
||||
"order_id": "Order ID",
|
||||
"gift_card_is_generated": "Gift Card is generated",
|
||||
"open_gift_card": "Open Gift Card",
|
||||
"contact_support": "Contact Support",
|
||||
"gift_cards_unavailable": "Gift cards are available to purchase only through Monero, Bitcoin, and Litecoin at this time"
|
||||
}
|
||||
|
|
|
@ -534,5 +534,103 @@
|
|||
"search_currency": "Moneda de búsqueda",
|
||||
"new_template" : "Nueva plantilla",
|
||||
"electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando",
|
||||
"wallet_name_exists": "Wallet con ese nombre ya ha existido"
|
||||
"wallet_name_exists": "Wallet con ese nombre ya ha existido",
|
||||
"market_place": "Mercado",
|
||||
"cake_pay_title": "Tarjetas de regalo Cake Pay",
|
||||
"cake_pay_subtitle": "Compra tarjetas de regalo y canjéalas al instante",
|
||||
"about_cake_pay": "Cake Pay le permite comprar fácilmente tarjetas de regalo con activos virtuales, gastables instantáneamente en más de 150 000 comerciantes en los Estados Unidos.",
|
||||
"cake_pay_account_note": "Crea una cuenta para ver las tarjetas disponibles. ¡Algunas incluso están disponibles con descuento!",
|
||||
"already_have_account": "¿Ya tienes una cuenta?",
|
||||
"create_account": "Crear Cuenta",
|
||||
"privacy_policy": "Política de privacidad",
|
||||
"welcome_to_cakepay": "¡Bienvenido a Cake Pay!",
|
||||
"sign_up": "Registrarse",
|
||||
"forgot_password": "Olvidé mi contraseña",
|
||||
"reset_password": "Restablecer contraseña",
|
||||
"gift_cards": "Tarjetas de regalo",
|
||||
"setup_your_debit_card": "Configura tu tarjeta de débito",
|
||||
"no_id_required": "No se requiere identificación. Recargue y gaste en cualquier lugar",
|
||||
"how_to_use_card": "Cómo usar esta tarjeta",
|
||||
"purchase_gift_card": "Comprar tarjeta de regalo",
|
||||
"verification": "Verificación",
|
||||
"fill_code": "Por favor complete el código de verificación proporcionado a su correo electrónico",
|
||||
"dont_get_code": "¿No obtienes el código?",
|
||||
"resend_code": "Por favor reenvíalo",
|
||||
"debit_card": "Tarjeta de Débito",
|
||||
"cakepay_prepaid_card": "Tarjeta de Débito Prepago CakePay",
|
||||
"no_id_needed": "¡No se necesita identificación!",
|
||||
"frequently_asked_questions": "Preguntas frecuentes",
|
||||
"debit_card_terms": "El almacenamiento y el uso de su número de tarjeta de pago (y las credenciales correspondientes a su número de tarjeta de pago) en esta billetera digital están sujetos a los Términos y condiciones del acuerdo del titular de la tarjeta aplicable con el emisor de la tarjeta de pago, en vigor desde tiempo al tiempo.",
|
||||
"please_reference_document": "Consulte los documentos a continuación para obtener más información.",
|
||||
"cardholder_agreement": "Acuerdo del titular de la tarjeta",
|
||||
"e_sign_consent": "Consentimiento de firma electrónica",
|
||||
"agree_and_continue": "Aceptar y continuar",
|
||||
"email_address": "Dirección de correo electrónico",
|
||||
"agree_to": "Al crear una cuenta, aceptas ",
|
||||
"and": "y",
|
||||
"enter_code": "Ingresar código",
|
||||
"congratulations": "Felicidades!",
|
||||
"you_now_have_debit_card": "Ahora tiene una tarjeta de débito",
|
||||
"min_amount" : "Mínimo: ${value}",
|
||||
"max_amount" : "Máx: ${value}",
|
||||
"enter_amount": "Ingrese la cantidad",
|
||||
"billing_address_info": "Si se le solicita una dirección de facturación, proporcione su dirección de envío",
|
||||
"order_physical_card": "Pedir tarjeta física",
|
||||
"add_value": "Añadir valor",
|
||||
"activate": "Activar",
|
||||
"get_a": "Obtener un",
|
||||
"digital_and_physical_card": " tarjeta de débito prepago digital y física",
|
||||
"get_card_note": " que puedes recargar con monedas digitales. ¡No se necesita información adicional!",
|
||||
"signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.",
|
||||
"add_fund_to_card": "Agregar fondos prepagos a las tarjetas (hasta ${value})",
|
||||
"use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.",
|
||||
"use_card_info_three": "Utilice la tarjeta digital en línea o con métodos de pago sin contacto.",
|
||||
"optionally_order_card": "Opcionalmente pide una tarjeta física.",
|
||||
"hide_details" : "Ocultar detalles",
|
||||
"show_details": "Mostrar detalles",
|
||||
"upto": "hasta ${value}",
|
||||
"discount": "Ahorra ${value}%",
|
||||
"gift_card_amount": "Cantidad de la tarjeta de regalo",
|
||||
"bill_amount": "Importe de la factura",
|
||||
"you_pay": "Tú pagas",
|
||||
"tip": "Consejo:",
|
||||
"personalizado": "personalizado",
|
||||
"by_cake_pay": "por Cake Pay",
|
||||
"expires": "Caduca",
|
||||
"mm": "mm",
|
||||
"yy": "YY",
|
||||
"online": "En línea",
|
||||
"offline": "fuera de línea",
|
||||
"gift_card_number": "Número de tarjeta de regalo",
|
||||
"pin_number": "Número PIN",
|
||||
"total_saving": "Ahorro Total",
|
||||
"last_30_days": "Últimos 30 días",
|
||||
"avg_savings": "Ahorro promedio",
|
||||
"view_all": "Ver todo",
|
||||
"active_cards": "Tarjetas activas",
|
||||
"delete_account": "Eliminar cuenta",
|
||||
"cards": "Cartas",
|
||||
"active": "Activo",
|
||||
"redeemed": "Redimido",
|
||||
"gift_card_balance_note": "Las tarjetas de regalo con saldo restante aparecerán aquí",
|
||||
"gift_card_redeemed_note": "Las tarjetas de regalo que hayas canjeado aparecerán aquí",
|
||||
"logout": "Cerrar sesión",
|
||||
"add_tip": "Agregar sugerencia",
|
||||
"percentageOf": "de ${amount}",
|
||||
"is_percentage": "es",
|
||||
"search_category": "Categoría de búsqueda",
|
||||
"mark_as_redeemed": "Marcar como canjeado",
|
||||
"more_options": "Más Opciones",
|
||||
"awaiting_payment_confirmation": "Esperando confirmación de pago",
|
||||
"transaction_sent_notice": "Si la pantalla no continúa después de 1 minuto, revisa un explorador de bloques y tu correo electrónico.",
|
||||
"agree": "De acuerdo",
|
||||
"in_store": "En la tienda",
|
||||
"generating_gift_card": "Generando tarjeta de regalo",
|
||||
"payment_was_received": "Su pago fue recibido.",
|
||||
"proceed_after_one_minute": "Si la pantalla no continúa después de 1 minuto, revisa tu correo electrónico.",
|
||||
"order_id": "Identificación del pedido",
|
||||
"gift_card_is_generated": "Se genera la tarjeta de regalo",
|
||||
"open_gift_card": "Abrir tarjeta de regalo",
|
||||
"contact_support": "Contactar con Soporte",
|
||||
"gift_cards_unavailable": "Las tarjetas de regalo están disponibles para comprar solo a través de Monero, Bitcoin y Litecoin en este momento"
|
||||
}
|
||||
|
|
|
@ -243,7 +243,7 @@
|
|||
"settings_only_transactions" : "Seulement les transactions",
|
||||
"settings_none" : "Rien",
|
||||
"settings_support" : "Support",
|
||||
"settings_terms_and_conditions" : "Termes et conditions",
|
||||
"settings_terms_and_conditions" : "Termes et Conditions",
|
||||
"pin_is_incorrect" : "Le code PIN est incorrect",
|
||||
|
||||
|
||||
|
@ -532,5 +532,103 @@
|
|||
"search_currency": "Devise de recherche",
|
||||
"new_template" : "Nouveau Modèle",
|
||||
"electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner",
|
||||
"wallet_name_exists": "Le portefeuille portant ce nom existe déjà"
|
||||
"wallet_name_exists": "Le portefeuille portant ce nom existe déjà",
|
||||
"market_place": "Place de marché",
|
||||
"cake_pay_title": "Cartes cadeaux Cake Pay",
|
||||
"cake_pay_subtitle": "Achetez des cartes-cadeaux et échangez-les instantanément",
|
||||
"about_cake_pay": "Cake Pay vous permet d'acheter facilement des cartes-cadeaux avec des actifs virtuels, utilisables instantanément chez plus de 150 000 marchands aux États-Unis.",
|
||||
"cake_pay_account_note": "Créez un compte pour voir les cartes disponibles. Certaines sont même disponibles à prix réduit !",
|
||||
"already_have_account": "Vous avez déjà un compte ?",
|
||||
"create_account": "Créer un compte",
|
||||
"privacy_policy": "Politique de confidentialité",
|
||||
"welcome_to_cakepay": "Bienvenue sur Cake Pay!",
|
||||
"sign_up": "S'inscrire",
|
||||
"forgot_password": "Mot de passe oublié",
|
||||
"reset_password": "Réinitialiser le mot de passe",
|
||||
"manage_cards": "Cartes cadeaux",
|
||||
"setup_your_debit_card": "Configurer votre carte de débit",
|
||||
"no_id_required": "Aucune pièce d'identité requise. Rechargez et dépensez n'importe où",
|
||||
"how_to_use_card": "Comment utiliser cette carte",
|
||||
"purchase_gift_card": "Acheter une carte-cadeau",
|
||||
"verification": "Vérification",
|
||||
"fill_code": "Veuillez remplir le code de vérification fourni sur votre e-mail",
|
||||
"dont_get_code": "Vous ne recevez pas le code ?",
|
||||
"resend_code": "Veuillez le renvoyer",
|
||||
"debit_card": "Carte de débit",
|
||||
"cakepay_prepaid_card": "Carte de débit prépayée CakePay",
|
||||
"no_id_needed": "Aucune pièce d'identité nécessaire !",
|
||||
"frequently_asked_questions": "Foire aux questions",
|
||||
"debit_card_terms": "Le stockage et l'utilisation de votre numéro de carte de paiement (et des informations d'identification correspondant à votre numéro de carte de paiement) dans ce portefeuille numérique sont soumis aux conditions générales de l'accord du titulaire de carte applicable avec l'émetteur de la carte de paiement, en vigueur à partir de de temps en temps.",
|
||||
"please_reference_document": "Veuillez vous référer aux documents ci-dessous pour plus d'informations.",
|
||||
"cardholder_agreement": "Contrat de titulaire de carte",
|
||||
"e_sign_consent": "Consentement de signature électronique",
|
||||
"agree_and_continue": "Accepter et continuer",
|
||||
"email_address": "Adresse e-mail",
|
||||
"agree_to": "En créant un compte, vous acceptez les ",
|
||||
"and": "et",
|
||||
"enter_code": "Entrez le code",
|
||||
"congratulations": "Félicitations !",
|
||||
"you_now_have_debit_card": "Vous avez maintenant une carte de débit",
|
||||
"min_amount" : "Min : ${value}",
|
||||
"max_amount" : "Max : ${value}",
|
||||
"enter_amount": "Entrez le montant",
|
||||
"billing_address_info": "Si une adresse de facturation vous est demandée, indiquez votre adresse de livraison",
|
||||
"order_physical_card": "Commander une carte physique",
|
||||
"add_value": "Ajouter une valeur",
|
||||
"activate": "Activer",
|
||||
"get_a": "Obtenir un ",
|
||||
"digital_and_physical_card": "carte de débit prépayée numérique et physique",
|
||||
"get_card_note": " que vous pouvez recharger avec des devises numériques. Aucune information supplémentaire n'est nécessaire !",
|
||||
"signup_for_card_accept_terms": "Inscrivez-vous pour la carte et acceptez les conditions.",
|
||||
"add_fund_to_card": "Ajouter des fonds prépayés aux cartes (jusqu'à ${value})",
|
||||
"use_card_info_two": "Les fonds sont convertis en USD lorsqu'ils sont détenus sur le compte prépayé, et non en devises numériques.",
|
||||
"use_card_info_three": "Utilisez la carte numérique en ligne ou avec des méthodes de paiement sans contact.",
|
||||
"optionally_order_card": "Commander éventuellement une carte physique.",
|
||||
"hide_details" : "Masquer les détails",
|
||||
"show_details" : "Afficher les détails",
|
||||
"upto": "jusqu'à ${value}",
|
||||
"discount": "Économisez ${value}%",
|
||||
"gift_card_amount": "Montant de la carte-cadeau",
|
||||
"bill_amount": "Montant de la facture",
|
||||
"you_pay": "Vous payez",
|
||||
"tip": "Astuce :",
|
||||
"custom": "personnalisé",
|
||||
"by_cake_pay": "par Cake Pay",
|
||||
"expire": "Expire",
|
||||
"mm": "MM",
|
||||
"yy": "AA",
|
||||
"online": "En ligne",
|
||||
"offline": "Hors ligne",
|
||||
"gift_card_number": "Numéro de carte cadeau",
|
||||
"pin_number": "Numéro PIN",
|
||||
"total_saving": "Économies totales",
|
||||
"last_30_days": "30 derniers jours",
|
||||
"avg_savings": "Économies moy.",
|
||||
"view_all": "Voir tout",
|
||||
"active_cards": "Cartes actives",
|
||||
"delete_account": "Supprimer le compte",
|
||||
"cards": "Cartes",
|
||||
"active": "Actif",
|
||||
"redeemed": "racheté",
|
||||
"gift_card_balance_note": "Les cartes-cadeaux avec un solde restant apparaîtront ici",
|
||||
"gift_card_redeemed_note": "Les cartes-cadeaux que vous avez utilisées apparaîtront ici",
|
||||
"logout": "Déconnexion",
|
||||
"add_tip": "Ajouter une astuce",
|
||||
"percentageOf": "sur ${amount}",
|
||||
"is_percentage": "est",
|
||||
"search_category": "Catégorie de recherche",
|
||||
"mark_as_redeemed": "Marquer comme échangé",
|
||||
"more_options": "Plus d'options",
|
||||
"awaiting_payment_confirmation": "En attente de confirmation de paiement",
|
||||
"transaction_sent_notice": "Si l'écran ne continue pas après 1 minute, vérifiez un explorateur de blocs et votre e-mail.",
|
||||
"agree": "d'accord",
|
||||
"in_store": "En magasin",
|
||||
"generating_gift_card": "Génération d'une carte-cadeau",
|
||||
"payment_was_received": "Votre paiement a été reçu.",
|
||||
"proceed_after_one_minute": "Si l'écran ne s'affiche pas après 1 minute, vérifiez vos e-mails.",
|
||||
"order_id": "Numéro de commande",
|
||||
"gift_card_is_generated": "La carte-cadeau est générée",
|
||||
"open_gift_card": "Ouvrir la carte-cadeau",
|
||||
"contact_support": "Contacter l'assistance",
|
||||
"gift_cards_unavailable": "Les cartes-cadeaux ne sont disponibles à l'achat que via Monero, Bitcoin et Litecoin pour le moment"
|
||||
}
|
||||
|
|
|
@ -534,5 +534,103 @@
|
|||
"search_currency": "मुद्रा खोजें",
|
||||
"new_template" : "नया टेम्पलेट",
|
||||
"electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं",
|
||||
"wallet_name_exists": "उस नाम वाला वॉलेट पहले से मौजूद है"
|
||||
"wallet_name_exists": "उस नाम वाला वॉलेट पहले से मौजूद है",
|
||||
"market_place": "मार्केटप्लेस",
|
||||
"cake_pay_title": "केक पे गिफ्ट कार्ड्स",
|
||||
"cake_pay_subtitle": "उपहार कार्ड खरीदें और तुरंत रिडीम करें",
|
||||
"about_cake_pay": "केक पे आपको वर्चुअल संपत्ति के साथ आसानी से उपहार कार्ड खरीदने की अनुमति देता है, जिसे संयुक्त राज्य में 150,000 से अधिक व्यापारियों पर तुरंत खर्च किया जा सकता है।",
|
||||
"cake_pay_account_note": "उपलब्ध कार्ड देखने के लिए एक खाता बनाएं। कुछ छूट पर भी उपलब्ध हैं!",
|
||||
"ready_have_account": "क्या आपके पास पहले से ही एक खाता है?",
|
||||
"create_account": "खाता बनाएं",
|
||||
"privacy_policy": "गोपनीयता नीति",
|
||||
"welcome_to_cakepay": "केकपे में आपका स्वागत है!",
|
||||
"sign_up": "साइन अप करें",
|
||||
"forgot_password": "पासवर्ड भूल गए",
|
||||
"reset_password": "पासवर्ड रीसेट करें",
|
||||
"gift_cards": "उपहार कार्ड",
|
||||
"setup_your_debit_card": "अपना डेबिट कार्ड सेट करें",
|
||||
"no_id_required": "कोई आईडी आवश्यक नहीं है। टॉप अप करें और कहीं भी खर्च करें",
|
||||
"how_to_use_card": "इस कार्ड का उपयोग कैसे करें",
|
||||
"purchase_gift_card": "गिफ्ट कार्ड खरीदें",
|
||||
"verification": "सत्यापन",
|
||||
"fill_code": "कृपया अपने ईमेल पर प्रदान किया गया सत्यापन कोड भरें",
|
||||
"dont_get_code": "कोड नहीं मिला?",
|
||||
"resend_code": "कृपया इसे फिर से भेजें",
|
||||
"debit_card": "डेबिट कार्ड",
|
||||
"cakepay_prepaid_card": "केकपे प्रीपेड डेबिट कार्ड",
|
||||
"no_id_needed": "कोई आईडी नहीं चाहिए!",
|
||||
"frequently_asked_questions": "अक्सर पूछे जाने वाले प्रश्न",
|
||||
"debit_card_terms": "इस डिजिटल वॉलेट में आपके भुगतान कार्ड नंबर (और आपके भुगतान कार्ड नंबर से संबंधित क्रेडेंशियल) का भंडारण और उपयोग भुगतान कार्ड जारीकर्ता के साथ लागू कार्डधारक समझौते के नियमों और शर्तों के अधीन है, जैसा कि प्रभावी है समय - समय पर।",
|
||||
"please_reference_document": "कृपया अधिक जानकारी के लिए नीचे दिए गए दस्तावेज़ देखें।",
|
||||
"cardholder_agreement": "कार्डधारक अनुबंध",
|
||||
"e_sign_consent": "ई-साइन सहमति",
|
||||
"agree_and_continue": "सहमत और जारी रखें",
|
||||
"email_address": "ईमेल पता",
|
||||
"agree_to": "खाता बनाकर आप इससे सहमत होते हैं ",
|
||||
"and": "और",
|
||||
"enter_code": "कोड दर्ज करें",
|
||||
"congratulations":"बधाई!",
|
||||
"you_now_have_debit_card": "अब आपके पास डेबिट कार्ड है",
|
||||
"min_amount" : "न्यूनतम: ${value}",
|
||||
"max_amount" : "अधिकतम: ${value}",
|
||||
"enter_amount": "राशि दर्ज करें",
|
||||
"billing_address_info": "यदि बिलिंग पता मांगा जाए, तो अपना शिपिंग पता प्रदान करें",
|
||||
"order_physical_card": "फिजिकल कार्ड ऑर्डर करें",
|
||||
"add_value": "मूल्य जोड़ें",
|
||||
"activate": "सक्रिय करें",
|
||||
"get_a": "एक प्राप्त करें",
|
||||
"digital_and_physical_card": "डिजिटल और भौतिक प्रीपेड डेबिट कार्ड",
|
||||
"get_card_note": " कि आप डिजिटल मुद्राओं के साथ पुनः लोड कर सकते हैं। कोई अतिरिक्त जानकारी की आवश्यकता नहीं है!",
|
||||
"signup_for_card_accept_terms": "कार्ड के लिए साइन अप करें और शर्तें स्वीकार करें।",
|
||||
"add_fund_to_card": "कार्ड में प्रीपेड धनराशि जोड़ें (${value} तक)",
|
||||
"use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।",
|
||||
"use_card_info_three": "डिजिटल कार्ड का ऑनलाइन या संपर्क रहित भुगतान विधियों के साथ उपयोग करें।",
|
||||
"optionally_order_card": "वैकल्पिक रूप से एक भौतिक कार्ड ऑर्डर करें।",
|
||||
"hide_details": "विवरण छुपाएं",
|
||||
"show_details": "विवरण दिखाएं",
|
||||
"upto": "${value} तक",
|
||||
"discount": "${value}% बचाएं",
|
||||
"gift_card_amount": "गिफ्ट कार्ड राशि",
|
||||
"bill_amount": "बिल राशि",
|
||||
"you_pay": "आप भुगतान करते हैं",
|
||||
"tip": "टिप:",
|
||||
"custom": "कस्टम",
|
||||
"by_cake_pay": "केकपे द्वारा",
|
||||
"expires": "समाप्त हो जाता है",
|
||||
"mm": "एमएम",
|
||||
"yy": "वाईवाई",
|
||||
"online": "ऑनलाइन",
|
||||
"offline": "ऑफ़लाइन",
|
||||
"gift_card_number": "गिफ्ट कार्ड नंबर",
|
||||
"pin_number": "पिन नंबर",
|
||||
"total_saving": "कुल बचत",
|
||||
"last_30_days": "पिछले 30 दिन",
|
||||
"avg_savings": "औसत बचत",
|
||||
"view_all": "सभी देखें",
|
||||
"active_cards": "सक्रिय कार्ड",
|
||||
"delete_account": "खाता हटाएं",
|
||||
"cards": "कार्ड",
|
||||
"active": "सक्रिय",
|
||||
"redeemed": "रिडीम किया गया",
|
||||
"gift_card_balance_note": "गिफ्ट कार्ड शेष राशि के साथ यहां दिखाई देंगे",
|
||||
"gift_card_redeemed_note": "आपके द्वारा भुनाए गए उपहार कार्ड यहां दिखाई देंगे",
|
||||
"logout": "लॉगआउट",
|
||||
"add_tip": "टिप जोड़ें",
|
||||
"percentageOf": "${amount} का",
|
||||
"is_percentage": "है",
|
||||
"search_category": "खोज श्रेणी",
|
||||
"mark_as_redeemed": "रिडीम किए गए के रूप में चिह्नित करें",
|
||||
"more_options": "और विकल्प",
|
||||
"awaiting_payment_confirmation": "भुगतान की पुष्टि की प्रतीक्षा में",
|
||||
"transaction_sent_notice": "अगर 1 मिनट के बाद भी स्क्रीन आगे नहीं बढ़ती है, तो ब्लॉक एक्सप्लोरर और अपना ईमेल देखें।",
|
||||
"agree": "सहमत",
|
||||
"in_store": "स्टोर में",
|
||||
"generating_gift_card": "गिफ्ट कार्ड जनरेट कर रहा है",
|
||||
"Payment_was_received": "आपका भुगतान प्राप्त हो गया था।",
|
||||
"proceed_after_one_minute": "यदि 1 मिनट के बाद भी स्क्रीन आगे नहीं बढ़ती है, तो अपना ईमेल देखें।",
|
||||
"order_id": "ऑर्डर आईडी",
|
||||
"gift_card_is_generated": "गिफ्ट कार्ड जनरेट हुआ",
|
||||
"open_gift_card": "गिफ्ट कार्ड खोलें",
|
||||
"contact_support": "सहायता से संपर्क करें",
|
||||
"gift_cards_unavailable": "उपहार कार्ड इस समय केवल मोनेरो, बिटकॉइन और लिटकोइन के माध्यम से खरीदने के लिए उपलब्ध हैं"
|
||||
}
|
||||
|
|