mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 11:39:22 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-447-mobile-scanner
This commit is contained in:
commit
6bfb6c8659
123 changed files with 3673 additions and 5455 deletions
4
.github/workflows/pr_test_build.yml
vendored
4
.github/workflows/pr_test_build.yml
vendored
|
@ -151,6 +151,10 @@ jobs:
|
||||||
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
|
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||||
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
|
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
|
||||||
|
echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart
|
||||||
echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
|
echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
|
||||||
echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
|
echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
|
||||||
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
In-app Cake Pay is Back
|
||||||
Bug fixes and generic enhancements
|
Bug fixes and generic enhancements
|
|
@ -1 +1,3 @@
|
||||||
|
In-app Cake Pay is Back
|
||||||
|
Bitcoin nodes stability enhancements
|
||||||
Bug fixes and generic enhancements
|
Bug fixes and generic enhancements
|
|
@ -1,8 +1,8 @@
|
||||||
-
|
-
|
||||||
uri: tron-rpc.publicnode.com:443
|
uri: tron-rpc.publicnode.com:443
|
||||||
is_default: true
|
is_default: false
|
||||||
useSSL: true
|
useSSL: true
|
||||||
-
|
-
|
||||||
uri: api.trongrid.io
|
uri: api.trongrid.io
|
||||||
is_default: false
|
is_default: true
|
||||||
useSSL: true
|
useSSL: true
|
|
@ -64,7 +64,7 @@ class ElectrumClient {
|
||||||
await socket?.close();
|
await socket?.close();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
if (useSSL == false) {
|
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
|
||||||
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
||||||
} else {
|
} else {
|
||||||
socket = await SecureSocket.connect(host, port,
|
socket = await SecureSocket.connect(host, port,
|
||||||
|
|
|
@ -96,13 +96,17 @@ abstract class ElectrumWalletBase
|
||||||
this.walletInfo = walletInfo;
|
this.walletInfo = walletInfo;
|
||||||
transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
|
transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
|
||||||
|
|
||||||
reaction((_) => syncStatus, (SyncStatus syncStatus) {
|
reaction((_) => syncStatus, (SyncStatus syncStatus) async {
|
||||||
if (syncStatus is! AttemptingSyncStatus && syncStatus is! SyncedTipSyncStatus)
|
if (syncStatus is! AttemptingSyncStatus && syncStatus is! SyncedTipSyncStatus) {
|
||||||
silentPaymentsScanningActive = syncStatus is SyncingSyncStatus;
|
silentPaymentsScanningActive = syncStatus is SyncingSyncStatus;
|
||||||
|
}
|
||||||
|
|
||||||
if (syncStatus is NotConnectedSyncStatus) {
|
if (syncStatus is NotConnectedSyncStatus) {
|
||||||
// Needs to re-subscribe to all scripthashes when reconnected
|
// Needs to re-subscribe to all scripthashes when reconnected
|
||||||
_scripthashesUpdateSubject = {};
|
_scripthashesUpdateSubject = {};
|
||||||
|
|
||||||
|
// TODO: double check this and make sure it doesn't cause any un-necessary calls
|
||||||
|
// await this.electrumClient.connectToUri(node!.uri, useSSL: node!.useSSL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message is shown on the UI for 3 seconds, revert to synced
|
// Message is shown on the UI for 3 seconds, revert to synced
|
||||||
|
@ -197,7 +201,7 @@ abstract class ElectrumWalletBase
|
||||||
bool silentPaymentsScanningActive = false;
|
bool silentPaymentsScanningActive = false;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> setSilentPaymentsScanning(bool active) async {
|
Future<void> setSilentPaymentsScanning(bool active, bool usingElectrs) async {
|
||||||
silentPaymentsScanningActive = active;
|
silentPaymentsScanningActive = active;
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
|
@ -210,18 +214,22 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tip > walletInfo.restoreHeight) {
|
if (tip > walletInfo.restoreHeight) {
|
||||||
_setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip);
|
_setListeners(
|
||||||
|
walletInfo.restoreHeight,
|
||||||
|
chainTipParam: _currentChainTip,
|
||||||
|
usingElectrs: usingElectrs,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alwaysScan = false;
|
alwaysScan = false;
|
||||||
|
|
||||||
(await _isolate)?.kill(priority: Isolate.immediate);
|
_isolate?.then((value) => value.kill(priority: Isolate.immediate));
|
||||||
|
|
||||||
if (electrumClient.isConnected) {
|
if (electrumClient.isConnected) {
|
||||||
syncStatus = SyncedSyncStatus();
|
syncStatus = SyncedSyncStatus();
|
||||||
} else {
|
} else {
|
||||||
if (electrumClient.uri != null) {
|
if (electrumClient.uri != null) {
|
||||||
await electrumClient.connectToUri(electrumClient.uri!);
|
await electrumClient.connectToUri(electrumClient.uri!, useSSL: electrumClient.useSSL);
|
||||||
startSync();
|
startSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,7 +285,12 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async {
|
Future<void> _setListeners(
|
||||||
|
int height, {
|
||||||
|
int? chainTipParam,
|
||||||
|
bool? doSingleScan,
|
||||||
|
bool? usingElectrs,
|
||||||
|
}) async {
|
||||||
final chainTip = chainTipParam ?? await getUpdatedChainTip();
|
final chainTip = chainTipParam ?? await getUpdatedChainTip();
|
||||||
|
|
||||||
if (chainTip == height) {
|
if (chainTip == height) {
|
||||||
|
@ -303,7 +316,7 @@ abstract class ElectrumWalletBase
|
||||||
chainTip: chainTip,
|
chainTip: chainTip,
|
||||||
electrumClient: ElectrumClient(),
|
electrumClient: ElectrumClient(),
|
||||||
transactionHistoryIds: transactionHistory.transactions.keys.toList(),
|
transactionHistoryIds: transactionHistory.transactions.keys.toList(),
|
||||||
node: ScanNode(node!.uri, node!.useSSL),
|
node: usingElectrs == true ? ScanNode(node!.uri, node!.useSSL) : null,
|
||||||
labels: walletAddresses.labels,
|
labels: walletAddresses.labels,
|
||||||
labelIndexes: walletAddresses.silentAddresses
|
labelIndexes: walletAddresses.silentAddresses
|
||||||
.where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1)
|
.where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1)
|
||||||
|
@ -454,17 +467,7 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
await electrumClient.close();
|
await electrumClient.close();
|
||||||
|
|
||||||
electrumClient.onConnectionStatusChange = (bool? isConnected) async {
|
electrumClient.onConnectionStatusChange = _onConnectionStatusChange;
|
||||||
if (syncStatus is SyncingSyncStatus) return;
|
|
||||||
|
|
||||||
if (isConnected == true && syncStatus is! SyncedSyncStatus) {
|
|
||||||
syncStatus = ConnectedSyncStatus();
|
|
||||||
} else if (isConnected == false) {
|
|
||||||
syncStatus = LostConnectionSyncStatus();
|
|
||||||
} else if (!(isConnected ?? false) && syncStatus is! ConnectingSyncStatus) {
|
|
||||||
syncStatus = NotConnectedSyncStatus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
|
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -1122,10 +1125,15 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@override
|
@override
|
||||||
Future<void> rescan(
|
Future<void> rescan({
|
||||||
{required int height, int? chainTip, ScanData? scanData, bool? doSingleScan}) async {
|
required int height,
|
||||||
|
int? chainTip,
|
||||||
|
ScanData? scanData,
|
||||||
|
bool? doSingleScan,
|
||||||
|
bool? usingElectrs,
|
||||||
|
}) async {
|
||||||
silentPaymentsScanningActive = true;
|
silentPaymentsScanningActive = true;
|
||||||
_setListeners(height, doSingleScan: doSingleScan);
|
_setListeners(height, doSingleScan: doSingleScan, usingElectrs: usingElectrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1643,6 +1651,7 @@ abstract class ElectrumWalletBase
|
||||||
if (_isTransactionUpdating) {
|
if (_isTransactionUpdating) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await getCurrentChainTip();
|
||||||
|
|
||||||
transactionHistory.transactions.values.forEach((tx) async {
|
transactionHistory.transactions.values.forEach((tx) async {
|
||||||
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height > 0) {
|
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height > 0) {
|
||||||
|
@ -1807,6 +1816,19 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
static String _hardenedDerivationPath(String derivationPath) =>
|
static String _hardenedDerivationPath(String derivationPath) =>
|
||||||
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
|
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
|
||||||
|
|
||||||
|
@action
|
||||||
|
void _onConnectionStatusChange(bool? isConnected) {
|
||||||
|
if (syncStatus is SyncingSyncStatus) return;
|
||||||
|
|
||||||
|
if (isConnected == true && syncStatus is! SyncedSyncStatus) {
|
||||||
|
syncStatus = ConnectedSyncStatus();
|
||||||
|
} else if (isConnected == false) {
|
||||||
|
syncStatus = LostConnectionSyncStatus();
|
||||||
|
} else if (isConnected != true && syncStatus is! ConnectingSyncStatus) {
|
||||||
|
syncStatus = NotConnectedSyncStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanNode {
|
class ScanNode {
|
||||||
|
@ -1820,7 +1842,7 @@ class ScanData {
|
||||||
final SendPort sendPort;
|
final SendPort sendPort;
|
||||||
final SilentPaymentOwner silentAddress;
|
final SilentPaymentOwner silentAddress;
|
||||||
final int height;
|
final int height;
|
||||||
final ScanNode node;
|
final ScanNode? node;
|
||||||
final BasedUtxoNetwork network;
|
final BasedUtxoNetwork network;
|
||||||
final int chainTip;
|
final int chainTip;
|
||||||
final ElectrumClient electrumClient;
|
final ElectrumClient electrumClient;
|
||||||
|
@ -1881,7 +1903,10 @@ Future<void> startRefresh(ScanData scanData) async {
|
||||||
scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
|
scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
|
||||||
|
|
||||||
final electrumClient = scanData.electrumClient;
|
final electrumClient = scanData.electrumClient;
|
||||||
await electrumClient.connectToUri(scanData.node.uri, useSSL: scanData.node.useSSL);
|
await electrumClient.connectToUri(
|
||||||
|
scanData.node?.uri ?? Uri.parse("tcp://electrs.cakewallet.com:50001"),
|
||||||
|
useSSL: scanData.node?.useSSL ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
if (tweaksSubscription == null) {
|
if (tweaksSubscription == null) {
|
||||||
final count = scanData.isSingleScan ? 1 : TWEAKS_COUNT;
|
final count = scanData.isSingleScan ? 1 : TWEAKS_COUNT;
|
||||||
|
|
|
@ -245,6 +245,7 @@ Future<int> getHavenCurrentHeight() async {
|
||||||
|
|
||||||
// Data taken from https://timechaincalendar.com/
|
// Data taken from https://timechaincalendar.com/
|
||||||
const bitcoinDates = {
|
const bitcoinDates = {
|
||||||
|
"2024-06": 846005,
|
||||||
"2024-05": 841590,
|
"2024-05": 841590,
|
||||||
"2024-04": 837182,
|
"2024-04": 837182,
|
||||||
"2024-03": 832623,
|
"2024-03": 832623,
|
||||||
|
|
|
@ -306,7 +306,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
}
|
}
|
||||||
|
|
||||||
final electrumClient = ElectrumClient();
|
final electrumClient = ElectrumClient();
|
||||||
await electrumClient.connectToUri(node.uri);
|
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
|
||||||
|
|
||||||
late BasedUtxoNetwork network;
|
late BasedUtxoNetwork network;
|
||||||
btc.NetworkType networkType;
|
btc.NetworkType networkType;
|
||||||
|
@ -514,18 +514,10 @@ class CWBitcoin extends Bitcoin {
|
||||||
@override
|
@override
|
||||||
Future<void> setScanningActive(Object wallet, bool active) async {
|
Future<void> setScanningActive(Object wallet, bool active) async {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
bitcoinWallet.setSilentPaymentsScanning(
|
||||||
if (active && !(await getNodeIsElectrsSPEnabled(wallet))) {
|
active,
|
||||||
final node = Node(
|
active && (await getNodeIsElectrsSPEnabled(wallet)),
|
||||||
useSSL: false,
|
);
|
||||||
uri: 'electrs.cakewallet.com:${(wallet.network == BitcoinNetwork.testnet ? 50002 : 50001)}',
|
|
||||||
);
|
|
||||||
node.type = WalletType.bitcoin;
|
|
||||||
|
|
||||||
await bitcoinWallet.connectToNode(node: node);
|
|
||||||
}
|
|
||||||
|
|
||||||
bitcoinWallet.setSilentPaymentsScanning(active);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -540,14 +532,6 @@ class CWBitcoin extends Bitcoin {
|
||||||
@override
|
@override
|
||||||
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}) async {
|
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}) async {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
if (!(await getNodeIsElectrsSPEnabled(wallet))) {
|
|
||||||
final node = Node(
|
|
||||||
useSSL: false,
|
|
||||||
uri: 'electrs.cakewallet.com:${(wallet.network == BitcoinNetwork.testnet ? 50002 : 50001)}',
|
|
||||||
);
|
|
||||||
node.type = WalletType.bitcoin;
|
|
||||||
await bitcoinWallet.connectToNode(node: node);
|
|
||||||
}
|
|
||||||
bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan);
|
bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,10 +560,16 @@ class CWBitcoin extends Bitcoin {
|
||||||
}
|
}
|
||||||
|
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
final tweaksResponse = await bitcoinWallet.electrumClient.getTweaks(height: 0);
|
try {
|
||||||
|
final tweaksResponse = await bitcoinWallet.electrumClient.getTweaks(height: 0);
|
||||||
|
|
||||||
if (tweaksResponse != null) {
|
if (tweaksResponse != null) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
} on RequestFailedTimeoutException {
|
||||||
|
return false;
|
||||||
|
} catch (_) {
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
245
lib/cake_pay/cake_pay_api.dart
Normal file
245
lib/cake_pay/cake_pay_api.dart
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_order.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_user_credentials.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
class CakePayApi {
|
||||||
|
static const testBaseUri = false;
|
||||||
|
|
||||||
|
static const baseTestCakePayUri = 'test.cakepay.com';
|
||||||
|
static const baseProdCakePayUri = 'buy.cakepay.com';
|
||||||
|
|
||||||
|
static const baseCakePayUri = testBaseUri ? baseTestCakePayUri : baseProdCakePayUri;
|
||||||
|
|
||||||
|
static const vendorsPath = '/api/vendors';
|
||||||
|
static const countriesPath = '/api/countries';
|
||||||
|
static const authPath = '/api/auth';
|
||||||
|
static final verifyEmailPath = '/api/verify';
|
||||||
|
static final logoutPath = '/api/logout';
|
||||||
|
static final createOrderPath = '/api/order';
|
||||||
|
static final simulatePaymentPath = '/api/simulate_payment';
|
||||||
|
|
||||||
|
/// AuthenticateUser
|
||||||
|
Future<String> authenticateUser({required String email, required String apiKey}) async {
|
||||||
|
try {
|
||||||
|
final uri = Uri.https(baseCakePayUri, authPath);
|
||||||
|
final headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Api-Key $apiKey',
|
||||||
|
};
|
||||||
|
final response = await http.post(uri, headers: headers, body: json.encode({'email': email}));
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final bodyJson = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
if (bodyJson.containsKey('user') && bodyJson['user']['email'] != null) {
|
||||||
|
return bodyJson['user']['email'] as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception('Failed to authenticate user with error: $bodyJson');
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to authenticate user with error: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify email
|
||||||
|
Future<CakePayUserCredentials> verifyEmail({
|
||||||
|
required String email,
|
||||||
|
required String code,
|
||||||
|
required String apiKey,
|
||||||
|
}) async {
|
||||||
|
final uri = Uri.https(baseCakePayUri, verifyEmailPath);
|
||||||
|
final headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Api-Key $apiKey',
|
||||||
|
};
|
||||||
|
final query = <String, String>{'email': email, 'otp': code};
|
||||||
|
|
||||||
|
final response = await http.post(uri, headers: headers, body: json.encode(query));
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final bodyJson = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
if (bodyJson.containsKey('error')) {
|
||||||
|
throw Exception(bodyJson['error'] as String);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bodyJson.containsKey('token')) {
|
||||||
|
final token = bodyJson['token'] as String;
|
||||||
|
final userEmail = bodyJson['user']['email'] as String;
|
||||||
|
return CakePayUserCredentials(userEmail, token);
|
||||||
|
} else {
|
||||||
|
throw Exception('E-mail verification failed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// createOrder
|
||||||
|
Future<CakePayOrder> createOrder({
|
||||||
|
required String apiKey,
|
||||||
|
required int cardId,
|
||||||
|
required String price,
|
||||||
|
required int quantity,
|
||||||
|
required String userEmail,
|
||||||
|
required String token,
|
||||||
|
}) async {
|
||||||
|
final uri = Uri.https(baseCakePayUri, createOrderPath);
|
||||||
|
final headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Api-Key $apiKey',
|
||||||
|
};
|
||||||
|
final query = <String, dynamic>{
|
||||||
|
'card_id': cardId,
|
||||||
|
'price': price,
|
||||||
|
'quantity': quantity,
|
||||||
|
'user_email': userEmail,
|
||||||
|
'token': token,
|
||||||
|
'send_email': true
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await http.post(uri, headers: headers, body: json.encode(query));
|
||||||
|
|
||||||
|
if (response.statusCode != 201) {
|
||||||
|
final responseBody = json.decode(response.body);
|
||||||
|
if (responseBody is List) {
|
||||||
|
throw '${responseBody[0]}';
|
||||||
|
} else {
|
||||||
|
throw Exception('Unexpected error: $responseBody');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final bodyJson = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
return CakePayOrder.fromMap(bodyJson);
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('${e}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///Simulate Payment
|
||||||
|
Future<void> simulatePayment(
|
||||||
|
{required String CSRFToken, required String authorization, required String orderId}) async {
|
||||||
|
final uri = Uri.https(baseCakePayUri, simulatePaymentPath + '/$orderId');
|
||||||
|
|
||||||
|
final headers = {
|
||||||
|
'accept': 'application/json',
|
||||||
|
'authorization': authorization,
|
||||||
|
'X-CSRFToken': CSRFToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
final response = await http.get(uri, headers: headers);
|
||||||
|
|
||||||
|
print('Response: ${response.statusCode}');
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final bodyJson = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
throw Exception('You just bot a gift card with id: ${bodyJson['order_id']}');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logout
|
||||||
|
Future<void> logoutUser({required String email, required String apiKey}) async {
|
||||||
|
final uri = Uri.https(baseCakePayUri, logoutPath);
|
||||||
|
final headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Api-Key $apiKey',
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await http.post(uri, headers: headers, body: json.encode({'email': email}));
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Caught exception: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get Countries
|
||||||
|
Future<List<String>> getCountries(
|
||||||
|
{required String CSRFToken, required String authorization}) async {
|
||||||
|
final uri = Uri.https(baseCakePayUri, countriesPath);
|
||||||
|
|
||||||
|
final headers = {
|
||||||
|
'accept': 'application/json',
|
||||||
|
'authorization': authorization,
|
||||||
|
'X-CSRFToken': CSRFToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
final response = await http.get(uri, headers: headers);
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final bodyJson = json.decode(response.body) as List;
|
||||||
|
|
||||||
|
return bodyJson.map<String>((country) => country['name'] as String).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get Vendors
|
||||||
|
Future<List<CakePayVendor>> getVendors({
|
||||||
|
required String CSRFToken,
|
||||||
|
required String authorization,
|
||||||
|
int? page,
|
||||||
|
String? country,
|
||||||
|
String? countryCode,
|
||||||
|
String? search,
|
||||||
|
List<String>? vendorIds,
|
||||||
|
bool? giftCards,
|
||||||
|
bool? prepaidCards,
|
||||||
|
bool? onDemand,
|
||||||
|
bool? custom,
|
||||||
|
}) async {
|
||||||
|
var queryParams = {
|
||||||
|
'page': page?.toString(),
|
||||||
|
'country': country,
|
||||||
|
'country_code': countryCode,
|
||||||
|
'search': search,
|
||||||
|
'vendor_ids': vendorIds?.join(','),
|
||||||
|
'gift_cards': giftCards?.toString(),
|
||||||
|
'prepaid_cards': prepaidCards?.toString(),
|
||||||
|
'on_demand': onDemand?.toString(),
|
||||||
|
'custom': custom?.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
final uri = Uri.https(baseCakePayUri, vendorsPath, queryParams);
|
||||||
|
|
||||||
|
var headers = {
|
||||||
|
'accept': 'application/json; charset=UTF-8',
|
||||||
|
'authorization': authorization,
|
||||||
|
'X-CSRFToken': CSRFToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await http.get(uri, headers: headers);
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception(response.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bodyJson = json.decode(response.body);
|
||||||
|
|
||||||
|
if (bodyJson is List<dynamic> && bodyJson.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bodyJson['results'] as List)
|
||||||
|
.map((e) => CakePayVendor.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
87
lib/cake_pay/cake_pay_card.dart
Normal file
87
lib/cake_pay/cake_pay_card.dart
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||||
|
|
||||||
|
class CakePayCard {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final String? description;
|
||||||
|
final String? termsAndConditions;
|
||||||
|
final String? howToUse;
|
||||||
|
final String? expiryAndValidity;
|
||||||
|
final String? cardImageUrl;
|
||||||
|
final String? country;
|
||||||
|
final FiatCurrency fiatCurrency;
|
||||||
|
final List<String> denominationsUsd;
|
||||||
|
final List<String> denominations;
|
||||||
|
final String? minValueUsd;
|
||||||
|
final String? maxValueUsd;
|
||||||
|
final String? minValue;
|
||||||
|
final String? maxValue;
|
||||||
|
|
||||||
|
CakePayCard({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
this.description,
|
||||||
|
this.termsAndConditions,
|
||||||
|
this.howToUse,
|
||||||
|
this.expiryAndValidity,
|
||||||
|
this.cardImageUrl,
|
||||||
|
this.country,
|
||||||
|
required this.fiatCurrency,
|
||||||
|
required this.denominationsUsd,
|
||||||
|
required this.denominations,
|
||||||
|
this.minValueUsd,
|
||||||
|
this.maxValueUsd,
|
||||||
|
this.minValue,
|
||||||
|
this.maxValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CakePayCard.fromJson(Map<String, dynamic> json) {
|
||||||
|
final name = stripHtmlIfNeeded(json['name'] as String? ?? '');
|
||||||
|
final decodedName = fixEncoding(name);
|
||||||
|
|
||||||
|
final description = stripHtmlIfNeeded(json['description'] as String? ?? '');
|
||||||
|
final decodedDescription = fixEncoding(description);
|
||||||
|
|
||||||
|
final termsAndConditions = stripHtmlIfNeeded(json['terms_and_conditions'] as String? ?? '');
|
||||||
|
final decodedTermsAndConditions = fixEncoding(termsAndConditions);
|
||||||
|
|
||||||
|
final howToUse = stripHtmlIfNeeded(json['how_to_use'] as String? ?? '');
|
||||||
|
final decodedHowToUse = fixEncoding(howToUse);
|
||||||
|
|
||||||
|
final fiatCurrency = FiatCurrency.deserialize(raw: json['currency_code'] as String? ?? '');
|
||||||
|
|
||||||
|
final List<String> denominationsUsd =
|
||||||
|
(json['denominations_usd'] as List?)?.map((e) => e.toString()).toList() ?? [];
|
||||||
|
final List<String> denominations =
|
||||||
|
(json['denominations'] as List?)?.map((e) => e.toString()).toList() ?? [];
|
||||||
|
|
||||||
|
return CakePayCard(
|
||||||
|
id: json['id'] as int? ?? 0,
|
||||||
|
name: decodedName,
|
||||||
|
description: decodedDescription,
|
||||||
|
termsAndConditions: decodedTermsAndConditions,
|
||||||
|
howToUse: decodedHowToUse,
|
||||||
|
expiryAndValidity: json['expiry_and_validity'] as String?,
|
||||||
|
cardImageUrl: json['card_image_url'] as String?,
|
||||||
|
country: json['country'] as String?,
|
||||||
|
fiatCurrency: fiatCurrency,
|
||||||
|
denominationsUsd: denominationsUsd,
|
||||||
|
denominations: denominations,
|
||||||
|
minValueUsd: json['min_value_usd'] as String?,
|
||||||
|
maxValueUsd: json['max_value_usd'] as String?,
|
||||||
|
minValue: json['min_value'] as String?,
|
||||||
|
maxValue: json['max_value'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String stripHtmlIfNeeded(String text) {
|
||||||
|
return text.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
static String fixEncoding(String text) {
|
||||||
|
final bytes = latin1.encode(text);
|
||||||
|
return utf8.decode(bytes, allowMalformed: true);
|
||||||
|
}
|
||||||
|
}
|
131
lib/cake_pay/cake_pay_order.dart
Normal file
131
lib/cake_pay/cake_pay_order.dart
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
|
||||||
|
class CakePayOrder {
|
||||||
|
final String orderId;
|
||||||
|
final List<OrderCard> cards;
|
||||||
|
final String? externalId;
|
||||||
|
final double amountUsd;
|
||||||
|
final String status;
|
||||||
|
final String? vouchers;
|
||||||
|
final PaymentData paymentData;
|
||||||
|
|
||||||
|
CakePayOrder({
|
||||||
|
required this.orderId,
|
||||||
|
required this.cards,
|
||||||
|
required this.externalId,
|
||||||
|
required this.amountUsd,
|
||||||
|
required this.status,
|
||||||
|
required this.vouchers,
|
||||||
|
required this.paymentData,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CakePayOrder.fromMap(Map<String, dynamic> map) {
|
||||||
|
return CakePayOrder(
|
||||||
|
orderId: map['order_id'] as String,
|
||||||
|
cards: (map['cards'] as List<dynamic>)
|
||||||
|
.map((x) => OrderCard.fromMap(x as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
externalId: map['external_id'] as String?,
|
||||||
|
amountUsd: map['amount_usd'] as double,
|
||||||
|
status: map['status'] as String,
|
||||||
|
vouchers: map['vouchers'] as String?,
|
||||||
|
paymentData: PaymentData.fromMap(map['payment_data'] as Map<String, dynamic>));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderCard {
|
||||||
|
final int cardId;
|
||||||
|
final int? externalId;
|
||||||
|
final String price;
|
||||||
|
final int quantity;
|
||||||
|
final String currencyCode;
|
||||||
|
|
||||||
|
OrderCard({
|
||||||
|
required this.cardId,
|
||||||
|
required this.externalId,
|
||||||
|
required this.price,
|
||||||
|
required this.quantity,
|
||||||
|
required this.currencyCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory OrderCard.fromMap(Map<String, dynamic> map) {
|
||||||
|
return OrderCard(
|
||||||
|
cardId: map['card_id'] as int,
|
||||||
|
externalId: map['external_id'] as int?,
|
||||||
|
price: map['price'] as String,
|
||||||
|
quantity: map['quantity'] as int,
|
||||||
|
currencyCode: map['currency_code'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaymentData {
|
||||||
|
final CryptoPaymentData btc;
|
||||||
|
final CryptoPaymentData xmr;
|
||||||
|
final DateTime invoiceTime;
|
||||||
|
final DateTime expirationTime;
|
||||||
|
final int? commission;
|
||||||
|
|
||||||
|
PaymentData({
|
||||||
|
required this.btc,
|
||||||
|
required this.xmr,
|
||||||
|
required this.invoiceTime,
|
||||||
|
required this.expirationTime,
|
||||||
|
required this.commission,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PaymentData.fromMap(Map<String, dynamic> map) {
|
||||||
|
return PaymentData(
|
||||||
|
btc: CryptoPaymentData.fromMap(map['BTC'] as Map<String, dynamic>),
|
||||||
|
xmr: CryptoPaymentData.fromMap(map['XMR'] as Map<String, dynamic>),
|
||||||
|
invoiceTime: DateTime.fromMillisecondsSinceEpoch(map['invoice_time'] as int),
|
||||||
|
expirationTime: DateTime.fromMillisecondsSinceEpoch(map['expiration_time'] as int),
|
||||||
|
commission: map['commission'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CryptoPaymentData {
|
||||||
|
final String price;
|
||||||
|
final PaymentUrl? paymentUrls;
|
||||||
|
final String address;
|
||||||
|
|
||||||
|
CryptoPaymentData({
|
||||||
|
required this.price,
|
||||||
|
this.paymentUrls,
|
||||||
|
required this.address,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CryptoPaymentData.fromMap(Map<String, dynamic> map) {
|
||||||
|
return CryptoPaymentData(
|
||||||
|
price: map['price'] as String,
|
||||||
|
paymentUrls: PaymentUrl.fromMap(map['paymentUrls'] as Map<String, dynamic>?),
|
||||||
|
address: map['address'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaymentUrl {
|
||||||
|
final String? bip21;
|
||||||
|
final String? bip72;
|
||||||
|
final String? bip72b;
|
||||||
|
final String? bip73;
|
||||||
|
final String? bolt11;
|
||||||
|
|
||||||
|
PaymentUrl({
|
||||||
|
this.bip21,
|
||||||
|
this.bip72,
|
||||||
|
this.bip72b,
|
||||||
|
this.bip73,
|
||||||
|
this.bolt11,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PaymentUrl.fromMap(Map<String, dynamic>? map) {
|
||||||
|
return PaymentUrl(
|
||||||
|
bip21: map?['BIP21'] as String?,
|
||||||
|
bip72: map?['BIP72'] as String?,
|
||||||
|
bip72b: map?['BIP72b'] as String?,
|
||||||
|
bip73: map?['BIP73'] as String?,
|
||||||
|
bolt11: map?['BOLT11'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
15
lib/cake_pay/cake_pay_payment_credantials.dart
Normal file
15
lib/cake_pay/cake_pay_payment_credantials.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class PaymentCredential {
|
||||||
|
final double amount;
|
||||||
|
final int quantity;
|
||||||
|
final double totalAmount;
|
||||||
|
final String? userName;
|
||||||
|
final String fiatCurrency;
|
||||||
|
|
||||||
|
PaymentCredential({
|
||||||
|
required this.amount,
|
||||||
|
required this.quantity,
|
||||||
|
required this.totalAmount,
|
||||||
|
required this.userName,
|
||||||
|
required this.fiatCurrency,
|
||||||
|
});
|
||||||
|
}
|
107
lib/cake_pay/cake_pay_service.dart
Normal file
107
lib/cake_pay/cake_pay_service.dart
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_api.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_order.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart';
|
||||||
|
import 'package:cake_wallet/core/secure_storage.dart';
|
||||||
|
|
||||||
|
class CakePayService {
|
||||||
|
CakePayService(this.secureStorage, this.cakePayApi);
|
||||||
|
|
||||||
|
static const cakePayEmailStorageKey = 'cake_pay_email';
|
||||||
|
static const cakePayUsernameStorageKey = 'cake_pay_username';
|
||||||
|
static const cakePayUserTokenKey = 'cake_pay_user_token';
|
||||||
|
|
||||||
|
static String get testCakePayApiKey => secrets.testCakePayApiKey;
|
||||||
|
|
||||||
|
static String get cakePayApiKey => secrets.cakePayApiKey;
|
||||||
|
|
||||||
|
static String get CSRFToken => secrets.CSRFToken;
|
||||||
|
|
||||||
|
static String get authorization => secrets.authorization;
|
||||||
|
|
||||||
|
final SecureStorage secureStorage;
|
||||||
|
final CakePayApi cakePayApi;
|
||||||
|
|
||||||
|
/// Get Available Countries
|
||||||
|
Future<List<String>> getCountries() async =>
|
||||||
|
await cakePayApi.getCountries(CSRFToken: CSRFToken, authorization: authorization);
|
||||||
|
|
||||||
|
/// Get Vendors
|
||||||
|
Future<List<CakePayVendor>> getVendors({
|
||||||
|
int? page,
|
||||||
|
String? country,
|
||||||
|
String? countryCode,
|
||||||
|
String? search,
|
||||||
|
List<String>? vendorIds,
|
||||||
|
bool? giftCards,
|
||||||
|
bool? prepaidCards,
|
||||||
|
bool? onDemand,
|
||||||
|
bool? custom,
|
||||||
|
}) async {
|
||||||
|
final result = await cakePayApi.getVendors(
|
||||||
|
CSRFToken: CSRFToken,
|
||||||
|
authorization: authorization,
|
||||||
|
page: page,
|
||||||
|
country: country,
|
||||||
|
countryCode: countryCode,
|
||||||
|
search: search,
|
||||||
|
vendorIds: vendorIds,
|
||||||
|
giftCards: giftCards,
|
||||||
|
prepaidCards: prepaidCards,
|
||||||
|
onDemand: onDemand,
|
||||||
|
custom: custom);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// LogIn
|
||||||
|
Future<void> logIn(String email) async {
|
||||||
|
final userName = await cakePayApi.authenticateUser(email: email, apiKey: cakePayApiKey);
|
||||||
|
await secureStorage.write(key: cakePayEmailStorageKey, value: userName);
|
||||||
|
await secureStorage.write(key: cakePayUsernameStorageKey, value: userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify email
|
||||||
|
Future<void> verifyEmail(String code) async {
|
||||||
|
final email = (await secureStorage.read(key: cakePayEmailStorageKey))!;
|
||||||
|
final credentials =
|
||||||
|
await cakePayApi.verifyEmail(email: email, code: code, apiKey: cakePayApiKey);
|
||||||
|
await secureStorage.write(key: cakePayUserTokenKey, value: credentials.token);
|
||||||
|
await secureStorage.write(key: cakePayUsernameStorageKey, value: credentials.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getUserEmail() async {
|
||||||
|
return (await secureStorage.read(key: cakePayEmailStorageKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check is user logged
|
||||||
|
Future<bool> isLogged() async {
|
||||||
|
final username = await secureStorage.read(key: cakePayUsernameStorageKey) ?? '';
|
||||||
|
final password = await secureStorage.read(key: cakePayUserTokenKey) ?? '';
|
||||||
|
return username.isNotEmpty && password.isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logout
|
||||||
|
Future<void> logout(String email) async {
|
||||||
|
await secureStorage.delete(key: cakePayUsernameStorageKey);
|
||||||
|
await secureStorage.delete(key: cakePayUserTokenKey);
|
||||||
|
await cakePayApi.logoutUser(email: email, apiKey: cakePayApiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Purchase Gift Card
|
||||||
|
Future<CakePayOrder> createOrder(
|
||||||
|
{required int cardId, required String price, required int quantity}) async {
|
||||||
|
final userEmail = (await secureStorage.read(key: cakePayEmailStorageKey))!;
|
||||||
|
final token = (await secureStorage.read(key: cakePayUserTokenKey))!;
|
||||||
|
return await cakePayApi.createOrder(
|
||||||
|
apiKey: cakePayApiKey,
|
||||||
|
cardId: cardId,
|
||||||
|
price: price,
|
||||||
|
quantity: quantity,
|
||||||
|
token: token,
|
||||||
|
userEmail: userEmail);
|
||||||
|
}
|
||||||
|
|
||||||
|
///Simulate Purchase Gift Card
|
||||||
|
Future<void> simulatePayment({required String orderId}) async => await cakePayApi.simulatePayment(
|
||||||
|
CSRFToken: CSRFToken, authorization: authorization, orderId: orderId);
|
||||||
|
}
|
67
lib/cake_pay/cake_pay_states.dart
Normal file
67
lib/cake_pay/cake_pay_states.dart
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import 'cake_pay_card.dart';
|
||||||
|
|
||||||
|
abstract class CakePayUserVerificationState {}
|
||||||
|
|
||||||
|
class CakePayUserVerificationStateInitial extends CakePayUserVerificationState {}
|
||||||
|
|
||||||
|
class CakePayUserVerificationStateSuccess extends CakePayUserVerificationState {}
|
||||||
|
|
||||||
|
class CakePayUserVerificationStatePending extends CakePayUserVerificationState {}
|
||||||
|
|
||||||
|
class CakePayUserVerificationStateLoading extends CakePayUserVerificationState {}
|
||||||
|
|
||||||
|
class CakePayUserVerificationStateFailure extends CakePayUserVerificationState {
|
||||||
|
CakePayUserVerificationStateFailure({required this.error});
|
||||||
|
|
||||||
|
final String error;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CakePayOtpState {}
|
||||||
|
|
||||||
|
class CakePayOtpValidating extends CakePayOtpState {}
|
||||||
|
|
||||||
|
class CakePayOtpSuccess extends CakePayOtpState {}
|
||||||
|
|
||||||
|
class CakePayOtpSendDisabled extends CakePayOtpState {}
|
||||||
|
|
||||||
|
class CakePayOtpSendEnabled extends CakePayOtpState {}
|
||||||
|
|
||||||
|
class CakePayOtpFailure extends CakePayOtpState {
|
||||||
|
CakePayOtpFailure({required this.error});
|
||||||
|
|
||||||
|
final String error;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CakePayCreateCardState {}
|
||||||
|
|
||||||
|
class CakePayCreateCardStateSuccess extends CakePayCreateCardState {}
|
||||||
|
|
||||||
|
class CakePayCreateCardStateLoading extends CakePayCreateCardState {}
|
||||||
|
|
||||||
|
class CakePayCreateCardStateFailure extends CakePayCreateCardState {
|
||||||
|
CakePayCreateCardStateFailure({required this.error});
|
||||||
|
|
||||||
|
final String error;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CakePayCardsState {}
|
||||||
|
|
||||||
|
class CakePayCardsStateNoCards extends CakePayCardsState {}
|
||||||
|
|
||||||
|
class CakePayCardsStateFetching extends CakePayCardsState {}
|
||||||
|
|
||||||
|
class CakePayCardsStateFailure extends CakePayCardsState {}
|
||||||
|
|
||||||
|
class CakePayCardsStateSuccess extends CakePayCardsState {
|
||||||
|
CakePayCardsStateSuccess({required this.card});
|
||||||
|
|
||||||
|
final CakePayCard card;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CakePayVendorState {}
|
||||||
|
|
||||||
|
class InitialCakePayVendorLoadingState extends CakePayVendorState {}
|
||||||
|
|
||||||
|
class CakePayVendorLoadingState extends CakePayVendorState {}
|
||||||
|
|
||||||
|
class CakePayVendorLoadedState extends CakePayVendorState {}
|
6
lib/cake_pay/cake_pay_user_credentials.dart
Normal file
6
lib/cake_pay/cake_pay_user_credentials.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class CakePayUserCredentials {
|
||||||
|
const CakePayUserCredentials(this.username, this.token);
|
||||||
|
|
||||||
|
final String username;
|
||||||
|
final String token;
|
||||||
|
}
|
51
lib/cake_pay/cake_pay_vendor.dart
Normal file
51
lib/cake_pay/cake_pay_vendor.dart
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'cake_pay_card.dart';
|
||||||
|
|
||||||
|
class CakePayVendor {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final bool unavailable;
|
||||||
|
final String? cakeWarnings;
|
||||||
|
final List<String> countries;
|
||||||
|
final CakePayCard? card;
|
||||||
|
|
||||||
|
CakePayVendor({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.unavailable,
|
||||||
|
this.cakeWarnings,
|
||||||
|
required this.countries,
|
||||||
|
this.card,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CakePayVendor.fromJson(Map<String, dynamic> json) {
|
||||||
|
final name = stripHtmlIfNeeded(json['name'] as String);
|
||||||
|
final decodedName = fixEncoding(name);
|
||||||
|
|
||||||
|
var cardsJson = json['cards'] as List?;
|
||||||
|
CakePayCard? firstCard;
|
||||||
|
|
||||||
|
if (cardsJson != null && cardsJson.isNotEmpty) {
|
||||||
|
firstCard = CakePayCard.fromJson(cardsJson.first as Map<String, dynamic>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CakePayVendor(
|
||||||
|
id: json['id'] as int,
|
||||||
|
name: decodedName,
|
||||||
|
unavailable: json['unavailable'] as bool? ?? false,
|
||||||
|
cakeWarnings: json['cake_warnings'] as String?,
|
||||||
|
countries: List<String>.from(json['countries'] as List? ?? []),
|
||||||
|
card: firstCard,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String stripHtmlIfNeeded(String text) {
|
||||||
|
return text.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
static String fixEncoding(String text) {
|
||||||
|
final bytes = latin1.encode(text);
|
||||||
|
return utf8.decode(bytes, allowMalformed: true);
|
||||||
|
}
|
||||||
|
}
|
230
lib/di.dart
230
lib/di.dart
|
@ -2,7 +2,6 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
import 'package:cake_wallet/anonpay/anonpay_api.dart';
|
import 'package:cake_wallet/anonpay/anonpay_api.dart';
|
||||||
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
|
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
|
||||||
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||||
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
|
|
||||||
import 'package:cake_wallet/anypay/anypay_api.dart';
|
import 'package:cake_wallet/anypay/anypay_api.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||||
|
@ -33,16 +32,10 @@ import 'package:cake_wallet/entities/qr_view_data.dart';
|
||||||
import 'package:cake_wallet/entities/template.dart';
|
import 'package:cake_wallet/entities/template.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_card.dart';
|
||||||
import 'package:cake_wallet/exchange/exchange_template.dart';
|
import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||||
import 'package:cake_wallet/exchange/trade.dart';
|
import 'package:cake_wallet/exchange/trade.dart';
|
||||||
import 'package:cake_wallet/haven/haven.dart';
|
import 'package:cake_wallet/haven/haven.dart';
|
||||||
import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_anypay.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_api.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_tip.dart';
|
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
import 'package:cake_wallet/nano/nano.dart';
|
import 'package:cake_wallet/nano/nano.dart';
|
||||||
import 'package:cake_wallet/polygon/polygon.dart';
|
import 'package:cake_wallet/polygon/polygon.dart';
|
||||||
|
@ -73,14 +66,6 @@ import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
|
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
|
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/faq/faq_page.dart';
|
import 'package:cake_wallet/src/screens/faq/faq_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_redeem_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/ionia/cards/ionia_more_options_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/ionia/ionia.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
|
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
|
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
|
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
|
||||||
|
@ -125,16 +110,57 @@ import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.d
|
||||||
import 'package:cake_wallet/src/screens/support/support_page.dart';
|
import 'package:cake_wallet/src/screens/support/support_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
|
import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
|
import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
|
||||||
|
import 'package:cake_wallet/themes/theme_list.dart';
|
||||||
|
import 'package:cake_wallet/utils/device_info.dart';
|
||||||
|
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
|
||||||
|
import 'package:cake_wallet/utils/payment_request.dart';
|
||||||
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/cake_pay/cake_pay_auth_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/cake_pay/cake_pay_buy_card_view_model.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_service.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_api.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/cake_pay/auth/cake_pay_account_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart';
|
||||||
|
import 'package:cake_wallet/view_model/cake_pay/cake_pay_account_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/cake_pay/cake_pay_cards_list_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/cake_pay/cake_pay_purchase_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
|
||||||
|
import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
|
||||||
|
import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
|
||||||
|
import 'package:cw_core/nano_account.dart';
|
||||||
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
import 'package:cw_core/wallet_service.dart';
|
||||||
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
import 'package:cw_core/node.dart';
|
||||||
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
|
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
|
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
|
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
||||||
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
|
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cake_wallet/store/authentication_store.dart';
|
import 'package:cake_wallet/store/authentication_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||||
|
@ -149,14 +175,7 @@ import 'package:cake_wallet/store/templates/exchange_template_store.dart';
|
||||||
import 'package:cake_wallet/store/templates/send_template_store.dart';
|
import 'package:cake_wallet/store/templates/send_template_store.dart';
|
||||||
import 'package:cake_wallet/store/wallet_list_store.dart';
|
import 'package:cake_wallet/store/wallet_list_store.dart';
|
||||||
import 'package:cake_wallet/store/yat/yat_store.dart';
|
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||||
import 'package:cake_wallet/themes/theme_list.dart';
|
|
||||||
import 'package:cake_wallet/tron/tron.dart';
|
import 'package:cake_wallet/tron/tron.dart';
|
||||||
import 'package:cake_wallet/utils/device_info.dart';
|
|
||||||
import 'package:cake_wallet/utils/payment_request.dart';
|
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
|
||||||
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/backup_view_model.dart';
|
import 'package:cake_wallet/view_model/backup_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
|
import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
|
||||||
|
@ -166,46 +185,22 @@ import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
|
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
|
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
|
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.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_custom_redeem_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/ionia/ionia_custom_tip_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_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_payment_status_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
|
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/order_details_view_model.dart';
|
import 'package:cake_wallet/view_model/order_details_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/rescan_view_model.dart';
|
import 'package:cake_wallet/view_model/rescan_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
|
|
||||||
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
|
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
|
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
|
|
||||||
import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
|
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
|
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/support_view_model.dart';
|
import 'package:cake_wallet/view_model/support_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
|
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
|
||||||
|
@ -214,25 +209,16 @@ import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_
|
||||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
|
||||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
|
|
||||||
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
|
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
|
|
||||||
import 'package:cake_wallet/view_model/wallet_restore_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/wallet_seed_view_model.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/nano_account.dart';
|
|
||||||
import 'package:cw_core/node.dart';
|
|
||||||
import 'package:cw_core/receive_page_option.dart';
|
import 'package:cw_core/receive_page_option.dart';
|
||||||
import 'package:cw_core/transaction_info.dart';
|
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_service.dart';
|
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
@ -240,6 +226,7 @@ import 'package:get_it/get_it.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'cake_pay/cake_pay_payment_credantials.dart';
|
||||||
|
|
||||||
final getIt = GetIt.instance;
|
final getIt = GetIt.instance;
|
||||||
|
|
||||||
|
@ -994,6 +981,8 @@ Future<void> setup({
|
||||||
trades: _tradesSource,
|
trades: _tradesSource,
|
||||||
settingsStore: getIt.get<SettingsStore>()));
|
settingsStore: getIt.get<SettingsStore>()));
|
||||||
|
|
||||||
|
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<CakePayService>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => BackupService(getIt.get<SecureStorage>(), _walletInfoSource,
|
getIt.registerFactory(() => BackupService(getIt.get<SecureStorage>(), _walletInfoSource,
|
||||||
getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
|
getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
|
||||||
|
|
||||||
|
@ -1089,113 +1078,60 @@ Future<void> setup({
|
||||||
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
|
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
|
||||||
(QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
|
(QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
|
||||||
|
|
||||||
getIt.registerFactory(() => IoniaApi());
|
getIt.registerFactory(() => CakePayApi());
|
||||||
|
|
||||||
getIt.registerFactory(() => AnyPayApi());
|
getIt.registerFactory(() => AnyPayApi());
|
||||||
|
|
||||||
getIt.registerFactory<IoniaService>(
|
getIt.registerFactory<CakePayService>(
|
||||||
() => IoniaService(getIt.get<SecureStorage>(), getIt.get<IoniaApi>()));
|
() => CakePayService(getIt.get<SecureStorage>(), getIt.get<CakePayApi>()));
|
||||||
|
|
||||||
getIt.registerFactory<IoniaAnyPay>(() => IoniaAnyPay(
|
getIt.registerFactory(() => CakePayCardsListViewModel(cakePayService: getIt.get<CakePayService>()));
|
||||||
getIt.get<IoniaService>(), getIt.get<AnyPayApi>(), getIt.get<AppStore>().wallet!));
|
|
||||||
|
|
||||||
getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
|
getIt.registerFactory(() => CakePayAuthViewModel(cakePayService: getIt.get<CakePayService>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<IoniaService>()));
|
getIt.registerFactoryParam<CakePayPurchaseViewModel, PaymentCredential, CakePayCard>(
|
||||||
|
(PaymentCredential paymentCredential, CakePayCard card) {
|
||||||
getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));
|
return CakePayPurchaseViewModel(
|
||||||
|
cakePayService: getIt.get<CakePayService>(),
|
||||||
getIt.registerFactoryParam<IoniaMerchPurchaseViewModel, double, IoniaMerchant>(
|
paymentCredential: paymentCredential,
|
||||||
(double amount, merchant) {
|
card: card,
|
||||||
return IoniaMerchPurchaseViewModel(
|
|
||||||
ioniaAnyPayService: getIt.get<IoniaAnyPay>(),
|
|
||||||
amount: amount,
|
|
||||||
ioniaMerchant: merchant,
|
|
||||||
sendViewModel: getIt.get<SendViewModel>());
|
sendViewModel: getIt.get<SendViewModel>());
|
||||||
});
|
});
|
||||||
|
|
||||||
getIt.registerFactoryParam<IoniaBuyCardViewModel, IoniaMerchant, void>(
|
getIt.registerFactoryParam<CakePayBuyCardViewModel, CakePayVendor, void>(
|
||||||
(IoniaMerchant merchant, _) {
|
(CakePayVendor vendor, _) {
|
||||||
return IoniaBuyCardViewModel(ioniaMerchant: merchant);
|
return CakePayBuyCardViewModel(vendor: vendor);
|
||||||
});
|
});
|
||||||
|
|
||||||
getIt.registerFactory(() => IoniaAccountViewModel(ioniaService: getIt.get<IoniaService>()));
|
getIt.registerFactory(() => CakePayAccountViewModel(cakePayService: getIt.get<CakePayService>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => IoniaCreateAccountPage(getIt.get<IoniaAuthViewModel>()));
|
getIt.registerFactory(() => CakePayWelcomePage(getIt.get<CakePayAuthViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => IoniaLoginPage(getIt.get<IoniaAuthViewModel>()));
|
getIt.registerFactoryParam<CakePayVerifyOtpPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||||
|
|
||||||
getIt.registerFactoryParam<IoniaVerifyIoniaOtp, List<dynamic>, void>((List<dynamic> args, _) {
|
|
||||||
final email = args.first as String;
|
final email = args.first as String;
|
||||||
final isSignIn = args[1] as bool;
|
final isSignIn = args[1] as bool;
|
||||||
|
|
||||||
return IoniaVerifyIoniaOtp(getIt.get<IoniaAuthViewModel>(), email, isSignIn);
|
return CakePayVerifyOtpPage(getIt.get<CakePayAuthViewModel>(), email, isSignIn);
|
||||||
});
|
});
|
||||||
|
|
||||||
getIt.registerFactory(() => IoniaWelcomePage());
|
getIt.registerFactoryParam<CakePayBuyCardPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||||
|
final vendor = args.first as CakePayVendor;
|
||||||
|
|
||||||
getIt.registerFactoryParam<IoniaBuyGiftCardPage, List<dynamic>, void>((List<dynamic> args, _) {
|
return CakePayBuyCardPage(getIt.get<CakePayBuyCardViewModel>(param1: vendor),
|
||||||
final merchant = args.first as IoniaMerchant;
|
getIt.get<CakePayService>());
|
||||||
|
|
||||||
return IoniaBuyGiftCardPage(getIt.get<IoniaBuyCardViewModel>(param1: merchant));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
getIt.registerFactoryParam<IoniaBuyGiftCardDetailPage, List<dynamic>, void>(
|
getIt.registerFactoryParam<CakePayBuyCardDetailPage, List<dynamic>, void>(
|
||||||
(List<dynamic> args, _) {
|
(List<dynamic> args, _) {
|
||||||
final amount = args.first as double;
|
final paymentCredential = args.first as PaymentCredential;
|
||||||
final merchant = args.last as IoniaMerchant;
|
final card = args[1] as CakePayCard;
|
||||||
return IoniaBuyGiftCardDetailPage(
|
return CakePayBuyCardDetailPage(
|
||||||
getIt.get<IoniaMerchPurchaseViewModel>(param1: amount, param2: merchant));
|
getIt.get<CakePayPurchaseViewModel>(param1: paymentCredential, param2: card));
|
||||||
});
|
});
|
||||||
|
|
||||||
getIt.registerFactoryParam<IoniaGiftCardDetailsViewModel, IoniaGiftCard, void>(
|
getIt.registerFactory(() => CakePayCardsPage(getIt.get<CakePayCardsListViewModel>()));
|
||||||
(IoniaGiftCard giftCard, _) {
|
|
||||||
return IoniaGiftCardDetailsViewModel(
|
|
||||||
ioniaService: getIt.get<IoniaService>(), giftCard: giftCard);
|
|
||||||
});
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<IoniaCustomTipViewModel, List<dynamic>, void>((List<dynamic> args, _) {
|
getIt.registerFactory(() => CakePayAccountPage(getIt.get<CakePayAccountViewModel>()));
|
||||||
final amount = args[0] as double;
|
|
||||||
final merchant = args[1] as IoniaMerchant;
|
|
||||||
final tip = args[2] as IoniaTip;
|
|
||||||
|
|
||||||
return IoniaCustomTipViewModel(amount: amount, tip: tip, ioniaMerchant: merchant);
|
|
||||||
});
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<IoniaGiftCardDetailPage, IoniaGiftCard, void>(
|
|
||||||
(IoniaGiftCard giftCard, _) {
|
|
||||||
return IoniaGiftCardDetailPage(getIt.get<IoniaGiftCardDetailsViewModel>(param1: giftCard));
|
|
||||||
});
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<IoniaMoreOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
|
|
||||||
final giftCard = args.first as IoniaGiftCard;
|
|
||||||
|
|
||||||
return IoniaMoreOptionsPage(giftCard);
|
|
||||||
});
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<IoniaCustomRedeemViewModel, IoniaGiftCard, void>(
|
|
||||||
(IoniaGiftCard giftCard, _) =>
|
|
||||||
IoniaCustomRedeemViewModel(giftCard: giftCard, ioniaService: getIt.get<IoniaService>()));
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<IoniaCustomRedeemPage, List<dynamic>, void>((List<dynamic> args, _) {
|
|
||||||
final giftCard = args.first as IoniaGiftCard;
|
|
||||||
|
|
||||||
return IoniaCustomRedeemPage(getIt.get<IoniaCustomRedeemViewModel>(param1: giftCard));
|
|
||||||
});
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<IoniaCustomTipPage, List<dynamic>, void>((List<dynamic> args, _) {
|
|
||||||
return IoniaCustomTipPage(getIt.get<IoniaCustomTipViewModel>(param1: args));
|
|
||||||
});
|
|
||||||
|
|
||||||
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<RBFDetailsPage, TransactionInfo, void>(
|
getIt.registerFactoryParam<RBFDetailsPage, TransactionInfo, void>(
|
||||||
(TransactionInfo transactionInfo, _) => RBFDetailsPage(
|
(TransactionInfo transactionInfo, _) => RBFDetailsPage(
|
||||||
|
@ -1226,18 +1162,6 @@ Future<void> setup({
|
||||||
(AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsPage(
|
(AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsPage(
|
||||||
anonpayDetailsViewModel: getIt.get<AnonpayDetailsViewModel>(param1: anonpayInvoiceInfo)));
|
anonpayDetailsViewModel: getIt.get<AnonpayDetailsViewModel>(param1: anonpayInvoiceInfo)));
|
||||||
|
|
||||||
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)));
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<HomeSettingsPage, BalanceViewModel, void>((balanceViewModel, _) =>
|
getIt.registerFactoryParam<HomeSettingsPage, BalanceViewModel, void>((balanceViewModel, _) =>
|
||||||
HomeSettingsPage(getIt.get<HomeSettingsViewModel>(param1: balanceViewModel)));
|
HomeSettingsPage(getIt.get<HomeSettingsViewModel>(param1: balanceViewModel)));
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
|
||||||
const nanoDefaultNodeUri = 'rpc.nano.to';
|
const nanoDefaultNodeUri = 'rpc.nano.to';
|
||||||
const nanoDefaultPowNodeUri = 'rpc.nano.to';
|
const nanoDefaultPowNodeUri = 'rpc.nano.to';
|
||||||
const solanaDefaultNodeUri = 'rpc.ankr.com';
|
const solanaDefaultNodeUri = 'rpc.ankr.com';
|
||||||
const tronDefaultNodeUri = 'tron-rpc.publicnode.com:443';
|
const tronDefaultNodeUri = 'api.trongrid.io';
|
||||||
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
|
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
|
||||||
|
|
||||||
Future<void> defaultSettingsMigration(
|
Future<void> defaultSettingsMigration(
|
||||||
|
@ -227,6 +227,11 @@ Future<void> defaultSettingsMigration(
|
||||||
break;
|
break;
|
||||||
case 34:
|
case 34:
|
||||||
await _addElectRsNode(nodes, sharedPreferences);
|
await _addElectRsNode(nodes, sharedPreferences);
|
||||||
|
case 35:
|
||||||
|
await _switchElectRsNode(nodes, sharedPreferences);
|
||||||
|
break;
|
||||||
|
case 36:
|
||||||
|
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -825,6 +830,39 @@ Future<void> _addElectRsNode(Box<Node> nodeSource, SharedPreferences sharedPrefe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _switchElectRsNode(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
||||||
|
final currentBitcoinNodeId =
|
||||||
|
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||||
|
final currentBitcoinNode =
|
||||||
|
nodeSource.values.firstWhere((node) => node.key == currentBitcoinNodeId);
|
||||||
|
final needToReplaceCurrentBitcoinNode =
|
||||||
|
currentBitcoinNode.uri.toString().contains('electrs.cakewallet.com');
|
||||||
|
|
||||||
|
if (!needToReplaceCurrentBitcoinNode) return;
|
||||||
|
|
||||||
|
final btcElectrumNode = nodeSource.values.firstWhereOrNull(
|
||||||
|
(node) => node.uri.toString().contains('btc-electrum.cakewallet.com'),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (btcElectrumNode == null) {
|
||||||
|
final newBtcElectrumBitcoinNode = Node(
|
||||||
|
uri: newCakeWalletBitcoinUri,
|
||||||
|
type: WalletType.bitcoin,
|
||||||
|
useSSL: false,
|
||||||
|
);
|
||||||
|
await nodeSource.add(newBtcElectrumBitcoinNode);
|
||||||
|
await sharedPreferences.setInt(
|
||||||
|
PreferencesKey.currentBitcoinElectrumSererIdKey,
|
||||||
|
newBtcElectrumBitcoinNode.key as int,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await sharedPreferences.setInt(
|
||||||
|
PreferencesKey.currentBitcoinElectrumSererIdKey,
|
||||||
|
btcElectrumNode.key as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> checkCurrentNodes(
|
Future<void> checkCurrentNodes(
|
||||||
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
|
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
|
||||||
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
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';
|
|
||||||
|
|
||||||
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),
|
|
||||||
formattedCryptoAmount: 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,440 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_order.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 = 'api.ionia.io';
|
|
||||||
static const pathPrefix = 'cake';
|
|
||||||
static const requestedUUIDHeader = 'requestedUUID';
|
|
||||||
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('Unexpected http status: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final bodyJson = json.decode(response.body) as Map<String, dynamic>;
|
|
||||||
final data = bodyJson['Data'] as Map<String, dynamic>;
|
|
||||||
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 email,
|
|
||||||
required String code,
|
|
||||||
required String clientId}) async {
|
|
||||||
final headers = <String, String>{
|
|
||||||
'clientId': clientId,
|
|
||||||
'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('Unexpected http status: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final bodyJson = json.decode(response.body) as Map<String, dynamic>;
|
|
||||||
final data = bodyJson['Data'] as Map<String, dynamic>;
|
|
||||||
final isSuccessful = bodyJson['Successful'] as bool;
|
|
||||||
|
|
||||||
if (!isSuccessful) {
|
|
||||||
throw Exception(bodyJson['ErrorMessage'] as String);
|
|
||||||
}
|
|
||||||
|
|
||||||
final password = data['password'] as String;
|
|
||||||
final username = data['username'] as String;
|
|
||||||
return IoniaUserCredentials(username, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign In
|
|
||||||
|
|
||||||
Future<void> 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('Unexpected http status: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final bodyJson = json.decode(response.body) as Map<String, dynamic>;
|
|
||||||
final data = bodyJson['Data'] as Map<String, dynamic>;
|
|
||||||
final isSuccessful = bodyJson['Successful'] as bool;
|
|
||||||
|
|
||||||
if (!isSuccessful) {
|
|
||||||
throw Exception(data['ErrorMessage'] 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('Unexpected http status: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final bodyJson = json.decode(response.body) as Map<String, dynamic>;
|
|
||||||
final data = bodyJson['Data'] as Map<String, dynamic>;
|
|
||||||
final isSuccessful = bodyJson['Successful'] as bool;
|
|
||||||
|
|
||||||
if (!isSuccessful) {
|
|
||||||
throw Exception(data['message'] as String);
|
|
||||||
}
|
|
||||||
|
|
||||||
final virtualCard = data['VirtualCard'] as Map<String, dynamic>;
|
|
||||||
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('Unexpected http status: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final bodyJson = json.decode(response.body) as Map<String, dynamic>;
|
|
||||||
final data = bodyJson['Data'] as Map<String, dynamic>;
|
|
||||||
final isSuccessful = bodyJson['Successful'] as bool? ?? false;
|
|
||||||
|
|
||||||
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>;
|
|
||||||
final merch = <IoniaMerchant>[];
|
|
||||||
|
|
||||||
for (final item in data) {
|
|
||||||
try {
|
|
||||||
final element = item as Map<String, dynamic>;
|
|
||||||
merch.add(IoniaMerchant.fromJsonMap(element));
|
|
||||||
} catch(_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return merch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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>;
|
|
||||||
final merch = <IoniaMerchant>[];
|
|
||||||
|
|
||||||
for (final item in data) {
|
|
||||||
try {
|
|
||||||
final element = item['Merchant'] as Map<String, dynamic>;
|
|
||||||
merch.add(IoniaMerchant.fromJsonMap(element));
|
|
||||||
} catch(_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return merch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purchase Gift Card
|
|
||||||
|
|
||||||
Future<IoniaOrder> purchaseGiftCard({
|
|
||||||
required String requestedUUID,
|
|
||||||
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,
|
|
||||||
requestedUUIDHeader: requestedUUID,
|
|
||||||
'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>;
|
|
||||||
final cards = <IoniaGiftCard>[];
|
|
||||||
|
|
||||||
for (final item in data) {
|
|
||||||
try {
|
|
||||||
final element = item as Map<String, dynamic>;
|
|
||||||
cards.add(IoniaGiftCard.fromJsonMap(element));
|
|
||||||
} catch(_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cards;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
class IoniaCategory {
|
|
||||||
const IoniaCategory({
|
|
||||||
required this.index,
|
|
||||||
required this.title,
|
|
||||||
required this.ids,
|
|
||||||
required 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;
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class IoniaMerchantState {}
|
|
||||||
|
|
||||||
class InitialIoniaMerchantLoadingState extends IoniaMerchantState {}
|
|
||||||
|
|
||||||
class IoniaLoadingMerchantState extends IoniaMerchantState {}
|
|
||||||
|
|
||||||
class IoniaLoadedMerchantState extends IoniaMerchantState {}
|
|
||||||
|
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
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?) ?? 0.0,
|
|
||||||
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;
|
|
||||||
double remainingAmount;
|
|
||||||
final String createdDateFormatted;
|
|
||||||
final String lastTransactionDateFormatted;
|
|
||||||
final bool isActive;
|
|
||||||
final bool isEmpty;
|
|
||||||
final String logoUrl;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
import 'package:cake_wallet/ionia/ionia_gift_card_instruction.dart';
|
|
||||||
import 'package:cake_wallet/generated/i18n.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.isActive,
|
|
||||||
required this.isOnline,
|
|
||||||
required this.isPhysical,
|
|
||||||
required this.isVariablePurchase,
|
|
||||||
required this.minimumCardPurchase,
|
|
||||||
required this.maximumCardPurchase,
|
|
||||||
required this.acceptsTips,
|
|
||||||
required this.createdDateFormatted,
|
|
||||||
required this.modifiedDateFormatted,
|
|
||||||
required this.usageInstructions,
|
|
||||||
required this.usageInstructionsBak,
|
|
||||||
required this.hasBarcode,
|
|
||||||
required this.instructions,
|
|
||||||
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,
|
|
||||||
isActive: element["IsActive"] 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?,
|
|
||||||
modifiedDateFormatted: element["ModifiedDate"] as String?,
|
|
||||||
usageInstructions: element["UsageInstructions"] as String?,
|
|
||||||
usageInstructionsBak: element["UsageInstructionsBak"] as String?,
|
|
||||||
hasBarcode: element["HasBarcode"] as bool,
|
|
||||||
instructions: IoniaGiftCardInstruction.parseListOfInstructions(element['PaymentInstructions'] as String),
|
|
||||||
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 bool? isActive;
|
|
||||||
final bool isOnline;
|
|
||||||
final bool? isPhysical;
|
|
||||||
final bool isVariablePurchase;
|
|
||||||
final double minimumCardPurchase;
|
|
||||||
final double maximumCardPurchase;
|
|
||||||
final bool acceptsTips;
|
|
||||||
final String? createdDateFormatted;
|
|
||||||
final String? modifiedDateFormatted;
|
|
||||||
final String? usageInstructions;
|
|
||||||
final String? usageInstructionsBak;
|
|
||||||
final bool hasBarcode;
|
|
||||||
final List<IoniaGiftCardInstruction> instructions;
|
|
||||||
final double savingsPercentage;
|
|
||||||
|
|
||||||
double get discount => savingsPercentage;
|
|
||||||
|
|
||||||
String get avaibilityStatus {
|
|
||||||
var status = '';
|
|
||||||
|
|
||||||
if (isOnline) {
|
|
||||||
status += S.current.online;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPhysical ?? false) {
|
|
||||||
if (status.isNotEmpty) {
|
|
||||||
status = '$status & ';
|
|
||||||
}
|
|
||||||
|
|
||||||
status = '${status}${S.current.in_store}';
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,171 +0,0 @@
|
||||||
import 'package:cake_wallet/core/secure_storage.dart';
|
|
||||||
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: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 SecureStorage 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 email = (await secureStorage.read(key: ioniaEmailStorageKey))!;
|
|
||||||
final credentials = await ioniaApi.verifyEmail(email: email, 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 {
|
|
||||||
await ioniaApi.signIn(email, clientId: clientId);
|
|
||||||
await secureStorage.write(key: ioniaEmailStorageKey, value: email);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getUserEmail() async {
|
|
||||||
return (await 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))!;
|
|
||||||
final deviceId = '';
|
|
||||||
return ioniaApi.purchaseGiftCard(
|
|
||||||
requestedUUID: deviceId,
|
|
||||||
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({required int giftCardId, required double amount}) async {
|
|
||||||
await chargeGiftCard(giftCardId: giftCardId, amount: amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
class IoniaTip {
|
|
||||||
const IoniaTip({
|
|
||||||
required this.originalAmount,
|
|
||||||
required this.percentage,
|
|
||||||
this.isCustom = false});
|
|
||||||
|
|
||||||
final double originalAmount;
|
|
||||||
final double percentage;
|
|
||||||
final bool isCustom;
|
|
||||||
|
|
||||||
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)
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
class IoniaUserCredentials {
|
|
||||||
const IoniaUserCredentials(this.username, this.password);
|
|
||||||
|
|
||||||
final String username;
|
|
||||||
final String password;
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
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, dynamic> 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;
|
|
||||||
}
|
|
|
@ -202,7 +202,7 @@ Future<void> initializeAppConfigs() async {
|
||||||
transactionDescriptions: transactionDescriptions,
|
transactionDescriptions: transactionDescriptions,
|
||||||
secureStorage: secureStorage,
|
secureStorage: secureStorage,
|
||||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||||
initialMigrationVersion: 34,
|
initialMigrationVersion: 36,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
|
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
|
||||||
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||||
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
|
|
||||||
import 'package:cake_wallet/buy/order.dart';
|
import 'package:cake_wallet/buy/order.dart';
|
||||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||||
|
@ -10,7 +9,6 @@ import 'package:cake_wallet/entities/qr_view_data.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_nft_response.dart';
|
import 'package:cake_wallet/entities/wallet_nft_response.dart';
|
||||||
import 'package:cake_wallet/exchange/trade.dart';
|
import 'package:cake_wallet/exchange/trade.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
|
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
|
import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||||
|
@ -50,14 +48,6 @@ import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
|
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
|
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/faq/faq_page.dart';
|
import 'package:cake_wallet/src/screens/faq/faq_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_redeem_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/ionia/cards/ionia_more_options_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/ionia/ionia.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
|
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/new_wallet/advanced_privacy_settings_page.dart';
|
import 'package:cake_wallet/src/screens/new_wallet/advanced_privacy_settings_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
|
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
|
||||||
|
@ -77,6 +67,7 @@ import 'package:cake_wallet/src/screens/send/send_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/send/send_template_page.dart';
|
import 'package:cake_wallet/src/screens/send/send_template_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
|
import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
|
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/cake_pay/auth/cake_pay_account_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
|
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
|
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
|
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
|
||||||
|
@ -121,7 +112,7 @@ import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart';
|
||||||
import 'src/screens/dashboard/pages/nft_import_page.dart';
|
import 'src/screens/dashboard/pages/nft_import_page.dart';
|
||||||
|
|
||||||
late RouteSettings currentRouteSettings;
|
late RouteSettings currentRouteSettings;
|
||||||
|
@ -519,73 +510,30 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
param1: settings.arguments as QrViewData,
|
param1: settings.arguments as QrViewData,
|
||||||
));
|
));
|
||||||
|
|
||||||
case Routes.ioniaWelcomePage:
|
case Routes.cakePayCardsPage:
|
||||||
|
return CupertinoPageRoute<void>(builder: (_) => getIt.get<CakePayCardsPage>());
|
||||||
|
|
||||||
|
case Routes.cakePayBuyCardPage:
|
||||||
|
final args = settings.arguments as List;
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
fullscreenDialog: true,
|
builder: (_) => getIt.get<CakePayBuyCardPage>(param1: args));
|
||||||
builder: (_) => getIt.get<IoniaWelcomePage>(),
|
|
||||||
|
case Routes.cakePayBuyCardDetailPage:
|
||||||
|
final args = settings.arguments as List;
|
||||||
|
return CupertinoPageRoute<void>(
|
||||||
|
builder: (_) => getIt.get<CakePayBuyCardDetailPage>(param1: args));
|
||||||
|
|
||||||
|
case Routes.cakePayWelcomePage:
|
||||||
|
return CupertinoPageRoute<void>(
|
||||||
|
builder: (_) => getIt.get<CakePayWelcomePage>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
case Routes.ioniaLoginPage:
|
case Routes.cakePayVerifyOtpPage:
|
||||||
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;
|
final args = settings.arguments as List;
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(builder: (_) => getIt.get<CakePayVerifyOtpPage>(param1: args));
|
||||||
builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
|
|
||||||
|
|
||||||
case Routes.ioniaBuyGiftCardDetailPage:
|
case Routes.cakePayAccountPage:
|
||||||
final args = settings.arguments as List;
|
return CupertinoPageRoute<void>(builder: (_) => getIt.get<CakePayAccountPage>());
|
||||||
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.ioniaCustomRedeemPage:
|
|
||||||
final args = settings.arguments as List;
|
|
||||||
return CupertinoPageRoute<void>(
|
|
||||||
builder: (_) => getIt.get<IoniaCustomRedeemPage>(param1: args));
|
|
||||||
|
|
||||||
case Routes.ioniaMoreOptionsPage:
|
|
||||||
final args = settings.arguments as List;
|
|
||||||
return CupertinoPageRoute<void>(
|
|
||||||
builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args));
|
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
case Routes.webViewPage:
|
case Routes.webViewPage:
|
||||||
final args = settings.arguments as List;
|
final args = settings.arguments as List;
|
||||||
|
|
|
@ -64,22 +64,13 @@ class Routes {
|
||||||
static const unspentCoinsDetails = '/unspent_coins_details';
|
static const unspentCoinsDetails = '/unspent_coins_details';
|
||||||
static const addressPage = '/address_page';
|
static const addressPage = '/address_page';
|
||||||
static const fullscreenQR = '/fullscreen_qr';
|
static const fullscreenQR = '/fullscreen_qr';
|
||||||
static const ioniaWelcomePage = '/cake_pay_welcome_page';
|
static const cakePayWelcomePage = '/cake_pay_welcome_page';
|
||||||
static const ioniaCreateAccountPage = '/cake_pay_create_account_page';
|
static const cakePayLoginPage = '/cake_pay_login_page';
|
||||||
static const ioniaLoginPage = '/cake_pay_login_page';
|
static const cakePayCardsPage = '/cake_pay_cards_page';
|
||||||
static const ioniaManageCardsPage = '/manage_cards_page';
|
static const cakePayBuyCardPage = '/cake_pay_buy_card_page';
|
||||||
static const ioniaBuyGiftCardPage = '/buy_gift_card_page';
|
static const cakePayBuyCardDetailPage = '/cake_pay_buy_card_detail_page';
|
||||||
static const ioniaBuyGiftCardDetailPage = '/buy_gift_card_detail_page';
|
static const cakePayVerifyOtpPage = '/cake_pay_verify_otp_page';
|
||||||
static const ioniaVerifyIoniaOtpPage = '/cake_pay_verify_otp_page';
|
static const cakePayAccountPage = '/cake_pay_account_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';
|
|
||||||
static const ioniaMoreOptionsPage = '/ionia_more_options_page';
|
|
||||||
static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page';
|
|
||||||
static const webViewPage = '/web_view_page';
|
static const webViewPage = '/web_view_page';
|
||||||
static const silentPaymentsSettings = '/silent_payments_settings';
|
static const silentPaymentsSettings = '/silent_payments_settings';
|
||||||
static const connectionSync = '/connection_sync_page';
|
static const connectionSync = '/connection_sync_page';
|
||||||
|
|
|
@ -7,7 +7,7 @@ import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/src/widgets/nav_bar.dart';
|
import 'package:cake_wallet/src/widgets/nav_bar.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
enum AppBarStyle { regular, withShadow, transparent }
|
enum AppBarStyle { regular, withShadow, transparent, completelyTransparent }
|
||||||
|
|
||||||
abstract class BasePage extends StatelessWidget {
|
abstract class BasePage extends StatelessWidget {
|
||||||
BasePage() : _scaffoldKey = GlobalKey<ScaffoldState>();
|
BasePage() : _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
@ -125,7 +125,7 @@ abstract class BasePage extends StatelessWidget {
|
||||||
|
|
||||||
Widget? floatingActionButton(BuildContext context) => null;
|
Widget? floatingActionButton(BuildContext context) => null;
|
||||||
|
|
||||||
ObstructingPreferredSizeWidget appBar(BuildContext context) {
|
PreferredSizeWidget appBar(BuildContext context) {
|
||||||
final appBarColor = pageBackgroundColor(context);
|
final appBarColor = pageBackgroundColor(context);
|
||||||
|
|
||||||
switch (appBarStyle) {
|
switch (appBarStyle) {
|
||||||
|
@ -156,6 +156,16 @@ abstract class BasePage extends StatelessWidget {
|
||||||
border: null,
|
border: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case AppBarStyle.completelyTransparent:
|
||||||
|
return AppBar(
|
||||||
|
leading: leading(context),
|
||||||
|
title: middle(context),
|
||||||
|
actions: <Widget>[if (trailing(context) != null) trailing(context)!],
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
centerTitle: true,
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// FIX-ME: NavBar no context
|
// FIX-ME: NavBar no context
|
||||||
return NavBar(
|
return NavBar(
|
||||||
|
|
90
lib/src/screens/cake_pay/auth/cake_pay_account_page.dart
Normal file
90
lib/src/screens/cake_pay/auth/cake_pay_account_page.dart
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
|
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/cake_pay/widgets/cake_pay_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/cake_pay/cake_pay_account_view_model.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
|
||||||
|
class CakePayAccountPage extends BasePage {
|
||||||
|
CakePayAccountPage(this.cakePayAccountViewModel);
|
||||||
|
|
||||||
|
final CakePayAccountViewModel cakePayAccountViewModel;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget leading(BuildContext context) {
|
||||||
|
return MergeSemantics(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 37,
|
||||||
|
width: 37,
|
||||||
|
child: ButtonTheme(
|
||||||
|
minWidth: double.minPositive,
|
||||||
|
child: Semantics(
|
||||||
|
label: S.of(context).seed_alert_back,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
overlayColor: MaterialStateColor.resolveWith(
|
||||||
|
(states) => Colors.transparent),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: backButton(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget middle(BuildContext context) {
|
||||||
|
return Text(
|
||||||
|
S.current.account,
|
||||||
|
style: textMediumSemiBold(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget body(BuildContext context) {
|
||||||
|
return ScrollableWithBottomSection(
|
||||||
|
contentPadding: EdgeInsets.all(24),
|
||||||
|
content: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Observer(
|
||||||
|
builder: (_) => Container(decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
width: 1.0,
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: CakePayTile(title: S.of(context).email_address, subTitle: cakePayAccountViewModel.email)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
bottomSectionPadding: EdgeInsets.all(30),
|
||||||
|
bottomSection: Column(
|
||||||
|
children: [
|
||||||
|
PrimaryButton(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
textColor: Colors.white,
|
||||||
|
text: S.of(context).logout,
|
||||||
|
onPressed: () {
|
||||||
|
cakePayAccountViewModel.logout();
|
||||||
|
Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (route) => false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,39 +1,38 @@
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
import 'package:cake_wallet/cake_pay/cake_pay_states.dart';
|
||||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
|
||||||
import 'package:cake_wallet/palette.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/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.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/base_text_form_field.dart';
|
||||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.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/primary_button.dart';
|
||||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||||
import 'package:cake_wallet/typography.dart';
|
import 'package:cake_wallet/typography.dart';
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
|
import 'package:cake_wallet/view_model/cake_pay/cake_pay_auth_view_model.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
class IoniaVerifyIoniaOtp extends BasePage {
|
class CakePayVerifyOtpPage extends BasePage {
|
||||||
IoniaVerifyIoniaOtp(this._authViewModel, this._email, this.isSignIn)
|
CakePayVerifyOtpPage(this._authViewModel, this._email, this.isSignIn)
|
||||||
: _codeController = TextEditingController(),
|
: _codeController = TextEditingController(),
|
||||||
_codeFocus = FocusNode() {
|
_codeFocus = FocusNode() {
|
||||||
_codeController.addListener(() {
|
_codeController.addListener(() {
|
||||||
final otp = _codeController.text;
|
final otp = _codeController.text;
|
||||||
_authViewModel.otp = otp;
|
_authViewModel.otp = otp;
|
||||||
if (otp.length > 3) {
|
if (otp.length > 5) {
|
||||||
_authViewModel.otpState = IoniaOtpSendEnabled();
|
_authViewModel.otpState = CakePayOtpSendEnabled();
|
||||||
} else {
|
} else {
|
||||||
_authViewModel.otpState = IoniaOtpSendDisabled();
|
_authViewModel.otpState = CakePayOtpSendDisabled();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final IoniaAuthViewModel _authViewModel;
|
final CakePayAuthViewModel _authViewModel;
|
||||||
final bool isSignIn;
|
final bool isSignIn;
|
||||||
|
|
||||||
final String _email;
|
final String _email;
|
||||||
|
@ -53,11 +52,11 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
reaction((_) => _authViewModel.otpState, (IoniaOtpState state) {
|
reaction((_) => _authViewModel.otpState, (CakePayOtpState state) {
|
||||||
if (state is IoniaOtpFailure) {
|
if (state is CakePayOtpFailure) {
|
||||||
_onOtpFailure(context, state.error);
|
_onOtpFailure(context, state.error);
|
||||||
}
|
}
|
||||||
if (state is IoniaOtpSuccess) {
|
if (state is CakePayOtpSuccess) {
|
||||||
_onOtpSuccessful(context);
|
_onOtpSuccessful(context);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -98,9 +97,7 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
||||||
Text(S.of(context).didnt_get_code),
|
Text(S.of(context).didnt_get_code),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => isSignIn
|
onTap: () => _authViewModel.logIn(_email),
|
||||||
? _authViewModel.signIn(_email)
|
|
||||||
: _authViewModel.createUser(_email),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
S.of(context).resend_code,
|
S.of(context).resend_code,
|
||||||
style: textSmallSemiBold(color: Palette.blueCraiola),
|
style: textSmallSemiBold(color: Palette.blueCraiola),
|
||||||
|
@ -120,8 +117,8 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
||||||
builder: (_) => LoadingPrimaryButton(
|
builder: (_) => LoadingPrimaryButton(
|
||||||
text: S.of(context).continue_text,
|
text: S.of(context).continue_text,
|
||||||
onPressed: _verify,
|
onPressed: _verify,
|
||||||
isDisabled: _authViewModel.otpState is IoniaOtpSendDisabled,
|
isDisabled: _authViewModel.otpState is CakePayOtpSendDisabled,
|
||||||
isLoading: _authViewModel.otpState is IoniaOtpValidating,
|
isLoading: _authViewModel.otpState is CakePayOtpValidating,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
),
|
),
|
||||||
|
@ -149,8 +146,7 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onOtpSuccessful(BuildContext context) =>
|
void _onOtpSuccessful(BuildContext context) =>
|
||||||
Navigator.of(context)
|
Navigator.pop(context);
|
||||||
.pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst);
|
|
||||||
|
|
||||||
void _verify() async => await _authViewModel.verifyEmail(_codeController.text);
|
void _verify() async => await _authViewModel.verifyEmail(_codeController.text);
|
||||||
}
|
}
|
|
@ -1,22 +1,22 @@
|
||||||
import 'package:cake_wallet/core/email_validator.dart';
|
import 'package:cake_wallet/core/email_validator.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
import 'package:cake_wallet/cake_pay/cake_pay_states.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.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/alert_with_one_action.dart';
|
||||||
import 'package:cake_wallet/src/widgets/base_text_form_field.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/primary_button.dart';
|
||||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
import 'package:cake_wallet/typography.dart';
|
import 'package:cake_wallet/typography.dart';
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
|
import 'package:cake_wallet/view_model/cake_pay/cake_pay_auth_view_model.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
class IoniaLoginPage extends BasePage {
|
class CakePayWelcomePage extends BasePage {
|
||||||
IoniaLoginPage(this._authViewModel)
|
CakePayWelcomePage(this._authViewModel)
|
||||||
: _formKey = GlobalKey<FormState>(),
|
: _formKey = GlobalKey<FormState>(),
|
||||||
_emailController = TextEditingController() {
|
_emailController = TextEditingController() {
|
||||||
_emailController.text = _authViewModel.email;
|
_emailController.text = _authViewModel.email;
|
||||||
|
@ -25,14 +25,14 @@ class IoniaLoginPage extends BasePage {
|
||||||
|
|
||||||
final GlobalKey<FormState> _formKey;
|
final GlobalKey<FormState> _formKey;
|
||||||
|
|
||||||
final IoniaAuthViewModel _authViewModel;
|
final CakePayAuthViewModel _authViewModel;
|
||||||
|
|
||||||
final TextEditingController _emailController;
|
final TextEditingController _emailController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget middle(BuildContext context) {
|
Widget middle(BuildContext context) {
|
||||||
return Text(
|
return Text(
|
||||||
S.current.login,
|
S.current.welcome_to_cakepay,
|
||||||
style: textMediumSemiBold(
|
style: textMediumSemiBold(
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
),
|
),
|
||||||
|
@ -41,25 +41,40 @@ class IoniaLoginPage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
reaction((_) => _authViewModel.signInState, (IoniaCreateAccountState state) {
|
reaction((_) => _authViewModel.userVerificationState, (CakePayUserVerificationState state) {
|
||||||
if (state is IoniaCreateStateFailure) {
|
if (state is CakePayUserVerificationStateFailure) {
|
||||||
_onLoginUserFailure(context, state.error);
|
_onLoginUserFailure(context, state.error);
|
||||||
}
|
}
|
||||||
if (state is IoniaCreateStateSuccess) {
|
if (state is CakePayUserVerificationStateSuccess) {
|
||||||
_onLoginSuccessful(context, _authViewModel);
|
_onLoginSuccessful(context, _authViewModel);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return ScrollableWithBottomSection(
|
return ScrollableWithBottomSection(
|
||||||
contentPadding: EdgeInsets.all(24),
|
contentPadding: EdgeInsets.all(24),
|
||||||
content: Form(
|
content: Column(
|
||||||
key: _formKey,
|
children: [
|
||||||
child: BaseTextFormField(
|
SizedBox(height: 90),
|
||||||
hintText: S.of(context).email_address,
|
Form(
|
||||||
keyboardType: TextInputType.emailAddress,
|
key: _formKey,
|
||||||
validator: EmailValidator(),
|
child: BaseTextFormField(
|
||||||
controller: _emailController,
|
hintText: S.of(context).email_address,
|
||||||
onSubmit: (text) => _login(),
|
keyboardType: TextInputType.emailAddress,
|
||||||
),
|
validator: EmailValidator(),
|
||||||
|
controller: _emailController,
|
||||||
|
onSubmit: (text) => _login(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
S.of(context).about_cake_pay,
|
||||||
|
style: textLarge(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Text(S.of(context).cake_pay_account_note,
|
||||||
|
style: textLarge(color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
|
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
|
||||||
bottomSection: Column(
|
bottomSection: Column(
|
||||||
|
@ -71,7 +86,8 @@ class IoniaLoginPage extends BasePage {
|
||||||
builder: (_) => LoadingPrimaryButton(
|
builder: (_) => LoadingPrimaryButton(
|
||||||
text: S.of(context).login,
|
text: S.of(context).login,
|
||||||
onPressed: _login,
|
onPressed: _login,
|
||||||
isLoading: _authViewModel.signInState is IoniaCreateStateLoading,
|
isLoading:
|
||||||
|
_authViewModel.userVerificationState is CakePayUserVerificationStateLoading,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
),
|
),
|
||||||
|
@ -98,9 +114,10 @@ class IoniaLoginPage extends BasePage {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onLoginSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed(
|
void _onLoginSuccessful(BuildContext context, CakePayAuthViewModel authViewModel) =>
|
||||||
|
Navigator.pushReplacementNamed(
|
||||||
context,
|
context,
|
||||||
Routes.ioniaVerifyIoniaOtpPage,
|
Routes.cakePayVerifyOtpPage,
|
||||||
arguments: [authViewModel.email, true],
|
arguments: [authViewModel.email, true],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -108,6 +125,6 @@ class IoniaLoginPage extends BasePage {
|
||||||
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
|
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await _authViewModel.signIn(_emailController.text);
|
await _authViewModel.logIn(_emailController.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
5
lib/src/screens/cake_pay/cake_pay.dart
Normal file
5
lib/src/screens/cake_pay/cake_pay.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export 'auth/cake_pay_welcome_page.dart';
|
||||||
|
export 'auth/cake_pay_verify_otp_page.dart';
|
||||||
|
export 'cards/cake_pay_confirm_purchase_card_page.dart';
|
||||||
|
export 'cards/cake_pay_cards_page.dart';
|
||||||
|
export 'cards/cake_pay_buy_card_page.dart';
|
474
lib/src/screens/cake_pay/cards/cake_pay_buy_card_page.dart
Normal file
474
lib/src/screens/cake_pay/cards/cake_pay_buy_card_page.dart
Normal file
|
@ -0,0 +1,474 @@
|
||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_card.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_payment_credantials.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_service.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/cake_pay/widgets/image_placeholder.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/cake_pay/widgets/link_extractor.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/number_text_fild_widget.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/extensions/cake_text_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||||
|
import 'package:cake_wallet/typography.dart';
|
||||||
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
|
import 'package:cake_wallet/view_model/cake_pay/cake_pay_buy_card_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item_widget.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';
|
||||||
|
|
||||||
|
class CakePayBuyCardPage extends BasePage {
|
||||||
|
CakePayBuyCardPage(
|
||||||
|
this.cakePayBuyCardViewModel,
|
||||||
|
this.cakePayService,
|
||||||
|
) : _amountFieldFocus = FocusNode(),
|
||||||
|
_amountController = TextEditingController(),
|
||||||
|
_quantityFieldFocus = FocusNode(),
|
||||||
|
_quantityController =
|
||||||
|
TextEditingController(text: cakePayBuyCardViewModel.quantity.toString()) {
|
||||||
|
_amountController.addListener(() {
|
||||||
|
cakePayBuyCardViewModel.onAmountChanged(_amountController.text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final CakePayBuyCardViewModel cakePayBuyCardViewModel;
|
||||||
|
final CakePayService cakePayService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => cakePayBuyCardViewModel.card.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get extendBodyBehindAppBar => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
AppBarStyle get appBarStyle => AppBarStyle.completelyTransparent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? middle(BuildContext context) {
|
||||||
|
return Text(
|
||||||
|
title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
maxLines: 2,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
color: titleColor(context)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final TextEditingController _amountController;
|
||||||
|
final FocusNode _amountFieldFocus;
|
||||||
|
final TextEditingController _quantityController;
|
||||||
|
final FocusNode _quantityFieldFocus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget body(BuildContext context) {
|
||||||
|
final card = cakePayBuyCardViewModel.card;
|
||||||
|
final vendor = cakePayBuyCardViewModel.vendor;
|
||||||
|
|
||||||
|
return KeyboardActions(
|
||||||
|
disableScroll: true,
|
||||||
|
config: KeyboardActionsConfig(
|
||||||
|
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||||
|
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||||
|
nextFocus: false,
|
||||||
|
actions: [
|
||||||
|
KeyboardActionsItem(
|
||||||
|
focusNode: _amountFieldFocus,
|
||||||
|
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
child: ScrollableWithBottomSection(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
content: Column(
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(25.0), bottomRight: Radius.circular(25.0)),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Theme.of(context).extension<SendPageTheme>()!.firstGradientColor,
|
||||||
|
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
height: responsiveLayoutUtil.screenHeight * 0.35,
|
||||||
|
width: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(flex: 4, child: const SizedBox()),
|
||||||
|
Expanded(
|
||||||
|
flex: 7,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
|
child: Image.network(
|
||||||
|
card.cardImageUrl ?? '',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
loadingBuilder: (BuildContext context, Widget child,
|
||||||
|
ImageChunkEvent? loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
},
|
||||||
|
errorBuilder: (context, error, stackTrace) =>
|
||||||
|
CakePayCardImagePlaceholder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(child: const SizedBox()),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Container(
|
||||||
|
height: responsiveLayoutUtil.screenHeight * 0.5,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 24),
|
||||||
|
Expanded(
|
||||||
|
child: Text(S.of(context).enter_amount,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
card.denominations.isNotEmpty
|
||||||
|
? Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: _DenominationsAmountWidget(
|
||||||
|
fiatCurrency: card.fiatCurrency.title,
|
||||||
|
denominations: card.denominations,
|
||||||
|
amountFieldFocus: _amountFieldFocus,
|
||||||
|
amountController: _amountController,
|
||||||
|
quantityFieldFocus: _quantityFieldFocus,
|
||||||
|
quantityController: _quantityController,
|
||||||
|
onAmountChanged: cakePayBuyCardViewModel.onAmountChanged,
|
||||||
|
onQuantityChanged: cakePayBuyCardViewModel.onQuantityChanged,
|
||||||
|
cakePayBuyCardViewModel: cakePayBuyCardViewModel,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: _EnterAmountWidget(
|
||||||
|
minValue: card.minValue ?? '-',
|
||||||
|
maxValue: card.maxValue ?? '-',
|
||||||
|
fiatCurrency: card.fiatCurrency.title,
|
||||||
|
amountFieldFocus: _amountFieldFocus,
|
||||||
|
amountController: _amountController,
|
||||||
|
onAmountChanged: cakePayBuyCardViewModel.onAmountChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 5,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (vendor.cakeWarnings != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(color: Colors.white.withOpacity(0.20)),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
vendor.cakeWarnings!,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: textSmallSemiBold(color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: ClickableLinksText(
|
||||||
|
text: card.description ?? '',
|
||||||
|
textStyle: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<CakeTextTheme>()!
|
||||||
|
.secondaryTextColor,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
bottomSection: Column(
|
||||||
|
children: [
|
||||||
|
Observer(builder: (_) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 12),
|
||||||
|
child: PrimaryButton(
|
||||||
|
onPressed: () => navigateToCakePayBuyCardDetailPage(context, card),
|
||||||
|
text: S.of(context).buy_now,
|
||||||
|
isDisabled: !cakePayBuyCardViewModel.isEnablePurchase,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
textColor: Colors.white,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> navigateToCakePayBuyCardDetailPage(BuildContext context, CakePayCard card) async {
|
||||||
|
final userName = await cakePayService.getUserEmail();
|
||||||
|
final paymentCredential = PaymentCredential(
|
||||||
|
amount: cakePayBuyCardViewModel.amount,
|
||||||
|
quantity: cakePayBuyCardViewModel.quantity,
|
||||||
|
totalAmount: cakePayBuyCardViewModel.totalAmount,
|
||||||
|
userName: userName,
|
||||||
|
fiatCurrency: card.fiatCurrency.title,
|
||||||
|
);
|
||||||
|
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.cakePayBuyCardDetailPage,
|
||||||
|
arguments: [paymentCredential, card],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DenominationsAmountWidget extends StatelessWidget {
|
||||||
|
const _DenominationsAmountWidget({
|
||||||
|
required this.fiatCurrency,
|
||||||
|
required this.denominations,
|
||||||
|
required this.amountFieldFocus,
|
||||||
|
required this.amountController,
|
||||||
|
required this.quantityFieldFocus,
|
||||||
|
required this.quantityController,
|
||||||
|
required this.cakePayBuyCardViewModel,
|
||||||
|
required this.onAmountChanged,
|
||||||
|
required this.onQuantityChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String fiatCurrency;
|
||||||
|
final List<String> denominations;
|
||||||
|
final FocusNode amountFieldFocus;
|
||||||
|
final TextEditingController amountController;
|
||||||
|
final FocusNode quantityFieldFocus;
|
||||||
|
final TextEditingController quantityController;
|
||||||
|
final CakePayBuyCardViewModel cakePayBuyCardViewModel;
|
||||||
|
final Function(String) onAmountChanged;
|
||||||
|
final Function(int?) onQuantityChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 12,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: DropdownFilterList(
|
||||||
|
items: denominations,
|
||||||
|
itemPrefix: fiatCurrency,
|
||||||
|
selectedItem: denominations.first,
|
||||||
|
textStyle: textMediumSemiBold(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
|
onItemSelected: (value) {
|
||||||
|
amountController.text = value;
|
||||||
|
onAmountChanged(value);
|
||||||
|
},
|
||||||
|
caption: '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
width: 1.0,
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(S.of(context).choose_card_value + ':',
|
||||||
|
maxLines: 2,
|
||||||
|
style: textSmall(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(child: const SizedBox()),
|
||||||
|
Expanded(
|
||||||
|
flex: 8,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: NumberTextField(
|
||||||
|
controller: quantityController,
|
||||||
|
focusNode: quantityFieldFocus,
|
||||||
|
min: 1,
|
||||||
|
max: 99,
|
||||||
|
onChanged: (value) => onQuantityChanged(value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
width: 1.0,
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(S.of(context).quantity + ':',
|
||||||
|
maxLines: 1,
|
||||||
|
style: textSmall(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(child: const SizedBox()),
|
||||||
|
Expanded(
|
||||||
|
flex: 12,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Observer(
|
||||||
|
builder: (_) => AutoSizeText(
|
||||||
|
'$fiatCurrency ${cakePayBuyCardViewModel.totalAmount}',
|
||||||
|
maxLines: 1,
|
||||||
|
style: textMediumSemiBold(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<CakeTextTheme>()!.titleColor)))),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
width: 1.0,
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(S.of(context).total + ':',
|
||||||
|
maxLines: 1,
|
||||||
|
style: textSmall(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EnterAmountWidget extends StatelessWidget {
|
||||||
|
const _EnterAmountWidget({
|
||||||
|
required this.minValue,
|
||||||
|
required this.maxValue,
|
||||||
|
required this.fiatCurrency,
|
||||||
|
required this.amountFieldFocus,
|
||||||
|
required this.amountController,
|
||||||
|
required this.onAmountChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String minValue;
|
||||||
|
final String maxValue;
|
||||||
|
final String fiatCurrency;
|
||||||
|
final FocusNode amountFieldFocus;
|
||||||
|
final TextEditingController amountController;
|
||||||
|
final Function(String) onAmountChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
width: 1.0,
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: BaseTextFormField(
|
||||||
|
controller: amountController,
|
||||||
|
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||||
|
hintText: '0.00',
|
||||||
|
maxLines: null,
|
||||||
|
borderColor: Colors.transparent,
|
||||||
|
prefixIcon: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 12),
|
||||||
|
child: Text(
|
||||||
|
'$fiatCurrency: ',
|
||||||
|
style: textMediumSemiBold(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textStyle:
|
||||||
|
textMediumSemiBold(color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
|
placeholderTextStyle: textMediumSemiBold(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.deny(RegExp('[\-|\ ]')),
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'^\d+(\.|\,)?\d{0,2}'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(S.of(context).min_amount(minValue) + ' $fiatCurrency',
|
||||||
|
style: textSmall(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor)),
|
||||||
|
Text(S.of(context).max_amount(maxValue) + ' $fiatCurrency',
|
||||||
|
style: textSmall(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,42 +1,40 @@
|
||||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
import 'package:cake_wallet/cake_pay/cake_pay_states.dart';
|
||||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
import 'package:cake_wallet/src/screens/cake_pay/widgets/card_item.dart';
|
||||||
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
|
import 'package:cake_wallet/src/screens/cake_pay/widgets/card_menu.dart';
|
||||||
import 'package:cake_wallet/src/screens/ionia/widgets/card_menu.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_widget.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/src/widgets/cake_scrollbar.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.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:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
|
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
|
||||||
|
import 'package:cake_wallet/typography.dart';
|
||||||
|
import 'package:cake_wallet/utils/debounce.dart';
|
||||||
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
|
import 'package:cake_wallet/view_model/cake_pay/cake_pay_cards_list_view_model.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
|
||||||
class IoniaManageCardsPage extends BasePage {
|
class CakePayCardsPage extends BasePage {
|
||||||
IoniaManageCardsPage(this._cardsListViewModel): searchFocusNode = FocusNode() {
|
CakePayCardsPage(this._cardsListViewModel) : searchFocusNode = FocusNode() {
|
||||||
_searchController.addListener(() {
|
_searchController.addListener(() {
|
||||||
if (_searchController.text != _cardsListViewModel.searchString) {
|
if (_searchController.text != _cardsListViewModel.searchString) {
|
||||||
_searchDebounce.run(() {
|
_searchDebounce.run(() {
|
||||||
_cardsListViewModel.searchMerchant(_searchController.text);
|
_cardsListViewModel.resetLoadingNextPageState();
|
||||||
|
_cardsListViewModel.getVendors(text: _searchController.text);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_cardsListViewModel.getMerchants();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final FocusNode searchFocusNode;
|
final FocusNode searchFocusNode;
|
||||||
final IoniaGiftCardsListViewModel _cardsListViewModel;
|
final CakePayCardsListViewModel _cardsListViewModel;
|
||||||
|
|
||||||
final _searchDebounce = Debounce(Duration(milliseconds: 500));
|
final _searchDebounce = Debounce(Duration(milliseconds: 500));
|
||||||
final _searchController = TextEditingController();
|
final _searchController = TextEditingController();
|
||||||
|
@ -46,8 +44,7 @@ class IoniaManageCardsPage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget Function(BuildContext, Widget) get rootWrapper =>
|
Widget Function(BuildContext, Widget) get rootWrapper =>
|
||||||
(BuildContext context, Widget scaffold) =>
|
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
|
||||||
GradientBackground(scaffold: scaffold);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get resizeToAvoidBottomInset => false;
|
bool get resizeToAvoidBottomInset => false;
|
||||||
|
@ -58,7 +55,7 @@ class IoniaManageCardsPage extends BasePage {
|
||||||
@override
|
@override
|
||||||
Widget middle(BuildContext context) {
|
Widget middle(BuildContext context) {
|
||||||
return Text(
|
return Text(
|
||||||
S.of(context).gift_cards,
|
'Cake Pay',
|
||||||
style: textMediumSemiBold(
|
style: textMediumSemiBold(
|
||||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||||
),
|
),
|
||||||
|
@ -68,9 +65,17 @@ class IoniaManageCardsPage extends BasePage {
|
||||||
@override
|
@override
|
||||||
Widget trailing(BuildContext context) {
|
Widget trailing(BuildContext context) {
|
||||||
return _TrailingIcon(
|
return _TrailingIcon(
|
||||||
asset: 'assets/images/profile.png',
|
asset: 'assets/images/profile.png',
|
||||||
onPressed: () => Navigator.pushNamed(context, Routes.ioniaAccountPage),
|
iconColor: pageIconColor(context) ?? Colors.white,
|
||||||
);
|
onPressed: () {
|
||||||
|
_cardsListViewModel.isCakePayUserAuthenticated().then((value) {
|
||||||
|
if (value) {
|
||||||
|
Navigator.pushNamed(context, Routes.cakePayAccountPage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.pushNamed(context, Routes.cakePayWelcomePage);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -79,8 +84,12 @@ class IoniaManageCardsPage extends BasePage {
|
||||||
label: S.of(context).filter_by,
|
label: S.of(context).filter_by,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await showCategoryFilter(context);
|
_cardsListViewModel.storeInitialFilterStates();
|
||||||
_cardsListViewModel.getMerchants();
|
await showFilterWidget(context);
|
||||||
|
if (_cardsListViewModel.hasFiltersChanged) {
|
||||||
|
_cardsListViewModel.resetLoadingNextPageState();
|
||||||
|
_cardsListViewModel.getVendors();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 32,
|
width: 32,
|
||||||
|
@ -120,7 +129,7 @@ class IoniaManageCardsPage extends BasePage {
|
||||||
),
|
),
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: IoniaManageCardsPageBody(
|
child: CakePayCardsPageBody(
|
||||||
cardsListViewModel: _cardsListViewModel,
|
cardsListViewModel: _cardsListViewModel,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -129,36 +138,35 @@ class IoniaManageCardsPage extends BasePage {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future <void> showCategoryFilter(BuildContext context) async {
|
Future<void> showFilterWidget(BuildContext context) async {
|
||||||
return showPopUp<void>(
|
return showPopUp<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return IoniaFilterModal(
|
return FilterWidget(filterItems: _cardsListViewModel.createFilterItems);
|
||||||
ioniaGiftCardsListViewModel: _cardsListViewModel,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IoniaManageCardsPageBody extends StatefulWidget {
|
class CakePayCardsPageBody extends StatefulWidget {
|
||||||
const IoniaManageCardsPageBody({
|
const CakePayCardsPageBody({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.cardsListViewModel,
|
required this.cardsListViewModel,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final IoniaGiftCardsListViewModel cardsListViewModel;
|
final CakePayCardsListViewModel cardsListViewModel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_IoniaManageCardsPageBodyState createState() => _IoniaManageCardsPageBodyState();
|
_CakePayCardsPageBodyState createState() => _CakePayCardsPageBodyState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _IoniaManageCardsPageBodyState extends State<IoniaManageCardsPageBody> {
|
class _CakePayCardsPageBodyState extends State<CakePayCardsPageBody> {
|
||||||
double get backgroundHeight => MediaQuery.of(context).size.height * 0.75;
|
double get backgroundHeight => MediaQuery.of(context).size.height * 0.75;
|
||||||
double thumbHeight = 72;
|
double thumbHeight = 72;
|
||||||
bool get isAlwaysShowScrollThumb => merchantsList == null ? false : merchantsList.length > 3;
|
|
||||||
|
|
||||||
List<IoniaMerchant> get merchantsList => widget.cardsListViewModel.ioniaMerchants;
|
bool get isAlwaysShowScrollThumb => merchantsList.isEmpty ? false : merchantsList.length > 3;
|
||||||
|
|
||||||
|
List<CakePayVendor> get merchantsList => widget.cardsListViewModel.cakePayVendors;
|
||||||
|
|
||||||
final _scrollController = ScrollController();
|
final _scrollController = ScrollController();
|
||||||
|
|
||||||
|
@ -166,61 +174,93 @@ class _IoniaManageCardsPageBodyState extends State<IoniaManageCardsPageBody> {
|
||||||
void initState() {
|
void initState() {
|
||||||
_scrollController.addListener(() {
|
_scrollController.addListener(() {
|
||||||
final scrollOffsetFromTop = _scrollController.hasClients
|
final scrollOffsetFromTop = _scrollController.hasClients
|
||||||
? (_scrollController.offset / _scrollController.position.maxScrollExtent * (backgroundHeight - thumbHeight))
|
? (_scrollController.offset /
|
||||||
|
_scrollController.position.maxScrollExtent *
|
||||||
|
(backgroundHeight - thumbHeight))
|
||||||
: 0.0;
|
: 0.0;
|
||||||
widget.cardsListViewModel.setScrollOffsetFromTop(scrollOffsetFromTop);
|
widget.cardsListViewModel.setScrollOffsetFromTop(scrollOffsetFromTop);
|
||||||
|
|
||||||
|
double threshold = 200.0;
|
||||||
|
bool isNearBottom =
|
||||||
|
_scrollController.offset >= _scrollController.position.maxScrollExtent - threshold;
|
||||||
|
if (isNearBottom && !_scrollController.position.outOfRange) {
|
||||||
|
widget.cardsListViewModel.fetchNextPage();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Observer(
|
return Observer(builder: (_) {
|
||||||
builder: (_) {
|
final vendorsState = widget.cardsListViewModel.vendorsState;
|
||||||
final merchantState = widget.cardsListViewModel.merchantState;
|
if (vendorsState is CakePayVendorLoadedState) {
|
||||||
if (merchantState is IoniaLoadedMerchantState) {
|
bool isLoadingMore = widget.cardsListViewModel.isLoadingNextPage;
|
||||||
|
final vendors = widget.cardsListViewModel.cakePayVendors;
|
||||||
|
|
||||||
|
if (vendors.isEmpty) {
|
||||||
|
return Center(child: Text(S.of(context).no_cards_found));
|
||||||
|
}
|
||||||
return Stack(children: [
|
return Stack(children: [
|
||||||
ListView.separated(
|
GridView.builder(
|
||||||
padding: EdgeInsets.only(left: 2, right: 22),
|
controller: _scrollController,
|
||||||
controller: _scrollController,
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
itemCount: merchantsList.length,
|
crossAxisCount: responsiveLayoutUtil.shouldRenderTabletUI ? 2 : 1,
|
||||||
separatorBuilder: (_, __) => SizedBox(height: 4),
|
childAspectRatio: 5,
|
||||||
itemBuilder: (_, index) {
|
crossAxisSpacing: responsiveLayoutUtil.shouldRenderTabletUI ? 10 : 5,
|
||||||
final merchant = merchantsList[index];
|
mainAxisSpacing: responsiveLayoutUtil.shouldRenderTabletUI ? 10 : 5,
|
||||||
return CardItem(
|
),
|
||||||
logoUrl: merchant.logoUrl,
|
padding: EdgeInsets.only(left: 2, right: 22),
|
||||||
onTap: () {
|
itemCount: vendors.length + (isLoadingMore ? 1 : 0),
|
||||||
Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]);
|
itemBuilder: (_, index) {
|
||||||
},
|
if (index >= vendors.length) {
|
||||||
title: merchant.legalName,
|
return _VendorLoadedIndicator();
|
||||||
subTitle: merchant.avaibilityStatus,
|
}
|
||||||
backgroundColor: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
final vendor = vendors[index];
|
||||||
titleColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
return CardItem(
|
||||||
subtitleColor: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
|
logoUrl: vendor.card?.cardImageUrl,
|
||||||
discount: merchant.discount,
|
onTap: () {
|
||||||
);
|
Navigator.of(context).pushNamed(Routes.cakePayBuyCardPage, arguments: [vendor]);
|
||||||
},
|
},
|
||||||
),
|
title: vendor.name,
|
||||||
isAlwaysShowScrollThumb
|
subTitle: vendor.card?.description ?? '',
|
||||||
? CakeScrollbar(
|
backgroundColor:
|
||||||
backgroundHeight: backgroundHeight,
|
Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||||
thumbHeight: thumbHeight,
|
titleColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||||
rightOffset: 1,
|
subtitleColor: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
|
||||||
width: 3,
|
discount: 0.0,
|
||||||
backgroundColor: Theme.of(context).extension<FilterTheme>()!.iconColor.withOpacity(0.05),
|
);
|
||||||
thumbColor: Theme.of(context).extension<FilterTheme>()!.iconColor.withOpacity(0.5),
|
},
|
||||||
fromTop: widget.cardsListViewModel.scrollOffsetFromTop,
|
|
||||||
)
|
|
||||||
: Offstage()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return Center(
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
backgroundColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).extension<ExchangePageTheme>()!.firstGradientBottomPanelColor),
|
|
||||||
),
|
),
|
||||||
);
|
isAlwaysShowScrollThumb
|
||||||
|
? CakeScrollbar(
|
||||||
|
backgroundHeight: backgroundHeight,
|
||||||
|
thumbHeight: thumbHeight,
|
||||||
|
rightOffset: 1,
|
||||||
|
width: 3,
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).extension<FilterTheme>()!.iconColor.withOpacity(0.05),
|
||||||
|
thumbColor:
|
||||||
|
Theme.of(context).extension<FilterTheme>()!.iconColor.withOpacity(0.5),
|
||||||
|
fromTop: widget.cardsListViewModel.scrollOffsetFromTop,
|
||||||
|
)
|
||||||
|
: Offstage()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
return _VendorLoadedIndicator();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VendorLoadedIndicator extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
backgroundColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Theme.of(context).extension<ExchangePageTheme>()!.firstGradientBottomPanelColor),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,6 +273,7 @@ class _SearchWidget extends StatelessWidget {
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final FocusNode focusNode;
|
final FocusNode focusNode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final searchIcon = ExcludeSemantics(
|
final searchIcon = ExcludeSemantics(
|
||||||
|
@ -284,30 +325,25 @@ class _SearchWidget extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TrailingIcon extends StatelessWidget {
|
class _TrailingIcon extends StatelessWidget {
|
||||||
const _TrailingIcon({required this.asset, this.onPressed});
|
const _TrailingIcon({required this.asset, this.onPressed, required this.iconColor});
|
||||||
|
|
||||||
final String asset;
|
final String asset;
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
|
final Color iconColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Semantics(
|
return Semantics(
|
||||||
label: S.of(context).profile,
|
label: S.of(context).profile,
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
constraints: BoxConstraints(),
|
constraints: BoxConstraints(),
|
||||||
highlightColor: Colors.transparent,
|
highlightColor: Colors.transparent,
|
||||||
splashColor: Colors.transparent,
|
onPressed: onPressed,
|
||||||
iconSize: 25,
|
icon: ImageIcon(AssetImage(asset), size: 25, color: iconColor),
|
||||||
onPressed: onPressed,
|
|
||||||
icon: Image.asset(
|
|
||||||
asset,
|
|
||||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
|
||||||
),
|
),
|
||||||
),
|
));
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,403 @@
|
||||||
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_card.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/cake_pay/widgets/cake_pay_alert_modal.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/cake_pay/widgets/image_placeholder.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/cake_pay/widgets/link_extractor.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/cake_pay/widgets/text_icon_button.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.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/themes/extensions/cake_text_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||||
|
import 'package:cake_wallet/typography.dart';
|
||||||
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
|
import 'package:cake_wallet/view_model/cake_pay/cake_pay_purchase_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
class CakePayBuyCardDetailPage extends BasePage {
|
||||||
|
CakePayBuyCardDetailPage(this.cakePayPurchaseViewModel);
|
||||||
|
|
||||||
|
final CakePayPurchaseViewModel cakePayPurchaseViewModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => cakePayPurchaseViewModel.card.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? middle(BuildContext context) {
|
||||||
|
return Text(
|
||||||
|
title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
maxLines: 2,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
color: titleColor(context)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? trailing(BuildContext context) => null;
|
||||||
|
|
||||||
|
bool _effectsInstalled = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget body(BuildContext context) {
|
||||||
|
_setEffects(context);
|
||||||
|
|
||||||
|
final card = cakePayPurchaseViewModel.card;
|
||||||
|
|
||||||
|
return ScrollableWithBottomSection(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
content: Observer(builder: (_) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 36),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.horizontal(left: Radius.circular(20), right: Radius.circular(20)),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).extension<PickerTheme>()!.searchBackgroundFillColor,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(color: Colors.white.withOpacity(0.20)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.horizontal(
|
||||||
|
left: Radius.circular(20), right: Radius.circular(20)),
|
||||||
|
child: Image.network(
|
||||||
|
card.cardImageUrl ?? '',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
loadingBuilder: (BuildContext context, Widget child,
|
||||||
|
ImageChunkEvent? loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
},
|
||||||
|
errorBuilder: (context, error, stackTrace) =>
|
||||||
|
CakePayCardImagePlaceholder(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: Column(children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
S.of(context).value + ':',
|
||||||
|
style: textLarge(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'${cakePayPurchaseViewModel.amount.toStringAsFixed(2)} ${cakePayPurchaseViewModel.fiatCurrency}',
|
||||||
|
style: textLarge(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
S.of(context).quantity + ':',
|
||||||
|
style: textLarge(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'${cakePayPurchaseViewModel.quantity}',
|
||||||
|
style: textLarge(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
S.of(context).total + ':',
|
||||||
|
style: textLarge(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'${cakePayPurchaseViewModel.totalAmount.toStringAsFixed(2)} ${cakePayPurchaseViewModel.fiatCurrency}',
|
||||||
|
style: textLarge(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
|
child: TextIconButton(
|
||||||
|
label: S.of(context).how_to_use_card,
|
||||||
|
onTap: () => _showHowToUseCard(context, card),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
if (card.expiryAndValidity != null && card.expiryAndValidity!.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(S.of(context).expiry_and_validity + ':',
|
||||||
|
style: textMediumSemiBold(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Text(
|
||||||
|
card.expiryAndValidity ?? '',
|
||||||
|
style: textMedium(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
bottomSection: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 12),
|
||||||
|
child: Observer(builder: (_) {
|
||||||
|
return LoadingPrimaryButton(
|
||||||
|
isLoading: cakePayPurchaseViewModel.sendViewModel.state is IsExecutingState,
|
||||||
|
onPressed: () => purchaseCard(context),
|
||||||
|
text: S.of(context).purchase_gift_card,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
textColor: Colors.white,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => _showTermsAndCondition(context, card.termsAndConditions),
|
||||||
|
child: Text(S.of(context).settings_terms_and_conditions,
|
||||||
|
style: textMediumSemiBold(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
).copyWith(fontSize: 12)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showTermsAndCondition(BuildContext context, String? termsAndConditions) {
|
||||||
|
showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return CakePayAlertModal(
|
||||||
|
title: S.of(context).settings_terms_and_conditions,
|
||||||
|
content: Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: ClickableLinksText(
|
||||||
|
text: termsAndConditions ?? '',
|
||||||
|
textStyle: TextStyle(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actionTitle: S.of(context).agree,
|
||||||
|
showCloseButton: false,
|
||||||
|
heightFactor: 0.6,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> purchaseCard(BuildContext context) async {
|
||||||
|
bool isLogged = await cakePayPurchaseViewModel.cakePayService.isLogged();
|
||||||
|
if (!isLogged) {
|
||||||
|
Navigator.of(context).pushNamed(Routes.cakePayWelcomePage);
|
||||||
|
} else {
|
||||||
|
await cakePayPurchaseViewModel.createOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showHowToUseCard(
|
||||||
|
BuildContext context,
|
||||||
|
CakePayCard card,
|
||||||
|
) {
|
||||||
|
showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return CakePayAlertModal(
|
||||||
|
title: S.of(context).how_to_use_card,
|
||||||
|
content: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
child: Text(
|
||||||
|
card.name,
|
||||||
|
style: textLargeSemiBold(
|
||||||
|
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
ClickableLinksText(
|
||||||
|
text: card.howToUse ?? '',
|
||||||
|
textStyle: TextStyle(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
|
fontSize: 18,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
actionTitle: S.current.got_it,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showConfirmSendingAlert(BuildContext context) async {
|
||||||
|
if (cakePayPurchaseViewModel.order == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ReactionDisposer? disposer;
|
||||||
|
|
||||||
|
disposer = reaction((_) => cakePayPurchaseViewModel.isOrderExpired, (bool isExpired) {
|
||||||
|
if (isExpired) {
|
||||||
|
if (Navigator.of(context).canPop()) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
if (disposer != null) {
|
||||||
|
disposer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final order = cakePayPurchaseViewModel.order;
|
||||||
|
final pendingTransaction = cakePayPurchaseViewModel.sendViewModel.pendingTransaction!;
|
||||||
|
|
||||||
|
await showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) {
|
||||||
|
return Observer(
|
||||||
|
builder: (_) => ConfirmSendingAlert(
|
||||||
|
alertTitle: S.of(context).confirm_sending,
|
||||||
|
paymentId: S.of(context).payment_id,
|
||||||
|
paymentIdValue: order?.orderId,
|
||||||
|
expirationTime: cakePayPurchaseViewModel.formattedRemainingTime,
|
||||||
|
onDispose: () => _handleDispose(disposer),
|
||||||
|
amount: S.of(context).send_amount,
|
||||||
|
amountValue: pendingTransaction.amountFormatted,
|
||||||
|
fiatAmountValue:
|
||||||
|
cakePayPurchaseViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||||
|
fee: S.of(context).send_fee,
|
||||||
|
feeValue: pendingTransaction.feeFormatted,
|
||||||
|
feeFiatAmount:
|
||||||
|
cakePayPurchaseViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||||
|
feeRate: pendingTransaction.feeRate,
|
||||||
|
outputs: cakePayPurchaseViewModel.sendViewModel.outputs,
|
||||||
|
rightButtonText: S.of(context).send,
|
||||||
|
leftButtonText: S.of(context).cancel,
|
||||||
|
actionRightButton: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
await cakePayPurchaseViewModel.sendViewModel.commitTransaction();
|
||||||
|
},
|
||||||
|
actionLeftButton: () => Navigator.of(context).pop()));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setEffects(BuildContext context) {
|
||||||
|
if (_effectsInstalled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reaction((_) => cakePayPurchaseViewModel.sendViewModel.state, (ExecutionState state) {
|
||||||
|
if (state is FailureState) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
showStateAlert(context, S.of(context).error, state.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state is ExecutedSuccessfullyState) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await _showConfirmSendingAlert(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state is TransactionCommitted) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
cakePayPurchaseViewModel.sendViewModel.clearOutputs();
|
||||||
|
if (context.mounted) {
|
||||||
|
showStateAlert(context, S.of(context).sending, S.of(context).transaction_sent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_effectsInstalled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showStateAlert(BuildContext context, String title, String content) {
|
||||||
|
showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertWithOneAction(
|
||||||
|
alertTitle: title,
|
||||||
|
alertContent: content,
|
||||||
|
buttonText: S.of(context).ok,
|
||||||
|
buttonAction: () => Navigator.of(context).pop());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDispose(ReactionDisposer? disposer) {
|
||||||
|
cakePayPurchaseViewModel.dispose();
|
||||||
|
if (disposer != null) {
|
||||||
|
disposer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,8 @@ import 'package:cake_wallet/themes/extensions/cake_scrollbar_theme.dart';
|
||||||
import 'package:cake_wallet/typography.dart';
|
import 'package:cake_wallet/typography.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class IoniaAlertModal extends StatelessWidget {
|
class CakePayAlertModal extends StatelessWidget {
|
||||||
const IoniaAlertModal({
|
const CakePayAlertModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.content,
|
required this.content,
|
|
@ -3,8 +3,8 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||||
|
|
||||||
class IoniaTile extends StatelessWidget {
|
class CakePayTile extends StatelessWidget {
|
||||||
const IoniaTile({
|
const CakePayTile({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.subTitle,
|
required this.subTitle,
|
104
lib/src/screens/cake_pay/widgets/card_item.dart
Normal file
104
lib/src/screens/cake_pay/widgets/card_item.dart
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'image_placeholder.dart';
|
||||||
|
|
||||||
|
class CardItem extends StatelessWidget {
|
||||||
|
CardItem({
|
||||||
|
required this.title,
|
||||||
|
required this.subTitle,
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.titleColor,
|
||||||
|
required this.subtitleColor,
|
||||||
|
this.hideBorder = false,
|
||||||
|
this.discount = 0.0,
|
||||||
|
this.isAmount = false,
|
||||||
|
this.discountBackground,
|
||||||
|
this.onTap,
|
||||||
|
this.logoUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
final String title;
|
||||||
|
final String subTitle;
|
||||||
|
final String? logoUrl;
|
||||||
|
final double discount;
|
||||||
|
final bool isAmount;
|
||||||
|
final bool hideBorder;
|
||||||
|
final Color backgroundColor;
|
||||||
|
final Color titleColor;
|
||||||
|
final Color subtitleColor;
|
||||||
|
final AssetImage? discountBackground;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Theme(
|
||||||
|
data: ThemeData(
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: hideBorder
|
||||||
|
? Border.all(color: Colors.transparent)
|
||||||
|
: Border.all(color: Colors.white.withOpacity(0.20)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (logoUrl != null)
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 1.8,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
|
child: Image.network(
|
||||||
|
logoUrl!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
loadingBuilder:
|
||||||
|
(BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
},
|
||||||
|
errorBuilder: (context, error, stackTrace) => CakePayCardImagePlaceholder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: titleColor,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
subTitle,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: titleColor,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
29
lib/src/screens/cake_pay/widgets/image_placeholder.dart
Normal file
29
lib/src/screens/cake_pay/widgets/image_placeholder.dart
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CakePayCardImagePlaceholder extends StatelessWidget {
|
||||||
|
const CakePayCardImagePlaceholder({this.text});
|
||||||
|
|
||||||
|
final String? text;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AspectRatio(
|
||||||
|
aspectRatio: 1.8,
|
||||||
|
child: Container(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
text ?? 'Image not found!',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
66
lib/src/screens/cake_pay/widgets/link_extractor.dart
Normal file
66
lib/src/screens/cake_pay/widgets/link_extractor.dart
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
class ClickableLinksText extends StatelessWidget {
|
||||||
|
const ClickableLinksText({
|
||||||
|
required this.text,
|
||||||
|
required this.textStyle,
|
||||||
|
this.linkStyle,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String text;
|
||||||
|
final TextStyle textStyle;
|
||||||
|
final TextStyle? linkStyle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<InlineSpan> spans = [];
|
||||||
|
RegExp linkRegExp = RegExp(r'(https?://[^\s]+)');
|
||||||
|
Iterable<Match> matches = linkRegExp.allMatches(text);
|
||||||
|
|
||||||
|
int previousEnd = 0;
|
||||||
|
matches.forEach((match) {
|
||||||
|
if (match.start > previousEnd) {
|
||||||
|
spans.add(TextSpan(text: text.substring(previousEnd, match.start), style: textStyle));
|
||||||
|
}
|
||||||
|
String url = text.substring(match.start, match.end);
|
||||||
|
if (url.toLowerCase().endsWith('.md')) {
|
||||||
|
spans.add(
|
||||||
|
TextSpan(
|
||||||
|
text: url,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () async {
|
||||||
|
if (await canLaunchUrl(Uri.parse(url))) {
|
||||||
|
await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
spans.add(
|
||||||
|
TextSpan(
|
||||||
|
text: url,
|
||||||
|
style: linkStyle,
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
launchUrl(Uri.parse(url));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
previousEnd = match.end;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (previousEnd < text.length) {
|
||||||
|
spans.add(TextSpan(text: text.substring(previousEnd), style: textStyle));
|
||||||
|
}
|
||||||
|
|
||||||
|
return RichText(text: TextSpan(children: spans));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,18 @@
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||||
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
|
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||||
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
class CakeFeaturesPage extends StatelessWidget {
|
class CakeFeaturesPage extends StatelessWidget {
|
||||||
CakeFeaturesPage({
|
CakeFeaturesPage({required this.dashboardViewModel, required this.cakeFeaturesViewModel});
|
||||||
required this.dashboardViewModel,
|
|
||||||
required this.cakeFeaturesViewModel,
|
|
||||||
});
|
|
||||||
|
|
||||||
final DashboardViewModel dashboardViewModel;
|
final DashboardViewModel dashboardViewModel;
|
||||||
final CakeFeaturesViewModel cakeFeaturesViewModel;
|
final CakeFeaturesViewModel cakeFeaturesViewModel;
|
||||||
|
@ -45,20 +46,11 @@ class CakeFeaturesPage extends StatelessWidget {
|
||||||
child: ListView(
|
child: ListView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// SizedBox(height: 20),
|
|
||||||
// DashBoardRoundedCardWidget(
|
|
||||||
// onTap: () => launchUrl(
|
|
||||||
// Uri.parse("https://cakelabs.com/news/cake-pay-mobile-to-shut-down/"),
|
|
||||||
// mode: LaunchMode.externalApplication,
|
|
||||||
// ),
|
|
||||||
// title: S.of(context).cake_pay_title,
|
|
||||||
// subTitle: S.of(context).cake_pay_subtitle,
|
|
||||||
// ),
|
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
DashBoardRoundedCardWidget(
|
DashBoardRoundedCardWidget(
|
||||||
onTap: () => _launchUrl("buy.cakepay.com"),
|
onTap: () => _navigatorToGiftCardsPage(context),
|
||||||
title: S.of(context).cake_pay_web_cards_title,
|
title: 'Cake Pay',
|
||||||
subTitle: S.of(context).cake_pay_web_cards_subtitle,
|
subTitle: S.of(context).cake_pay_subtitle,
|
||||||
svgPicture: SvgPicture.asset(
|
svgPicture: SvgPicture.asset(
|
||||||
'assets/images/cards.svg',
|
'assets/images/cards.svg',
|
||||||
height: 125,
|
height: 125,
|
||||||
|
@ -88,6 +80,28 @@ class CakeFeaturesPage extends StatelessWidget {
|
||||||
Uri.https(url),
|
Uri.https(url),
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
);
|
);
|
||||||
} catch (_) {}
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.pushNamed(context, Routes.cakePayCardsPage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,18 +3,21 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/filter_tile.dart';
|
||||||
import 'package:cake_wallet/src/widgets/section_divider.dart';
|
import 'package:cake_wallet/src/widgets/section_divider.dart';
|
||||||
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
|
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item_widget.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
|
import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
|
||||||
//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
|
//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
|
||||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||||
|
|
||||||
class FilterWidget extends StatelessWidget {
|
class FilterWidget extends StatelessWidget {
|
||||||
FilterWidget({required this.dashboardViewModel});
|
FilterWidget({required this.filterItems});
|
||||||
|
|
||||||
final DashboardViewModel dashboardViewModel;
|
final Map<String, List<FilterItem>> filterItems;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -27,75 +30,90 @@ class FilterWidget extends StatelessWidget {
|
||||||
borderRadius: BorderRadius.all(Radius.circular(24)),
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor,
|
color: Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor,
|
||||||
child: Column(
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Padding(
|
||||||
children: [
|
padding: EdgeInsets.all(24.0),
|
||||||
Padding(
|
child: Text(
|
||||||
padding: EdgeInsets.all(24.0),
|
S.of(context).filter_by,
|
||||||
child: Text(
|
style: TextStyle(
|
||||||
S.of(context).filter_by,
|
color:
|
||||||
style: TextStyle(
|
Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
fontSize: 16,
|
||||||
fontSize: 16,
|
fontFamily: 'Lato',
|
||||||
fontFamily: 'Lato',
|
decoration: TextDecoration.none,
|
||||||
decoration: TextDecoration.none,
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sectionDivider,
|
||||||
|
ListView.separated(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: filterItems.length,
|
||||||
|
separatorBuilder: (context, _) => sectionDivider,
|
||||||
|
itemBuilder: (_, index1) {
|
||||||
|
final title = filterItems.keys.elementAt(index1);
|
||||||
|
final section = filterItems.values.elementAt(index1);
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 20, left: 24, right: 24),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
|
fontSize: 16,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
decoration: TextDecoration.none),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
ListView.builder(
|
||||||
),
|
padding: EdgeInsets.symmetric(horizontal: 28.0),
|
||||||
sectionDivider,
|
shrinkWrap: true,
|
||||||
ListView.separated(
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding: EdgeInsets.zero,
|
itemCount: section.length,
|
||||||
shrinkWrap: true,
|
itemBuilder: (_, index2) {
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
final item = section[index2];
|
||||||
itemCount: dashboardViewModel.filterItems.length,
|
|
||||||
separatorBuilder: (context, _) => sectionDivider,
|
if (item is DropdownFilterItem) {
|
||||||
itemBuilder: (_, index1) {
|
return Padding(
|
||||||
final title = dashboardViewModel.filterItems.keys
|
padding: EdgeInsets.fromLTRB(8, 0, 8, 16),
|
||||||
.elementAt(index1);
|
child: Container(
|
||||||
final section = dashboardViewModel.filterItems.values
|
decoration: BoxDecoration(
|
||||||
.elementAt(index1);
|
border: Border(
|
||||||
return Column(
|
bottom: BorderSide(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
width: 1.0,
|
||||||
children: <Widget>[
|
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||||
Padding(
|
),
|
||||||
padding:
|
),
|
||||||
EdgeInsets.only(top: 20, left: 24, right: 24),
|
child: DropdownFilterList(
|
||||||
child: Text(
|
items: item.items,
|
||||||
title,
|
caption: item.caption,
|
||||||
style: TextStyle(
|
selectedItem: item.selectedItem,
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
onItemSelected: item.onItemSelected,
|
||||||
fontSize: 16,
|
),
|
||||||
fontFamily: 'Lato',
|
),
|
||||||
fontWeight: FontWeight.bold,
|
);
|
||||||
decoration: TextDecoration.none),
|
}
|
||||||
),
|
final content = Observer(
|
||||||
),
|
builder: (_) => StandardCheckbox(
|
||||||
ListView.builder(
|
value: item.value(),
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
caption: item.caption,
|
||||||
shrinkWrap: true,
|
gradientBackground: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
borderColor: Theme.of(context).dividerColor,
|
||||||
itemCount: section.length,
|
iconColor: Colors.white,
|
||||||
itemBuilder: (_, index2) {
|
onChanged: (value) => item.onChanged(),
|
||||||
final item = section[index2];
|
));
|
||||||
final content = Observer(
|
return FilterTile(child: content);
|
||||||
builder: (_) => StandardCheckbox(
|
},
|
||||||
value: item.value(),
|
)
|
||||||
caption: item.caption,
|
],
|
||||||
gradientBackground: true,
|
);
|
||||||
borderColor:
|
},
|
||||||
Theme.of(context).dividerColor,
|
),
|
||||||
iconColor: Colors.white,
|
]),
|
||||||
onChanged: (value) =>
|
|
||||||
item.onChanged(),
|
|
||||||
));
|
|
||||||
return FilterTile(child: content);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -37,7 +37,7 @@ class HeaderRow extends StatelessWidget {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showPopUp<void>(
|
showPopUp<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => FilterWidget(dashboardViewModel: dashboardViewModel),
|
builder: (context) => FilterWidget(filterItems: dashboardViewModel.filterItems),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
|
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart';
|
import 'package:cake_wallet/src/screens/cake_pay/widgets/rounded_checkbox.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||||
import 'package:cake_wallet/typography.dart';
|
import 'package:cake_wallet/typography.dart';
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
|
||||||
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/jhjvdn7qq7k3ukwt';
|
|
||||||
static const termsAndConditionsUrl = 'https://ionia.docsend.com/view/uceirymz2ijacq5g';
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget middle(BuildContext context) {
|
|
||||||
return Text(
|
|
||||||
S.current.sign_up,
|
|
||||||
style: textMediumSemiBold(
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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,
|
|
||||||
onSubmit: (_) => _createAccount(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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: _createAccount,
|
|
||||||
isLoading:
|
|
||||||
_authViewModel.createUserState is IoniaCreateStateLoading,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
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).primaryColor,
|
|
||||||
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).primaryColor,
|
|
||||||
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],
|
|
||||||
);
|
|
||||||
|
|
||||||
void _createAccount() async {
|
|
||||||
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await _authViewModel.createUser(_emailController.text);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/primary_button.dart';
|
|
||||||
import 'package:cake_wallet/typography.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
|
||||||
|
|
||||||
class IoniaWelcomePage extends BasePage {
|
|
||||||
IoniaWelcomePage();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget middle(BuildContext context) {
|
|
||||||
return Text(
|
|
||||||
S.current.welcome_to_cakepay,
|
|
||||||
style: textMediumSemiBold(
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget body(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(height: 90),
|
|
||||||
Text(
|
|
||||||
S.of(context).about_cake_pay,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
fontFamily: 'Lato',
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
Text(
|
|
||||||
S.of(context).cake_pay_account_note,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
fontFamily: 'Lato',
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: <Widget>[
|
|
||||||
PrimaryButton(
|
|
||||||
text: S.of(context).create_account,
|
|
||||||
onPressed: () => Navigator.of(context).pushNamed(Routes.ioniaCreateAccountPage),
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
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).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,204 +0,0 @@
|
||||||
|
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_create_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/card_item.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/order_theme.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';
|
|
||||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.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).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 {
|
|
||||||
_IoniaCardTabsState();
|
|
||||||
|
|
||||||
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).extension<CakeTextTheme>()!.titleColor
|
|
||||||
.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
25.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Theme(
|
|
||||||
data: ThemeData(primaryTextTheme: TextTheme(bodyLarge: TextStyle(backgroundColor: Colors.transparent))),
|
|
||||||
child: TabBar(
|
|
||||||
controller: _tabController,
|
|
||||||
indicator: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
25.0,
|
|
||||||
),
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
labelColor: Theme.of(context).extension<OrderTheme>()!.iconColor,
|
|
||||||
unselectedLabelColor:
|
|
||||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
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,
|
|
||||||
isLoading: viewModel.merchantState is IoniaLoadingMerchantState,
|
|
||||||
onTap: (giftCard) {
|
|
||||||
Navigator.pushNamed(
|
|
||||||
context,
|
|
||||||
Routes.ioniaGiftCardDetailPage,
|
|
||||||
arguments: [giftCard])
|
|
||||||
.then((_) => viewModel.updateUserGiftCards());
|
|
||||||
}),
|
|
||||||
_IoniaCardListView(
|
|
||||||
emptyText: S.of(context).gift_card_redeemed_note,
|
|
||||||
merchList: viewModel.redeemedMerchs,
|
|
||||||
isLoading: viewModel.merchantState is IoniaLoadingMerchantState,
|
|
||||||
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,
|
|
||||||
this.isLoading = false,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final String emptyText;
|
|
||||||
final List<IoniaGiftCard> merchList;
|
|
||||||
final void Function(IoniaGiftCard giftCard) onTap;
|
|
||||||
final bool isLoading;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if(isLoading){
|
|
||||||
return Center(
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
backgroundColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(
|
|
||||||
Theme.of(context).extension<ExchangePageTheme>()!.firstGradientBottomPanelColor),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return merchList.isEmpty
|
|
||||||
? Center(
|
|
||||||
child: Text(
|
|
||||||
emptyText,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: textSmall(
|
|
||||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: 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).extension<CakeTextTheme>()!.titleColor
|
|
||||||
.withOpacity(0.1),
|
|
||||||
discount: 0,
|
|
||||||
hideBorder: true,
|
|
||||||
discountBackground: AssetImage('assets/images/red_badge_discount.png'),
|
|
||||||
titleColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
subtitleColor: Theme.of(context).hintColor,
|
|
||||||
subTitle: '',
|
|
||||||
logoUrl: merchant.logoUrl,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
|
||||||
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';
|
|
||||||
import 'package:cake_wallet/themes/extensions/send_page_theme.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).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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).primaryColor,
|
|
||||||
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,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final Widget content;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
child: content,
|
|
||||||
padding: EdgeInsets.all(24),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(15),
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
|
||||||
Theme.of(context).extension<SendPageTheme>()!.firstGradientColor,
|
|
||||||
],
|
|
||||||
begin: Alignment.topRight,
|
|
||||||
end: Alignment.bottomLeft,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/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).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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).primaryColor,
|
|
||||||
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(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,478 +0,0 @@
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/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/themes/extensions/exchange_page_theme.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';
|
|
||||||
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.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).extension<CakeTextTheme>()!.titleColor),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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).extension<SendPageTheme>()!.firstGradientColor,
|
|
||||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
|
||||||
],
|
|
||||||
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(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if(ioniaPurchaseViewModel.ioniaMerchant.acceptsTips)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(24.0, 24.0, 0, 24.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
S.of(context).tip,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 4),
|
|
||||||
Observer(
|
|
||||||
builder: (_) => TipButtonGroup(
|
|
||||||
selectedTip: ioniaPurchaseViewModel.selectedTip!.percentage,
|
|
||||||
tipsList: ioniaPurchaseViewModel.tips,
|
|
||||||
onSelect: (value) => ioniaPurchaseViewModel.addTip(value),
|
|
||||||
amount: ioniaPurchaseViewModel.amount,
|
|
||||||
merchant: ioniaPurchaseViewModel.ioniaMerchant,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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).primaryColor,
|
|
||||||
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).extension<ExchangePageTheme>()!.firstGradientBottomPanelColor,
|
|
||||||
).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).extension<ReceivePageTheme>()!.tilesTextColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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).extension<ReceivePageTheme>()!.tilesTextColor,
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
Text(
|
|
||||||
instruction.body,
|
|
||||||
style: textMedium(
|
|
||||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
];
|
|
||||||
})
|
|
||||||
.expand((e) => e)
|
|
||||||
.toList()),
|
|
||||||
actionTitle: S.current.got_it,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _presentSuccessfulInvoiceCreationPopup(BuildContext context) async {
|
|
||||||
if (ioniaPurchaseViewModel.invoice == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final amount = ioniaPurchaseViewModel.invoice!.totalAmount;
|
|
||||||
final addresses = ioniaPurchaseViewModel.invoice!.outAddresses;
|
|
||||||
ioniaPurchaseViewModel.sendViewModel.outputs.first.setCryptoAmount(amount);
|
|
||||||
ioniaPurchaseViewModel.sendViewModel.outputs.first.address = addresses.first;
|
|
||||||
|
|
||||||
await showPopUp<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) {
|
|
||||||
return ConfirmSendingAlert(
|
|
||||||
alertTitle: S.of(context).confirm_sending,
|
|
||||||
paymentId: S.of(context).payment_id,
|
|
||||||
paymentIdValue: ioniaPurchaseViewModel.invoice!.paymentId,
|
|
||||||
amount: S.of(context).send_amount,
|
|
||||||
amountValue: '$amount ${ioniaPurchaseViewModel.invoice!.chain}',
|
|
||||||
fiatAmountValue:
|
|
||||||
'~ ${ioniaPurchaseViewModel.sendViewModel.outputs.first.fiatAmount} '
|
|
||||||
'${ioniaPurchaseViewModel.sendViewModel.fiat.title}',
|
|
||||||
fee: S.of(context).send_fee,
|
|
||||||
feeValue:
|
|
||||||
'${ioniaPurchaseViewModel.sendViewModel.outputs.first.estimatedFee} '
|
|
||||||
'${ioniaPurchaseViewModel.invoice!.chain}',
|
|
||||||
feeFiatAmount:
|
|
||||||
'${ioniaPurchaseViewModel.sendViewModel.outputs.first.estimatedFeeFiatAmount} '
|
|
||||||
'${ioniaPurchaseViewModel.sendViewModel.fiat.title}',
|
|
||||||
outputs: ioniaPurchaseViewModel.sendViewModel.outputs,
|
|
||||||
rightButtonText: S.of(context).ok,
|
|
||||||
leftButtonText: S.of(context).cancel,
|
|
||||||
alertLeftActionButtonTextColor: Colors.white,
|
|
||||||
alertRightActionButtonTextColor: Colors.white,
|
|
||||||
alertLeftActionButtonColor: Palette.brightOrange,
|
|
||||||
alertRightActionButtonColor: Theme.of(context).primaryColor,
|
|
||||||
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,
|
|
||||||
required this.amount,
|
|
||||||
required this.merchant,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final Function(IoniaTip) onSelect;
|
|
||||||
final double selectedTip;
|
|
||||||
final List<IoniaTip> tipsList;
|
|
||||||
final double amount;
|
|
||||||
final IoniaMerchant merchant;
|
|
||||||
|
|
||||||
bool _isSelected(double value) => selectedTip == value;
|
|
||||||
Set<double> get filter => tipsList.map((e) => e.percentage).toSet();
|
|
||||||
bool get _isCustomSelected => !filter.contains(selectedTip);
|
|
||||||
|
|
||||||
@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: tip.isCustom ? _isCustomSelected : _isSelected(tip.percentage),
|
|
||||||
onTap: () async {
|
|
||||||
IoniaTip ioniaTip = tip;
|
|
||||||
if(tip.isCustom){
|
|
||||||
final customTip = await Navigator.pushNamed(context, Routes.ioniaCustomTipPage, arguments: [amount, merchant, tip]) as IoniaTip?;
|
|
||||||
ioniaTip = customTip ?? tip;
|
|
||||||
}
|
|
||||||
onSelect(ioniaTip);
|
|
||||||
},
|
|
||||||
caption: tip.isCustom ? S.of(context).custom : '${tip.percentage.toStringAsFixed(0)}%',
|
|
||||||
subTitle: tip.isCustom ? null : '\$${tip.additionalAmount.toStringAsFixed(2)}',
|
|
||||||
));
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TipButton extends StatelessWidget {
|
|
||||||
const TipButton({
|
|
||||||
required this.caption,
|
|
||||||
required this.onTap,
|
|
||||||
this.subTitle,
|
|
||||||
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).extension<CakeTextTheme>()!.titleColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isSelected
|
|
||||||
? Theme.of(context).dialogTheme.backgroundColor!
|
|
||||||
: Theme.of(context).extension<CakeTextTheme>()!.titleColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
Color subTitleTextColor(BuildContext context) {
|
|
||||||
if (isDark(context)) {
|
|
||||||
return Theme.of(context).extension<CakeTextTheme>()!.titleColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isSelected
|
|
||||||
? Theme.of(context).dialogTheme.backgroundColor!
|
|
||||||
: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
Color? backgroundColor(BuildContext context) {
|
|
||||||
if (isDark(context)) {
|
|
||||||
return isSelected
|
|
||||||
? null
|
|
||||||
: Theme.of(context).extension<CakeTextTheme>()!.titleColor.withOpacity(0.01);
|
|
||||||
}
|
|
||||||
|
|
||||||
return isSelected
|
|
||||||
? null
|
|
||||||
: Theme.of(context).extension<CakeTextTheme>()!.titleColor.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).extension<SendPageTheme>()!.firstGradientColor,
|
|
||||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
|
||||||
],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,186 +0,0 @@
|
||||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/utils/responsive_layout_util.dart';
|
|
||||||
import 'package:cake_wallet/view_model/ionia/ionia_buy_card_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';
|
|
||||||
import 'package:cake_wallet/themes/extensions/send_page_theme.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
|
|
||||||
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 merchant = ioniaBuyCardViewModel.ioniaMerchant;
|
|
||||||
return KeyboardActions(
|
|
||||||
disableScroll: true,
|
|
||||||
config: KeyboardActionsConfig(
|
|
||||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
|
||||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
|
||||||
nextFocus: false,
|
|
||||||
actions: [
|
|
||||||
KeyboardActionsItem(
|
|
||||||
focusNode: _amountFieldFocus,
|
|
||||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
child: Container(
|
|
||||||
color: Theme.of(context).colorScheme.background,
|
|
||||||
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).extension<SendPageTheme>()!.firstGradientColor,
|
|
||||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
|
||||||
], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
SizedBox(height: 150),
|
|
||||||
SizedBox(
|
|
||||||
width: 200,
|
|
||||||
child: BaseTextFormField(
|
|
||||||
controller: _amountController,
|
|
||||||
focusNode: _amountFieldFocus,
|
|
||||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter.deny(RegExp('[\-|\ ]')),
|
|
||||||
FilteringTextInputFormatter.allow(
|
|
||||||
RegExp(r'^\d+(\.|\,)?\d{0,2}'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
hintText: '1000',
|
|
||||||
placeholderTextStyle: TextStyle(
|
|
||||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 36,
|
|
||||||
),
|
|
||||||
prefixIcon: Text(
|
|
||||||
'USD: ',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 36,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
textColor: Colors.white,
|
|
||||||
textStyle: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 36,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Divider(
|
|
||||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
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).extension<SendPageTheme>()!.textFieldBorderColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
S.of(context).max_amount(merchant.maximumCardPurchase.toStringAsFixed(2)),
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 24),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: CardItem(
|
|
||||||
title: merchant.legalName,
|
|
||||||
backgroundColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor
|
|
||||||
.withOpacity(0.1),
|
|
||||||
discount: merchant.discount,
|
|
||||||
titleColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
subtitleColor: Theme.of(context).hintColor,
|
|
||||||
subTitle: merchant.avaibilityStatus,
|
|
||||||
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).primaryColor,
|
|
||||||
textColor: Colors.white,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
SizedBox(height: 30),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,175 +0,0 @@
|
||||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
|
||||||
import 'package:cake_wallet/core/execution_state.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_custom_redeem_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';
|
|
||||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
|
||||||
|
|
||||||
class IoniaCustomRedeemPage extends BasePage {
|
|
||||||
IoniaCustomRedeemPage(
|
|
||||||
this.ioniaCustomRedeemViewModel,
|
|
||||||
) : _amountFieldFocus = FocusNode(),
|
|
||||||
_amountController = TextEditingController() {
|
|
||||||
_amountController.addListener(() {
|
|
||||||
ioniaCustomRedeemViewModel.updateAmount(_amountController.text);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final IoniaCustomRedeemViewModel ioniaCustomRedeemViewModel;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get title => S.current.custom_redeem_amount;
|
|
||||||
|
|
||||||
@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 giftCard = ioniaCustomRedeemViewModel.giftCard;
|
|
||||||
return KeyboardActions(
|
|
||||||
disableScroll: true,
|
|
||||||
config: KeyboardActionsConfig(
|
|
||||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
|
||||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
|
||||||
nextFocus: false,
|
|
||||||
actions: [
|
|
||||||
KeyboardActionsItem(
|
|
||||||
focusNode: _amountFieldFocus,
|
|
||||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
child: Container(
|
|
||||||
color: Theme.of(context).colorScheme.background,
|
|
||||||
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).extension<SendPageTheme>()!.firstGradientColor,
|
|
||||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
|
||||||
], 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).extension<SendPageTheme>()!.textFieldBorderColor,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 36,
|
|
||||||
),
|
|
||||||
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
|
||||||
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: (_) => !ioniaCustomRedeemViewModel.disableRedeem
|
|
||||||
? Center(
|
|
||||||
child: Text(
|
|
||||||
'\$${giftCard.remainingAmount} - \$${ioniaCustomRedeemViewModel.amount} = \$${ioniaCustomRedeemViewModel.formattedRemaining} ${S.of(context).remaining}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
SizedBox(height: 24),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: CardItem(
|
|
||||||
title: giftCard.legalName,
|
|
||||||
backgroundColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor
|
|
||||||
.withOpacity(0.1),
|
|
||||||
discount: giftCard.remainingAmount,
|
|
||||||
isAmount: true,
|
|
||||||
discountBackground: AssetImage('assets/images/red_badge_discount.png'),
|
|
||||||
titleColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
subtitleColor: Theme.of(context).hintColor,
|
|
||||||
subTitle: S.of(context).online,
|
|
||||||
logoUrl: giftCard.logoUrl,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
bottomSection: Column(
|
|
||||||
children: [
|
|
||||||
Observer(
|
|
||||||
builder: (_) => Padding(
|
|
||||||
padding: EdgeInsets.only(bottom: 12),
|
|
||||||
child: LoadingPrimaryButton(
|
|
||||||
isLoading: ioniaCustomRedeemViewModel.redeemState is IsExecutingState,
|
|
||||||
isDisabled: ioniaCustomRedeemViewModel.disableRedeem,
|
|
||||||
text: S.of(context).add_custom_redemption,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
textColor: Colors.white,
|
|
||||||
onPressed: () => ioniaCustomRedeemViewModel.addCustomRedeem().then((value) {
|
|
||||||
Navigator.of(context).pop(ioniaCustomRedeemViewModel.remaining.toString());
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 30),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
|
|
||||||
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_custom_tip_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';
|
|
||||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
|
||||||
|
|
||||||
class IoniaCustomTipPage extends BasePage {
|
|
||||||
IoniaCustomTipPage(
|
|
||||||
this.customTipViewModel,
|
|
||||||
) : _amountFieldFocus = FocusNode(),
|
|
||||||
_amountController = TextEditingController() {
|
|
||||||
_amountController.addListener(() {
|
|
||||||
customTipViewModel.onTipChanged(_amountController.text);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final IoniaCustomTipViewModel customTipViewModel;
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get title => S.current.enter_amount;
|
|
||||||
|
|
||||||
@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 = customTipViewModel.ioniaMerchant;
|
|
||||||
return KeyboardActions(
|
|
||||||
disableScroll: true,
|
|
||||||
config: KeyboardActionsConfig(
|
|
||||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
|
||||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
|
||||||
nextFocus: false,
|
|
||||||
actions: [
|
|
||||||
KeyboardActionsItem(
|
|
||||||
focusNode: _amountFieldFocus,
|
|
||||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
child: Container(
|
|
||||||
color: Theme.of(context).colorScheme.background,
|
|
||||||
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).extension<SendPageTheme>()!.firstGradientColor,
|
|
||||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
|
||||||
], 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).extension<SendPageTheme>()!.textFieldBorderColor,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 36,
|
|
||||||
),
|
|
||||||
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
|
||||||
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 (customTipViewModel.percentage == 0.0) {
|
|
||||||
return SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
return RichText(
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
text: TextSpan(
|
|
||||||
text: '\$${_amountController.text}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
TextSpan(text: ' ${S.of(context).is_percentage} '),
|
|
||||||
TextSpan(text: '${customTipViewModel.percentage.toStringAsFixed(2)}%'),
|
|
||||||
TextSpan(text: ' ${S.of(context).percentageOf(customTipViewModel.amount.toStringAsFixed(2))} '),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
SizedBox(height: 24),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: CardItem(
|
|
||||||
title: merchant.legalName,
|
|
||||||
backgroundColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor
|
|
||||||
.withOpacity(0.1),
|
|
||||||
discount: 0.0,
|
|
||||||
titleColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
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(customTipViewModel.customTip);
|
|
||||||
},
|
|
||||||
text: S.of(context).add_tip,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
textColor: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 30),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,393 +0,0 @@
|
||||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/themes/extensions/cake_scrollbar_theme.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';
|
|
||||||
import 'package:cake_wallet/themes/extensions/send_page_theme.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).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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).extension<ReceivePageTheme>()!.iconsColor),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 24),
|
|
||||||
PrimaryButton(
|
|
||||||
text: S.of(context).order_physical_card,
|
|
||||||
onPressed: () {},
|
|
||||||
color: Color(0xffE9F2FC),
|
|
||||||
textColor: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
|
||||||
),
|
|
||||||
SizedBox(height: 8),
|
|
||||||
PrimaryButton(
|
|
||||||
text: S.of(context).add_value,
|
|
||||||
onPressed: () {},
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
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).extension<ReceivePageTheme>()!.tilesTextColor),
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: S.of(context).digital_and_physical_card,
|
|
||||||
style: textMediumBold(
|
|
||||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
|
|
||||||
),
|
|
||||||
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).primaryColor,
|
|
||||||
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).colorScheme.background,
|
|
||||||
borderRadius: BorderRadius.circular(30),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
S.of(context).how_to_use_card,
|
|
||||||
style: textLargeSemiBold(
|
|
||||||
color:
|
|
||||||
Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 24),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.bottomLeft,
|
|
||||||
child: Text(
|
|
||||||
S.of(context).signup_for_card_accept_terms,
|
|
||||||
style: textSmallSemiBold(
|
|
||||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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).got_it,
|
|
||||||
color: Color.fromRGBO(233, 242, 252, 1),
|
|
||||||
textColor:
|
|
||||||
Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
|
||||||
),
|
|
||||||
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 {
|
|
||||||
const _IoniaDebitCard({
|
|
||||||
Key? key,
|
|
||||||
this.cardInfo,
|
|
||||||
this.isCardSample = false,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final bool isCardSample;
|
|
||||||
final IoniaVirtualCard? cardInfo;
|
|
||||||
|
|
||||||
@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).extension<SendPageTheme>()!.firstGradientColor,
|
|
||||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
|
||||||
],
|
|
||||||
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 {
|
|
||||||
const _TitleSubtitleTile({
|
|
||||||
Key? key,
|
|
||||||
required this.title,
|
|
||||||
required this.subtitle,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
final String subtitle;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: textSmallSemiBold(
|
|
||||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
|
|
||||||
),
|
|
||||||
SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: textSmall(
|
|
||||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,215 +0,0 @@
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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_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/utils/route_aware.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:cake_wallet/generated/i18n.dart';
|
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
import 'package:screen_brightness/screen_brightness.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).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
size: 16,
|
|
||||||
);
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10.0),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 37,
|
|
||||||
width: 37,
|
|
||||||
child: ButtonTheme(
|
|
||||||
minWidth: double.minPositive,
|
|
||||||
child: TextButton(
|
|
||||||
// FIX-ME: Style
|
|
||||||
//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).extension<CakeTextTheme>()!.titleColor),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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) ...[
|
|
||||||
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.remainingAmount.toStringAsFixed(2),
|
|
||||||
)),
|
|
||||||
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 Column(
|
|
||||||
children: [
|
|
||||||
PrimaryButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
Routes.ioniaMoreOptionsPage,
|
|
||||||
arguments: [viewModel.giftCard]) as String?;
|
|
||||||
viewModel.refeshCard();
|
|
||||||
},
|
|
||||||
text: S.of(context).more_options,
|
|
||||||
color: Theme.of(context).cardColor,
|
|
||||||
textColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
SizedBox(height: 12),
|
|
||||||
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).primaryColor,
|
|
||||||
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).extension<ReceivePageTheme>()!.tilesTextColor,
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
Text(
|
|
||||||
instruction.body,
|
|
||||||
style: textMedium(
|
|
||||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
];
|
|
||||||
})
|
|
||||||
.expand((e) => e)
|
|
||||||
.toList()),
|
|
||||||
actionTitle: S.of(context).got_it,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
|
||||||
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/themes/extensions/dashboard_page_theme.dart';
|
|
||||||
import 'package:cake_wallet/typography.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class IoniaMoreOptionsPage extends BasePage {
|
|
||||||
IoniaMoreOptionsPage(this.giftCard);
|
|
||||||
|
|
||||||
final IoniaGiftCard giftCard;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget middle(BuildContext context) {
|
|
||||||
return Text(
|
|
||||||
S.current.more_options,
|
|
||||||
style: textMediumSemiBold(
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget body(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
S.of(context).choose_from_available_options,
|
|
||||||
style: textMedium(
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 40),
|
|
||||||
InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
final amount = await Navigator.of(context)
|
|
||||||
.pushNamed(Routes.ioniaCustomRedeemPage, arguments: [giftCard]) as String?;
|
|
||||||
if (amount != null && amount.isNotEmpty) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: _GradiantContainer(
|
|
||||||
content: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 24, left: 20, right: 24, bottom: 50),
|
|
||||||
child: Text(
|
|
||||||
S.of(context).custom_redeem_amount,
|
|
||||||
style: textXLargeSemiBold(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GradiantContainer extends StatelessWidget {
|
|
||||||
const _GradiantContainer({Key? key, required this.content}) : super(key: key);
|
|
||||||
|
|
||||||
final Widget content;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
child: content,
|
|
||||||
padding: EdgeInsets.all(24),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(15),
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
Theme.of(context).extension<DashboardPageTheme>()!.secondGradientBackgroundColor,
|
|
||||||
Theme.of(context).extension<DashboardPageTheme>()!.firstGradientBackgroundColor,
|
|
||||||
],
|
|
||||||
begin: Alignment.topRight,
|
|
||||||
end: Alignment.bottomLeft,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,222 +0,0 @@
|
||||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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';
|
|
||||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.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).extension<CakeTextTheme>()!.titleColor));
|
|
||||||
}
|
|
||||||
|
|
||||||
@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).extension<CakeTextTheme>()!.titleColor))
|
|
||||||
]),
|
|
||||||
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)),
|
|
||||||
if (widget.viewModel.paymentInfo.ioniaOrder.id != null)
|
|
||||||
...[Divider(height: 30),
|
|
||||||
buildDescriptionTileWithCopy(context, S.of(context).order_id, widget.viewModel.paymentInfo.ioniaOrder.id)],
|
|
||||||
if (widget.viewModel.paymentInfo.ioniaOrder.paymentId != null)
|
|
||||||
...[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).extension<CakeTextTheme>()!.titleColor))
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
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).extension<CakeTextTheme>()!.titleColor))]);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
bottomSection: Padding(
|
|
||||||
padding: EdgeInsets.only(bottom: 12),
|
|
||||||
child: Column(children: [
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.only(left: 40, right: 40, bottom: 20),
|
|
||||||
child: Text(
|
|
||||||
widget.viewModel.payingByBitcoin ? S.of(context).bitcoin_payments_require_1_confirmation
|
|
||||||
: S.of(context).proceed_after_one_minute,
|
|
||||||
style: textMedium(
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
).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).primaryColor,
|
|
||||||
textColor: Colors.white);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PrimaryButton(
|
|
||||||
onPressed: () => Navigator.of(context).pushNamed(Routes.support),
|
|
||||||
text: S.of(context).contact_support,
|
|
||||||
color: Theme.of(context).cardColor,
|
|
||||||
textColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor);
|
|
||||||
})
|
|
||||||
])
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: textMedium(
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
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';
|
|
|
@ -1,144 +0,0 @@
|
||||||
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.hideBorder = false,
|
|
||||||
this.discount = 0.0,
|
|
||||||
this.isAmount = false,
|
|
||||||
this.discountBackground,
|
|
||||||
this.onTap,
|
|
||||||
this.logoUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
final VoidCallback? onTap;
|
|
||||||
final String title;
|
|
||||||
final String subTitle;
|
|
||||||
final String? logoUrl;
|
|
||||||
final double discount;
|
|
||||||
final bool isAmount;
|
|
||||||
final bool hideBorder;
|
|
||||||
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: hideBorder ? Border.symmetric(horizontal: BorderSide.none, vertical: BorderSide.none) : 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,
|
|
||||||
isAmount: isAmount,
|
|
||||||
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),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
|
||||||
import 'package:cake_wallet/palette.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
|
||||||
|
|
||||||
class IoniaFilterModal extends StatelessWidget {
|
|
||||||
IoniaFilterModal({required this.ioniaGiftCardsListViewModel}){
|
|
||||||
ioniaGiftCardsListViewModel.resetIoniaCategories();
|
|
||||||
}
|
|
||||||
|
|
||||||
final IoniaGiftCardsListViewModel ioniaGiftCardsListViewModel;
|
|
||||||
|
|
||||||
@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).primaryColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
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).colorScheme.background,
|
|
||||||
borderRadius: BorderRadius.circular(30),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 40,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
|
||||||
child: TextField(
|
|
||||||
onChanged: ioniaGiftCardsListViewModel.onSearchFilter,
|
|
||||||
style: textMedium(
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
filled: true,
|
|
||||||
prefixIcon: searchIcon,
|
|
||||||
hintText: S.of(context).search_category,
|
|
||||||
contentPadding: EdgeInsets.only(bottom: 5),
|
|
||||||
fillColor: Theme.of(context).extension<CakeMenuTheme>()!.dividerColor.withOpacity(0.5),
|
|
||||||
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: ioniaGiftCardsListViewModel.ioniaCategories.length,
|
|
||||||
itemBuilder: (_, index) {
|
|
||||||
final category = ioniaGiftCardsListViewModel.ioniaCategories[index];
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => ioniaGiftCardsListViewModel.setSelectedFilter(category),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Image.asset(
|
|
||||||
category.iconPath,
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
),
|
|
||||||
SizedBox(width: 10),
|
|
||||||
Text(category.title,
|
|
||||||
style: textSmall(
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
).copyWith(fontWeight: FontWeight.w500)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Observer(builder: (_) {
|
|
||||||
final value = ioniaGiftCardsListViewModel.selectedIndices;
|
|
||||||
return RoundedCheckbox(
|
|
||||||
value: value.contains(category),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () => Navigator.pop(context),
|
|
||||||
child: Container(
|
|
||||||
margin: EdgeInsets.only(bottom: 40),
|
|
||||||
child: CircleAvatar(
|
|
||||||
child: Icon(
|
|
||||||
Icons.close,
|
|
||||||
color: Palette.darkBlueCraiola,
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -136,9 +136,11 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
break;
|
break;
|
||||||
case AppLifecycleState.resumed:
|
case AppLifecycleState.resumed:
|
||||||
widget.authService.requireAuth().then((value) {
|
widget.authService.requireAuth().then((value) {
|
||||||
setState(() {
|
if (mounted) {
|
||||||
_requestAuth = value;
|
setState(() {
|
||||||
});
|
_requestAuth = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -12,6 +12,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||||
{required this.alertTitle,
|
{required this.alertTitle,
|
||||||
this.paymentId,
|
this.paymentId,
|
||||||
this.paymentIdValue,
|
this.paymentIdValue,
|
||||||
|
this.expirationTime,
|
||||||
required this.amount,
|
required this.amount,
|
||||||
required this.amountValue,
|
required this.amountValue,
|
||||||
required this.fiatAmountValue,
|
required this.fiatAmountValue,
|
||||||
|
@ -28,11 +29,13 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||||
this.alertLeftActionButtonTextColor,
|
this.alertLeftActionButtonTextColor,
|
||||||
this.alertRightActionButtonTextColor,
|
this.alertRightActionButtonTextColor,
|
||||||
this.alertLeftActionButtonColor,
|
this.alertLeftActionButtonColor,
|
||||||
this.alertRightActionButtonColor});
|
this.alertRightActionButtonColor,
|
||||||
|
this.onDispose});
|
||||||
|
|
||||||
final String alertTitle;
|
final String alertTitle;
|
||||||
final String? paymentId;
|
final String? paymentId;
|
||||||
final String? paymentIdValue;
|
final String? paymentIdValue;
|
||||||
|
final String? expirationTime;
|
||||||
final String amount;
|
final String amount;
|
||||||
final String amountValue;
|
final String amountValue;
|
||||||
final String fiatAmountValue;
|
final String fiatAmountValue;
|
||||||
|
@ -50,6 +53,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||||
final Color? alertRightActionButtonTextColor;
|
final Color? alertRightActionButtonTextColor;
|
||||||
final Color? alertLeftActionButtonColor;
|
final Color? alertLeftActionButtonColor;
|
||||||
final Color? alertRightActionButtonColor;
|
final Color? alertRightActionButtonColor;
|
||||||
|
final Function? onDispose;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get titleText => alertTitle;
|
String get titleText => alertTitle;
|
||||||
|
@ -88,6 +92,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||||
Widget content(BuildContext context) => ConfirmSendingAlertContent(
|
Widget content(BuildContext context) => ConfirmSendingAlertContent(
|
||||||
paymentId: paymentId,
|
paymentId: paymentId,
|
||||||
paymentIdValue: paymentIdValue,
|
paymentIdValue: paymentIdValue,
|
||||||
|
expirationTime: expirationTime,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
amountValue: amountValue,
|
amountValue: amountValue,
|
||||||
fiatAmountValue: fiatAmountValue,
|
fiatAmountValue: fiatAmountValue,
|
||||||
|
@ -95,13 +100,15 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||||
feeRate: feeRate,
|
feeRate: feeRate,
|
||||||
feeValue: feeValue,
|
feeValue: feeValue,
|
||||||
feeFiatAmount: feeFiatAmount,
|
feeFiatAmount: feeFiatAmount,
|
||||||
outputs: outputs);
|
outputs: outputs,
|
||||||
|
onDispose: onDispose);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfirmSendingAlertContent extends StatefulWidget {
|
class ConfirmSendingAlertContent extends StatefulWidget {
|
||||||
ConfirmSendingAlertContent(
|
ConfirmSendingAlertContent(
|
||||||
{this.paymentId,
|
{this.paymentId,
|
||||||
this.paymentIdValue,
|
this.paymentIdValue,
|
||||||
|
this.expirationTime,
|
||||||
required this.amount,
|
required this.amount,
|
||||||
required this.amountValue,
|
required this.amountValue,
|
||||||
required this.fiatAmountValue,
|
required this.fiatAmountValue,
|
||||||
|
@ -109,10 +116,12 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||||
this.feeRate,
|
this.feeRate,
|
||||||
required this.feeValue,
|
required this.feeValue,
|
||||||
required this.feeFiatAmount,
|
required this.feeFiatAmount,
|
||||||
required this.outputs});
|
required this.outputs,
|
||||||
|
required this.onDispose}) {}
|
||||||
|
|
||||||
final String? paymentId;
|
final String? paymentId;
|
||||||
final String? paymentIdValue;
|
final String? paymentIdValue;
|
||||||
|
final String? expirationTime;
|
||||||
final String amount;
|
final String amount;
|
||||||
final String amountValue;
|
final String amountValue;
|
||||||
final String fiatAmountValue;
|
final String fiatAmountValue;
|
||||||
|
@ -121,11 +130,13 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||||
final String feeValue;
|
final String feeValue;
|
||||||
final String feeFiatAmount;
|
final String feeFiatAmount;
|
||||||
final List<Output> outputs;
|
final List<Output> outputs;
|
||||||
|
final Function? onDispose;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConfirmSendingAlertContentState createState() => ConfirmSendingAlertContentState(
|
ConfirmSendingAlertContentState createState() => ConfirmSendingAlertContentState(
|
||||||
paymentId: paymentId,
|
paymentId: paymentId,
|
||||||
paymentIdValue: paymentIdValue,
|
paymentIdValue: paymentIdValue,
|
||||||
|
expirationTime: expirationTime,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
amountValue: amountValue,
|
amountValue: amountValue,
|
||||||
fiatAmountValue: fiatAmountValue,
|
fiatAmountValue: fiatAmountValue,
|
||||||
|
@ -133,13 +144,15 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||||
feeRate: feeRate,
|
feeRate: feeRate,
|
||||||
feeValue: feeValue,
|
feeValue: feeValue,
|
||||||
feeFiatAmount: feeFiatAmount,
|
feeFiatAmount: feeFiatAmount,
|
||||||
outputs: outputs);
|
outputs: outputs,
|
||||||
|
onDispose: onDispose);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> {
|
class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> {
|
||||||
ConfirmSendingAlertContentState(
|
ConfirmSendingAlertContentState(
|
||||||
{this.paymentId,
|
{this.paymentId,
|
||||||
this.paymentIdValue,
|
this.paymentIdValue,
|
||||||
|
this.expirationTime,
|
||||||
required this.amount,
|
required this.amount,
|
||||||
required this.amountValue,
|
required this.amountValue,
|
||||||
required this.fiatAmountValue,
|
required this.fiatAmountValue,
|
||||||
|
@ -147,7 +160,8 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||||
this.feeRate,
|
this.feeRate,
|
||||||
required this.feeValue,
|
required this.feeValue,
|
||||||
required this.feeFiatAmount,
|
required this.feeFiatAmount,
|
||||||
required this.outputs})
|
required this.outputs,
|
||||||
|
this.onDispose})
|
||||||
: recipientTitle = '' {
|
: recipientTitle = '' {
|
||||||
recipientTitle = outputs.length > 1
|
recipientTitle = outputs.length > 1
|
||||||
? S.current.transaction_details_recipient_address
|
? S.current.transaction_details_recipient_address
|
||||||
|
@ -156,6 +170,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||||
|
|
||||||
final String? paymentId;
|
final String? paymentId;
|
||||||
final String? paymentIdValue;
|
final String? paymentIdValue;
|
||||||
|
final String? expirationTime;
|
||||||
final String amount;
|
final String amount;
|
||||||
final String amountValue;
|
final String amountValue;
|
||||||
final String fiatAmountValue;
|
final String fiatAmountValue;
|
||||||
|
@ -164,6 +179,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||||
final String feeValue;
|
final String feeValue;
|
||||||
final String feeFiatAmount;
|
final String feeFiatAmount;
|
||||||
final List<Output> outputs;
|
final List<Output> outputs;
|
||||||
|
final Function? onDispose;
|
||||||
|
|
||||||
final double backgroundHeight = 160;
|
final double backgroundHeight = 160;
|
||||||
final double thumbHeight = 72;
|
final double thumbHeight = 72;
|
||||||
|
@ -172,6 +188,12 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||||
String recipientTitle;
|
String recipientTitle;
|
||||||
bool showScrollbar = false;
|
bool showScrollbar = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (onDispose != null) onDispose!();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
controller.addListener(() {
|
controller.addListener(() {
|
||||||
|
@ -217,14 +239,18 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Container(
|
||||||
paymentIdValue!,
|
width: 160,
|
||||||
style: TextStyle(
|
child: Text(
|
||||||
fontSize: 18,
|
paymentIdValue!,
|
||||||
fontWeight: FontWeight.w600,
|
textAlign: TextAlign.right,
|
||||||
fontFamily: 'Lato',
|
style: TextStyle(
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
fontSize: 16,
|
||||||
decoration: TextDecoration.none,
|
fontWeight: FontWeight.w600,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -232,6 +258,8 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (widget.expirationTime != null)
|
||||||
|
ExpirationTimeWidget(expirationTime: widget.expirationTime!),
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
@ -468,3 +496,46 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ExpirationTimeWidget extends StatelessWidget {
|
||||||
|
const ExpirationTimeWidget({
|
||||||
|
required this.expirationTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String expirationTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 32),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
S.current.offer_expires_in,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
expirationTime,
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
145
lib/src/widgets/number_text_fild_widget.dart
Normal file
145
lib/src/widgets/number_text_fild_widget.dart
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
|
import 'package:cake_wallet/typography.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class NumberTextField extends StatefulWidget {
|
||||||
|
final TextEditingController? controller;
|
||||||
|
final FocusNode? focusNode;
|
||||||
|
final int min;
|
||||||
|
final int max;
|
||||||
|
final int step;
|
||||||
|
final double arrowsWidth;
|
||||||
|
final double arrowsHeight;
|
||||||
|
final EdgeInsets contentPadding;
|
||||||
|
final double borderWidth;
|
||||||
|
final ValueChanged<int?>? onChanged;
|
||||||
|
|
||||||
|
const NumberTextField({
|
||||||
|
Key? key,
|
||||||
|
this.controller,
|
||||||
|
this.focusNode,
|
||||||
|
this.min = 0,
|
||||||
|
this.max = 999,
|
||||||
|
this.step = 1,
|
||||||
|
this.arrowsWidth = 24,
|
||||||
|
this.arrowsHeight = kMinInteractiveDimension,
|
||||||
|
this.contentPadding = const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
this.borderWidth = 2,
|
||||||
|
this.onChanged,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _NumberTextFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NumberTextFieldState extends State<NumberTextField> {
|
||||||
|
late TextEditingController _controller;
|
||||||
|
late FocusNode _focusNode;
|
||||||
|
bool _canGoUp = false;
|
||||||
|
bool _canGoDown = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = widget.controller ?? TextEditingController();
|
||||||
|
_focusNode = widget.focusNode ?? FocusNode();
|
||||||
|
_updateArrows(int.tryParse(_controller.text));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant NumberTextField oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
_controller = widget.controller ?? _controller;
|
||||||
|
_focusNode = widget.focusNode ?? _focusNode;
|
||||||
|
_updateArrows(int.tryParse(_controller.text));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => TextField(
|
||||||
|
style: textMediumSemiBold(color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
|
enableInteractiveSelection: false,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textAlignVertical: TextAlignVertical.bottom,
|
||||||
|
controller: _controller,
|
||||||
|
focusNode: _focusNode,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
maxLength: widget.max.toString().length + (widget.min.isNegative ? 1 : 0),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.all(0),
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
counterText: '',
|
||||||
|
isDense: true,
|
||||||
|
filled: true,
|
||||||
|
suffixIconConstraints: BoxConstraints(
|
||||||
|
maxHeight: widget.arrowsHeight,
|
||||||
|
maxWidth: widget.arrowsWidth + widget.contentPadding.right),
|
||||||
|
prefixIconConstraints: BoxConstraints(
|
||||||
|
maxHeight: widget.arrowsHeight,
|
||||||
|
maxWidth: widget.arrowsWidth + widget.contentPadding.left),
|
||||||
|
prefixIcon: Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: InkWell(
|
||||||
|
child: Container(
|
||||||
|
width: widget.arrowsWidth,
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Icon(Icons.arrow_left_outlined, size: widget.arrowsWidth)),
|
||||||
|
onTap: _canGoDown ? () => _update(false) : null)),
|
||||||
|
suffixIcon: Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: InkWell(
|
||||||
|
child: Container(
|
||||||
|
width: widget.arrowsWidth,
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Icon(Icons.arrow_right_outlined, size: widget.arrowsWidth)),
|
||||||
|
onTap: _canGoUp ? () => _update(true) : null))),
|
||||||
|
maxLines: 1,
|
||||||
|
onChanged: (value) {
|
||||||
|
final intValue = int.tryParse(value);
|
||||||
|
widget.onChanged?.call(intValue);
|
||||||
|
_updateArrows(intValue);
|
||||||
|
},
|
||||||
|
inputFormatters: [_NumberTextInputFormatter(widget.min, widget.max)]);
|
||||||
|
|
||||||
|
void _update(bool up) {
|
||||||
|
var intValue = int.tryParse(_controller.text);
|
||||||
|
intValue == null ? intValue = widget.min : intValue += up ? widget.step : -widget.step;
|
||||||
|
intValue = intValue.clamp(widget.min, widget.max); // Ensure intValue is within range
|
||||||
|
_controller.text = intValue.toString();
|
||||||
|
|
||||||
|
// Manually call the onChanged callback after updating the controller's text
|
||||||
|
widget.onChanged?.call(intValue);
|
||||||
|
|
||||||
|
_updateArrows(intValue);
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateArrows(int? value) {
|
||||||
|
final canGoUp = value == null || value < widget.max;
|
||||||
|
final canGoDown = value == null || value > widget.min;
|
||||||
|
if (_canGoUp != canGoUp || _canGoDown != canGoDown)
|
||||||
|
setState(() {
|
||||||
|
_canGoUp = canGoUp;
|
||||||
|
_canGoDown = canGoDown;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NumberTextInputFormatter extends TextInputFormatter {
|
||||||
|
final int min;
|
||||||
|
final int max;
|
||||||
|
|
||||||
|
_NumberTextInputFormatter(this.min, this.max);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
|
||||||
|
if (const ['-', ''].contains(newValue.text)) return newValue;
|
||||||
|
final intValue = int.tryParse(newValue.text);
|
||||||
|
if (intValue == null) return oldValue;
|
||||||
|
if (intValue < min) return newValue.copyWith(text: min.toString());
|
||||||
|
if (intValue > max) return newValue.copyWith(text: max.toString());
|
||||||
|
return newValue.copyWith(text: intValue.toString());
|
||||||
|
}
|
||||||
|
}
|
|
@ -118,25 +118,27 @@ class ExceptionHandler {
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
(timeStamp) async {
|
(timeStamp) async {
|
||||||
await showPopUp<void>(
|
if (navigatorKey.currentContext != null) {
|
||||||
context: navigatorKey.currentContext!,
|
await showPopUp<void>(
|
||||||
builder: (context) {
|
context: navigatorKey.currentContext!,
|
||||||
return AlertWithTwoActions(
|
builder: (context) {
|
||||||
isDividerExist: true,
|
return AlertWithTwoActions(
|
||||||
alertTitle: S.of(context).error,
|
isDividerExist: true,
|
||||||
alertContent: S.of(context).error_dialog_content,
|
alertTitle: S.of(context).error,
|
||||||
rightButtonText: S.of(context).send,
|
alertContent: S.of(context).error_dialog_content,
|
||||||
leftButtonText: S.of(context).do_not_send,
|
rightButtonText: S.of(context).send,
|
||||||
actionRightButton: () {
|
leftButtonText: S.of(context).do_not_send,
|
||||||
Navigator.of(context).pop();
|
actionRightButton: () {
|
||||||
_sendExceptionFile();
|
Navigator.of(context).pop();
|
||||||
},
|
_sendExceptionFile();
|
||||||
actionLeftButton: () {
|
},
|
||||||
Navigator.of(context).pop();
|
actionLeftButton: () {
|
||||||
},
|
Navigator.of(context).pop();
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_hasError = false;
|
_hasError = false;
|
||||||
},
|
},
|
||||||
|
|
20
lib/view_model/cake_pay/cake_pay_account_view_model.dart
Normal file
20
lib/view_model/cake_pay/cake_pay_account_view_model.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_service.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'cake_pay_account_view_model.g.dart';
|
||||||
|
|
||||||
|
class CakePayAccountViewModel = CakePayAccountViewModelBase with _$CakePayAccountViewModel;
|
||||||
|
|
||||||
|
abstract class CakePayAccountViewModelBase with Store {
|
||||||
|
CakePayAccountViewModelBase({required this.cakePayService}) : email = '' {
|
||||||
|
cakePayService.getUserEmail().then((email) => this.email = email ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
final CakePayService cakePayService;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String email;
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> logout() async => cakePayService.logout(email);
|
||||||
|
}
|
51
lib/view_model/cake_pay/cake_pay_auth_view_model.dart
Normal file
51
lib/view_model/cake_pay/cake_pay_auth_view_model.dart
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_states.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_service.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'cake_pay_auth_view_model.g.dart';
|
||||||
|
|
||||||
|
class CakePayAuthViewModel = CakePayAuthViewModelBase with _$CakePayAuthViewModel;
|
||||||
|
|
||||||
|
abstract class CakePayAuthViewModelBase with Store {
|
||||||
|
CakePayAuthViewModelBase({required this.cakePayService})
|
||||||
|
: userVerificationState = CakePayUserVerificationStateInitial(),
|
||||||
|
otpState = CakePayOtpSendDisabled(),
|
||||||
|
email = '',
|
||||||
|
otp = '';
|
||||||
|
|
||||||
|
final CakePayService cakePayService;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
CakePayUserVerificationState userVerificationState;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
CakePayOtpState otpState;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String email;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String otp;
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> verifyEmail(String code) async {
|
||||||
|
try {
|
||||||
|
otpState = CakePayOtpValidating();
|
||||||
|
await cakePayService.verifyEmail(code);
|
||||||
|
otpState = CakePayOtpSuccess();
|
||||||
|
} catch (_) {
|
||||||
|
otpState = CakePayOtpFailure(error: 'Invalid OTP. Try again');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> logIn(String email) async {
|
||||||
|
try {
|
||||||
|
userVerificationState = CakePayUserVerificationStateLoading();
|
||||||
|
await cakePayService.logIn(email);
|
||||||
|
userVerificationState = CakePayUserVerificationStateSuccess();
|
||||||
|
} catch (e) {
|
||||||
|
userVerificationState = CakePayUserVerificationStateFailure(error: e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
lib/view_model/cake_pay/cake_pay_buy_card_view_model.dart
Normal file
48
lib/view_model/cake_pay/cake_pay_buy_card_view_model.dart
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_card.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'cake_pay_buy_card_view_model.g.dart';
|
||||||
|
|
||||||
|
class CakePayBuyCardViewModel = CakePayBuyCardViewModelBase with _$CakePayBuyCardViewModel;
|
||||||
|
|
||||||
|
abstract class CakePayBuyCardViewModelBase with Store {
|
||||||
|
CakePayBuyCardViewModelBase({required this.vendor})
|
||||||
|
: amount = vendor.card!.denominations.isNotEmpty
|
||||||
|
? double.parse(vendor.card!.denominations.first)
|
||||||
|
: 0,
|
||||||
|
quantity = 1,
|
||||||
|
min = double.parse(vendor.card!.minValue ?? '0'),
|
||||||
|
max = double.parse(vendor.card!.maxValue ?? '0'),
|
||||||
|
card = vendor.card!;
|
||||||
|
|
||||||
|
final CakePayVendor vendor;
|
||||||
|
final CakePayCard card;
|
||||||
|
|
||||||
|
final double min;
|
||||||
|
final double max;
|
||||||
|
|
||||||
|
bool get isDenominationSelected => card.denominations.isNotEmpty;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
double amount;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
int quantity;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get isEnablePurchase =>
|
||||||
|
(amount >= min && amount <= max) || (isDenominationSelected && quantity > 0);
|
||||||
|
|
||||||
|
@computed
|
||||||
|
double get totalAmount => amount * quantity;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void onQuantityChanged(int? input) => quantity = input ?? 1;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void onAmountChanged(String input) {
|
||||||
|
if (input.isEmpty) return;
|
||||||
|
amount = double.parse(input.replaceAll(',', '.'));
|
||||||
|
}
|
||||||
|
}
|
221
lib/view_model/cake_pay/cake_pay_cards_list_view_model.dart
Normal file
221
lib/view_model/cake_pay/cake_pay_cards_list_view_model.dart
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_service.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_states.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'cake_pay_cards_list_view_model.g.dart';
|
||||||
|
|
||||||
|
class CakePayCardsListViewModel = CakePayCardsListViewModelBase with _$CakePayCardsListViewModel;
|
||||||
|
|
||||||
|
abstract class CakePayCardsListViewModelBase with Store {
|
||||||
|
CakePayCardsListViewModelBase({
|
||||||
|
required this.cakePayService,
|
||||||
|
}) : cardState = CakePayCardsStateNoCards(),
|
||||||
|
cakePayVendors = [],
|
||||||
|
availableCountries = [],
|
||||||
|
page = 1,
|
||||||
|
selectedCountry = 'USA',
|
||||||
|
displayPrepaidCards = true,
|
||||||
|
displayGiftCards = true,
|
||||||
|
displayDenominationsCards = true,
|
||||||
|
displayCustomValueCards = true,
|
||||||
|
scrollOffsetFromTop = 0.0,
|
||||||
|
vendorsState = InitialCakePayVendorLoadingState(),
|
||||||
|
createCardState = CakePayCreateCardState(),
|
||||||
|
searchString = '',
|
||||||
|
CakePayVendorList = <CakePayVendor>[] {
|
||||||
|
initialization();
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialization() async {
|
||||||
|
await getCountries();
|
||||||
|
selectedCountry = availableCountries.first;
|
||||||
|
getVendors();
|
||||||
|
}
|
||||||
|
|
||||||
|
final CakePayService cakePayService;
|
||||||
|
|
||||||
|
List<CakePayVendor> CakePayVendorList;
|
||||||
|
|
||||||
|
Map<String, List<FilterItem>> get createFilterItems => {
|
||||||
|
S.current.filter_by: [
|
||||||
|
FilterItem(
|
||||||
|
value: () => displayPrepaidCards,
|
||||||
|
caption: S.current.prepaid_cards,
|
||||||
|
onChanged: togglePrepaidCards),
|
||||||
|
FilterItem(
|
||||||
|
value: () => displayGiftCards,
|
||||||
|
caption: S.current.gift_cards,
|
||||||
|
onChanged: toggleGiftCards),
|
||||||
|
],
|
||||||
|
S.current.value_type: [
|
||||||
|
FilterItem(
|
||||||
|
value: () => displayDenominationsCards,
|
||||||
|
caption: S.current.denominations,
|
||||||
|
onChanged: toggleDenominationsCards),
|
||||||
|
FilterItem(
|
||||||
|
value: () => displayCustomValueCards,
|
||||||
|
caption: S.current.custom_value,
|
||||||
|
onChanged: toggleCustomValueCards),
|
||||||
|
],
|
||||||
|
S.current.countries: [
|
||||||
|
DropdownFilterItem(
|
||||||
|
items: availableCountries,
|
||||||
|
caption: '',
|
||||||
|
selectedItem: selectedCountry,
|
||||||
|
onItemSelected: (String value) => setSelectedCountry(value),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
String searchString;
|
||||||
|
|
||||||
|
int page;
|
||||||
|
|
||||||
|
late String _initialSelectedCountry;
|
||||||
|
|
||||||
|
late bool _initialDisplayPrepaidCards;
|
||||||
|
|
||||||
|
late bool _initialDisplayGiftCards;
|
||||||
|
|
||||||
|
late bool _initialDisplayDenominationsCards;
|
||||||
|
|
||||||
|
late bool _initialDisplayCustomValueCards;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
double scrollOffsetFromTop;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
CakePayCreateCardState createCardState;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
CakePayCardsState cardState;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
CakePayVendorState vendorsState;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool hasMoreDataToFetch = true;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool isLoadingNextPage = false;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
List<CakePayVendor> cakePayVendors;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
List<String> availableCountries;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool displayPrepaidCards;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool displayGiftCards;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool displayDenominationsCards;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool displayCustomValueCards;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String selectedCountry;
|
||||||
|
|
||||||
|
bool get hasFiltersChanged =>
|
||||||
|
selectedCountry != _initialSelectedCountry ||
|
||||||
|
displayPrepaidCards != _initialDisplayPrepaidCards ||
|
||||||
|
displayGiftCards != _initialDisplayGiftCards ||
|
||||||
|
displayDenominationsCards != _initialDisplayDenominationsCards ||
|
||||||
|
displayCustomValueCards != _initialDisplayCustomValueCards;
|
||||||
|
|
||||||
|
Future<void> getCountries() async {
|
||||||
|
availableCountries = await cakePayService.getCountries();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> getVendors({
|
||||||
|
String? text,
|
||||||
|
int? currentPage,
|
||||||
|
}) async {
|
||||||
|
vendorsState = CakePayVendorLoadingState();
|
||||||
|
searchString = text ?? '';
|
||||||
|
var newVendors = await cakePayService.getVendors(
|
||||||
|
country: selectedCountry,
|
||||||
|
page: currentPage ?? page,
|
||||||
|
search: searchString,
|
||||||
|
giftCards: displayGiftCards,
|
||||||
|
prepaidCards: displayPrepaidCards,
|
||||||
|
custom: displayCustomValueCards,
|
||||||
|
onDemand: displayDenominationsCards);
|
||||||
|
|
||||||
|
cakePayVendors = CakePayVendorList = newVendors;
|
||||||
|
|
||||||
|
vendorsState = CakePayVendorLoadedState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> fetchNextPage() async {
|
||||||
|
if (vendorsState is CakePayVendorLoadingState || !hasMoreDataToFetch || isLoadingNextPage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
isLoadingNextPage = true;
|
||||||
|
page++;
|
||||||
|
try {
|
||||||
|
var newVendors = await cakePayService.getVendors(
|
||||||
|
country: selectedCountry,
|
||||||
|
page: page,
|
||||||
|
search: searchString,
|
||||||
|
giftCards: displayGiftCards,
|
||||||
|
prepaidCards: displayPrepaidCards,
|
||||||
|
custom: displayCustomValueCards,
|
||||||
|
onDemand: displayDenominationsCards);
|
||||||
|
|
||||||
|
cakePayVendors.addAll(newVendors);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.toString().contains('detail":"Invalid page."')) {
|
||||||
|
hasMoreDataToFetch = false;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isLoadingNextPage = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> isCakePayUserAuthenticated() async {
|
||||||
|
return await cakePayService.isLogged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetLoadingNextPageState() {
|
||||||
|
hasMoreDataToFetch = true;
|
||||||
|
page = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void storeInitialFilterStates() {
|
||||||
|
_initialSelectedCountry = selectedCountry;
|
||||||
|
_initialDisplayPrepaidCards = displayPrepaidCards;
|
||||||
|
_initialDisplayGiftCards = displayGiftCards;
|
||||||
|
_initialDisplayDenominationsCards = displayDenominationsCards;
|
||||||
|
_initialDisplayCustomValueCards = displayCustomValueCards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setSelectedCountry(String country) => selectedCountry = country;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void togglePrepaidCards() => displayPrepaidCards = !displayPrepaidCards;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void toggleGiftCards() => displayGiftCards = !displayGiftCards;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void toggleDenominationsCards() => displayDenominationsCards = !displayDenominationsCards;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void toggleCustomValueCards() => displayCustomValueCards = !displayCustomValueCards;
|
||||||
|
|
||||||
|
void setScrollOffsetFromTop(double scrollOffset) {
|
||||||
|
scrollOffsetFromTop = scrollOffset;
|
||||||
|
}
|
||||||
|
}
|
162
lib/view_model/cake_pay/cake_pay_purchase_view_model.dart
Normal file
162
lib/view_model/cake_pay/cake_pay_purchase_view_model.dart
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_card.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_order.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_payment_credantials.dart';
|
||||||
|
import 'package:cake_wallet/cake_pay/cake_pay_service.dart';
|
||||||
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
|
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'cake_pay_purchase_view_model.g.dart';
|
||||||
|
|
||||||
|
class CakePayPurchaseViewModel = CakePayPurchaseViewModelBase with _$CakePayPurchaseViewModel;
|
||||||
|
|
||||||
|
abstract class CakePayPurchaseViewModelBase with Store {
|
||||||
|
CakePayPurchaseViewModelBase({
|
||||||
|
required this.cakePayService,
|
||||||
|
required this.paymentCredential,
|
||||||
|
required this.card,
|
||||||
|
required this.sendViewModel,
|
||||||
|
}) : walletType = sendViewModel.walletType;
|
||||||
|
|
||||||
|
final WalletType walletType;
|
||||||
|
|
||||||
|
final PaymentCredential paymentCredential;
|
||||||
|
|
||||||
|
final CakePayCard card;
|
||||||
|
|
||||||
|
final SendViewModel sendViewModel;
|
||||||
|
|
||||||
|
final CakePayService cakePayService;
|
||||||
|
|
||||||
|
CakePayOrder? order;
|
||||||
|
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
DateTime? expirationTime;
|
||||||
|
|
||||||
|
Duration? remainingTime;
|
||||||
|
|
||||||
|
String? get userName => paymentCredential.userName;
|
||||||
|
|
||||||
|
double get amount => paymentCredential.amount;
|
||||||
|
|
||||||
|
int get quantity => paymentCredential.quantity;
|
||||||
|
|
||||||
|
double get totalAmount => paymentCredential.totalAmount;
|
||||||
|
|
||||||
|
String get fiatCurrency => paymentCredential.fiatCurrency;
|
||||||
|
|
||||||
|
CryptoPaymentData? get cryptoPaymentData {
|
||||||
|
if (order == null) return null;
|
||||||
|
|
||||||
|
if (WalletType.monero == walletType) {
|
||||||
|
return order!.paymentData.xmr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WalletType.bitcoin == walletType) {
|
||||||
|
final paymentUrls = order!.paymentData.btc.paymentUrls!.bip21;
|
||||||
|
|
||||||
|
final uri = Uri.parse(paymentUrls!);
|
||||||
|
|
||||||
|
final address = uri.path;
|
||||||
|
final price = uri.queryParameters['amount'];
|
||||||
|
|
||||||
|
return CryptoPaymentData(
|
||||||
|
address: address,
|
||||||
|
price: price ?? '0',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool isOrderExpired = false;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String formattedRemainingTime = '';
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> createOrder() async {
|
||||||
|
if (walletType != WalletType.bitcoin && walletType != WalletType.monero) {
|
||||||
|
sendViewModel.state = FailureState('Unsupported wallet type, please use Bitcoin or Monero.');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
order = await cakePayService.createOrder(
|
||||||
|
cardId: card.id,
|
||||||
|
price: paymentCredential.amount.toString(),
|
||||||
|
quantity: paymentCredential.quantity);
|
||||||
|
await confirmSending();
|
||||||
|
expirationTime = order!.paymentData.expirationTime;
|
||||||
|
updateRemainingTime();
|
||||||
|
_startExpirationTimer();
|
||||||
|
} catch (e) {
|
||||||
|
sendViewModel.state = FailureState(
|
||||||
|
sendViewModel.translateErrorMessage(e, walletType, sendViewModel.wallet.currency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> confirmSending() async {
|
||||||
|
final cryptoPaymentData = this.cryptoPaymentData;
|
||||||
|
try {
|
||||||
|
if (order == null || cryptoPaymentData == null) return;
|
||||||
|
|
||||||
|
sendViewModel.clearOutputs();
|
||||||
|
final output = sendViewModel.outputs.first;
|
||||||
|
output.address = cryptoPaymentData.address;
|
||||||
|
output.setCryptoAmount(cryptoPaymentData.price);
|
||||||
|
|
||||||
|
await sendViewModel.createTransaction();
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void updateRemainingTime() {
|
||||||
|
if (expirationTime == null) {
|
||||||
|
formattedRemainingTime = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingTime = expirationTime!.difference(DateTime.now());
|
||||||
|
|
||||||
|
isOrderExpired = remainingTime!.isNegative;
|
||||||
|
|
||||||
|
if (isOrderExpired) {
|
||||||
|
disposeExpirationTimer();
|
||||||
|
sendViewModel.state = FailureState('Order has expired.');
|
||||||
|
} else {
|
||||||
|
formattedRemainingTime = formatDuration(remainingTime!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startExpirationTimer() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = Timer.periodic(Duration(seconds: 1), (_) {
|
||||||
|
updateRemainingTime();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatDuration(Duration duration) {
|
||||||
|
final hours = duration.inHours;
|
||||||
|
final minutes = duration.inMinutes.remainder(60);
|
||||||
|
final seconds = duration.inSeconds.remainder(60);
|
||||||
|
return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
void disposeExpirationTimer() {
|
||||||
|
_timer?.cancel();
|
||||||
|
remainingTime = null;
|
||||||
|
formattedRemainingTime = '';
|
||||||
|
expirationTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
disposeExpirationTimer();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
import 'package:cake_wallet/cake_pay/cake_pay_service.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
part 'cake_features_view_model.g.dart';
|
part 'cake_features_view_model.g.dart';
|
||||||
|
@ -6,11 +6,11 @@ part 'cake_features_view_model.g.dart';
|
||||||
class CakeFeaturesViewModel = CakeFeaturesViewModelBase with _$CakeFeaturesViewModel;
|
class CakeFeaturesViewModel = CakeFeaturesViewModelBase with _$CakeFeaturesViewModel;
|
||||||
|
|
||||||
abstract class CakeFeaturesViewModelBase with Store {
|
abstract class CakeFeaturesViewModelBase with Store {
|
||||||
final IoniaService _ioniaService;
|
final CakePayService _cakePayService;
|
||||||
|
|
||||||
CakeFeaturesViewModelBase(this._ioniaService);
|
CakeFeaturesViewModelBase(this._cakePayService);
|
||||||
|
|
||||||
Future<bool> isIoniaUserAuthenticated() async {
|
Future<bool> isIoniaUserAuthenticated() async {
|
||||||
return await _ioniaService.isLogined();
|
return await _cakePayService.isLogged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
lib/view_model/dashboard/dropdown_filter_item.dart
Normal file
19
lib/view_model/dashboard/dropdown_filter_item.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
|
||||||
|
|
||||||
|
class DropdownFilterItem extends FilterItem {
|
||||||
|
DropdownFilterItem({
|
||||||
|
required this.items,
|
||||||
|
required this.caption,
|
||||||
|
required this.selectedItem,
|
||||||
|
required this.onItemSelected,
|
||||||
|
}) : super(
|
||||||
|
value: () => false,
|
||||||
|
caption: caption,
|
||||||
|
onChanged: (_) {},
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<String> items;
|
||||||
|
final String caption;
|
||||||
|
final String selectedItem;
|
||||||
|
final Function(String) onItemSelected;
|
||||||
|
}
|
68
lib/view_model/dashboard/dropdown_filter_item_widget.dart
Normal file
68
lib/view_model/dashboard/dropdown_filter_item_widget.dart
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DropdownFilterList extends StatefulWidget {
|
||||||
|
DropdownFilterList({
|
||||||
|
Key? key,
|
||||||
|
required this.items,
|
||||||
|
this.itemPrefix,
|
||||||
|
this.textStyle,
|
||||||
|
required this.caption,
|
||||||
|
required this.selectedItem,
|
||||||
|
required this.onItemSelected,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final List<String> items;
|
||||||
|
final String? itemPrefix;
|
||||||
|
final TextStyle? textStyle;
|
||||||
|
final String caption;
|
||||||
|
final String selectedItem;
|
||||||
|
final Function(String) onItemSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DropdownFilterListState createState() => _DropdownFilterListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DropdownFilterListState extends State<DropdownFilterList> {
|
||||||
|
String? selectedValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
selectedValue = widget.selectedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DropdownButtonHideUnderline(
|
||||||
|
child: Container(
|
||||||
|
child: DropdownButton<String>(
|
||||||
|
isExpanded: true,
|
||||||
|
icon: Container(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.arrow_drop_down, color: Theme.of(context).extension<PickerTheme>()!.searchIconColor),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dropdownColor: Theme.of(context).extension<PickerTheme>()!.searchBackgroundFillColor,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
items: widget.items
|
||||||
|
.map((item) => DropdownMenuItem<String>(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
value: item,
|
||||||
|
child: AutoSizeText('${widget.itemPrefix ?? ''} $item', style: widget.textStyle),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
value: selectedValue,
|
||||||
|
onChanged: (newValue) {
|
||||||
|
setState(() => selectedValue = newValue);
|
||||||
|
widget.onItemSelected(newValue!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,50 +0,0 @@
|
||||||
import 'package:cake_wallet/ionia/ionia_create_state.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({required this.ioniaService})
|
|
||||||
: email = '',
|
|
||||||
giftCards = [],
|
|
||||||
merchantState = InitialIoniaMerchantLoadingState() {
|
|
||||||
ioniaService.getUserEmail().then((email) => this.email = email);
|
|
||||||
updateUserGiftCards();
|
|
||||||
}
|
|
||||||
|
|
||||||
final IoniaService ioniaService;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
String email;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
List<IoniaGiftCard> giftCards;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
IoniaMerchantState merchantState;
|
|
||||||
|
|
||||||
@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 {
|
|
||||||
merchantState = IoniaLoadingMerchantState();
|
|
||||||
giftCards = await ioniaService.getCurrentUserGiftCardSummaries();
|
|
||||||
merchantState = IoniaLoadedMerchantState();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
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({required this.ioniaService}):
|
|
||||||
createUserState = IoniaInitialCreateState(),
|
|
||||||
signInState = IoniaInitialCreateState(),
|
|
||||||
otpState = IoniaOtpSendDisabled(),
|
|
||||||
email = '',
|
|
||||||
otp = '';
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
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({required 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
part 'ionia_custom_redeem_view_model.g.dart';
|
|
||||||
|
|
||||||
class IoniaCustomRedeemViewModel = IoniaCustomRedeemViewModelBase with _$IoniaCustomRedeemViewModel;
|
|
||||||
|
|
||||||
abstract class IoniaCustomRedeemViewModelBase with Store {
|
|
||||||
IoniaCustomRedeemViewModelBase({
|
|
||||||
required this.giftCard,
|
|
||||||
required this.ioniaService,
|
|
||||||
}) : amount = 0,
|
|
||||||
redeemState = InitialExecutionState();
|
|
||||||
|
|
||||||
final IoniaGiftCard giftCard;
|
|
||||||
|
|
||||||
final IoniaService ioniaService;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
ExecutionState redeemState;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
double amount;
|
|
||||||
|
|
||||||
@computed
|
|
||||||
double get remaining =>
|
|
||||||
amount <= giftCard.remainingAmount ? giftCard.remainingAmount - amount : 0;
|
|
||||||
|
|
||||||
@computed
|
|
||||||
String get formattedRemaining => remaining.toStringAsFixed(2);
|
|
||||||
|
|
||||||
@computed
|
|
||||||
bool get disableRedeem => amount > giftCard.remainingAmount;
|
|
||||||
|
|
||||||
@action
|
|
||||||
void updateAmount(String text) {
|
|
||||||
amount = double.tryParse(text.replaceAll(',', '.')) ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
Future<void> addCustomRedeem() async {
|
|
||||||
try {
|
|
||||||
redeemState = IsExecutingState();
|
|
||||||
await ioniaService.redeem(giftCardId: giftCard.id, amount: amount);
|
|
||||||
redeemState = ExecutedSuccessfullyState();
|
|
||||||
} catch (e) {
|
|
||||||
redeemState = FailureState(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
|
||||||
import 'package:cake_wallet/ionia/ionia_tip.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
|
|
||||||
part 'ionia_custom_tip_view_model.g.dart';
|
|
||||||
|
|
||||||
class IoniaCustomTipViewModel = IoniaCustomTipViewModelBase with _$IoniaCustomTipViewModel;
|
|
||||||
|
|
||||||
abstract class IoniaCustomTipViewModelBase with Store {
|
|
||||||
IoniaCustomTipViewModelBase({
|
|
||||||
required this.amount,
|
|
||||||
required this.tip,
|
|
||||||
required this.ioniaMerchant})
|
|
||||||
: customTip = tip,
|
|
||||||
percentage = 0;
|
|
||||||
|
|
||||||
final IoniaMerchant ioniaMerchant;
|
|
||||||
final double amount;
|
|
||||||
final IoniaTip tip;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
IoniaTip customTip;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
double percentage;
|
|
||||||
|
|
||||||
@action
|
|
||||||
void onTipChanged(String value){
|
|
||||||
|
|
||||||
final _amount = value.isEmpty ? 0 : double.parse(value.replaceAll(',', '.'));
|
|
||||||
percentage = _amount/amount * 100;
|
|
||||||
customTip = IoniaTip(percentage: percentage, originalAmount: amount);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
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';
|
|
||||||
import 'package:screen_brightness/screen_brightness.dart';
|
|
||||||
|
|
||||||
part 'ionia_gift_card_details_view_model.g.dart';
|
|
||||||
|
|
||||||
class IoniaGiftCardDetailsViewModel = IoniaGiftCardDetailsViewModelBase
|
|
||||||
with _$IoniaGiftCardDetailsViewModel;
|
|
||||||
|
|
||||||
abstract class IoniaGiftCardDetailsViewModelBase with Store {
|
|
||||||
IoniaGiftCardDetailsViewModelBase({required this.ioniaService, required this.giftCard})
|
|
||||||
: redeemState = InitialExecutionState(),
|
|
||||||
remainingAmount = giftCard.remainingAmount,
|
|
||||||
brightness = 0;
|
|
||||||
|
|
||||||
final IoniaService ioniaService;
|
|
||||||
|
|
||||||
double brightness;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
IoniaGiftCard giftCard;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
double remainingAmount;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
ExecutionState redeemState;
|
|
||||||
|
|
||||||
@action
|
|
||||||
Future<void> redeem() async {
|
|
||||||
giftCard.remainingAmount = remainingAmount;
|
|
||||||
try {
|
|
||||||
redeemState = IsExecutingState();
|
|
||||||
await ioniaService.redeem(giftCardId: giftCard.id, amount: giftCard.remainingAmount);
|
|
||||||
giftCard = await ioniaService.getGiftCard(id: giftCard.id);
|
|
||||||
redeemState = ExecutedSuccessfullyState();
|
|
||||||
} catch (e) {
|
|
||||||
redeemState = FailureState(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
Future<void> refeshCard() async {
|
|
||||||
giftCard = await ioniaService.getGiftCard(id: giftCard.id);
|
|
||||||
remainingAmount = giftCard.remainingAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
void increaseBrightness() async {
|
|
||||||
brightness = await ScreenBrightness().current;
|
|
||||||
await ScreenBrightness().setScreenBrightness(1.0);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,139 +0,0 @@
|
||||||
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: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 = [],
|
|
||||||
ioniaCategories = IoniaCategory.allCategories,
|
|
||||||
selectedIndices = ObservableList<IoniaCategory>.of([IoniaCategory.all]),
|
|
||||||
scrollOffsetFromTop = 0.0,
|
|
||||||
merchantState = InitialIoniaMerchantLoadingState(),
|
|
||||||
createCardState = IoniaCreateCardState(),
|
|
||||||
searchString = '',
|
|
||||||
ioniaMerchantList = <IoniaMerchant>[] {
|
|
||||||
}
|
|
||||||
|
|
||||||
final IoniaService ioniaService;
|
|
||||||
|
|
||||||
List<IoniaMerchant> ioniaMerchantList;
|
|
||||||
|
|
||||||
String searchString;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
double scrollOffsetFromTop;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
IoniaCreateCardState createCardState;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
IoniaFetchCardState cardState;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
IoniaMerchantState merchantState;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
List<IoniaMerchant> ioniaMerchants;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
List<IoniaCategory> ioniaCategories;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
ObservableList<IoniaCategory> selectedIndices;
|
|
||||||
|
|
||||||
@action
|
|
||||||
Future<void> createCard() async {
|
|
||||||
try {
|
|
||||||
createCardState = IoniaCreateCardLoading();
|
|
||||||
await ioniaService.createCard();
|
|
||||||
createCardState = IoniaCreateCardSuccess();
|
|
||||||
} catch (e) {
|
|
||||||
createCardState = IoniaCreateCardFailure(error: e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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() {
|
|
||||||
merchantState = IoniaLoadingMerchantState();
|
|
||||||
ioniaService.getMerchantsByFilter(categories: selectedIndices).then((value) {
|
|
||||||
value.sort((a, b) => a.legalName.toLowerCase().compareTo(b.legalName.toLowerCase()));
|
|
||||||
ioniaMerchants = ioniaMerchantList = value;
|
|
||||||
merchantState = IoniaLoadedMerchantState();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
void setSelectedFilter(IoniaCategory category) {
|
|
||||||
if (category == IoniaCategory.all) {
|
|
||||||
selectedIndices.clear();
|
|
||||||
selectedIndices.add(category);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (category != IoniaCategory.all) {
|
|
||||||
selectedIndices.remove(IoniaCategory.all);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedIndices.contains(category)) {
|
|
||||||
selectedIndices.remove(category);
|
|
||||||
|
|
||||||
if (selectedIndices.isEmpty) {
|
|
||||||
selectedIndices.add(IoniaCategory.all);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedIndices.add(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 resetIoniaCategories() {
|
|
||||||
ioniaCategories = IoniaCategory.allCategories;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setScrollOffsetFromTop(double scrollOffset) {
|
|
||||||
scrollOffsetFromTop = scrollOffset;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'package:cake_wallet/anypay/any_pay_chain.dart';
|
|
||||||
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})
|
|
||||||
: error = '' {
|
|
||||||
_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;
|
|
||||||
|
|
||||||
bool get payingByBitcoin => paymentInfo.anyPayPayment.chain == AnyPayChain.btc;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
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';
|
|
||||||
import 'package:cake_wallet/view_model/send/send_view_model.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,
|
|
||||||
required this.sendViewModel,
|
|
||||||
}) : tipAmount = 0.0,
|
|
||||||
percentage = 0.0,
|
|
||||||
invoiceCreationState = InitialExecutionState(),
|
|
||||||
invoiceCommittingState = InitialExecutionState(),
|
|
||||||
tips = <IoniaTip>[
|
|
||||||
IoniaTip(percentage: 0, originalAmount: amount),
|
|
||||||
IoniaTip(percentage: 15, originalAmount: amount),
|
|
||||||
IoniaTip(percentage: 18, originalAmount: amount),
|
|
||||||
IoniaTip(percentage: 20, originalAmount: amount),
|
|
||||||
IoniaTip(percentage: 0, originalAmount: amount, isCustom: true),
|
|
||||||
] {
|
|
||||||
selectedTip = tips.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
final double amount;
|
|
||||||
|
|
||||||
List<IoniaTip> tips;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
IoniaTip? selectedTip;
|
|
||||||
|
|
||||||
final IoniaMerchant ioniaMerchant;
|
|
||||||
|
|
||||||
final SendViewModel sendViewModel;
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if (invoice == null) {
|
|
||||||
throw Exception('Invoice is created. Invoince is null');
|
|
||||||
}
|
|
||||||
|
|
||||||
invoiceCommittingState = IsExecutingState();
|
|
||||||
committedInfo = await ioniaAnyPayService.commitInvoice(invoice!);
|
|
||||||
invoiceCommittingState = ExecutedSuccessfullyState(payload: committedInfo!);
|
|
||||||
} catch (e) {
|
|
||||||
invoiceCommittingState = FailureState(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -87,6 +87,7 @@
|
||||||
"buy": "اشتري",
|
"buy": "اشتري",
|
||||||
"buy_alert_content": ".ﺎﻬﻴﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ Monero ﻭﺃ Litecoin ﻭﺃ Ethereum ﻭﺃ Bitcoin ﺔﻈﻔﺤﻣ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .",
|
"buy_alert_content": ".ﺎﻬﻴﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ Monero ﻭﺃ Litecoin ﻭﺃ Ethereum ﻭﺃ Bitcoin ﺔﻈﻔﺤﻣ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .",
|
||||||
"buy_bitcoin": "شراء Bitcoin",
|
"buy_bitcoin": "شراء Bitcoin",
|
||||||
|
"buy_now": "اشتري الآن",
|
||||||
"buy_provider_unavailable": "مزود حاليا غير متوفر.",
|
"buy_provider_unavailable": "مزود حاليا غير متوفر.",
|
||||||
"buy_with": "اشتر بواسطة",
|
"buy_with": "اشتر بواسطة",
|
||||||
"by_cake_pay": "عن طريق Cake Pay",
|
"by_cake_pay": "عن طريق Cake Pay",
|
||||||
|
@ -94,8 +95,7 @@
|
||||||
"cake_dark_theme": "موضوع الكعكة الظلام",
|
"cake_dark_theme": "موضوع الكعكة الظلام",
|
||||||
"cake_pay_account_note": "قم بالتسجيل باستخدام عنوان بريد إلكتروني فقط لمشاهدة البطاقات وشرائها. حتى أن بعضها متوفر بسعر مخفض!",
|
"cake_pay_account_note": "قم بالتسجيل باستخدام عنوان بريد إلكتروني فقط لمشاهدة البطاقات وشرائها. حتى أن بعضها متوفر بسعر مخفض!",
|
||||||
"cake_pay_learn_more": "شراء واسترداد بطاقات الهدايا على الفور في التطبيق!\nاسحب من اليسار إلى اليمين لمعرفة المزيد.",
|
"cake_pay_learn_more": "شراء واسترداد بطاقات الهدايا على الفور في التطبيق!\nاسحب من اليسار إلى اليمين لمعرفة المزيد.",
|
||||||
"cake_pay_subtitle": "شراء بطاقات هدايا مخفضة السعر (الولايات المتحدة فقط)",
|
"cake_pay_subtitle": "شراء بطاقات مسبقة الدفع وبطاقات الهدايا في جميع أنحاء العالم",
|
||||||
"cake_pay_title": "بطاقات هدايا Cake Pay",
|
|
||||||
"cake_pay_web_cards_subtitle": "اشتري بطاقات مدفوعة مسبقا وبطاقات هدايا في جميع أنحاء العالم",
|
"cake_pay_web_cards_subtitle": "اشتري بطاقات مدفوعة مسبقا وبطاقات هدايا في جميع أنحاء العالم",
|
||||||
"cake_pay_web_cards_title": "بطاقات Cake Pay Web",
|
"cake_pay_web_cards_title": "بطاقات Cake Pay Web",
|
||||||
"cake_wallet": "Cake Wallet",
|
"cake_wallet": "Cake Wallet",
|
||||||
|
@ -123,6 +123,7 @@
|
||||||
"change_wallet_alert_title": "تغيير المحفظة الحالية",
|
"change_wallet_alert_title": "تغيير المحفظة الحالية",
|
||||||
"choose_account": "اختر حساب",
|
"choose_account": "اختر حساب",
|
||||||
"choose_address": "\n\nالرجاء اختيار عنوان:",
|
"choose_address": "\n\nالرجاء اختيار عنوان:",
|
||||||
|
"choose_card_value": "اختر قيمة بطاقة",
|
||||||
"choose_derivation": "اختر اشتقاق المحفظة",
|
"choose_derivation": "اختر اشتقاق المحفظة",
|
||||||
"choose_from_available_options": "اختر من بين الخيارات المتاحة:",
|
"choose_from_available_options": "اختر من بين الخيارات المتاحة:",
|
||||||
"choose_one": "اختر واحدة",
|
"choose_one": "اختر واحدة",
|
||||||
|
@ -166,6 +167,7 @@
|
||||||
"copy_address": "نسخ العنوان",
|
"copy_address": "نسخ العنوان",
|
||||||
"copy_id": "نسخ معرف العملية",
|
"copy_id": "نسخ معرف العملية",
|
||||||
"copyWalletConnectLink": "ﺎﻨﻫ ﻪﻘﺼﻟﺍﻭ dApp ﻦﻣ WalletConnect ﻂﺑﺍﺭ ﺦﺴﻧﺍ",
|
"copyWalletConnectLink": "ﺎﻨﻫ ﻪﻘﺼﻟﺍﻭ dApp ﻦﻣ WalletConnect ﻂﺑﺍﺭ ﺦﺴﻧﺍ",
|
||||||
|
"countries": "بلدان",
|
||||||
"create_account": "إنشاء حساب",
|
"create_account": "إنشاء حساب",
|
||||||
"create_backup": "انشئ نسخة احتياطية",
|
"create_backup": "انشئ نسخة احتياطية",
|
||||||
"create_donation_link": "إنشاء رابط التبرع",
|
"create_donation_link": "إنشاء رابط التبرع",
|
||||||
|
@ -178,6 +180,7 @@
|
||||||
"custom": "مخصصة",
|
"custom": "مخصصة",
|
||||||
"custom_drag": "مخصص (عقد وسحب)",
|
"custom_drag": "مخصص (عقد وسحب)",
|
||||||
"custom_redeem_amount": "مبلغ الاسترداد مخصص",
|
"custom_redeem_amount": "مبلغ الاسترداد مخصص",
|
||||||
|
"custom_value": "القيمة الجمركية",
|
||||||
"dark_theme": "داكن",
|
"dark_theme": "داكن",
|
||||||
"debit_card": "بطاقة ائتمان",
|
"debit_card": "بطاقة ائتمان",
|
||||||
"debit_card_terms": "يخضع تخزين واستخدام رقم بطاقة الدفع الخاصة بك (وبيانات الاعتماد المقابلة لرقم بطاقة الدفع الخاصة بك) في هذه المحفظة الرقمية لشروط وأحكام اتفاقية حامل البطاقة المعمول بها مع جهة إصدار بطاقة الدفع ، كما هو معمول به من وقت لآخر.",
|
"debit_card_terms": "يخضع تخزين واستخدام رقم بطاقة الدفع الخاصة بك (وبيانات الاعتماد المقابلة لرقم بطاقة الدفع الخاصة بك) في هذه المحفظة الرقمية لشروط وأحكام اتفاقية حامل البطاقة المعمول بها مع جهة إصدار بطاقة الدفع ، كما هو معمول به من وقت لآخر.",
|
||||||
|
@ -190,10 +193,11 @@
|
||||||
"delete_wallet": "حذف المحفظة",
|
"delete_wallet": "حذف المحفظة",
|
||||||
"delete_wallet_confirm_message": "هل أنت متأكد أنك تريد حذف محفظة ${wallet_name}؟",
|
"delete_wallet_confirm_message": "هل أنت متأكد أنك تريد حذف محفظة ${wallet_name}؟",
|
||||||
"deleteConnectionConfirmationPrompt": "ـﺑ ﻝﺎﺼﺗﻻﺍ ﻑﺬﺣ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ",
|
"deleteConnectionConfirmationPrompt": "ـﺑ ﻝﺎﺼﺗﻻﺍ ﻑﺬﺣ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ",
|
||||||
|
"denominations": "الطوائف",
|
||||||
"descending": "النزول",
|
"descending": "النزول",
|
||||||
"description": "ﻒﺻﻭ",
|
"description": "ﻒﺻﻭ",
|
||||||
"destination_tag": "علامة الوجهة:",
|
"destination_tag": "علامة الوجهة:",
|
||||||
"dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ",
|
"dfx_option_description": "شراء التشفير مع EUR & CHF. لعملاء البيع بالتجزئة والشركات في أوروبا",
|
||||||
"didnt_get_code": "لم تحصل على رمز؟",
|
"didnt_get_code": "لم تحصل على رمز؟",
|
||||||
"digit_pin": "-رقم PIN",
|
"digit_pin": "-رقم PIN",
|
||||||
"digital_and_physical_card": " بطاقة ائتمان رقمية ومادية مسبقة الدفع",
|
"digital_and_physical_card": " بطاقة ائتمان رقمية ومادية مسبقة الدفع",
|
||||||
|
@ -277,6 +281,7 @@
|
||||||
"expired": "منتهي الصلاحية",
|
"expired": "منتهي الصلاحية",
|
||||||
"expires": "تنتهي",
|
"expires": "تنتهي",
|
||||||
"expiresOn": "ﻲﻓ ﻪﺘﻴﺣﻼﺻ ﻲﻬﺘﻨﺗ",
|
"expiresOn": "ﻲﻓ ﻪﺘﻴﺣﻼﺻ ﻲﻬﺘﻨﺗ",
|
||||||
|
"expiry_and_validity": "انتهاء الصلاحية والصلاحية",
|
||||||
"export_backup": "تصدير نسخة احتياطية",
|
"export_backup": "تصدير نسخة احتياطية",
|
||||||
"extra_id": "معرف إضافي:",
|
"extra_id": "معرف إضافي:",
|
||||||
"extracted_address_content": "سوف ترسل الأموال إلى\n${recipient_name}",
|
"extracted_address_content": "سوف ترسل الأموال إلى\n${recipient_name}",
|
||||||
|
@ -384,6 +389,7 @@
|
||||||
"new_template": "قالب جديد",
|
"new_template": "قالب جديد",
|
||||||
"new_wallet": "إنشاء محفظة جديدة",
|
"new_wallet": "إنشاء محفظة جديدة",
|
||||||
"newConnection": "ﺪﻳﺪﺟ ﻝﺎﺼﺗﺍ",
|
"newConnection": "ﺪﻳﺪﺟ ﻝﺎﺼﺗﺍ",
|
||||||
|
"no_cards_found": "لم يتم العثور على بطاقات",
|
||||||
"no_id_needed": "لا حاجة لID!",
|
"no_id_needed": "لا حاجة لID!",
|
||||||
"no_id_required": "لا ID مطلوب. اشحن وانفق في أي مكان",
|
"no_id_required": "لا ID مطلوب. اشحن وانفق في أي مكان",
|
||||||
"no_relay_on_domain": ".ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ .ﺡﺎﺘﻣ ﺮﻴﻏ ﻞﻴﺣﺮﺘﻟﺍ ﻥﺃ ﻭﺃ ﻡﺪﺨﺘﺴﻤﻟﺍ ﻝﺎﺠﻤﻟ ﻞﻴﺣﺮﺗ ﺪ",
|
"no_relay_on_domain": ".ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ .ﺡﺎﺘﻣ ﺮﻴﻏ ﻞﻴﺣﺮﺘﻟﺍ ﻥﺃ ﻭﺃ ﻡﺪﺨﺘﺴﻤﻟﺍ ﻝﺎﺠﻤﻟ ﻞﻴﺣﺮﺗ ﺪ",
|
||||||
|
@ -452,6 +458,7 @@
|
||||||
"pre_seed_button_text": "انا أفهم. أرني سييد الخاص بي",
|
"pre_seed_button_text": "انا أفهم. أرني سييد الخاص بي",
|
||||||
"pre_seed_description": "في الصفحة التالية ستشاهد سلسلة من الكلمات ${words}. هذه هي سييد الفريدة والخاصة بك وهي الطريقة الوحيدة لاسترداد محفظتك في حالة فقدها أو عطلها. تقع على عاتقك مسؤولية تدوينها وتخزينها في مكان آمن خارج تطبيق Cake Wallet.",
|
"pre_seed_description": "في الصفحة التالية ستشاهد سلسلة من الكلمات ${words}. هذه هي سييد الفريدة والخاصة بك وهي الطريقة الوحيدة لاسترداد محفظتك في حالة فقدها أو عطلها. تقع على عاتقك مسؤولية تدوينها وتخزينها في مكان آمن خارج تطبيق Cake Wallet.",
|
||||||
"pre_seed_title": "مهم",
|
"pre_seed_title": "مهم",
|
||||||
|
"prepaid_cards": "البطاقات المدفوعة مسبقا",
|
||||||
"prevent_screenshots": "منع لقطات الشاشة وتسجيل الشاشة",
|
"prevent_screenshots": "منع لقطات الشاشة وتسجيل الشاشة",
|
||||||
"privacy": "خصوصية",
|
"privacy": "خصوصية",
|
||||||
"privacy_policy": "سياسة الخصوصية",
|
"privacy_policy": "سياسة الخصوصية",
|
||||||
|
@ -467,6 +474,7 @@
|
||||||
"purple_dark_theme": "موضوع الظلام الأرجواني",
|
"purple_dark_theme": "موضوع الظلام الأرجواني",
|
||||||
"qr_fullscreen": "انقر لفتح ال QR بملء الشاشة",
|
"qr_fullscreen": "انقر لفتح ال QR بملء الشاشة",
|
||||||
"qr_payment_amount": "يحتوي هذا ال QR على مبلغ الدفع. هل تريد تغير المبلغ فوق القيمة الحالية؟",
|
"qr_payment_amount": "يحتوي هذا ال QR على مبلغ الدفع. هل تريد تغير المبلغ فوق القيمة الحالية؟",
|
||||||
|
"quantity": "كمية",
|
||||||
"question_to_disable_2fa": "هل أنت متأكد أنك تريد تعطيل Cake 2FA؟ لن تكون هناك حاجة إلى رمز 2FA للوصول إلى المحفظة ووظائف معينة.",
|
"question_to_disable_2fa": "هل أنت متأكد أنك تريد تعطيل Cake 2FA؟ لن تكون هناك حاجة إلى رمز 2FA للوصول إلى المحفظة ووظائف معينة.",
|
||||||
"receivable_balance": "التوازن القادم",
|
"receivable_balance": "التوازن القادم",
|
||||||
"receive": "استلام",
|
"receive": "استلام",
|
||||||
|
@ -708,6 +716,7 @@
|
||||||
"tokenID": "ﻒﻳﺮﻌﺗ ﺔﻗﺎﻄﺑ",
|
"tokenID": "ﻒﻳﺮﻌﺗ ﺔﻗﺎﻄﺑ",
|
||||||
"tor_connection": "ﺭﻮﺗ ﻝﺎﺼﺗﺍ",
|
"tor_connection": "ﺭﻮﺗ ﻝﺎﺼﺗﺍ",
|
||||||
"tor_only": "Tor فقط",
|
"tor_only": "Tor فقط",
|
||||||
|
"total": "المجموع",
|
||||||
"total_saving": "إجمالي المدخرات",
|
"total_saving": "إجمالي المدخرات",
|
||||||
"totp_2fa_failure": "شفرة خاطئة. يرجى تجربة رمز مختلف أو إنشاء مفتاح سري جديد. استخدم تطبيق 2FA متوافقًا يدعم الرموز المكونة من 8 أرقام و SHA512.",
|
"totp_2fa_failure": "شفرة خاطئة. يرجى تجربة رمز مختلف أو إنشاء مفتاح سري جديد. استخدم تطبيق 2FA متوافقًا يدعم الرموز المكونة من 8 أرقام و SHA512.",
|
||||||
"totp_2fa_success": "نجاح! تم تمكين Cake 2FA لهذه المحفظة. تذكر حفظ بذرة ذاكري في حالة فقد الوصول إلى المحفظة.",
|
"totp_2fa_success": "نجاح! تم تمكين Cake 2FA لهذه المحفظة. تذكر حفظ بذرة ذاكري في حالة فقد الوصول إلى المحفظة.",
|
||||||
|
@ -798,6 +807,8 @@
|
||||||
"use_ssl": "استخدم SSL",
|
"use_ssl": "استخدم SSL",
|
||||||
"use_suggested": "استخدام المقترح",
|
"use_suggested": "استخدام المقترح",
|
||||||
"use_testnet": "استخدم testnet",
|
"use_testnet": "استخدم testnet",
|
||||||
|
"value": "قيمة",
|
||||||
|
"value_type": "نوع القيمة",
|
||||||
"variable_pair_not_supported": "هذا الزوج المتغير غير مدعوم في التبادلات المحددة",
|
"variable_pair_not_supported": "هذا الزوج المتغير غير مدعوم في التبادلات المحددة",
|
||||||
"verification": "تَحَقّق",
|
"verification": "تَحَقّق",
|
||||||
"verify_with_2fa": "تحقق مع Cake 2FA",
|
"verify_with_2fa": "تحقق مع Cake 2FA",
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
"buy": "Купуване",
|
"buy": "Купуване",
|
||||||
"buy_alert_content": "В момента поддържаме само закупуването на Bitcoin, Ethereum, Litecoin и Monero. Моля, създайте или превключете към своя портфейл Bitcoin, Ethereum, Litecoin или Monero.",
|
"buy_alert_content": "В момента поддържаме само закупуването на Bitcoin, Ethereum, Litecoin и Monero. Моля, създайте или превключете към своя портфейл Bitcoin, Ethereum, Litecoin или Monero.",
|
||||||
"buy_bitcoin": "Купуване на Bitcoin",
|
"buy_bitcoin": "Купуване на Bitcoin",
|
||||||
|
"buy_now": "Купи сега",
|
||||||
"buy_provider_unavailable": "Понастоящем доставчик не е наличен.",
|
"buy_provider_unavailable": "Понастоящем доставчик не е наличен.",
|
||||||
"buy_with": "Купуване чрез",
|
"buy_with": "Купуване чрез",
|
||||||
"by_cake_pay": "от Cake Pay",
|
"by_cake_pay": "от Cake Pay",
|
||||||
|
@ -94,8 +95,7 @@
|
||||||
"cake_dark_theme": "Торта тъмна тема",
|
"cake_dark_theme": "Торта тъмна тема",
|
||||||
"cake_pay_account_note": "Регистрайте се само с един имейл, за да виждате и купувате карти. За някои има дори и отстъпка!",
|
"cake_pay_account_note": "Регистрайте се само с един имейл, за да виждате и купувате карти. За някои има дори и отстъпка!",
|
||||||
"cake_pay_learn_more": "Купете и използвайте гифткарти директно в приложението!\nПлъзнете отляво надясно, за да научите още.",
|
"cake_pay_learn_more": "Купете и използвайте гифткарти директно в приложението!\nПлъзнете отляво надясно, за да научите още.",
|
||||||
"cake_pay_subtitle": "Купете гифткарти на намалени цени (само за САЩ)",
|
"cake_pay_subtitle": "Купете предплатени карти и карти за подаръци в световен мащаб",
|
||||||
"cake_pay_title": "Cake Pay Gift Карти",
|
|
||||||
"cake_pay_web_cards_subtitle": "Купете световно признати предплатени и гифт карти",
|
"cake_pay_web_cards_subtitle": "Купете световно признати предплатени и гифт карти",
|
||||||
"cake_pay_web_cards_title": "Cake Pay Онлайн Карти",
|
"cake_pay_web_cards_title": "Cake Pay Онлайн Карти",
|
||||||
"cake_wallet": "Cake Wallet",
|
"cake_wallet": "Cake Wallet",
|
||||||
|
@ -123,6 +123,7 @@
|
||||||
"change_wallet_alert_title": "Смяна на сегашния портфейл",
|
"change_wallet_alert_title": "Смяна на сегашния портфейл",
|
||||||
"choose_account": "Избиране на профил",
|
"choose_account": "Избиране на профил",
|
||||||
"choose_address": "\n\nМоля, изберете адреса:",
|
"choose_address": "\n\nМоля, изберете адреса:",
|
||||||
|
"choose_card_value": "Изберете стойност на картата",
|
||||||
"choose_derivation": "Изберете производно на портфейла",
|
"choose_derivation": "Изберете производно на портфейла",
|
||||||
"choose_from_available_options": "Изберете от следните опции:",
|
"choose_from_available_options": "Изберете от следните опции:",
|
||||||
"choose_one": "Изберете едно",
|
"choose_one": "Изберете едно",
|
||||||
|
@ -166,6 +167,7 @@
|
||||||
"copy_address": "Copy Address",
|
"copy_address": "Copy Address",
|
||||||
"copy_id": "Копиране на ID",
|
"copy_id": "Копиране на ID",
|
||||||
"copyWalletConnectLink": "Копирайте връзката WalletConnect от dApp и я поставете тук",
|
"copyWalletConnectLink": "Копирайте връзката WalletConnect от dApp и я поставете тук",
|
||||||
|
"countries": "Държави",
|
||||||
"create_account": "Създаване на профил",
|
"create_account": "Създаване на профил",
|
||||||
"create_backup": "Създаване на резервно копие",
|
"create_backup": "Създаване на резервно копие",
|
||||||
"create_donation_link": "Създайте връзка за дарение",
|
"create_donation_link": "Създайте връзка за дарение",
|
||||||
|
@ -178,6 +180,7 @@
|
||||||
"custom": "персонализирано",
|
"custom": "персонализирано",
|
||||||
"custom_drag": "Персонализиране (задръжте и плъзнете)",
|
"custom_drag": "Персонализиране (задръжте и плъзнете)",
|
||||||
"custom_redeem_amount": "Персонализирана сума за използване",
|
"custom_redeem_amount": "Персонализирана сума за използване",
|
||||||
|
"custom_value": "Персонализирана стойност",
|
||||||
"dark_theme": "Тъмно",
|
"dark_theme": "Тъмно",
|
||||||
"debit_card": "Дебитна карта",
|
"debit_card": "Дебитна карта",
|
||||||
"debit_card_terms": "Съхранението и използването на данните от вашата платежна карта в този дигитален портфейл подлежат на условията на съответното съгласие за картодържец от издателя на картата.",
|
"debit_card_terms": "Съхранението и използването на данните от вашата платежна карта в този дигитален портфейл подлежат на условията на съответното съгласие за картодържец от издателя на картата.",
|
||||||
|
@ -190,10 +193,11 @@
|
||||||
"delete_wallet": "Изтриване на портфейл",
|
"delete_wallet": "Изтриване на портфейл",
|
||||||
"delete_wallet_confirm_message": "Сигурни ли сте, че искате да изтриете протфейла ${wallet_name}?",
|
"delete_wallet_confirm_message": "Сигурни ли сте, че искате да изтриете протфейла ${wallet_name}?",
|
||||||
"deleteConnectionConfirmationPrompt": "Сигурни ли сте, че искате да изтриете връзката към",
|
"deleteConnectionConfirmationPrompt": "Сигурни ли сте, че искате да изтриете връзката към",
|
||||||
|
"denominations": "Деноминации",
|
||||||
"descending": "Низходящ",
|
"descending": "Низходящ",
|
||||||
"description": "Описание",
|
"description": "Описание",
|
||||||
"destination_tag": "Destination tag:",
|
"destination_tag": "Destination tag:",
|
||||||
"dfx_option_description": "Купете крипто с EUR и CHF. До 990 € без допълнителен KYC. За клиенти на дребно и корпоративни клиенти в Европа",
|
"dfx_option_description": "Купете криптовалута с Eur & CHF. За търговски и корпоративни клиенти в Европа",
|
||||||
"didnt_get_code": "Не получихте код?",
|
"didnt_get_code": "Не получихте код?",
|
||||||
"digit_pin": "-цифрен PIN",
|
"digit_pin": "-цифрен PIN",
|
||||||
"digital_and_physical_card": " дигитална или физическа предплатена дебитна карта",
|
"digital_and_physical_card": " дигитална или физическа предплатена дебитна карта",
|
||||||
|
@ -277,6 +281,7 @@
|
||||||
"expired": "Изтекло",
|
"expired": "Изтекло",
|
||||||
"expires": "Изтича",
|
"expires": "Изтича",
|
||||||
"expiresOn": "Изтича на",
|
"expiresOn": "Изтича на",
|
||||||
|
"expiry_and_validity": "Изтичане и валидност",
|
||||||
"export_backup": "Експортиране на резервно копие",
|
"export_backup": "Експортиране на резервно копие",
|
||||||
"extra_id": "Допълнително ID:",
|
"extra_id": "Допълнително ID:",
|
||||||
"extracted_address_content": "Ще изпратите средства на \n${recipient_name}",
|
"extracted_address_content": "Ще изпратите средства на \n${recipient_name}",
|
||||||
|
@ -308,7 +313,7 @@
|
||||||
"gift_card_is_generated": "Gift Card бе създадена",
|
"gift_card_is_generated": "Gift Card бе създадена",
|
||||||
"gift_card_number": "Номер на Gift Card",
|
"gift_card_number": "Номер на Gift Card",
|
||||||
"gift_card_redeemed_note": "Използваните гифткарти ще се покажат тук",
|
"gift_card_redeemed_note": "Използваните гифткарти ще се покажат тук",
|
||||||
"gift_cards": "Gift Карти",
|
"gift_cards": "Карти за подаръци",
|
||||||
"gift_cards_unavailable": "В момента гифткарти могат да бъдат закупени само с Monero, Bitcoin и Litecoin",
|
"gift_cards_unavailable": "В момента гифткарти могат да бъдат закупени само с Monero, Bitcoin и Litecoin",
|
||||||
"got_it": "Готово",
|
"got_it": "Готово",
|
||||||
"gross_balance": "Брутен баланс",
|
"gross_balance": "Брутен баланс",
|
||||||
|
@ -384,6 +389,7 @@
|
||||||
"new_template": "Нов шаблон",
|
"new_template": "Нов шаблон",
|
||||||
"new_wallet": "Нов портфейл",
|
"new_wallet": "Нов портфейл",
|
||||||
"newConnection": "Нова връзка",
|
"newConnection": "Нова връзка",
|
||||||
|
"no_cards_found": "Не са намерени карти",
|
||||||
"no_id_needed": "Без нужда от документ за самоличност!",
|
"no_id_needed": "Без нужда от документ за самоличност!",
|
||||||
"no_id_required": "Без нужда от документ за самоличност. Използвайте навсякъде",
|
"no_id_required": "Без нужда от документ за самоличност. Използвайте навсякъде",
|
||||||
"no_relay_on_domain": "Няма реле за домейна на потребителя или релето не е налично. Моля, изберете реле, което да използвате.",
|
"no_relay_on_domain": "Няма реле за домейна на потребителя или релето не е налично. Моля, изберете реле, което да използвате.",
|
||||||
|
@ -452,6 +458,7 @@
|
||||||
"pre_seed_button_text": "Разбирам. Покажи seed",
|
"pre_seed_button_text": "Разбирам. Покажи seed",
|
||||||
"pre_seed_description": "На следващата страница ще видите поредица от ${words} думи. Това е вашият таен личен seed и е единственият начин да възстановите портфейла си. Отговорността за съхранението му на сигурно място извън приложението на Cake Wallet е изцяло ВАША.",
|
"pre_seed_description": "На следващата страница ще видите поредица от ${words} думи. Това е вашият таен личен seed и е единственият начин да възстановите портфейла си. Отговорността за съхранението му на сигурно място извън приложението на Cake Wallet е изцяло ВАША.",
|
||||||
"pre_seed_title": "ВАЖНО",
|
"pre_seed_title": "ВАЖНО",
|
||||||
|
"prepaid_cards": "Предплатени карти",
|
||||||
"prevent_screenshots": "Предотвратете екранни снимки и запис на екрана",
|
"prevent_screenshots": "Предотвратете екранни снимки и запис на екрана",
|
||||||
"privacy": "Поверителност",
|
"privacy": "Поверителност",
|
||||||
"privacy_policy": "Политика за поверителността",
|
"privacy_policy": "Политика за поверителността",
|
||||||
|
@ -467,6 +474,7 @@
|
||||||
"purple_dark_theme": "Лилава тъмна тема",
|
"purple_dark_theme": "Лилава тъмна тема",
|
||||||
"qr_fullscreen": "Натиснете, за да отворите QR кода на цял екран",
|
"qr_fullscreen": "Натиснете, за да отворите QR кода на цял екран",
|
||||||
"qr_payment_amount": "Този QR код съдържа сума за плащане. Искате ли да промените стойността?",
|
"qr_payment_amount": "Този QR код съдържа сума за плащане. Искате ли да промените стойността?",
|
||||||
|
"quantity": "Количество",
|
||||||
"question_to_disable_2fa": "Сигурни ли сте, че искате да деактивирате Cake 2FA? Вече няма да е необходим 2FA код за достъп до портфейла и определени функции.",
|
"question_to_disable_2fa": "Сигурни ли сте, че искате да деактивирате Cake 2FA? Вече няма да е необходим 2FA код за достъп до портфейла и определени функции.",
|
||||||
"receivable_balance": "Баланс за вземания",
|
"receivable_balance": "Баланс за вземания",
|
||||||
"receive": "Получи",
|
"receive": "Получи",
|
||||||
|
@ -708,6 +716,7 @@
|
||||||
"tokenID": "документ за самоличност",
|
"tokenID": "документ за самоличност",
|
||||||
"tor_connection": "Tor връзка",
|
"tor_connection": "Tor връзка",
|
||||||
"tor_only": "Само чрез Tor",
|
"tor_only": "Само чрез Tor",
|
||||||
|
"total": "Обща сума",
|
||||||
"total_saving": "Общо спестявания",
|
"total_saving": "Общо спестявания",
|
||||||
"totp_2fa_failure": "Грешен код. Моля, опитайте с различен код или генерирайте нов таен ключ. Използвайте съвместимо 2FA приложение, което поддържа 8-цифрени кодове и SHA512.",
|
"totp_2fa_failure": "Грешен код. Моля, опитайте с различен код или генерирайте нов таен ключ. Използвайте съвместимо 2FA приложение, което поддържа 8-цифрени кодове и SHA512.",
|
||||||
"totp_2fa_success": "Успех! Cake 2FA е активиран за този портфейл. Не забравяйте да запазите мнемоничното начало, в случай че загубите достъп до портфейла.",
|
"totp_2fa_success": "Успех! Cake 2FA е активиран за този портфейл. Не забравяйте да запазите мнемоничното начало, в случай че загубите достъп до портфейла.",
|
||||||
|
@ -798,6 +807,8 @@
|
||||||
"use_ssl": "Използване на SSL",
|
"use_ssl": "Използване на SSL",
|
||||||
"use_suggested": "Използване на предложеното",
|
"use_suggested": "Използване на предложеното",
|
||||||
"use_testnet": "Използвайте TestNet",
|
"use_testnet": "Използвайте TestNet",
|
||||||
|
"value": "Стойност",
|
||||||
|
"value_type": "Тип стойност",
|
||||||
"variable_pair_not_supported": "Този variable pair не се поддържа от избраната борса",
|
"variable_pair_not_supported": "Този variable pair не се поддържа от избраната борса",
|
||||||
"verification": "Потвърждаване",
|
"verification": "Потвърждаване",
|
||||||
"verify_with_2fa": "Проверете с Cake 2FA",
|
"verify_with_2fa": "Проверете с Cake 2FA",
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
"buy": "Koupit",
|
"buy": "Koupit",
|
||||||
"buy_alert_content": "V současné době podporujeme pouze nákup bitcoinů, etherea, litecoinů a monero. Vytvořte nebo přepněte na svou peněženku bitcoinů, etherea, litecoinů nebo monero.",
|
"buy_alert_content": "V současné době podporujeme pouze nákup bitcoinů, etherea, litecoinů a monero. Vytvořte nebo přepněte na svou peněženku bitcoinů, etherea, litecoinů nebo monero.",
|
||||||
"buy_bitcoin": "Nakoupit Bitcoin",
|
"buy_bitcoin": "Nakoupit Bitcoin",
|
||||||
|
"buy_now": "Kup nyní",
|
||||||
"buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.",
|
"buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.",
|
||||||
"buy_with": "Nakoupit pomocí",
|
"buy_with": "Nakoupit pomocí",
|
||||||
"by_cake_pay": "od Cake Pay",
|
"by_cake_pay": "od Cake Pay",
|
||||||
|
@ -94,8 +95,7 @@
|
||||||
"cake_dark_theme": "Dort tmavé téma",
|
"cake_dark_theme": "Dort tmavé téma",
|
||||||
"cake_pay_account_note": "Přihlaste se svou e-mailovou adresou pro zobrazení a nákup karet. Některé jsou dostupné ve slevě!",
|
"cake_pay_account_note": "Přihlaste se svou e-mailovou adresou pro zobrazení a nákup karet. Některé jsou dostupné ve slevě!",
|
||||||
"cake_pay_learn_more": "Okamžitý nákup a uplatnění dárkových karet v aplikaci!\nPřejeďte prstem zleva doprava pro další informace.",
|
"cake_pay_learn_more": "Okamžitý nákup a uplatnění dárkových karet v aplikaci!\nPřejeďte prstem zleva doprava pro další informace.",
|
||||||
"cake_pay_subtitle": "Kupte si zlevněné dárkové karty (pouze USA)",
|
"cake_pay_subtitle": "Kupte si celosvětové předplacené karty a dárkové karty",
|
||||||
"cake_pay_title": "Cake Pay dárkové karty",
|
|
||||||
"cake_pay_web_cards_subtitle": "Kupte si celosvětové předplacené a dárkové karty",
|
"cake_pay_web_cards_subtitle": "Kupte si celosvětové předplacené a dárkové karty",
|
||||||
"cake_pay_web_cards_title": "Cake Pay webové karty",
|
"cake_pay_web_cards_title": "Cake Pay webové karty",
|
||||||
"cake_wallet": "Cake Wallet",
|
"cake_wallet": "Cake Wallet",
|
||||||
|
@ -123,6 +123,7 @@
|
||||||
"change_wallet_alert_title": "Přepnout peněženku",
|
"change_wallet_alert_title": "Přepnout peněženku",
|
||||||
"choose_account": "Zvolte částku",
|
"choose_account": "Zvolte částku",
|
||||||
"choose_address": "\n\nProsím vyberte adresu:",
|
"choose_address": "\n\nProsím vyberte adresu:",
|
||||||
|
"choose_card_value": "Vyberte hodnotu karty",
|
||||||
"choose_derivation": "Vyberte derivaci peněženky",
|
"choose_derivation": "Vyberte derivaci peněženky",
|
||||||
"choose_from_available_options": "Zvolte si z dostupných možností:",
|
"choose_from_available_options": "Zvolte si z dostupných možností:",
|
||||||
"choose_one": "Zvolte si",
|
"choose_one": "Zvolte si",
|
||||||
|
@ -166,6 +167,7 @@
|
||||||
"copy_address": "Zkopírovat adresu",
|
"copy_address": "Zkopírovat adresu",
|
||||||
"copy_id": "Kopírovat ID",
|
"copy_id": "Kopírovat ID",
|
||||||
"copyWalletConnectLink": "Zkopírujte odkaz WalletConnect z dApp a vložte jej sem",
|
"copyWalletConnectLink": "Zkopírujte odkaz WalletConnect z dApp a vložte jej sem",
|
||||||
|
"countries": "Země",
|
||||||
"create_account": "Vytvořit účet",
|
"create_account": "Vytvořit účet",
|
||||||
"create_backup": "Vytvořit zálohu",
|
"create_backup": "Vytvořit zálohu",
|
||||||
"create_donation_link": "Vytvořit odkaz na darování",
|
"create_donation_link": "Vytvořit odkaz na darování",
|
||||||
|
@ -178,6 +180,7 @@
|
||||||
"custom": "vlastní",
|
"custom": "vlastní",
|
||||||
"custom_drag": "Custom (Hold and Drag)",
|
"custom_drag": "Custom (Hold and Drag)",
|
||||||
"custom_redeem_amount": "Vlastní částka pro uplatnění",
|
"custom_redeem_amount": "Vlastní částka pro uplatnění",
|
||||||
|
"custom_value": "Vlastní hodnota",
|
||||||
"dark_theme": "Tmavý",
|
"dark_theme": "Tmavý",
|
||||||
"debit_card": "Debetní karta",
|
"debit_card": "Debetní karta",
|
||||||
"debit_card_terms": "Uložení a použití vašeho čísla platební karty (a přihlašovací údaje k vašemu číslu karty) v této digitální peněžence se řídí Obchodními podmínkami smlouvy příslušného držitele karty s vydavatelem karty (v jejich nejaktuálnější verzi).",
|
"debit_card_terms": "Uložení a použití vašeho čísla platební karty (a přihlašovací údaje k vašemu číslu karty) v této digitální peněžence se řídí Obchodními podmínkami smlouvy příslušného držitele karty s vydavatelem karty (v jejich nejaktuálnější verzi).",
|
||||||
|
@ -190,10 +193,11 @@
|
||||||
"delete_wallet": "Smazat peněženku",
|
"delete_wallet": "Smazat peněženku",
|
||||||
"delete_wallet_confirm_message": "Opravdu chcete smazat ${wallet_name} peněženku?",
|
"delete_wallet_confirm_message": "Opravdu chcete smazat ${wallet_name} peněženku?",
|
||||||
"deleteConnectionConfirmationPrompt": "Jste si jisti, že chcete smazat připojení k?",
|
"deleteConnectionConfirmationPrompt": "Jste si jisti, že chcete smazat připojení k?",
|
||||||
|
"denominations": "Označení",
|
||||||
"descending": "Klesající",
|
"descending": "Klesající",
|
||||||
"description": "Popis",
|
"description": "Popis",
|
||||||
"destination_tag": "Destination Tag:",
|
"destination_tag": "Destination Tag:",
|
||||||
"dfx_option_description": "Nakupujte kryptoměny za EUR a CHF. Až 990 € bez dalších KYC. Pro maloobchodní a firemní zákazníky v Evropě",
|
"dfx_option_description": "Koupit krypto s EUR & CHF. Pro maloobchodní a firemní zákazníky v Evropě",
|
||||||
"didnt_get_code": "Nepřišel Vám kód?",
|
"didnt_get_code": "Nepřišel Vám kód?",
|
||||||
"digit_pin": "-číselný PIN",
|
"digit_pin": "-číselný PIN",
|
||||||
"digital_and_physical_card": " digitální a fyzické předplacené debetní karty,",
|
"digital_and_physical_card": " digitální a fyzické předplacené debetní karty,",
|
||||||
|
@ -277,6 +281,7 @@
|
||||||
"expired": "Vypršelo",
|
"expired": "Vypršelo",
|
||||||
"expires": "Vyprší",
|
"expires": "Vyprší",
|
||||||
"expiresOn": "Vyprší dne",
|
"expiresOn": "Vyprší dne",
|
||||||
|
"expiry_and_validity": "Vypršení a platnost",
|
||||||
"export_backup": "Exportovat zálohu",
|
"export_backup": "Exportovat zálohu",
|
||||||
"extra_id": "Extra ID:",
|
"extra_id": "Extra ID:",
|
||||||
"extracted_address_content": "Prostředky budete posílat na\n${recipient_name}",
|
"extracted_address_content": "Prostředky budete posílat na\n${recipient_name}",
|
||||||
|
@ -384,6 +389,7 @@
|
||||||
"new_template": "Nová šablona",
|
"new_template": "Nová šablona",
|
||||||
"new_wallet": "Nová peněženka",
|
"new_wallet": "Nová peněženka",
|
||||||
"newConnection": "Nové připojení",
|
"newConnection": "Nové připojení",
|
||||||
|
"no_cards_found": "Žádné karty nenalezeny",
|
||||||
"no_id_needed": "Žádné ID není potřeba!",
|
"no_id_needed": "Žádné ID není potřeba!",
|
||||||
"no_id_required": "Žádní ID není potřeba. Dobijte si a utrácejte kdekoliv",
|
"no_id_required": "Žádní ID není potřeba. Dobijte si a utrácejte kdekoliv",
|
||||||
"no_relay_on_domain": "Pro doménu uživatele neexistuje přenos nebo je přenos nedostupný. Vyberte relé, které chcete použít.",
|
"no_relay_on_domain": "Pro doménu uživatele neexistuje přenos nebo je přenos nedostupný. Vyberte relé, které chcete použít.",
|
||||||
|
@ -452,6 +458,7 @@
|
||||||
"pre_seed_button_text": "Rozumím. Ukaž mi můj seed.",
|
"pre_seed_button_text": "Rozumím. Ukaž mi můj seed.",
|
||||||
"pre_seed_description": "Na následující stránce uvidíte sérii ${words} slov. Je to váš tzv. seed a je to JEDINÁ možnost, jak můžete později obnovit svou peněženku v případě ztráty nebo poruchy. Je VAŠÍ zodpovědností zapsat si ho a uložit si ho na bezpečném místě mimo aplikaci Cake Wallet.",
|
"pre_seed_description": "Na následující stránce uvidíte sérii ${words} slov. Je to váš tzv. seed a je to JEDINÁ možnost, jak můžete později obnovit svou peněženku v případě ztráty nebo poruchy. Je VAŠÍ zodpovědností zapsat si ho a uložit si ho na bezpečném místě mimo aplikaci Cake Wallet.",
|
||||||
"pre_seed_title": "DŮLEŽITÉ",
|
"pre_seed_title": "DŮLEŽITÉ",
|
||||||
|
"prepaid_cards": "Předplacené karty",
|
||||||
"prevent_screenshots": "Zabránit vytváření snímků obrazovky a nahrávání obrazovky",
|
"prevent_screenshots": "Zabránit vytváření snímků obrazovky a nahrávání obrazovky",
|
||||||
"privacy": "Soukromí",
|
"privacy": "Soukromí",
|
||||||
"privacy_policy": "Zásady ochrany soukromí",
|
"privacy_policy": "Zásady ochrany soukromí",
|
||||||
|
@ -467,6 +474,7 @@
|
||||||
"purple_dark_theme": "Fialové temné téma",
|
"purple_dark_theme": "Fialové temné téma",
|
||||||
"qr_fullscreen": "Poklepáním otevřete QR kód na celé obrazovce",
|
"qr_fullscreen": "Poklepáním otevřete QR kód na celé obrazovce",
|
||||||
"qr_payment_amount": "Tento QR kód obsahuje i částku. Chcete přepsat současnou hodnotu?",
|
"qr_payment_amount": "Tento QR kód obsahuje i částku. Chcete přepsat současnou hodnotu?",
|
||||||
|
"quantity": "Množství",
|
||||||
"question_to_disable_2fa": "Opravdu chcete deaktivovat Cake 2FA? Pro přístup k peněžence a některým funkcím již nebude potřeba kód 2FA.",
|
"question_to_disable_2fa": "Opravdu chcete deaktivovat Cake 2FA? Pro přístup k peněžence a některým funkcím již nebude potřeba kód 2FA.",
|
||||||
"receivable_balance": "Zůstatek pohledávek",
|
"receivable_balance": "Zůstatek pohledávek",
|
||||||
"receive": "Přijmout",
|
"receive": "Přijmout",
|
||||||
|
@ -708,6 +716,7 @@
|
||||||
"tokenID": "ID",
|
"tokenID": "ID",
|
||||||
"tor_connection": "Připojení Tor",
|
"tor_connection": "Připojení Tor",
|
||||||
"tor_only": "Pouze Tor",
|
"tor_only": "Pouze Tor",
|
||||||
|
"total": "Celkový",
|
||||||
"total_saving": "Celkem ušetřeno",
|
"total_saving": "Celkem ušetřeno",
|
||||||
"totp_2fa_failure": "Nesprávný kód. Zkuste prosím jiný kód nebo vygenerujte nový tajný klíč. Použijte kompatibilní aplikaci 2FA, která podporuje 8místné kódy a SHA512.",
|
"totp_2fa_failure": "Nesprávný kód. Zkuste prosím jiný kód nebo vygenerujte nový tajný klíč. Použijte kompatibilní aplikaci 2FA, která podporuje 8místné kódy a SHA512.",
|
||||||
"totp_2fa_success": "Úspěch! Pro tuto peněženku povolen Cake 2FA. Nezapomeňte si uložit mnemotechnický klíč pro případ, že ztratíte přístup k peněžence.",
|
"totp_2fa_success": "Úspěch! Pro tuto peněženku povolen Cake 2FA. Nezapomeňte si uložit mnemotechnický klíč pro případ, že ztratíte přístup k peněžence.",
|
||||||
|
@ -798,6 +807,8 @@
|
||||||
"use_ssl": "Použít SSL",
|
"use_ssl": "Použít SSL",
|
||||||
"use_suggested": "Použít doporučený",
|
"use_suggested": "Použít doporučený",
|
||||||
"use_testnet": "Použijte testNet",
|
"use_testnet": "Použijte testNet",
|
||||||
|
"value": "Hodnota",
|
||||||
|
"value_type": "Typ hodnoty",
|
||||||
"variable_pair_not_supported": "Tento pár s tržním kurzem není ve zvolené směnárně podporován",
|
"variable_pair_not_supported": "Tento pár s tržním kurzem není ve zvolené směnárně podporován",
|
||||||
"verification": "Ověření",
|
"verification": "Ověření",
|
||||||
"verify_with_2fa": "Ověřte pomocí Cake 2FA",
|
"verify_with_2fa": "Ověřte pomocí Cake 2FA",
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
"buy": "Kaufen",
|
"buy": "Kaufen",
|
||||||
"buy_alert_content": "Derzeit unterstützen wir nur den Kauf von Bitcoin, Ethereum, Litecoin und Monero. Bitte erstellen Sie Ihr Bitcoin-, Ethereum-, Litecoin- oder Monero-Wallet oder wechseln Sie zu diesem.",
|
"buy_alert_content": "Derzeit unterstützen wir nur den Kauf von Bitcoin, Ethereum, Litecoin und Monero. Bitte erstellen Sie Ihr Bitcoin-, Ethereum-, Litecoin- oder Monero-Wallet oder wechseln Sie zu diesem.",
|
||||||
"buy_bitcoin": "Bitcoin kaufen",
|
"buy_bitcoin": "Bitcoin kaufen",
|
||||||
|
"buy_now": "Kaufe jetzt",
|
||||||
"buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.",
|
"buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.",
|
||||||
"buy_with": "Kaufen mit",
|
"buy_with": "Kaufen mit",
|
||||||
"by_cake_pay": "von Cake Pay",
|
"by_cake_pay": "von Cake Pay",
|
||||||
|
@ -94,8 +95,7 @@
|
||||||
"cake_dark_theme": "Cake Dark Thema",
|
"cake_dark_theme": "Cake Dark Thema",
|
||||||
"cake_pay_account_note": "Melden Sie sich nur mit einer E-Mail-Adresse an, um Karten anzuzeigen und zu kaufen. Einige sind sogar mit Rabatt erhältlich!",
|
"cake_pay_account_note": "Melden Sie sich nur mit einer E-Mail-Adresse an, um Karten anzuzeigen und zu kaufen. Einige sind sogar mit Rabatt erhältlich!",
|
||||||
"cake_pay_learn_more": "Kaufen und lösen Sie Geschenkkarten sofort in der App ein!\nWischen Sie von links nach rechts, um mehr zu erfahren.",
|
"cake_pay_learn_more": "Kaufen und lösen Sie Geschenkkarten sofort in der App ein!\nWischen Sie von links nach rechts, um mehr zu erfahren.",
|
||||||
"cake_pay_subtitle": "Kaufen Sie ermäßigte Geschenkkarten (nur USA)",
|
"cake_pay_subtitle": "Kaufen Sie weltweite Prepaid -Karten und Geschenkkarten",
|
||||||
"cake_pay_title": "Cake Pay-Geschenkkarten",
|
|
||||||
"cake_pay_web_cards_subtitle": "Kaufen Sie weltweit Prepaid-Karten und Geschenkkarten",
|
"cake_pay_web_cards_subtitle": "Kaufen Sie weltweit Prepaid-Karten und Geschenkkarten",
|
||||||
"cake_pay_web_cards_title": "Cake Pay-Webkarten",
|
"cake_pay_web_cards_title": "Cake Pay-Webkarten",
|
||||||
"cake_wallet": "Cake Wallet",
|
"cake_wallet": "Cake Wallet",
|
||||||
|
@ -123,6 +123,7 @@
|
||||||
"change_wallet_alert_title": "Aktuelle Wallet ändern",
|
"change_wallet_alert_title": "Aktuelle Wallet ändern",
|
||||||
"choose_account": "Konto auswählen",
|
"choose_account": "Konto auswählen",
|
||||||
"choose_address": "\n\nBitte wählen Sie die Adresse:",
|
"choose_address": "\n\nBitte wählen Sie die Adresse:",
|
||||||
|
"choose_card_value": "Wählen Sie einen Kartenwert",
|
||||||
"choose_derivation": "Wählen Sie Wallet-Ableitung",
|
"choose_derivation": "Wählen Sie Wallet-Ableitung",
|
||||||
"choose_from_available_options": "Wähle aus verfügbaren Optionen:",
|
"choose_from_available_options": "Wähle aus verfügbaren Optionen:",
|
||||||
"choose_one": "Wähle ein",
|
"choose_one": "Wähle ein",
|
||||||
|
@ -166,6 +167,7 @@
|
||||||
"copy_address": "Adresse kopieren",
|
"copy_address": "Adresse kopieren",
|
||||||
"copy_id": "ID kopieren",
|
"copy_id": "ID kopieren",
|
||||||
"copyWalletConnectLink": "Kopieren Sie den WalletConnect-Link von dApp und fügen Sie ihn hier ein",
|
"copyWalletConnectLink": "Kopieren Sie den WalletConnect-Link von dApp und fügen Sie ihn hier ein",
|
||||||
|
"countries": "Länder",
|
||||||
"create_account": "Konto erstellen",
|
"create_account": "Konto erstellen",
|
||||||
"create_backup": "Backup erstellen",
|
"create_backup": "Backup erstellen",
|
||||||
"create_donation_link": "Spendenlink erstellen",
|
"create_donation_link": "Spendenlink erstellen",
|
||||||
|
@ -178,6 +180,7 @@
|
||||||
"custom": "benutzerdefiniert",
|
"custom": "benutzerdefiniert",
|
||||||
"custom_drag": "Custom (Hold and Drag)",
|
"custom_drag": "Custom (Hold and Drag)",
|
||||||
"custom_redeem_amount": "Benutzerdefinierter Einlösungsbetrag",
|
"custom_redeem_amount": "Benutzerdefinierter Einlösungsbetrag",
|
||||||
|
"custom_value": "Benutzerdefinierten Wert",
|
||||||
"dark_theme": "Dunkel",
|
"dark_theme": "Dunkel",
|
||||||
"debit_card": "Debitkarte",
|
"debit_card": "Debitkarte",
|
||||||
"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.",
|
"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.",
|
||||||
|
@ -190,10 +193,11 @@
|
||||||
"delete_wallet": "Wallet löschen",
|
"delete_wallet": "Wallet löschen",
|
||||||
"delete_wallet_confirm_message": "Sind Sie sicher, dass Sie das ${wallet_name} Wallet löschen möchten?",
|
"delete_wallet_confirm_message": "Sind Sie sicher, dass Sie das ${wallet_name} Wallet löschen möchten?",
|
||||||
"deleteConnectionConfirmationPrompt": "Sind Sie sicher, dass Sie die Verbindung zu löschen möchten?",
|
"deleteConnectionConfirmationPrompt": "Sind Sie sicher, dass Sie die Verbindung zu löschen möchten?",
|
||||||
|
"denominations": "Konfessionen",
|
||||||
"descending": "Absteigend",
|
"descending": "Absteigend",
|
||||||
"description": "Beschreibung",
|
"description": "Beschreibung",
|
||||||
"destination_tag": "Ziel-Tag:",
|
"destination_tag": "Ziel-Tag:",
|
||||||
"dfx_option_description": "Krypto mit EUR und CHF kaufen. Bis zu 990€ ohne zusätzliches KYC. Für Privat- und Firmenkunden in Europa",
|
"dfx_option_description": "Kaufen Sie Krypto mit EUR & CHF. Für Einzelhandel und Unternehmenskunden in Europa",
|
||||||
"didnt_get_code": "Kein Code?",
|
"didnt_get_code": "Kein Code?",
|
||||||
"digit_pin": "-stellige PIN",
|
"digit_pin": "-stellige PIN",
|
||||||
"digital_and_physical_card": "digitale und physische Prepaid-Debitkarte",
|
"digital_and_physical_card": "digitale und physische Prepaid-Debitkarte",
|
||||||
|
@ -277,6 +281,7 @@
|
||||||
"expired": "Abgelaufen",
|
"expired": "Abgelaufen",
|
||||||
"expires": "Läuft ab",
|
"expires": "Läuft ab",
|
||||||
"expiresOn": "Läuft aus am",
|
"expiresOn": "Läuft aus am",
|
||||||
|
"expiry_and_validity": "Ablauf und Gültigkeit",
|
||||||
"export_backup": "Sicherung exportieren",
|
"export_backup": "Sicherung exportieren",
|
||||||
"extra_id": "Extra ID:",
|
"extra_id": "Extra ID:",
|
||||||
"extracted_address_content": "Sie senden Geld an\n${recipient_name}",
|
"extracted_address_content": "Sie senden Geld an\n${recipient_name}",
|
||||||
|
@ -384,6 +389,7 @@
|
||||||
"new_template": "neue Vorlage",
|
"new_template": "neue Vorlage",
|
||||||
"new_wallet": "Neue Wallet",
|
"new_wallet": "Neue Wallet",
|
||||||
"newConnection": "Neue Verbindung",
|
"newConnection": "Neue Verbindung",
|
||||||
|
"no_cards_found": "Keine Karten gefunden",
|
||||||
"no_id_needed": "Keine ID erforderlich!",
|
"no_id_needed": "Keine ID erforderlich!",
|
||||||
"no_id_required": "Keine ID erforderlich. Upgraden und überall ausgeben",
|
"no_id_required": "Keine ID erforderlich. Upgraden und überall ausgeben",
|
||||||
"no_relay_on_domain": "Es gibt kein Relay für die Domäne des Benutzers oder das Relay ist nicht verfügbar. Bitte wählen Sie ein zu verwendendes Relais aus.",
|
"no_relay_on_domain": "Es gibt kein Relay für die Domäne des Benutzers oder das Relay ist nicht verfügbar. Bitte wählen Sie ein zu verwendendes Relais aus.",
|
||||||
|
@ -442,8 +448,8 @@
|
||||||
"placeholder_transactions": "Ihre Transaktionen werden hier angezeigt",
|
"placeholder_transactions": "Ihre Transaktionen werden hier angezeigt",
|
||||||
"please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät vorhanden ist",
|
"please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät vorhanden ist",
|
||||||
"please_make_selection": "Bitte treffen Sie unten eine Auswahl zum Erstellen oder Wiederherstellen Ihrer Wallet.",
|
"please_make_selection": "Bitte treffen Sie unten eine Auswahl zum Erstellen oder Wiederherstellen Ihrer Wallet.",
|
||||||
"please_reference_document": "Bitte verweisen Sie auf die folgenden Dokumente, um weitere Informationen zu erhalten.",
|
|
||||||
"Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.",
|
"Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.",
|
||||||
|
"please_reference_document": "Bitte verweisen Sie auf die folgenden Dokumente, um weitere Informationen zu erhalten.",
|
||||||
"please_select": "Bitte auswählen:",
|
"please_select": "Bitte auswählen:",
|
||||||
"please_select_backup_file": "Bitte wählen Sie die Sicherungsdatei und geben Sie das Sicherungskennwort ein.",
|
"please_select_backup_file": "Bitte wählen Sie die Sicherungsdatei und geben Sie das Sicherungskennwort ein.",
|
||||||
"please_try_to_connect_to_another_node": "Bitte versuchen Sie, sich mit einem anderen Knoten zu verbinden",
|
"please_try_to_connect_to_another_node": "Bitte versuchen Sie, sich mit einem anderen Knoten zu verbinden",
|
||||||
|
@ -453,6 +459,7 @@
|
||||||
"pre_seed_button_text": "Verstanden. Zeig mir meinen Seed",
|
"pre_seed_button_text": "Verstanden. Zeig mir meinen Seed",
|
||||||
"pre_seed_description": "Auf der nächsten Seite sehen Sie eine Reihe von ${words} Wörtern. Dies ist Ihr einzigartiger und privater Seed und der EINZIGE Weg, um Ihre Wallet im Falle eines Verlusts oder einer Fehlfunktion wiederherzustellen. Es liegt in IHRER Verantwortung, ihn aufzuschreiben und an einem sicheren Ort außerhalb der Cake Wallet-App aufzubewahren.",
|
"pre_seed_description": "Auf der nächsten Seite sehen Sie eine Reihe von ${words} Wörtern. Dies ist Ihr einzigartiger und privater Seed und der EINZIGE Weg, um Ihre Wallet im Falle eines Verlusts oder einer Fehlfunktion wiederherzustellen. Es liegt in IHRER Verantwortung, ihn aufzuschreiben und an einem sicheren Ort außerhalb der Cake Wallet-App aufzubewahren.",
|
||||||
"pre_seed_title": "WICHTIG",
|
"pre_seed_title": "WICHTIG",
|
||||||
|
"prepaid_cards": "Karten mit Guthaben",
|
||||||
"prevent_screenshots": "Verhindern Sie Screenshots und Bildschirmaufzeichnungen",
|
"prevent_screenshots": "Verhindern Sie Screenshots und Bildschirmaufzeichnungen",
|
||||||
"privacy": "Datenschutz",
|
"privacy": "Datenschutz",
|
||||||
"privacy_policy": "Datenschutzrichtlinie",
|
"privacy_policy": "Datenschutzrichtlinie",
|
||||||
|
@ -468,6 +475,7 @@
|
||||||
"purple_dark_theme": "Lila dunkle Thema",
|
"purple_dark_theme": "Lila dunkle Thema",
|
||||||
"qr_fullscreen": "Tippen Sie hier, um den QR-Code im Vollbildmodus zu öffnen",
|
"qr_fullscreen": "Tippen Sie hier, um den QR-Code im Vollbildmodus zu öffnen",
|
||||||
"qr_payment_amount": "This QR code contains a payment amount. Do you want to overwrite the current value?",
|
"qr_payment_amount": "This QR code contains a payment amount. Do you want to overwrite the current value?",
|
||||||
|
"quantity": "Menge",
|
||||||
"question_to_disable_2fa": "Sind Sie sicher, dass Sie Cake 2FA deaktivieren möchten? Für den Zugriff auf die Wallet und bestimmte Funktionen wird kein 2FA-Code mehr benötigt.",
|
"question_to_disable_2fa": "Sind Sie sicher, dass Sie Cake 2FA deaktivieren möchten? Für den Zugriff auf die Wallet und bestimmte Funktionen wird kein 2FA-Code mehr benötigt.",
|
||||||
"receivable_balance": "Forderungsbilanz",
|
"receivable_balance": "Forderungsbilanz",
|
||||||
"receive": "Empfangen",
|
"receive": "Empfangen",
|
||||||
|
@ -709,6 +717,7 @@
|
||||||
"tokenID": "AUSWEIS",
|
"tokenID": "AUSWEIS",
|
||||||
"tor_connection": "Tor-Verbindung",
|
"tor_connection": "Tor-Verbindung",
|
||||||
"tor_only": "Nur Tor",
|
"tor_only": "Nur Tor",
|
||||||
|
"total": "Gesamt",
|
||||||
"total_saving": "Gesamteinsparungen",
|
"total_saving": "Gesamteinsparungen",
|
||||||
"totp_2fa_failure": "Falscher Code. Bitte versuchen Sie es mit einem anderen Code oder generieren Sie einen neuen geheimen Schlüssel. Verwenden Sie eine kompatible 2FA-App, die 8-stellige Codes und SHA512 unterstützt.",
|
"totp_2fa_failure": "Falscher Code. Bitte versuchen Sie es mit einem anderen Code oder generieren Sie einen neuen geheimen Schlüssel. Verwenden Sie eine kompatible 2FA-App, die 8-stellige Codes und SHA512 unterstützt.",
|
||||||
"totp_2fa_success": "Erfolg! Cake 2FA für dieses Wallet aktiviert. Denken Sie daran, Ihren mnemonischen Seed zu speichern, falls Sie den Zugriff auf die Wallet verlieren.",
|
"totp_2fa_success": "Erfolg! Cake 2FA für dieses Wallet aktiviert. Denken Sie daran, Ihren mnemonischen Seed zu speichern, falls Sie den Zugriff auf die Wallet verlieren.",
|
||||||
|
@ -800,6 +809,8 @@
|
||||||
"use_ssl": "SSL verwenden",
|
"use_ssl": "SSL verwenden",
|
||||||
"use_suggested": "Vorgeschlagen verwenden",
|
"use_suggested": "Vorgeschlagen verwenden",
|
||||||
"use_testnet": "TESTNET verwenden",
|
"use_testnet": "TESTNET verwenden",
|
||||||
|
"value": "Wert",
|
||||||
|
"value_type": "Werttyp",
|
||||||
"variable_pair_not_supported": "Dieses Variablenpaar wird von den ausgewählten Börsen nicht unterstützt",
|
"variable_pair_not_supported": "Dieses Variablenpaar wird von den ausgewählten Börsen nicht unterstützt",
|
||||||
"verification": "Verifizierung",
|
"verification": "Verifizierung",
|
||||||
"verify_with_2fa": "Verifizieren Sie mit Cake 2FA",
|
"verify_with_2fa": "Verifizieren Sie mit Cake 2FA",
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
"buy": "Buy",
|
"buy": "Buy",
|
||||||
"buy_alert_content": "Currently we only support the purchase of Bitcoin, Ethereum, Litecoin, and Monero. Please create or switch to your Bitcoin, Ethereum, Litecoin, or Monero wallet.",
|
"buy_alert_content": "Currently we only support the purchase of Bitcoin, Ethereum, Litecoin, and Monero. Please create or switch to your Bitcoin, Ethereum, Litecoin, or Monero wallet.",
|
||||||
"buy_bitcoin": "Buy Bitcoin",
|
"buy_bitcoin": "Buy Bitcoin",
|
||||||
|
"buy_now": "Buy Now",
|
||||||
"buy_provider_unavailable": "Provider currently unavailable.",
|
"buy_provider_unavailable": "Provider currently unavailable.",
|
||||||
"buy_with": "Buy with",
|
"buy_with": "Buy with",
|
||||||
"by_cake_pay": "by Cake Pay",
|
"by_cake_pay": "by Cake Pay",
|
||||||
|
@ -94,8 +95,7 @@
|
||||||
"cake_dark_theme": "Cake Dark Theme",
|
"cake_dark_theme": "Cake Dark Theme",
|
||||||
"cake_pay_account_note": "Sign up with just an email address to see and purchase cards. Some are even available at a discount!",
|
"cake_pay_account_note": "Sign up with just an email address to see and purchase cards. Some are even available at a discount!",
|
||||||
"cake_pay_learn_more": "Instantly purchase and redeem gift cards in the app!\nSwipe left to right to learn more.",
|
"cake_pay_learn_more": "Instantly purchase and redeem gift cards in the app!\nSwipe left to right to learn more.",
|
||||||
"cake_pay_subtitle": "Buy discounted gift cards (USA only)",
|
"cake_pay_subtitle": "Buy worldwide prepaid cards and gift cards",
|
||||||
"cake_pay_title": "Cake Pay Gift Cards",
|
|
||||||
"cake_pay_web_cards_subtitle": "Buy worldwide prepaid cards and gift cards",
|
"cake_pay_web_cards_subtitle": "Buy worldwide prepaid cards and gift cards",
|
||||||
"cake_pay_web_cards_title": "Cake Pay Web Cards",
|
"cake_pay_web_cards_title": "Cake Pay Web Cards",
|
||||||
"cake_wallet": "Cake Wallet",
|
"cake_wallet": "Cake Wallet",
|
||||||
|
@ -123,6 +123,7 @@
|
||||||
"change_wallet_alert_title": "Change current wallet",
|
"change_wallet_alert_title": "Change current wallet",
|
||||||
"choose_account": "Choose account",
|
"choose_account": "Choose account",
|
||||||
"choose_address": "\n\nPlease choose the address:",
|
"choose_address": "\n\nPlease choose the address:",
|
||||||
|
"choose_card_value": "Choose a card value",
|
||||||
"choose_derivation": "Choose Wallet Derivation",
|
"choose_derivation": "Choose Wallet Derivation",
|
||||||
"choose_from_available_options": "Choose from the available options:",
|
"choose_from_available_options": "Choose from the available options:",
|
||||||
"choose_one": "Choose one",
|
"choose_one": "Choose one",
|
||||||
|
@ -166,6 +167,7 @@
|
||||||
"copy_address": "Copy Address",
|
"copy_address": "Copy Address",
|
||||||
"copy_id": "Copy ID",
|
"copy_id": "Copy ID",
|
||||||
"copyWalletConnectLink": "Copy the WalletConnect link from dApp and paste here",
|
"copyWalletConnectLink": "Copy the WalletConnect link from dApp and paste here",
|
||||||
|
"countries": "Countries",
|
||||||
"create_account": "Create Account",
|
"create_account": "Create Account",
|
||||||
"create_backup": "Create backup",
|
"create_backup": "Create backup",
|
||||||
"create_donation_link": "Create donation link",
|
"create_donation_link": "Create donation link",
|
||||||
|
@ -178,6 +180,7 @@
|
||||||
"custom": "Custom",
|
"custom": "Custom",
|
||||||
"custom_drag": "Custom (Hold and Drag)",
|
"custom_drag": "Custom (Hold and Drag)",
|
||||||
"custom_redeem_amount": "Custom Redeem Amount",
|
"custom_redeem_amount": "Custom Redeem Amount",
|
||||||
|
"custom_value": "Custom Value",
|
||||||
"dark_theme": "Dark",
|
"dark_theme": "Dark",
|
||||||
"debit_card": "Debit Card",
|
"debit_card": "Debit Card",
|
||||||
"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.",
|
"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.",
|
||||||
|
@ -190,10 +193,11 @@
|
||||||
"delete_wallet": "Delete wallet",
|
"delete_wallet": "Delete wallet",
|
||||||
"delete_wallet_confirm_message": "Are you sure that you want to delete ${wallet_name} wallet?",
|
"delete_wallet_confirm_message": "Are you sure that you want to delete ${wallet_name} wallet?",
|
||||||
"deleteConnectionConfirmationPrompt": "Are you sure that you want to delete the connection to",
|
"deleteConnectionConfirmationPrompt": "Are you sure that you want to delete the connection to",
|
||||||
|
"denominations": "Denominations",
|
||||||
"descending": "Descending",
|
"descending": "Descending",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"destination_tag": "Destination tag:",
|
"destination_tag": "Destination tag:",
|
||||||
"dfx_option_description": "Buy crypto with EUR & CHF. Up to 990€ without additional KYC. For retail and corporate customers in Europe",
|
"dfx_option_description": "Buy crypto with EUR & CHF. For retail and corporate customers in Europe",
|
||||||
"didnt_get_code": "Didn't get code?",
|
"didnt_get_code": "Didn't get code?",
|
||||||
"digit_pin": "-digit PIN",
|
"digit_pin": "-digit PIN",
|
||||||
"digital_and_physical_card": " digital and physical prepaid debit card",
|
"digital_and_physical_card": " digital and physical prepaid debit card",
|
||||||
|
@ -277,6 +281,7 @@
|
||||||
"expired": "Expired",
|
"expired": "Expired",
|
||||||
"expires": "Expires",
|
"expires": "Expires",
|
||||||
"expiresOn": "Expires on",
|
"expiresOn": "Expires on",
|
||||||
|
"expiry_and_validity": "Expiry and Validity",
|
||||||
"export_backup": "Export backup",
|
"export_backup": "Export backup",
|
||||||
"extra_id": "Extra ID:",
|
"extra_id": "Extra ID:",
|
||||||
"extracted_address_content": "You will be sending funds to\n${recipient_name}",
|
"extracted_address_content": "You will be sending funds to\n${recipient_name}",
|
||||||
|
@ -384,6 +389,7 @@
|
||||||
"new_template": "New Template",
|
"new_template": "New Template",
|
||||||
"new_wallet": "New Wallet",
|
"new_wallet": "New Wallet",
|
||||||
"newConnection": "New Connection",
|
"newConnection": "New Connection",
|
||||||
|
"no_cards_found": "No cards found",
|
||||||
"no_id_needed": "No ID needed!",
|
"no_id_needed": "No ID needed!",
|
||||||
"no_id_required": "No ID required. Top up and spend anywhere",
|
"no_id_required": "No ID required. Top up and spend anywhere",
|
||||||
"no_relay_on_domain": "There isn't a relay for user's domain or the relay is unavailable. Please choose a relay to use.",
|
"no_relay_on_domain": "There isn't a relay for user's domain or the relay is unavailable. Please choose a relay to use.",
|
||||||
|
@ -452,6 +458,7 @@
|
||||||
"pre_seed_button_text": "I understand. Show me my seed",
|
"pre_seed_button_text": "I understand. Show me my seed",
|
||||||
"pre_seed_description": "On the next page you will see a series of ${words} words. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cake Wallet app.",
|
"pre_seed_description": "On the next page you will see a series of ${words} words. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cake Wallet app.",
|
||||||
"pre_seed_title": "IMPORTANT",
|
"pre_seed_title": "IMPORTANT",
|
||||||
|
"prepaid_cards": "Prepaid Cards",
|
||||||
"prevent_screenshots": "Prevent screenshots and screen recording",
|
"prevent_screenshots": "Prevent screenshots and screen recording",
|
||||||
"privacy": "Privacy",
|
"privacy": "Privacy",
|
||||||
"privacy_policy": "Privacy Policy",
|
"privacy_policy": "Privacy Policy",
|
||||||
|
@ -467,6 +474,7 @@
|
||||||
"purple_dark_theme": "Purple Dark Theme",
|
"purple_dark_theme": "Purple Dark Theme",
|
||||||
"qr_fullscreen": "Tap to open full screen QR code",
|
"qr_fullscreen": "Tap to open full screen QR code",
|
||||||
"qr_payment_amount": "This QR code contains a payment amount. Do you want to overwrite the current value?",
|
"qr_payment_amount": "This QR code contains a payment amount. Do you want to overwrite the current value?",
|
||||||
|
"quantity": "Quantity",
|
||||||
"question_to_disable_2fa": "Are you sure that you want to disable Cake 2FA? A 2FA code will no longer be needed to access the wallet and certain functions.",
|
"question_to_disable_2fa": "Are you sure that you want to disable Cake 2FA? A 2FA code will no longer be needed to access the wallet and certain functions.",
|
||||||
"receivable_balance": "Receivable Balance",
|
"receivable_balance": "Receivable Balance",
|
||||||
"receive": "Receive",
|
"receive": "Receive",
|
||||||
|
@ -708,6 +716,7 @@
|
||||||
"tokenID": "ID",
|
"tokenID": "ID",
|
||||||
"tor_connection": "Tor connection",
|
"tor_connection": "Tor connection",
|
||||||
"tor_only": "Tor only",
|
"tor_only": "Tor only",
|
||||||
|
"total": "Total",
|
||||||
"total_saving": "Total Savings",
|
"total_saving": "Total Savings",
|
||||||
"totp_2fa_failure": "Incorrect code. Please try a different code or generate a new secret key. Use a compatible 2FA app that supports 8-digit codes and SHA512.",
|
"totp_2fa_failure": "Incorrect code. Please try a different code or generate a new secret key. Use a compatible 2FA app that supports 8-digit codes and SHA512.",
|
||||||
"totp_2fa_success": "Success! Cake 2FA enabled for this wallet. Remember to save your mnemonic seed in case you lose wallet access.",
|
"totp_2fa_success": "Success! Cake 2FA enabled for this wallet. Remember to save your mnemonic seed in case you lose wallet access.",
|
||||||
|
@ -798,6 +807,8 @@
|
||||||
"use_ssl": "Use SSL",
|
"use_ssl": "Use SSL",
|
||||||
"use_suggested": "Use Suggested",
|
"use_suggested": "Use Suggested",
|
||||||
"use_testnet": "Use Testnet",
|
"use_testnet": "Use Testnet",
|
||||||
|
"value": "Value",
|
||||||
|
"value_type": "Value Type",
|
||||||
"variable_pair_not_supported": "This variable pair is not supported with the selected exchanges",
|
"variable_pair_not_supported": "This variable pair is not supported with the selected exchanges",
|
||||||
"verification": "Verification",
|
"verification": "Verification",
|
||||||
"verify_with_2fa": "Verify with Cake 2FA",
|
"verify_with_2fa": "Verify with Cake 2FA",
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
"buy": "Comprar",
|
"buy": "Comprar",
|
||||||
"buy_alert_content": "Actualmente solo admitimos la compra de Bitcoin, Ethereum, Litecoin y Monero. Cree o cambie a su billetera Bitcoin, Ethereum, Litecoin o Monero.",
|
"buy_alert_content": "Actualmente solo admitimos la compra de Bitcoin, Ethereum, Litecoin y Monero. Cree o cambie a su billetera Bitcoin, Ethereum, Litecoin o Monero.",
|
||||||
"buy_bitcoin": "Comprar Bitcoin",
|
"buy_bitcoin": "Comprar Bitcoin",
|
||||||
|
"buy_now": "Comprar ahora",
|
||||||
"buy_provider_unavailable": "Proveedor actualmente no disponible.",
|
"buy_provider_unavailable": "Proveedor actualmente no disponible.",
|
||||||
"buy_with": "Compra con",
|
"buy_with": "Compra con",
|
||||||
"by_cake_pay": "por Cake Pay",
|
"by_cake_pay": "por Cake Pay",
|
||||||
|
@ -94,8 +95,7 @@
|
||||||
"cake_dark_theme": "Tema oscuro del pastel",
|
"cake_dark_theme": "Tema oscuro del pastel",
|
||||||
"cake_pay_account_note": "Regístrese con solo una dirección de correo electrónico para ver y comprar tarjetas. ¡Algunas incluso están disponibles con descuento!",
|
"cake_pay_account_note": "Regístrese con solo una dirección de correo electrónico para ver y comprar tarjetas. ¡Algunas incluso están disponibles con descuento!",
|
||||||
"cake_pay_learn_more": "¡Compre y canjee tarjetas de regalo al instante en la aplicación!\nDeslice el dedo de izquierda a derecha para obtener más información.",
|
"cake_pay_learn_more": "¡Compre y canjee tarjetas de regalo al instante en la aplicación!\nDeslice el dedo de izquierda a derecha para obtener más información.",
|
||||||
"cake_pay_subtitle": "Compre tarjetas de regalo con descuento (solo EE. UU.)",
|
"cake_pay_subtitle": "Compre tarjetas prepagas y tarjetas de regalo en todo el mundo",
|
||||||
"cake_pay_title": "Tarjetas de regalo Cake Pay",
|
|
||||||
"cake_pay_web_cards_subtitle": "Compre tarjetas de prepago y tarjetas de regalo en todo el mundo",
|
"cake_pay_web_cards_subtitle": "Compre tarjetas de prepago y tarjetas de regalo en todo el mundo",
|
||||||
"cake_pay_web_cards_title": "Tarjetas Web Cake Pay",
|
"cake_pay_web_cards_title": "Tarjetas Web Cake Pay",
|
||||||
"cake_wallet": "Cake Wallet",
|
"cake_wallet": "Cake Wallet",
|
||||||
|
@ -123,6 +123,7 @@
|
||||||
"change_wallet_alert_title": "Cambiar billetera actual",
|
"change_wallet_alert_title": "Cambiar billetera actual",
|
||||||
"choose_account": "Elegir cuenta",
|
"choose_account": "Elegir cuenta",
|
||||||
"choose_address": "\n\nPor favor elija la dirección:",
|
"choose_address": "\n\nPor favor elija la dirección:",
|
||||||
|
"choose_card_value": "Elija un valor de tarjeta",
|
||||||
"choose_derivation": "Elija la derivación de la billetera",
|
"choose_derivation": "Elija la derivación de la billetera",
|
||||||
"choose_from_available_options": "Elija entre las opciones disponibles:",
|
"choose_from_available_options": "Elija entre las opciones disponibles:",
|
||||||
"choose_one": "Elige uno",
|
"choose_one": "Elige uno",
|
||||||
|
@ -166,6 +167,7 @@
|
||||||
"copy_address": "Copiar dirección ",
|
"copy_address": "Copiar dirección ",
|
||||||
"copy_id": "Copiar ID",
|
"copy_id": "Copiar ID",
|
||||||
"copyWalletConnectLink": "Copie el enlace de WalletConnect de dApp y péguelo aquí",
|
"copyWalletConnectLink": "Copie el enlace de WalletConnect de dApp y péguelo aquí",
|
||||||
|
"countries": "Países",
|
||||||
"create_account": "Crear Cuenta",
|
"create_account": "Crear Cuenta",
|
||||||
"create_backup": "Crear copia de seguridad",
|
"create_backup": "Crear copia de seguridad",
|
||||||
"create_donation_link": "Crear enlace de donación",
|
"create_donation_link": "Crear enlace de donación",
|
||||||
|
@ -178,6 +180,7 @@
|
||||||
"custom": "Costumbre",
|
"custom": "Costumbre",
|
||||||
"custom_drag": "Custom (mantenía y arrastre)",
|
"custom_drag": "Custom (mantenía y arrastre)",
|
||||||
"custom_redeem_amount": "Cantidad de canje personalizada",
|
"custom_redeem_amount": "Cantidad de canje personalizada",
|
||||||
|
"custom_value": "Valor personalizado",
|
||||||
"dark_theme": "Oscura",
|
"dark_theme": "Oscura",
|
||||||
"debit_card": "Tarjeta de Débito",
|
"debit_card": "Tarjeta de Débito",
|
||||||
"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.",
|
"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.",
|
||||||
|
@ -190,10 +193,11 @@
|
||||||
"delete_wallet": "Eliminar billetera",
|
"delete_wallet": "Eliminar billetera",
|
||||||
"delete_wallet_confirm_message": "¿Está seguro de que desea eliminar la billetera ${wallet_name}?",
|
"delete_wallet_confirm_message": "¿Está seguro de que desea eliminar la billetera ${wallet_name}?",
|
||||||
"deleteConnectionConfirmationPrompt": "¿Está seguro de que desea eliminar la conexión a",
|
"deleteConnectionConfirmationPrompt": "¿Está seguro de que desea eliminar la conexión a",
|
||||||
|
"denominations": "Denominaciones",
|
||||||
"descending": "Descendente",
|
"descending": "Descendente",
|
||||||
"description": "Descripción",
|
"description": "Descripción",
|
||||||
"destination_tag": "Etiqueta de destino:",
|
"destination_tag": "Etiqueta de destino:",
|
||||||
"dfx_option_description": "Compre criptomonedas con EUR y CHF. Hasta 990€ sin KYC adicional. Para clientes minoristas y corporativos en Europa",
|
"dfx_option_description": "Compre criptografía con EUR y CHF. Para clientes minoristas y corporativos en Europa",
|
||||||
"didnt_get_code": "¿No recibiste el código?",
|
"didnt_get_code": "¿No recibiste el código?",
|
||||||
"digit_pin": "-dígito PIN",
|
"digit_pin": "-dígito PIN",
|
||||||
"digital_and_physical_card": " tarjeta de débito prepago digital y física",
|
"digital_and_physical_card": " tarjeta de débito prepago digital y física",
|
||||||
|
@ -277,6 +281,7 @@
|
||||||
"expired": "Muerto",
|
"expired": "Muerto",
|
||||||
"expires": "Caduca",
|
"expires": "Caduca",
|
||||||
"expiresOn": "Expira el",
|
"expiresOn": "Expira el",
|
||||||
|
"expiry_and_validity": "Vencimiento y validez",
|
||||||
"export_backup": "Exportar copia de seguridad",
|
"export_backup": "Exportar copia de seguridad",
|
||||||
"extra_id": "ID adicional:",
|
"extra_id": "ID adicional:",
|
||||||
"extracted_address_content": "Enviará fondos a\n${recipient_name}",
|
"extracted_address_content": "Enviará fondos a\n${recipient_name}",
|
||||||
|
@ -384,6 +389,7 @@
|
||||||
"new_template": "Nueva plantilla",
|
"new_template": "Nueva plantilla",
|
||||||
"new_wallet": "Nueva billetera",
|
"new_wallet": "Nueva billetera",
|
||||||
"newConnection": "Nueva conexión",
|
"newConnection": "Nueva conexión",
|
||||||
|
"no_cards_found": "No se encuentran cartas",
|
||||||
"no_id_needed": "¡No se necesita identificación!",
|
"no_id_needed": "¡No se necesita identificación!",
|
||||||
"no_id_required": "No se requiere identificación. Recargue y gaste en cualquier lugar",
|
"no_id_required": "No se requiere identificación. Recargue y gaste en cualquier lugar",
|
||||||
"no_relay_on_domain": "No hay una retransmisión para el dominio del usuario o la retransmisión no está disponible. Elija un relé para usar.",
|
"no_relay_on_domain": "No hay una retransmisión para el dominio del usuario o la retransmisión no está disponible. Elija un relé para usar.",
|
||||||
|
@ -453,6 +459,7 @@
|
||||||
"pre_seed_button_text": "Entiendo. Muéstrame mi semilla",
|
"pre_seed_button_text": "Entiendo. Muéstrame mi semilla",
|
||||||
"pre_seed_description": "En la página siguiente verá una serie de ${words} palabras. Esta es su semilla única y privada y es la ÚNICA forma de recuperar su billetera en caso de pérdida o mal funcionamiento. Es SU responsabilidad escribirlo y guardarlo en un lugar seguro fuera de la aplicación Cake Wallet.",
|
"pre_seed_description": "En la página siguiente verá una serie de ${words} palabras. Esta es su semilla única y privada y es la ÚNICA forma de recuperar su billetera en caso de pérdida o mal funcionamiento. Es SU responsabilidad escribirlo y guardarlo en un lugar seguro fuera de la aplicación Cake Wallet.",
|
||||||
"pre_seed_title": "IMPORTANTE",
|
"pre_seed_title": "IMPORTANTE",
|
||||||
|
"prepaid_cards": "Tajetas prepagadas",
|
||||||
"prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla",
|
"prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla",
|
||||||
"privacy": "Privacidad",
|
"privacy": "Privacidad",
|
||||||
"privacy_policy": "Política de privacidad",
|
"privacy_policy": "Política de privacidad",
|
||||||
|
@ -468,6 +475,7 @@
|
||||||
"purple_dark_theme": "Tema morado oscuro",
|
"purple_dark_theme": "Tema morado oscuro",
|
||||||
"qr_fullscreen": "Toque para abrir el código QR en pantalla completa",
|
"qr_fullscreen": "Toque para abrir el código QR en pantalla completa",
|
||||||
"qr_payment_amount": "This QR code contains a payment amount. Do you want to overwrite the current value?",
|
"qr_payment_amount": "This QR code contains a payment amount. Do you want to overwrite the current value?",
|
||||||
|
"quantity": "Cantidad",
|
||||||
"question_to_disable_2fa": "¿Está seguro de que desea deshabilitar Cake 2FA? Ya no se necesitará un código 2FA para acceder a la billetera y a ciertas funciones.",
|
"question_to_disable_2fa": "¿Está seguro de que desea deshabilitar Cake 2FA? Ya no se necesitará un código 2FA para acceder a la billetera y a ciertas funciones.",
|
||||||
"receivable_balance": "Saldo de cuentas por cobrar",
|
"receivable_balance": "Saldo de cuentas por cobrar",
|
||||||
"receive": "Recibir",
|
"receive": "Recibir",
|
||||||
|
@ -709,6 +717,7 @@
|
||||||
"tokenID": "IDENTIFICACIÓN",
|
"tokenID": "IDENTIFICACIÓN",
|
||||||
"tor_connection": "conexión tor",
|
"tor_connection": "conexión tor",
|
||||||
"tor_only": "solo Tor",
|
"tor_only": "solo Tor",
|
||||||
|
"total": "Total",
|
||||||
"total_saving": "Ahorro Total",
|
"total_saving": "Ahorro Total",
|
||||||
"totp_2fa_failure": "Código incorrecto. Intente con un código diferente o genere una nueva clave secreta. Use una aplicación 2FA compatible que admita códigos de 8 dígitos y SHA512.",
|
"totp_2fa_failure": "Código incorrecto. Intente con un código diferente o genere una nueva clave secreta. Use una aplicación 2FA compatible que admita códigos de 8 dígitos y SHA512.",
|
||||||
"totp_2fa_success": "¡Éxito! Cake 2FA habilitado para esta billetera. Recuerde guardar su semilla mnemotécnica en caso de que pierda el acceso a la billetera.",
|
"totp_2fa_success": "¡Éxito! Cake 2FA habilitado para esta billetera. Recuerde guardar su semilla mnemotécnica en caso de que pierda el acceso a la billetera.",
|
||||||
|
@ -799,6 +808,8 @@
|
||||||
"use_ssl": "Utilice SSL",
|
"use_ssl": "Utilice SSL",
|
||||||
"use_suggested": "Usar sugerido",
|
"use_suggested": "Usar sugerido",
|
||||||
"use_testnet": "Use TestNet",
|
"use_testnet": "Use TestNet",
|
||||||
|
"value": "Valor",
|
||||||
|
"value_type": "Tipo de valor",
|
||||||
"variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados",
|
"variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados",
|
||||||
"verification": "Verificación",
|
"verification": "Verificación",
|
||||||
"verify_with_2fa": "Verificar con Cake 2FA",
|
"verify_with_2fa": "Verificar con Cake 2FA",
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
"buy": "Acheter",
|
"buy": "Acheter",
|
||||||
"buy_alert_content": "Actuellement, nous ne prenons en charge que l'achat de Bitcoin, Ethereum, Litecoin et Monero. Veuillez créer ou basculer vers votre portefeuille Bitcoin, Ethereum, Litecoin ou Monero.",
|
"buy_alert_content": "Actuellement, nous ne prenons en charge que l'achat de Bitcoin, Ethereum, Litecoin et Monero. Veuillez créer ou basculer vers votre portefeuille Bitcoin, Ethereum, Litecoin ou Monero.",
|
||||||
"buy_bitcoin": "Acheter du Bitcoin",
|
"buy_bitcoin": "Acheter du Bitcoin",
|
||||||
|
"buy_now": "Acheter maintenant",
|
||||||
"buy_provider_unavailable": "Fournisseur actuellement indisponible.",
|
"buy_provider_unavailable": "Fournisseur actuellement indisponible.",
|
||||||
"buy_with": "Acheter avec",
|
"buy_with": "Acheter avec",
|
||||||
"by_cake_pay": "par Cake Pay",
|
"by_cake_pay": "par Cake Pay",
|
||||||
|
@ -94,8 +95,7 @@
|
||||||
"cake_dark_theme": "Thème sombre du gâteau",
|
"cake_dark_theme": "Thème sombre du gâteau",
|
||||||
"cake_pay_account_note": "Inscrivez-vous avec juste une adresse e-mail pour voir et acheter des cartes. Certaines sont même disponibles à prix réduit !",
|
"cake_pay_account_note": "Inscrivez-vous avec juste une adresse e-mail pour voir et acheter des cartes. Certaines sont même disponibles à prix réduit !",
|
||||||
"cake_pay_learn_more": "Achetez et utilisez instantanément des cartes-cadeaux dans l'application !\nBalayer de gauche à droite pour en savoir plus.",
|
"cake_pay_learn_more": "Achetez et utilisez instantanément des cartes-cadeaux dans l'application !\nBalayer de gauche à droite pour en savoir plus.",
|
||||||
"cake_pay_subtitle": "Achetez des cartes-cadeaux à prix réduit (États-Unis uniquement)",
|
"cake_pay_subtitle": "Achetez des cartes et des cartes-cadeaux prépayées mondiales",
|
||||||
"cake_pay_title": "Cartes cadeaux Cake Pay",
|
|
||||||
"cake_pay_web_cards_subtitle": "Achetez des cartes prépayées et des cartes-cadeaux dans le monde entier",
|
"cake_pay_web_cards_subtitle": "Achetez des cartes prépayées et des cartes-cadeaux dans le monde entier",
|
||||||
"cake_pay_web_cards_title": "Cartes Web Cake Pay",
|
"cake_pay_web_cards_title": "Cartes Web Cake Pay",
|
||||||
"cake_wallet": "Cake Wallet",
|
"cake_wallet": "Cake Wallet",
|
||||||
|
@ -123,6 +123,7 @@
|
||||||
"change_wallet_alert_title": "Changer le portefeuille (wallet) actuel",
|
"change_wallet_alert_title": "Changer le portefeuille (wallet) actuel",
|
||||||
"choose_account": "Choisir le compte",
|
"choose_account": "Choisir le compte",
|
||||||
"choose_address": "\n\nMerci de choisir l'adresse :",
|
"choose_address": "\n\nMerci de choisir l'adresse :",
|
||||||
|
"choose_card_value": "Choisissez une valeur de carte",
|
||||||
"choose_derivation": "Choisissez le chemin de dérivation du portefeuille",
|
"choose_derivation": "Choisissez le chemin de dérivation du portefeuille",
|
||||||
"choose_from_available_options": "Choisissez parmi les options disponibles :",
|
"choose_from_available_options": "Choisissez parmi les options disponibles :",
|
||||||
"choose_one": "Choisissez-en un",
|
"choose_one": "Choisissez-en un",
|
||||||
|
@ -166,6 +167,7 @@
|
||||||
"copy_address": "Copier l'Adresse",
|
"copy_address": "Copier l'Adresse",
|
||||||
"copy_id": "Copier l'ID",
|
"copy_id": "Copier l'ID",
|
||||||
"copyWalletConnectLink": "Copiez le lien WalletConnect depuis l'application décentralisée (dApp) et collez-le ici",
|
"copyWalletConnectLink": "Copiez le lien WalletConnect depuis l'application décentralisée (dApp) et collez-le ici",
|
||||||
|
"countries": "Des pays",
|
||||||
"create_account": "Créer un compte",
|
"create_account": "Créer un compte",
|
||||||
"create_backup": "Créer une sauvegarde",
|
"create_backup": "Créer une sauvegarde",
|
||||||
"create_donation_link": "Créer un lien de don",
|
"create_donation_link": "Créer un lien de don",
|
||||||
|
@ -178,6 +180,7 @@
|
||||||
"custom": "personnalisé",
|
"custom": "personnalisé",
|
||||||
"custom_drag": "Custom (maintenir et traîner)",
|
"custom_drag": "Custom (maintenir et traîner)",
|
||||||
"custom_redeem_amount": "Montant d'échange personnalisé",
|
"custom_redeem_amount": "Montant d'échange personnalisé",
|
||||||
|
"custom_value": "Valeur personnalisée",
|
||||||
"dark_theme": "Sombre",
|
"dark_theme": "Sombre",
|
||||||
"debit_card": "Carte de débit",
|
"debit_card": "Carte de débit",
|
||||||
"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 (wallet) numérique peuvent être soumis aux conditions générales de l'accord du titulaire de carte parfois en vigueur avec l'émetteur de la carte de paiement.",
|
"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 (wallet) numérique peuvent être soumis aux conditions générales de l'accord du titulaire de carte parfois en vigueur avec l'émetteur de la carte de paiement.",
|
||||||
|
@ -190,10 +193,11 @@
|
||||||
"delete_wallet": "Supprimer le portefeuille (wallet)",
|
"delete_wallet": "Supprimer le portefeuille (wallet)",
|
||||||
"delete_wallet_confirm_message": "Êtes-vous sûr de vouloir supprimer le portefeuille (wallet) ${wallet_name}?",
|
"delete_wallet_confirm_message": "Êtes-vous sûr de vouloir supprimer le portefeuille (wallet) ${wallet_name}?",
|
||||||
"deleteConnectionConfirmationPrompt": "Êtes-vous sûr de vouloir supprimer la connexion à",
|
"deleteConnectionConfirmationPrompt": "Êtes-vous sûr de vouloir supprimer la connexion à",
|
||||||
|
"denominations": "Dénominations",
|
||||||
"descending": "Descendant",
|
"descending": "Descendant",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"destination_tag": "Tag de destination :",
|
"destination_tag": "Tag de destination :",
|
||||||
"dfx_option_description": "Achetez des crypto-monnaies avec EUR et CHF. Jusqu'à 990€ sans KYC supplémentaire. Pour les clients particuliers et entreprises en Europe",
|
"dfx_option_description": "Achetez de la crypto avec EUR & CHF. Pour les clients de la vente au détail et des entreprises en Europe",
|
||||||
"didnt_get_code": "Vous n'avez pas reçu le code ?",
|
"didnt_get_code": "Vous n'avez pas reçu le code ?",
|
||||||
"digit_pin": " chiffres",
|
"digit_pin": " chiffres",
|
||||||
"digital_and_physical_card": "carte de débit prépayée numérique et physique",
|
"digital_and_physical_card": "carte de débit prépayée numérique et physique",
|
||||||
|
@ -277,6 +281,7 @@
|
||||||
"expired": "Expirée",
|
"expired": "Expirée",
|
||||||
"expires": "Expire",
|
"expires": "Expire",
|
||||||
"expiresOn": "Expire le",
|
"expiresOn": "Expire le",
|
||||||
|
"expiry_and_validity": "Expiration et validité",
|
||||||
"export_backup": "Exporter la sauvegarde",
|
"export_backup": "Exporter la sauvegarde",
|
||||||
"extra_id": "ID supplémentaire :",
|
"extra_id": "ID supplémentaire :",
|
||||||
"extracted_address_content": "Vous allez envoyer des fonds à\n${recipient_name}",
|
"extracted_address_content": "Vous allez envoyer des fonds à\n${recipient_name}",
|
||||||
|
@ -308,7 +313,7 @@
|
||||||
"gift_card_is_generated": "La carte-cadeau est générée",
|
"gift_card_is_generated": "La carte-cadeau est générée",
|
||||||
"gift_card_number": "Numéro de carte cadeau",
|
"gift_card_number": "Numéro de carte cadeau",
|
||||||
"gift_card_redeemed_note": "Les cartes-cadeaux que vous avez utilisées apparaîtront ici",
|
"gift_card_redeemed_note": "Les cartes-cadeaux que vous avez utilisées apparaîtront ici",
|
||||||
"gift_cards": "Cartes-Cadeaux",
|
"gift_cards": "Cartes cadeaux",
|
||||||
"gift_cards_unavailable": "Les cartes-cadeaux ne sont disponibles à l'achat que via Monero, Bitcoin et Litecoin pour le moment",
|
"gift_cards_unavailable": "Les cartes-cadeaux ne sont disponibles à l'achat que via Monero, Bitcoin et Litecoin pour le moment",
|
||||||
"got_it": "Compris",
|
"got_it": "Compris",
|
||||||
"gross_balance": "Solde brut",
|
"gross_balance": "Solde brut",
|
||||||
|
@ -384,6 +389,7 @@
|
||||||
"new_template": "Nouveau Modèle",
|
"new_template": "Nouveau Modèle",
|
||||||
"new_wallet": "Nouveau Portefeuille (Wallet)",
|
"new_wallet": "Nouveau Portefeuille (Wallet)",
|
||||||
"newConnection": "Nouvelle connexion",
|
"newConnection": "Nouvelle connexion",
|
||||||
|
"no_cards_found": "Pas de cartes trouvées",
|
||||||
"no_id_needed": "Aucune pièce d'identité nécessaire !",
|
"no_id_needed": "Aucune pièce d'identité nécessaire !",
|
||||||
"no_id_required": "Aucune pièce d'identité requise. Rechargez et dépensez n'importe où",
|
"no_id_required": "Aucune pièce d'identité requise. Rechargez et dépensez n'importe où",
|
||||||
"no_relay_on_domain": "Il n'existe pas de relais pour le domaine de l'utilisateur ou le relais n'est pas disponible. Veuillez choisir un relais à utiliser.",
|
"no_relay_on_domain": "Il n'existe pas de relais pour le domaine de l'utilisateur ou le relais n'est pas disponible. Veuillez choisir un relais à utiliser.",
|
||||||
|
@ -452,6 +458,7 @@
|
||||||
"pre_seed_button_text": "J'ai compris. Montrez moi ma phrase secrète (seed)",
|
"pre_seed_button_text": "J'ai compris. Montrez moi ma phrase secrète (seed)",
|
||||||
"pre_seed_description": "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre phrase secrète (seed) unique et privée et sont le SEUL moyen de restaurer votre portefeuille (wallet) en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de la stocker dans un lieu sûr en dehors de l'application Cake Wallet.",
|
"pre_seed_description": "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre phrase secrète (seed) unique et privée et sont le SEUL moyen de restaurer votre portefeuille (wallet) en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de la stocker dans un lieu sûr en dehors de l'application Cake Wallet.",
|
||||||
"pre_seed_title": "IMPORTANT",
|
"pre_seed_title": "IMPORTANT",
|
||||||
|
"prepaid_cards": "Cartes prépayées",
|
||||||
"prevent_screenshots": "Empêcher les captures d'écran et l'enregistrement d'écran",
|
"prevent_screenshots": "Empêcher les captures d'écran et l'enregistrement d'écran",
|
||||||
"privacy": "Confidentialité",
|
"privacy": "Confidentialité",
|
||||||
"privacy_policy": "Politique de confidentialité",
|
"privacy_policy": "Politique de confidentialité",
|
||||||
|
@ -467,6 +474,7 @@
|
||||||
"purple_dark_theme": "THÈME PURPLE DARK",
|
"purple_dark_theme": "THÈME PURPLE DARK",
|
||||||
"qr_fullscreen": "Appuyez pour ouvrir le QR code en mode plein écran",
|
"qr_fullscreen": "Appuyez pour ouvrir le QR code en mode plein écran",
|
||||||
"qr_payment_amount": "Ce QR code contient un montant de paiement. Voulez-vous remplacer la valeur actuelle ?",
|
"qr_payment_amount": "Ce QR code contient un montant de paiement. Voulez-vous remplacer la valeur actuelle ?",
|
||||||
|
"quantity": "Quantité",
|
||||||
"question_to_disable_2fa": "Êtes-vous sûr de vouloir désactiver Cake 2FA ? Un code 2FA ne sera plus nécessaire pour accéder au portefeuille (wallet) et à certaines fonctions.",
|
"question_to_disable_2fa": "Êtes-vous sûr de vouloir désactiver Cake 2FA ? Un code 2FA ne sera plus nécessaire pour accéder au portefeuille (wallet) et à certaines fonctions.",
|
||||||
"receivable_balance": "Solde de créances",
|
"receivable_balance": "Solde de créances",
|
||||||
"receive": "Recevoir",
|
"receive": "Recevoir",
|
||||||
|
@ -708,6 +716,7 @@
|
||||||
"tokenID": "IDENTIFIANT",
|
"tokenID": "IDENTIFIANT",
|
||||||
"tor_connection": "Connexion Tor",
|
"tor_connection": "Connexion Tor",
|
||||||
"tor_only": "Tor uniquement",
|
"tor_only": "Tor uniquement",
|
||||||
|
"total": "Total",
|
||||||
"total_saving": "Économies totales",
|
"total_saving": "Économies totales",
|
||||||
"totp_2fa_failure": "Code incorrect. Veuillez essayer un code différent ou générer un nouveau secret TOTP. Utilisez une application 2FA compatible qui prend en charge les codes à 8 chiffres et SHA512.",
|
"totp_2fa_failure": "Code incorrect. Veuillez essayer un code différent ou générer un nouveau secret TOTP. Utilisez une application 2FA compatible qui prend en charge les codes à 8 chiffres et SHA512.",
|
||||||
"totp_2fa_success": "Succès! Cake 2FA est activé pour ce portefeuille. N'oubliez pas de sauvegarder votre phrase secrète (seed) au cas où vous perdriez l'accès au portefeuille (wallet).",
|
"totp_2fa_success": "Succès! Cake 2FA est activé pour ce portefeuille. N'oubliez pas de sauvegarder votre phrase secrète (seed) au cas où vous perdriez l'accès au portefeuille (wallet).",
|
||||||
|
@ -798,6 +807,8 @@
|
||||||
"use_ssl": "Utiliser SSL",
|
"use_ssl": "Utiliser SSL",
|
||||||
"use_suggested": "Suivre la suggestion",
|
"use_suggested": "Suivre la suggestion",
|
||||||
"use_testnet": "Utiliser TestNet",
|
"use_testnet": "Utiliser TestNet",
|
||||||
|
"value": "Valeur",
|
||||||
|
"value_type": "Type de valeur",
|
||||||
"variable_pair_not_supported": "Cette paire variable n'est pas prise en charge avec les échanges sélectionnés",
|
"variable_pair_not_supported": "Cette paire variable n'est pas prise en charge avec les échanges sélectionnés",
|
||||||
"verification": "Vérification",
|
"verification": "Vérification",
|
||||||
"verify_with_2fa": "Vérifier avec Cake 2FA",
|
"verify_with_2fa": "Vérifier avec Cake 2FA",
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
"buy": "Sayi",
|
"buy": "Sayi",
|
||||||
"buy_alert_content": "A halin yanzu muna tallafawa kawai siyan Bitcoin, Ethereum, Litecoin, da Monero. Da fatan za a ƙirƙiri ko canza zuwa Bitcoin, Ethereum, Litecoin, ko Monero walat.",
|
"buy_alert_content": "A halin yanzu muna tallafawa kawai siyan Bitcoin, Ethereum, Litecoin, da Monero. Da fatan za a ƙirƙiri ko canza zuwa Bitcoin, Ethereum, Litecoin, ko Monero walat.",
|
||||||
"buy_bitcoin": "Sayi Bitcoin",
|
"buy_bitcoin": "Sayi Bitcoin",
|
||||||
|
"buy_now": "Saya yanzu",
|
||||||
"buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.",
|
"buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.",
|
||||||
"buy_with": "Saya da",
|
"buy_with": "Saya da",
|
||||||
"by_cake_pay": "da Cake Pay",
|
"by_cake_pay": "da Cake Pay",
|
||||||
|
@ -94,8 +95,7 @@
|
||||||
"cake_dark_theme": "Cake Dark Jigo",
|
"cake_dark_theme": "Cake Dark Jigo",
|
||||||
"cake_pay_account_note": "Yi rajista tare da adireshin imel kawai don gani da siyan katunan. Wasu ma suna samuwa a rangwame!",
|
"cake_pay_account_note": "Yi rajista tare da adireshin imel kawai don gani da siyan katunan. Wasu ma suna samuwa a rangwame!",
|
||||||
"cake_pay_learn_more": "Nan take siya ku kwaso katunan kyaututtuka a cikin app!\nTake hagu zuwa dama don ƙarin koyo.",
|
"cake_pay_learn_more": "Nan take siya ku kwaso katunan kyaututtuka a cikin app!\nTake hagu zuwa dama don ƙarin koyo.",
|
||||||
"cake_pay_subtitle": "Sayi katunan kyauta masu rahusa (Amurka kawai)",
|
"cake_pay_subtitle": "Sayi katunan shirye-shiryen duniya da katunan kyauta",
|
||||||
"cake_pay_title": "Cake Pay Gift Cards",
|
|
||||||
"cake_pay_web_cards_subtitle": "Sayi katunan da aka riga aka biya na duniya da katunan kyauta",
|
"cake_pay_web_cards_subtitle": "Sayi katunan da aka riga aka biya na duniya da katunan kyauta",
|
||||||
"cake_pay_web_cards_title": "Cake Pay Web Cards",
|
"cake_pay_web_cards_title": "Cake Pay Web Cards",
|
||||||
"cake_wallet": "Cake Wallet",
|
"cake_wallet": "Cake Wallet",
|
||||||
|
@ -123,6 +123,7 @@
|
||||||
"change_wallet_alert_title": "Canja walat yanzu",
|
"change_wallet_alert_title": "Canja walat yanzu",
|
||||||
"choose_account": "Zaɓi asusu",
|
"choose_account": "Zaɓi asusu",
|
||||||
"choose_address": "\n\n Da fatan za a zaɓi adireshin:",
|
"choose_address": "\n\n Da fatan za a zaɓi adireshin:",
|
||||||
|
"choose_card_value": "Zabi darajar katin",
|
||||||
"choose_derivation": "Zaɓi walatawa",
|
"choose_derivation": "Zaɓi walatawa",
|
||||||
"choose_from_available_options": "Zaɓi daga zaɓuɓɓukan da ake da su:",
|
"choose_from_available_options": "Zaɓi daga zaɓuɓɓukan da ake da su:",
|
||||||
"choose_one": "Zaɓi ɗaya",
|
"choose_one": "Zaɓi ɗaya",
|
||||||
|
@ -166,6 +167,7 @@
|
||||||
"copy_address": "Kwafi Adireshin",
|
"copy_address": "Kwafi Adireshin",
|
||||||
"copy_id": "Kwafi ID",
|
"copy_id": "Kwafi ID",
|
||||||
"copyWalletConnectLink": "Kwafi hanyar haɗin WalletConnect daga dApp kuma liƙa a nan",
|
"copyWalletConnectLink": "Kwafi hanyar haɗin WalletConnect daga dApp kuma liƙa a nan",
|
||||||
|
"countries": "Kasashe",
|
||||||
"create_account": "Kirkira ajiya",
|
"create_account": "Kirkira ajiya",
|
||||||
"create_backup": "Ƙirƙiri madadin",
|
"create_backup": "Ƙirƙiri madadin",
|
||||||
"create_donation_link": "Sanya hanyar sadaka",
|
"create_donation_link": "Sanya hanyar sadaka",
|
||||||
|
@ -178,6 +180,7 @@
|
||||||
"custom": "al'ada",
|
"custom": "al'ada",
|
||||||
"custom_drag": "Al'ada (riƙe da ja)",
|
"custom_drag": "Al'ada (riƙe da ja)",
|
||||||
"custom_redeem_amount": "Adadin Fansa na Musamman",
|
"custom_redeem_amount": "Adadin Fansa na Musamman",
|
||||||
|
"custom_value": "Darajar al'ada",
|
||||||
"dark_theme": "Duhu",
|
"dark_theme": "Duhu",
|
||||||
"debit_card": "Katin Zare kudi",
|
"debit_card": "Katin Zare kudi",
|
||||||
"debit_card_terms": "Adana da amfani da lambar katin kuɗin ku (da takaddun shaida masu dacewa da lambar katin kuɗin ku) a cikin wannan walat ɗin dijital suna ƙarƙashin Sharuɗɗa da Sharuɗɗa na yarjejeniya mai amfani da katin tare da mai fitar da katin biyan kuɗi, kamar yadda yake aiki daga lokaci zuwa lokaci.",
|
"debit_card_terms": "Adana da amfani da lambar katin kuɗin ku (da takaddun shaida masu dacewa da lambar katin kuɗin ku) a cikin wannan walat ɗin dijital suna ƙarƙashin Sharuɗɗa da Sharuɗɗa na yarjejeniya mai amfani da katin tare da mai fitar da katin biyan kuɗi, kamar yadda yake aiki daga lokaci zuwa lokaci.",
|
||||||
|
@ -190,10 +193,11 @@
|
||||||
"delete_wallet": "Share walat",
|
"delete_wallet": "Share walat",
|
||||||
"delete_wallet_confirm_message": "Shin kun tabbata cewa kuna son share jakar ${wallet_name}?",
|
"delete_wallet_confirm_message": "Shin kun tabbata cewa kuna son share jakar ${wallet_name}?",
|
||||||
"deleteConnectionConfirmationPrompt": "Shin kun tabbata cewa kuna son share haɗin zuwa",
|
"deleteConnectionConfirmationPrompt": "Shin kun tabbata cewa kuna son share haɗin zuwa",
|
||||||
|
"denominations": "Denominations",
|
||||||
"descending": "Saukowa",
|
"descending": "Saukowa",
|
||||||
"description": "Bayani",
|
"description": "Bayani",
|
||||||
"destination_tag": "Tambarin makoma:",
|
"destination_tag": "Tambarin makoma:",
|
||||||
"dfx_option_description": "Sayi crypto tare da EUR & CHF. Har zuwa € 990 ba tare da ƙarin KYC ba. Don 'yan kasuwa da abokan ciniki na kamfanoni a Turai",
|
"dfx_option_description": "Buy crypto tare da Eur & Chf. Don Retail da abokan ciniki na kamfanoni a Turai",
|
||||||
"didnt_get_code": "Ba a samun code?",
|
"didnt_get_code": "Ba a samun code?",
|
||||||
"digit_pin": "-lambar PIN",
|
"digit_pin": "-lambar PIN",
|
||||||
"digital_and_physical_card": "katin zare kudi na dijital da na zahiri",
|
"digital_and_physical_card": "katin zare kudi na dijital da na zahiri",
|
||||||
|
@ -277,6 +281,7 @@
|
||||||
"expired": "Karewa",
|
"expired": "Karewa",
|
||||||
"expires": "Ya ƙare",
|
"expires": "Ya ƙare",
|
||||||
"expiresOn": "Yana ƙarewa",
|
"expiresOn": "Yana ƙarewa",
|
||||||
|
"expiry_and_validity": "Karewa da inganci",
|
||||||
"export_backup": "Ajiyayyen fitarwa",
|
"export_backup": "Ajiyayyen fitarwa",
|
||||||
"extra_id": "Karin ID:",
|
"extra_id": "Karin ID:",
|
||||||
"extracted_address_content": "Za ku aika da kudade zuwa\n${recipient_name}",
|
"extracted_address_content": "Za ku aika da kudade zuwa\n${recipient_name}",
|
||||||
|
@ -384,6 +389,7 @@
|
||||||
"new_template": "Sabon Samfura",
|
"new_template": "Sabon Samfura",
|
||||||
"new_wallet": "Sabuwar Wallet",
|
"new_wallet": "Sabuwar Wallet",
|
||||||
"newConnection": "Sabuwar Haɗi",
|
"newConnection": "Sabuwar Haɗi",
|
||||||
|
"no_cards_found": "Babu katunan da aka samo",
|
||||||
"no_id_needed": "Babu ID da ake buƙata!",
|
"no_id_needed": "Babu ID da ake buƙata!",
|
||||||
"no_id_required": "Babu ID da ake buƙata. Yi da kuma ciyar a ko'ina",
|
"no_id_required": "Babu ID da ake buƙata. Yi da kuma ciyar a ko'ina",
|
||||||
"no_relay_on_domain": "Babu gudun ba da sanda ga yankin mai amfani ko kuma ba a samu ba. Da fatan za a zaɓi gudun ba da sanda don amfani.",
|
"no_relay_on_domain": "Babu gudun ba da sanda ga yankin mai amfani ko kuma ba a samu ba. Da fatan za a zaɓi gudun ba da sanda don amfani.",
|
||||||
|
@ -454,6 +460,7 @@
|
||||||
"pre_seed_button_text": "Ina fahimta. Nuna mini seed din nawa",
|
"pre_seed_button_text": "Ina fahimta. Nuna mini seed din nawa",
|
||||||
"pre_seed_description": "A kan shafin nan za ku ga wata ƙungiya na ${words} kalmomi. Wannan shine tsarin daban-daban ku kuma na sirri kuma shine hanya ɗaya kadai don mai da purse dinku a cikin yanayin rasa ko rashin aiki. Yana da damar da kuke a cikin tabbatar da kuyi rubuta shi kuma kuyi ajiye shi a wuri na aminci wanda ya wuce wurin app na Cake Wallet.",
|
"pre_seed_description": "A kan shafin nan za ku ga wata ƙungiya na ${words} kalmomi. Wannan shine tsarin daban-daban ku kuma na sirri kuma shine hanya ɗaya kadai don mai da purse dinku a cikin yanayin rasa ko rashin aiki. Yana da damar da kuke a cikin tabbatar da kuyi rubuta shi kuma kuyi ajiye shi a wuri na aminci wanda ya wuce wurin app na Cake Wallet.",
|
||||||
"pre_seed_title": "MUHIMMANCI",
|
"pre_seed_title": "MUHIMMANCI",
|
||||||
|
"prepaid_cards": "Katunan shirye-shirye",
|
||||||
"prevent_screenshots": "Fada lambobi da jarrabobi na kayan lambobi",
|
"prevent_screenshots": "Fada lambobi da jarrabobi na kayan lambobi",
|
||||||
"privacy": "Keɓantawa",
|
"privacy": "Keɓantawa",
|
||||||
"privacy_policy": "takardar kebantawa",
|
"privacy_policy": "takardar kebantawa",
|
||||||
|
@ -469,6 +476,7 @@
|
||||||
"purple_dark_theme": "M duhu jigo",
|
"purple_dark_theme": "M duhu jigo",
|
||||||
"qr_fullscreen": "Matsa don buɗe lambar QR na cikakken allo",
|
"qr_fullscreen": "Matsa don buɗe lambar QR na cikakken allo",
|
||||||
"qr_payment_amount": "Wannan QR code yana da adadin kuɗi. Kuna so ku overwrite wannan adadi?",
|
"qr_payment_amount": "Wannan QR code yana da adadin kuɗi. Kuna so ku overwrite wannan adadi?",
|
||||||
|
"quantity": "Yawa",
|
||||||
"question_to_disable_2fa": "Ka tabbata cewa kana son kashe cake 2fa? Ba za a sake buƙatar lambar 2FA ba don samun damar yin walat da takamaiman ayyuka.",
|
"question_to_disable_2fa": "Ka tabbata cewa kana son kashe cake 2fa? Ba za a sake buƙatar lambar 2FA ba don samun damar yin walat da takamaiman ayyuka.",
|
||||||
"receivable_balance": "Daidaituwa da daidaituwa",
|
"receivable_balance": "Daidaituwa da daidaituwa",
|
||||||
"receive": "Samu",
|
"receive": "Samu",
|
||||||
|
@ -710,6 +718,7 @@
|
||||||
"tokenID": "ID",
|
"tokenID": "ID",
|
||||||
"tor_connection": "Tor haɗin gwiwa",
|
"tor_connection": "Tor haɗin gwiwa",
|
||||||
"tor_only": "Tor kawai",
|
"tor_only": "Tor kawai",
|
||||||
|
"total": "Duka",
|
||||||
"total_saving": "Jimlar Adana",
|
"total_saving": "Jimlar Adana",
|
||||||
"totp_2fa_failure": "Ba daidai ba. Da fatan za a gwada wata lamba ta daban ko samar da sabon maɓallin asirin. Yi amfani da aikace-aikacen da ya dace 2FA wanda ke tallafawa lambobin lambobi 8 da Sha512.",
|
"totp_2fa_failure": "Ba daidai ba. Da fatan za a gwada wata lamba ta daban ko samar da sabon maɓallin asirin. Yi amfani da aikace-aikacen da ya dace 2FA wanda ke tallafawa lambobin lambobi 8 da Sha512.",
|
||||||
"totp_2fa_success": "Nasara! Cake 2FA ya dogara da wannan waljin. Ka tuna domin adana zuriyar mnemmonic naka idan ka rasa damar walat.",
|
"totp_2fa_success": "Nasara! Cake 2FA ya dogara da wannan waljin. Ka tuna domin adana zuriyar mnemmonic naka idan ka rasa damar walat.",
|
||||||
|
@ -800,6 +809,8 @@
|
||||||
"use_ssl": "Yi amfani da SSL",
|
"use_ssl": "Yi amfani da SSL",
|
||||||
"use_suggested": "Amfani da Shawarwari",
|
"use_suggested": "Amfani da Shawarwari",
|
||||||
"use_testnet": "Amfani da gwaji",
|
"use_testnet": "Amfani da gwaji",
|
||||||
|
"value": "Daraja",
|
||||||
|
"value_type": "Nau'in darajar",
|
||||||
"variable_pair_not_supported": "Ba a samun goyan bayan wannan m biyu tare da zaɓaɓɓun musayar",
|
"variable_pair_not_supported": "Ba a samun goyan bayan wannan m biyu tare da zaɓaɓɓun musayar",
|
||||||
"verification": "tabbatar",
|
"verification": "tabbatar",
|
||||||
"verify_with_2fa": "Tabbatar da Cake 2FA",
|
"verify_with_2fa": "Tabbatar da Cake 2FA",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue