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_linux_direct_input_password
Conflicts: lib/di.dart lib/ionia/ionia_service.dart lib/router.dart
This commit is contained in:
commit
60177ab37d
134 changed files with 3793 additions and 5483 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 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 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 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
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
In-app Cake Pay is Back
|
||||
Bug fixes and generic enhancements
|
|
@ -1,2 +1,3 @@
|
|||
Bitcoin Silent Payments
|
||||
Bug fixes and generic enhancements
|
||||
In-app Cake Pay is Back
|
||||
Bitcoin nodes stability enhancements
|
||||
Bug fixes and generic enhancements
|
|
@ -1,8 +1,8 @@
|
|||
-
|
||||
uri: tron-rpc.publicnode.com:443
|
||||
is_default: true
|
||||
is_default: false
|
||||
useSSL: true
|
||||
-
|
||||
uri: api.trongrid.io
|
||||
is_default: false
|
||||
is_default: true
|
||||
useSSL: true
|
|
@ -64,7 +64,7 @@ class ElectrumClient {
|
|||
await socket?.close();
|
||||
} catch (_) {}
|
||||
|
||||
if (useSSL == false) {
|
||||
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
|
||||
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
||||
} else {
|
||||
socket = await SecureSocket.connect(host, port,
|
||||
|
|
|
@ -103,13 +103,17 @@ abstract class ElectrumWalletBase
|
|||
password: password,
|
||||
encryptionFileUtils: encryptionFileUtils);
|
||||
|
||||
reaction((_) => syncStatus, (SyncStatus syncStatus) {
|
||||
if (syncStatus is! AttemptingSyncStatus && syncStatus is! SyncedTipSyncStatus)
|
||||
reaction((_) => syncStatus, (SyncStatus syncStatus) async {
|
||||
if (syncStatus is! AttemptingSyncStatus && syncStatus is! SyncedTipSyncStatus) {
|
||||
silentPaymentsScanningActive = syncStatus is SyncingSyncStatus;
|
||||
}
|
||||
|
||||
if (syncStatus is NotConnectedSyncStatus) {
|
||||
// Needs to re-subscribe to all scripthashes when reconnected
|
||||
_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
|
||||
|
@ -209,7 +213,7 @@ abstract class ElectrumWalletBase
|
|||
bool silentPaymentsScanningActive = false;
|
||||
|
||||
@action
|
||||
Future<void> setSilentPaymentsScanning(bool active) async {
|
||||
Future<void> setSilentPaymentsScanning(bool active, bool usingElectrs) async {
|
||||
silentPaymentsScanningActive = active;
|
||||
|
||||
if (active) {
|
||||
|
@ -222,18 +226,22 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
if (tip > walletInfo.restoreHeight) {
|
||||
_setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip);
|
||||
_setListeners(
|
||||
walletInfo.restoreHeight,
|
||||
chainTipParam: _currentChainTip,
|
||||
usingElectrs: usingElectrs,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
alwaysScan = false;
|
||||
|
||||
(await _isolate)?.kill(priority: Isolate.immediate);
|
||||
_isolate?.then((value) => value.kill(priority: Isolate.immediate));
|
||||
|
||||
if (electrumClient.isConnected) {
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} else {
|
||||
if (electrumClient.uri != null) {
|
||||
await electrumClient.connectToUri(electrumClient.uri!);
|
||||
await electrumClient.connectToUri(electrumClient.uri!, useSSL: electrumClient.useSSL);
|
||||
startSync();
|
||||
}
|
||||
}
|
||||
|
@ -289,7 +297,12 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
@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();
|
||||
|
||||
if (chainTip == height) {
|
||||
|
@ -315,7 +328,7 @@ abstract class ElectrumWalletBase
|
|||
chainTip: chainTip,
|
||||
electrumClient: ElectrumClient(),
|
||||
transactionHistoryIds: transactionHistory.transactions.keys.toList(),
|
||||
node: ScanNode(node!.uri, node!.useSSL),
|
||||
node: usingElectrs == true ? ScanNode(node!.uri, node!.useSSL) : null,
|
||||
labels: walletAddresses.labels,
|
||||
labelIndexes: walletAddresses.silentAddresses
|
||||
.where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1)
|
||||
|
@ -466,17 +479,7 @@ abstract class ElectrumWalletBase
|
|||
|
||||
await electrumClient.close();
|
||||
|
||||
electrumClient.onConnectionStatusChange = (bool? isConnected) async {
|
||||
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();
|
||||
}
|
||||
};
|
||||
electrumClient.onConnectionStatusChange = _onConnectionStatusChange;
|
||||
|
||||
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
|
||||
} catch (e) {
|
||||
|
@ -1134,10 +1137,15 @@ abstract class ElectrumWalletBase
|
|||
|
||||
@action
|
||||
@override
|
||||
Future<void> rescan(
|
||||
{required int height, int? chainTip, ScanData? scanData, bool? doSingleScan}) async {
|
||||
Future<void> rescan({
|
||||
required int height,
|
||||
int? chainTip,
|
||||
ScanData? scanData,
|
||||
bool? doSingleScan,
|
||||
bool? usingElectrs,
|
||||
}) async {
|
||||
silentPaymentsScanningActive = true;
|
||||
_setListeners(height, doSingleScan: doSingleScan);
|
||||
_setListeners(height, doSingleScan: doSingleScan, usingElectrs: usingElectrs);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1477,7 +1485,7 @@ abstract class ElectrumWalletBase
|
|||
|
||||
time = status["block_time"] as int?;
|
||||
final height = status["block_height"] as int? ?? 0;
|
||||
final tip = await getCurrentChainTip();
|
||||
final tip = await getUpdatedChainTip();
|
||||
if (tip > 0) confirmations = height > 0 ? tip - height + 1 : 0;
|
||||
} else {
|
||||
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
|
||||
|
@ -1531,6 +1539,23 @@ abstract class ElectrumWalletBase
|
|||
await fetchTransactionsForAddressType(historiesWithDetails, SegwitAddresType.p2wpkh);
|
||||
}
|
||||
|
||||
transactionHistory.transactions.values.forEach((tx) async {
|
||||
final isPendingSilentPaymentUtxo =
|
||||
(tx.isPending || tx.confirmations == 0) && historiesWithDetails[tx.id] == null;
|
||||
|
||||
if (isPendingSilentPaymentUtxo) {
|
||||
final info =
|
||||
await fetchTransactionInfo(hash: tx.id, height: tx.height, retryOnFailure: true);
|
||||
|
||||
if (info != null) {
|
||||
tx.confirmations = info.confirmations;
|
||||
tx.isPending = tx.confirmations == 0;
|
||||
transactionHistory.addOne(tx);
|
||||
await transactionHistory.save();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return historiesWithDetails;
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
|
@ -1638,6 +1663,7 @@ abstract class ElectrumWalletBase
|
|||
if (_isTransactionUpdating) {
|
||||
return;
|
||||
}
|
||||
await getCurrentChainTip();
|
||||
|
||||
transactionHistory.transactions.values.forEach((tx) async {
|
||||
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height > 0) {
|
||||
|
@ -1802,6 +1828,19 @@ abstract class ElectrumWalletBase
|
|||
|
||||
static String _hardenedDerivationPath(String derivationPath) =>
|
||||
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 {
|
||||
|
@ -1815,7 +1854,7 @@ class ScanData {
|
|||
final SendPort sendPort;
|
||||
final SilentPaymentOwner silentAddress;
|
||||
final int height;
|
||||
final ScanNode node;
|
||||
final ScanNode? node;
|
||||
final BasedUtxoNetwork network;
|
||||
final int chainTip;
|
||||
final ElectrumClient electrumClient;
|
||||
|
@ -1876,7 +1915,10 @@ Future<void> startRefresh(ScanData scanData) async {
|
|||
scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
|
||||
|
||||
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) {
|
||||
final count = scanData.isSingleScan ? 1 : TWEAKS_COUNT;
|
||||
|
|
|
@ -245,6 +245,7 @@ Future<int> getHavenCurrentHeight() async {
|
|||
|
||||
// Data taken from https://timechaincalendar.com/
|
||||
const bitcoinDates = {
|
||||
"2024-06": 846005,
|
||||
"2024-05": 841590,
|
||||
"2024-04": 837182,
|
||||
"2024-03": 832623,
|
||||
|
|
|
@ -410,7 +410,6 @@ extern "C"
|
|||
return false;
|
||||
}
|
||||
|
||||
wallet->store(std::string(path));
|
||||
change_current_wallet(wallet);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -306,7 +306,7 @@ class CWBitcoin extends Bitcoin {
|
|||
}
|
||||
|
||||
final electrumClient = ElectrumClient();
|
||||
await electrumClient.connectToUri(node.uri);
|
||||
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
|
||||
|
||||
late BasedUtxoNetwork network;
|
||||
btc.NetworkType networkType;
|
||||
|
@ -514,18 +514,10 @@ class CWBitcoin extends Bitcoin {
|
|||
@override
|
||||
Future<void> setScanningActive(Object wallet, bool active) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
|
||||
if (active && !(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.setSilentPaymentsScanning(active);
|
||||
bitcoinWallet.setSilentPaymentsScanning(
|
||||
active,
|
||||
active && (await getNodeIsElectrsSPEnabled(wallet)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -540,14 +532,6 @@ class CWBitcoin extends Bitcoin {
|
|||
@override
|
||||
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}) async {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -576,10 +560,16 @@ class CWBitcoin extends Bitcoin {
|
|||
}
|
||||
|
||||
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) {
|
||||
return true;
|
||||
if (tweaksResponse != null) {
|
||||
return true;
|
||||
}
|
||||
} on RequestFailedTimeoutException {
|
||||
return false;
|
||||
} catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
231
lib/di.dart
231
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_info_base.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/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
|
@ -34,16 +33,10 @@ import 'package:cake_wallet/entities/qr_view_data.dart';
|
|||
import 'package:cake_wallet/entities/template.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.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/trade.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/nano/nano.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_trade_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_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
|
||||
|
@ -125,13 +110,55 @@ 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_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/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/transaction_details/rbf_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_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_list/wallet_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
||||
|
@ -151,14 +178,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/wallet_list_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/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/backup_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
|
||||
|
@ -168,46 +188,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/cake_features_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/exchange/exchange_trade_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/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/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_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_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/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/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_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/trocador_providers_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/trade_details_view_model.dart';
|
||||
|
@ -216,27 +212,18 @@ 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_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_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_hardware_restore_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_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_seed_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_unlock_loadable_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_unlock_verifiable_view_model.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/transaction_info.dart';
|
||||
import 'package:cw_core/unspent_coins_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:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -244,6 +231,7 @@ import 'package:get_it/get_it.dart';
|
|||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'cake_pay/cake_pay_payment_credantials.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
|
@ -575,7 +563,7 @@ Future<void> setup({
|
|||
getIt.registerFactory<Modify2FAPage>(
|
||||
() => Modify2FAPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>()));
|
||||
|
||||
getIt.registerFactory<DesktopSettingsPage>(() => DesktopSettingsPage());
|
||||
getIt.registerFactory<DesktopSettingsPage>(() => DesktopSettingsPage(getIt.get<DashboardViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>(
|
||||
(pageOption, _) => ReceiveOptionViewModel(getIt.get<AppStore>().wallet!, pageOption));
|
||||
|
@ -1004,6 +992,8 @@ Future<void> setup({
|
|||
trades: _tradesSource,
|
||||
settingsStore: getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<CakePayService>()));
|
||||
|
||||
getIt.registerFactory(() => BackupService(getIt.get<SecureStorage>(), _walletInfoSource,
|
||||
getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
|
||||
|
||||
|
@ -1099,113 +1089,60 @@ Future<void> setup({
|
|||
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
|
||||
(QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
|
||||
|
||||
getIt.registerFactory(() => IoniaApi());
|
||||
getIt.registerFactory(() => CakePayApi());
|
||||
|
||||
getIt.registerFactory(() => AnyPayApi());
|
||||
|
||||
getIt.registerFactory<IoniaService>(
|
||||
() => IoniaService(getIt.get<SecureStorage>(), getIt.get<IoniaApi>()));
|
||||
getIt.registerFactory<CakePayService>(
|
||||
() => CakePayService(getIt.get<SecureStorage>(), getIt.get<CakePayApi>()));
|
||||
|
||||
getIt.registerFactory<IoniaAnyPay>(() => IoniaAnyPay(
|
||||
getIt.get<IoniaService>(), getIt.get<AnyPayApi>(), getIt.get<AppStore>().wallet!));
|
||||
getIt.registerFactory(() => CakePayCardsListViewModel(cakePayService: getIt.get<CakePayService>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
|
||||
getIt.registerFactory(() => CakePayAuthViewModel(cakePayService: getIt.get<CakePayService>()));
|
||||
|
||||
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<IoniaService>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));
|
||||
|
||||
getIt.registerFactoryParam<IoniaMerchPurchaseViewModel, double, IoniaMerchant>(
|
||||
(double amount, merchant) {
|
||||
return IoniaMerchPurchaseViewModel(
|
||||
ioniaAnyPayService: getIt.get<IoniaAnyPay>(),
|
||||
amount: amount,
|
||||
ioniaMerchant: merchant,
|
||||
getIt.registerFactoryParam<CakePayPurchaseViewModel, PaymentCredential, CakePayCard>(
|
||||
(PaymentCredential paymentCredential, CakePayCard card) {
|
||||
return CakePayPurchaseViewModel(
|
||||
cakePayService: getIt.get<CakePayService>(),
|
||||
paymentCredential: paymentCredential,
|
||||
card: card,
|
||||
sendViewModel: getIt.get<SendViewModel>());
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaBuyCardViewModel, IoniaMerchant, void>(
|
||||
(IoniaMerchant merchant, _) {
|
||||
return IoniaBuyCardViewModel(ioniaMerchant: merchant);
|
||||
getIt.registerFactoryParam<CakePayBuyCardViewModel, CakePayVendor, void>(
|
||||
(CakePayVendor vendor, _) {
|
||||
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<IoniaVerifyIoniaOtp, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
getIt.registerFactoryParam<CakePayVerifyOtpPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final email = args.first as String;
|
||||
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, _) {
|
||||
final merchant = args.first as IoniaMerchant;
|
||||
|
||||
return IoniaBuyGiftCardPage(getIt.get<IoniaBuyCardViewModel>(param1: merchant));
|
||||
return CakePayBuyCardPage(getIt.get<CakePayBuyCardViewModel>(param1: vendor),
|
||||
getIt.get<CakePayService>());
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaBuyGiftCardDetailPage, List<dynamic>, void>(
|
||||
getIt.registerFactoryParam<CakePayBuyCardDetailPage, List<dynamic>, void>(
|
||||
(List<dynamic> args, _) {
|
||||
final amount = args.first as double;
|
||||
final merchant = args.last as IoniaMerchant;
|
||||
return IoniaBuyGiftCardDetailPage(
|
||||
getIt.get<IoniaMerchPurchaseViewModel>(param1: amount, param2: merchant));
|
||||
final paymentCredential = args.first as PaymentCredential;
|
||||
final card = args[1] as CakePayCard;
|
||||
return CakePayBuyCardDetailPage(
|
||||
getIt.get<CakePayPurchaseViewModel>(param1: paymentCredential, param2: card));
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaGiftCardDetailsViewModel, IoniaGiftCard, void>(
|
||||
(IoniaGiftCard giftCard, _) {
|
||||
return IoniaGiftCardDetailsViewModel(
|
||||
ioniaService: getIt.get<IoniaService>(), giftCard: giftCard);
|
||||
});
|
||||
getIt.registerFactory(() => CakePayCardsPage(getIt.get<CakePayCardsListViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<IoniaCustomTipViewModel, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
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.registerFactory(() => CakePayAccountPage(getIt.get<CakePayAccountViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<RBFDetailsPage, TransactionInfo, void>(
|
||||
(TransactionInfo transactionInfo, _) => RBFDetailsPage(
|
||||
|
@ -1236,18 +1173,6 @@ Future<void> setup({
|
|||
(AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsPage(
|
||||
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<WalletUnlockLoadableViewModel, WalletUnlockArguments, void>((args, _) {
|
||||
final currentWalletName =
|
||||
getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName) ?? '';
|
||||
|
|
|
@ -37,7 +37,7 @@ const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
|
|||
const nanoDefaultNodeUri = 'rpc.nano.to';
|
||||
const nanoDefaultPowNodeUri = 'rpc.nano.to';
|
||||
const solanaDefaultNodeUri = 'rpc.ankr.com';
|
||||
const tronDefaultNodeUri = 'tron-rpc.publicnode.com:443';
|
||||
const tronDefaultNodeUri = 'api.trongrid.io';
|
||||
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
|
||||
|
||||
Future<void> defaultSettingsMigration(
|
||||
|
@ -227,6 +227,11 @@ Future<void> defaultSettingsMigration(
|
|||
break;
|
||||
case 34:
|
||||
await _addElectRsNode(nodes, sharedPreferences);
|
||||
case 35:
|
||||
await _switchElectRsNode(nodes, sharedPreferences);
|
||||
break;
|
||||
case 36:
|
||||
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
break;
|
||||
default:
|
||||
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(
|
||||
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
|
||||
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,173 +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))!;
|
||||
// TODO: deprecated
|
||||
// final deviceId = await PlatformDeviceId.getDeviceId;
|
||||
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;
|
||||
}
|
|
@ -203,7 +203,7 @@ Future<void> initializeAppConfigs() async {
|
|||
transactionDescriptions: transactionDescriptions,
|
||||
secureStorage: secureStorage,
|
||||
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_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/core/totp_request_details.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/exchange/trade.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/src/screens/anonpay_details/anonpay_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
|
@ -36,14 +34,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_trade_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/nano/nano_change_rep_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
|
||||
|
@ -76,9 +66,11 @@ import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
|
|||
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/security_backup_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/tor_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
|
||||
|
@ -123,6 +115,7 @@ import 'package:cw_core/wallet_type.dart';
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart';
|
||||
import 'package:cake_wallet/wallet_types.g.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
|
||||
|
@ -572,73 +565,30 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
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>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => getIt.get<IoniaWelcomePage>(),
|
||||
builder: (_) => getIt.get<CakePayBuyCardPage>(param1: args));
|
||||
|
||||
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:
|
||||
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:
|
||||
case Routes.cakePayVerifyOtpPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<CakePayVerifyOtpPage>(param1: args));
|
||||
|
||||
case Routes.ioniaBuyGiftCardDetailPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
|
||||
|
||||
case Routes.ioniaVerifyIoniaOtpPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaVerifyIoniaOtp>(param1: args));
|
||||
|
||||
case Routes.ioniaDebitCardPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaDebitCardPage>());
|
||||
|
||||
case Routes.ioniaActivateDebitCardPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaActivateDebitCardPage>());
|
||||
|
||||
case Routes.ioniaAccountPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountPage>());
|
||||
|
||||
case Routes.ioniaAccountCardsPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountCardsPage>());
|
||||
|
||||
case Routes.ioniaCustomTipPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomTipPage>(param1: args));
|
||||
|
||||
case Routes.ioniaGiftCardDetailPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
|
||||
|
||||
case Routes.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.cakePayAccountPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<CakePayAccountPage>());
|
||||
|
||||
case Routes.webViewPage:
|
||||
final args = settings.arguments as List;
|
||||
|
|
|
@ -64,22 +64,13 @@ class Routes {
|
|||
static const unspentCoinsDetails = '/unspent_coins_details';
|
||||
static const addressPage = '/address_page';
|
||||
static const fullscreenQR = '/fullscreen_qr';
|
||||
static const ioniaWelcomePage = '/cake_pay_welcome_page';
|
||||
static const ioniaCreateAccountPage = '/cake_pay_create_account_page';
|
||||
static const ioniaLoginPage = '/cake_pay_login_page';
|
||||
static const ioniaManageCardsPage = '/manage_cards_page';
|
||||
static const ioniaBuyGiftCardPage = '/buy_gift_card_page';
|
||||
static const ioniaBuyGiftCardDetailPage = '/buy_gift_card_detail_page';
|
||||
static const ioniaVerifyIoniaOtpPage = '/cake_pay_verify_otp_page';
|
||||
static const ioniaDebitCardPage = '/debit_card_page';
|
||||
static const ioniaActivateDebitCardPage = '/activate_debit_card_page';
|
||||
static const ioniaAccountPage = 'ionia_account_page';
|
||||
static const ioniaAccountCardsPage = 'ionia_account_cards_page';
|
||||
static const ioniaCustomTipPage = 'ionia_custom_tip_page';
|
||||
static const ioniaGiftCardDetailPage = '/ionia_gift_card_detail_page';
|
||||
static const ioniaPaymentStatusPage = '/ionia_payment_status_page';
|
||||
static const ioniaMoreOptionsPage = '/ionia_more_options_page';
|
||||
static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page';
|
||||
static const cakePayWelcomePage = '/cake_pay_welcome_page';
|
||||
static const cakePayLoginPage = '/cake_pay_login_page';
|
||||
static const cakePayCardsPage = '/cake_pay_cards_page';
|
||||
static const cakePayBuyCardPage = '/cake_pay_buy_card_page';
|
||||
static const cakePayBuyCardDetailPage = '/cake_pay_buy_card_detail_page';
|
||||
static const cakePayVerifyOtpPage = '/cake_pay_verify_otp_page';
|
||||
static const cakePayAccountPage = '/cake_pay_account_page';
|
||||
static const webViewPage = '/web_view_page';
|
||||
static const silentPaymentsSettings = '/silent_payments_settings';
|
||||
static const connectionSync = '/connection_sync_page';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/route_aware.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
|
@ -6,7 +7,7 @@ import 'package:cake_wallet/store/settings_store.dart';
|
|||
import 'package:cake_wallet/src/widgets/nav_bar.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
enum AppBarStyle { regular, withShadow, transparent }
|
||||
enum AppBarStyle { regular, withShadow, transparent, completelyTransparent }
|
||||
|
||||
abstract class BasePage extends StatelessWidget {
|
||||
BasePage() : _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
@ -32,6 +33,14 @@ abstract class BasePage extends StatelessWidget {
|
|||
|
||||
Widget? get endDrawer => null;
|
||||
|
||||
Function(BuildContext context)? get pushToWidget => null;
|
||||
|
||||
Function(BuildContext context)? get pushToNextWidget => null;
|
||||
|
||||
Function(BuildContext context)? get popWidget => null;
|
||||
|
||||
Function(BuildContext context)? get popNextWidget => null;
|
||||
|
||||
AppBarStyle get appBarStyle => AppBarStyle.regular;
|
||||
|
||||
Widget Function(BuildContext, Widget)? get rootWrapper => null;
|
||||
|
@ -116,7 +125,7 @@ abstract class BasePage extends StatelessWidget {
|
|||
|
||||
Widget? floatingActionButton(BuildContext context) => null;
|
||||
|
||||
ObstructingPreferredSizeWidget appBar(BuildContext context) {
|
||||
PreferredSizeWidget appBar(BuildContext context) {
|
||||
final appBarColor = pageBackgroundColor(context);
|
||||
|
||||
switch (appBarStyle) {
|
||||
|
@ -147,6 +156,16 @@ abstract class BasePage extends StatelessWidget {
|
|||
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:
|
||||
// FIX-ME: NavBar no context
|
||||
return NavBar(
|
||||
|
@ -162,15 +181,21 @@ abstract class BasePage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final root = Scaffold(
|
||||
key: _scaffoldKey,
|
||||
backgroundColor: pageBackgroundColor(context),
|
||||
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
|
||||
extendBodyBehindAppBar: extendBodyBehindAppBar,
|
||||
endDrawer: endDrawer,
|
||||
appBar: appBar(context),
|
||||
body: body(context),
|
||||
floatingActionButton: floatingActionButton(context));
|
||||
final root = RouteAwareWidget(
|
||||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
backgroundColor: pageBackgroundColor(context),
|
||||
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
|
||||
extendBodyBehindAppBar: extendBodyBehindAppBar,
|
||||
endDrawer: endDrawer,
|
||||
appBar: appBar(context),
|
||||
body: body(context),
|
||||
floatingActionButton: floatingActionButton(context)),
|
||||
pushToWidget: (context) => pushToWidget?.call(context),
|
||||
pushToNextWidget: (context) => pushToNextWidget?.call(context),
|
||||
popWidget: (context) => popWidget?.call(context),
|
||||
popNextWidget: (context) => popNextWidget?.call(context),
|
||||
);
|
||||
|
||||
return rootWrapper?.call(context, root) ?? root;
|
||||
}
|
||||
|
|
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/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay_states.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/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/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:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class IoniaVerifyIoniaOtp extends BasePage {
|
||||
IoniaVerifyIoniaOtp(this._authViewModel, this._email, this.isSignIn)
|
||||
class CakePayVerifyOtpPage extends BasePage {
|
||||
CakePayVerifyOtpPage(this._authViewModel, this._email, this.isSignIn)
|
||||
: _codeController = TextEditingController(),
|
||||
_codeFocus = FocusNode() {
|
||||
_codeController.addListener(() {
|
||||
final otp = _codeController.text;
|
||||
_authViewModel.otp = otp;
|
||||
if (otp.length > 3) {
|
||||
_authViewModel.otpState = IoniaOtpSendEnabled();
|
||||
if (otp.length > 5) {
|
||||
_authViewModel.otpState = CakePayOtpSendEnabled();
|
||||
} else {
|
||||
_authViewModel.otpState = IoniaOtpSendDisabled();
|
||||
_authViewModel.otpState = CakePayOtpSendDisabled();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final IoniaAuthViewModel _authViewModel;
|
||||
final CakePayAuthViewModel _authViewModel;
|
||||
final bool isSignIn;
|
||||
|
||||
final String _email;
|
||||
|
@ -53,11 +52,11 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
|||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _authViewModel.otpState, (IoniaOtpState state) {
|
||||
if (state is IoniaOtpFailure) {
|
||||
reaction((_) => _authViewModel.otpState, (CakePayOtpState state) {
|
||||
if (state is CakePayOtpFailure) {
|
||||
_onOtpFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaOtpSuccess) {
|
||||
if (state is CakePayOtpSuccess) {
|
||||
_onOtpSuccessful(context);
|
||||
}
|
||||
});
|
||||
|
@ -98,9 +97,7 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
|||
Text(S.of(context).didnt_get_code),
|
||||
SizedBox(width: 20),
|
||||
InkWell(
|
||||
onTap: () => isSignIn
|
||||
? _authViewModel.signIn(_email)
|
||||
: _authViewModel.createUser(_email),
|
||||
onTap: () => _authViewModel.logIn(_email),
|
||||
child: Text(
|
||||
S.of(context).resend_code,
|
||||
style: textSmallSemiBold(color: Palette.blueCraiola),
|
||||
|
@ -120,8 +117,8 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
|||
builder: (_) => LoadingPrimaryButton(
|
||||
text: S.of(context).continue_text,
|
||||
onPressed: _verify,
|
||||
isDisabled: _authViewModel.otpState is IoniaOtpSendDisabled,
|
||||
isLoading: _authViewModel.otpState is IoniaOtpValidating,
|
||||
isDisabled: _authViewModel.otpState is CakePayOtpSendDisabled,
|
||||
isLoading: _authViewModel.otpState is CakePayOtpValidating,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
|
@ -149,8 +146,7 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
|||
}
|
||||
|
||||
void _onOtpSuccessful(BuildContext context) =>
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst);
|
||||
Navigator.pop(context);
|
||||
|
||||
void _verify() async => await _authViewModel.verifyEmail(_codeController.text);
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
import 'package:cake_wallet/core/email_validator.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay_states.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/themes/extensions/cake_text_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_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:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class IoniaLoginPage extends BasePage {
|
||||
IoniaLoginPage(this._authViewModel)
|
||||
class CakePayWelcomePage extends BasePage {
|
||||
CakePayWelcomePage(this._authViewModel)
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
_emailController = TextEditingController() {
|
||||
_emailController.text = _authViewModel.email;
|
||||
|
@ -25,14 +25,14 @@ class IoniaLoginPage extends BasePage {
|
|||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
|
||||
final IoniaAuthViewModel _authViewModel;
|
||||
final CakePayAuthViewModel _authViewModel;
|
||||
|
||||
final TextEditingController _emailController;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.login,
|
||||
S.current.welcome_to_cakepay,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
|
@ -41,25 +41,40 @@ class IoniaLoginPage extends BasePage {
|
|||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _authViewModel.signInState, (IoniaCreateAccountState state) {
|
||||
if (state is IoniaCreateStateFailure) {
|
||||
reaction((_) => _authViewModel.userVerificationState, (CakePayUserVerificationState state) {
|
||||
if (state is CakePayUserVerificationStateFailure) {
|
||||
_onLoginUserFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaCreateStateSuccess) {
|
||||
if (state is CakePayUserVerificationStateSuccess) {
|
||||
_onLoginSuccessful(context, _authViewModel);
|
||||
}
|
||||
});
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.all(24),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: BaseTextFormField(
|
||||
hintText: S.of(context).email_address,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: EmailValidator(),
|
||||
controller: _emailController,
|
||||
onSubmit: (text) => _login(),
|
||||
),
|
||||
content: Column(
|
||||
children: [
|
||||
SizedBox(height: 90),
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: BaseTextFormField(
|
||||
hintText: S.of(context).email_address,
|
||||
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),
|
||||
bottomSection: Column(
|
||||
|
@ -71,7 +86,8 @@ class IoniaLoginPage extends BasePage {
|
|||
builder: (_) => LoadingPrimaryButton(
|
||||
text: S.of(context).login,
|
||||
onPressed: _login,
|
||||
isLoading: _authViewModel.signInState is IoniaCreateStateLoading,
|
||||
isLoading:
|
||||
_authViewModel.userVerificationState is CakePayUserVerificationStateLoading,
|
||||
color: Theme.of(context).primaryColor,
|
||||
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,
|
||||
Routes.ioniaVerifyIoniaOtpPage,
|
||||
Routes.cakePayVerifyOtpPage,
|
||||
arguments: [authViewModel.email, true],
|
||||
);
|
||||
|
||||
|
@ -108,6 +125,6 @@ class IoniaLoginPage extends BasePage {
|
|||
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
|
||||
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/ionia/ionia_merchant.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/routes.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/ionia/widgets/card_item.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/card_menu.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_filter_modal.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/card_item.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/card_menu.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
|
||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.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/src/widgets/gradient_background.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/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 {
|
||||
IoniaManageCardsPage(this._cardsListViewModel): searchFocusNode = FocusNode() {
|
||||
class CakePayCardsPage extends BasePage {
|
||||
CakePayCardsPage(this._cardsListViewModel) : searchFocusNode = FocusNode() {
|
||||
_searchController.addListener(() {
|
||||
if (_searchController.text != _cardsListViewModel.searchString) {
|
||||
_searchDebounce.run(() {
|
||||
_cardsListViewModel.searchMerchant(_searchController.text);
|
||||
_cardsListViewModel.resetLoadingNextPageState();
|
||||
_cardsListViewModel.getVendors(text: _searchController.text);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_cardsListViewModel.getMerchants();
|
||||
|
||||
}
|
||||
|
||||
final FocusNode searchFocusNode;
|
||||
final IoniaGiftCardsListViewModel _cardsListViewModel;
|
||||
final CakePayCardsListViewModel _cardsListViewModel;
|
||||
|
||||
final _searchDebounce = Debounce(Duration(milliseconds: 500));
|
||||
final _searchController = TextEditingController();
|
||||
|
@ -46,8 +44,7 @@ class IoniaManageCardsPage extends BasePage {
|
|||
|
||||
@override
|
||||
Widget Function(BuildContext, Widget) get rootWrapper =>
|
||||
(BuildContext context, Widget scaffold) =>
|
||||
GradientBackground(scaffold: scaffold);
|
||||
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
|
||||
|
||||
@override
|
||||
bool get resizeToAvoidBottomInset => false;
|
||||
|
@ -58,7 +55,7 @@ class IoniaManageCardsPage extends BasePage {
|
|||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.of(context).gift_cards,
|
||||
'Cake Pay',
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
),
|
||||
|
@ -68,9 +65,17 @@ class IoniaManageCardsPage extends BasePage {
|
|||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
return _TrailingIcon(
|
||||
asset: 'assets/images/profile.png',
|
||||
onPressed: () => Navigator.pushNamed(context, Routes.ioniaAccountPage),
|
||||
);
|
||||
asset: 'assets/images/profile.png',
|
||||
iconColor: pageIconColor(context) ?? Colors.white,
|
||||
onPressed: () {
|
||||
_cardsListViewModel.isCakePayUserAuthenticated().then((value) {
|
||||
if (value) {
|
||||
Navigator.pushNamed(context, Routes.cakePayAccountPage);
|
||||
return;
|
||||
}
|
||||
Navigator.pushNamed(context, Routes.cakePayWelcomePage);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -79,8 +84,12 @@ class IoniaManageCardsPage extends BasePage {
|
|||
label: S.of(context).filter_by,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
await showCategoryFilter(context);
|
||||
_cardsListViewModel.getMerchants();
|
||||
_cardsListViewModel.storeInitialFilterStates();
|
||||
await showFilterWidget(context);
|
||||
if (_cardsListViewModel.hasFiltersChanged) {
|
||||
_cardsListViewModel.resetLoadingNextPageState();
|
||||
_cardsListViewModel.getVendors();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 32,
|
||||
|
@ -120,7 +129,7 @@ class IoniaManageCardsPage extends BasePage {
|
|||
),
|
||||
SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: IoniaManageCardsPageBody(
|
||||
child: CakePayCardsPageBody(
|
||||
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>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return IoniaFilterModal(
|
||||
ioniaGiftCardsListViewModel: _cardsListViewModel,
|
||||
);
|
||||
return FilterWidget(filterItems: _cardsListViewModel.createFilterItems);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IoniaManageCardsPageBody extends StatefulWidget {
|
||||
const IoniaManageCardsPageBody({
|
||||
class CakePayCardsPageBody extends StatefulWidget {
|
||||
const CakePayCardsPageBody({
|
||||
Key? key,
|
||||
required this.cardsListViewModel,
|
||||
}) : super(key: key);
|
||||
|
||||
final IoniaGiftCardsListViewModel cardsListViewModel;
|
||||
final CakePayCardsListViewModel cardsListViewModel;
|
||||
|
||||
@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 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();
|
||||
|
||||
|
@ -166,61 +174,93 @@ class _IoniaManageCardsPageBodyState extends State<IoniaManageCardsPageBody> {
|
|||
void initState() {
|
||||
_scrollController.addListener(() {
|
||||
final scrollOffsetFromTop = _scrollController.hasClients
|
||||
? (_scrollController.offset / _scrollController.position.maxScrollExtent * (backgroundHeight - thumbHeight))
|
||||
? (_scrollController.offset /
|
||||
_scrollController.position.maxScrollExtent *
|
||||
(backgroundHeight - thumbHeight))
|
||||
: 0.0;
|
||||
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();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
final merchantState = widget.cardsListViewModel.merchantState;
|
||||
if (merchantState is IoniaLoadedMerchantState) {
|
||||
return Observer(builder: (_) {
|
||||
final vendorsState = widget.cardsListViewModel.vendorsState;
|
||||
if (vendorsState is CakePayVendorLoadedState) {
|
||||
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: [
|
||||
ListView.separated(
|
||||
padding: EdgeInsets.only(left: 2, right: 22),
|
||||
controller: _scrollController,
|
||||
itemCount: merchantsList.length,
|
||||
separatorBuilder: (_, __) => SizedBox(height: 4),
|
||||
itemBuilder: (_, index) {
|
||||
final merchant = merchantsList[index];
|
||||
return CardItem(
|
||||
logoUrl: merchant.logoUrl,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]);
|
||||
},
|
||||
title: merchant.legalName,
|
||||
subTitle: merchant.avaibilityStatus,
|
||||
backgroundColor: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
titleColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
subtitleColor: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
|
||||
discount: merchant.discount,
|
||||
);
|
||||
},
|
||||
),
|
||||
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 Center(
|
||||
child: CircularProgressIndicator(
|
||||
backgroundColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).extension<ExchangePageTheme>()!.firstGradientBottomPanelColor),
|
||||
GridView.builder(
|
||||
controller: _scrollController,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: responsiveLayoutUtil.shouldRenderTabletUI ? 2 : 1,
|
||||
childAspectRatio: 5,
|
||||
crossAxisSpacing: responsiveLayoutUtil.shouldRenderTabletUI ? 10 : 5,
|
||||
mainAxisSpacing: responsiveLayoutUtil.shouldRenderTabletUI ? 10 : 5,
|
||||
),
|
||||
padding: EdgeInsets.only(left: 2, right: 22),
|
||||
itemCount: vendors.length + (isLoadingMore ? 1 : 0),
|
||||
itemBuilder: (_, index) {
|
||||
if (index >= vendors.length) {
|
||||
return _VendorLoadedIndicator();
|
||||
}
|
||||
final vendor = vendors[index];
|
||||
return CardItem(
|
||||
logoUrl: vendor.card?.cardImageUrl,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.cakePayBuyCardPage, arguments: [vendor]);
|
||||
},
|
||||
title: vendor.name,
|
||||
subTitle: vendor.card?.description ?? '',
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
titleColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
subtitleColor: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
|
||||
discount: 0.0,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
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);
|
||||
final TextEditingController controller;
|
||||
final FocusNode focusNode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final searchIcon = ExcludeSemantics(
|
||||
|
@ -284,30 +325,25 @@ class _SearchWidget 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 VoidCallback? onPressed;
|
||||
final Color iconColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Semantics(
|
||||
label: S.of(context).profile,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: BoxConstraints(),
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
iconSize: 25,
|
||||
onPressed: onPressed,
|
||||
icon: Image.asset(
|
||||
asset,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
label: S.of(context).profile,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: BoxConstraints(),
|
||||
highlightColor: Colors.transparent,
|
||||
onPressed: onPressed,
|
||||
icon: ImageIcon(AssetImage(asset), size: 25, color: iconColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
|
@ -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:flutter/material.dart';
|
||||
|
||||
class IoniaAlertModal extends StatelessWidget {
|
||||
const IoniaAlertModal({
|
||||
class CakePayAlertModal extends StatelessWidget {
|
||||
const CakePayAlertModal({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.content,
|
|
@ -3,8 +3,8 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
|
||||
class IoniaTile extends StatelessWidget {
|
||||
const IoniaTile({
|
||||
class CakePayTile extends StatelessWidget {
|
||||
const CakePayTile({
|
||||
Key? key,
|
||||
required this.title,
|
||||
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/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:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.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';
|
||||
|
||||
class CakeFeaturesPage extends StatelessWidget {
|
||||
CakeFeaturesPage({
|
||||
required this.dashboardViewModel,
|
||||
required this.cakeFeaturesViewModel,
|
||||
});
|
||||
CakeFeaturesPage({required this.dashboardViewModel, required this.cakeFeaturesViewModel});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final CakeFeaturesViewModel cakeFeaturesViewModel;
|
||||
|
@ -45,20 +46,11 @@ class CakeFeaturesPage extends StatelessWidget {
|
|||
child: ListView(
|
||||
controller: _scrollController,
|
||||
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),
|
||||
DashBoardRoundedCardWidget(
|
||||
onTap: () => _launchUrl("buy.cakepay.com"),
|
||||
title: S.of(context).cake_pay_web_cards_title,
|
||||
subTitle: S.of(context).cake_pay_web_cards_subtitle,
|
||||
onTap: () => _navigatorToGiftCardsPage(context),
|
||||
title: 'Cake Pay',
|
||||
subTitle: S.of(context).cake_pay_subtitle,
|
||||
svgPicture: SvgPicture.asset(
|
||||
'assets/images/cards.svg',
|
||||
height: 125,
|
||||
|
@ -88,6 +80,28 @@ class CakeFeaturesPage extends StatelessWidget {
|
|||
Uri.https(url),
|
||||
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/standard_checkbox.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:cake_wallet/src/widgets/picker_wrapper_widget.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
|
||||
class FilterWidget extends StatelessWidget {
|
||||
FilterWidget({required this.dashboardViewModel});
|
||||
FilterWidget({required this.filterItems});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final Map<String, List<FilterItem>> filterItems;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -27,75 +30,90 @@ class FilterWidget extends StatelessWidget {
|
|||
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||
child: Container(
|
||||
color: Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(24.0),
|
||||
child: Text(
|
||||
S.of(context).filter_by,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
decoration: TextDecoration.none,
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(24.0),
|
||||
child: Text(
|
||||
S.of(context).filter_by,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
sectionDivider,
|
||||
ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: dashboardViewModel.filterItems.length,
|
||||
separatorBuilder: (context, _) => sectionDivider,
|
||||
itemBuilder: (_, index1) {
|
||||
final title = dashboardViewModel.filterItems.keys
|
||||
.elementAt(index1);
|
||||
final section = dashboardViewModel.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(vertical: 8.0),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: section.length,
|
||||
itemBuilder: (_, index2) {
|
||||
final item = section[index2];
|
||||
final content = Observer(
|
||||
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);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
]),
|
||||
ListView.builder(
|
||||
padding: EdgeInsets.symmetric(horizontal: 28.0),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: section.length,
|
||||
itemBuilder: (_, index2) {
|
||||
final item = section[index2];
|
||||
|
||||
if (item is DropdownFilterItem) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, 0, 8, 16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
width: 1.0,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||
),
|
||||
),
|
||||
child: DropdownFilterList(
|
||||
items: item.items,
|
||||
caption: item.caption,
|
||||
selectedItem: item.selectedItem,
|
||||
onItemSelected: item.onItemSelected,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
final content = Observer(
|
||||
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: () {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (context) => FilterWidget(dashboardViewModel: dashboardViewModel),
|
||||
builder: (context) => FilterWidget(filterItems: dashboardViewModel.filterItems),
|
||||
);
|
||||
},
|
||||
child: Semantics(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:cake_wallet/src/widgets/alert_close_button.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/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
|
|
|
@ -99,6 +99,14 @@ class ExchangePage extends BasePage {
|
|||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
|
|
@ -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,220 +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:device_display_brightness/device_display_brightness.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';
|
||||
|
||||
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 RouteAwareWidget(
|
||||
pushToWidget: ()=> viewModel.increaseBrightness(),
|
||||
pushToNextWidget: ()=> DeviceDisplayBrightness.setBrightness(viewModel.brightness),
|
||||
popNextWidget: ()=> viewModel.increaseBrightness(),
|
||||
popWidget: ()=> DeviceDisplayBrightness.setBrightness(viewModel.brightness),
|
||||
child: 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,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -38,6 +38,14 @@ class NewWalletPage extends BasePage {
|
|||
@override
|
||||
String get title => S.current.new_wallet;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => WalletNameForm(
|
||||
_walletNewVM,
|
||||
|
|
|
@ -34,6 +34,14 @@ class NewWalletTypePage extends BasePage {
|
|||
String get title =>
|
||||
isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => WalletTypeForm(
|
||||
onTypeSelected: onTypeSelected,
|
||||
|
|
|
@ -21,6 +21,14 @@ class RestoreFromBackupPage extends BasePage {
|
|||
@override
|
||||
String get title => S.current.restore_title_from_backup;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => restoreFromBackupViewModel.state, (ExecutionState state) {
|
||||
|
|
|
@ -107,6 +107,14 @@ class WalletRestorePage extends BasePage {
|
|||
// String? derivationPath = null;
|
||||
DerivationInfo? derivationInfo;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => walletRestoreViewModel.state, (ExecutionState state) {
|
||||
|
|
|
@ -137,9 +137,11 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
break;
|
||||
case AppLifecycleState.resumed:
|
||||
widget.authService.requireAuth().then((value) {
|
||||
setState(() {
|
||||
_requestAuth = value;
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_requestAuth = value;
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -66,6 +66,14 @@ class SendPage extends BasePage {
|
|||
@override
|
||||
bool get extendBodyBehindAppBar => true;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget? leading(BuildContext context) {
|
||||
final _backButton = Icon(
|
||||
|
|
|
@ -32,6 +32,14 @@ class SendTemplatePage extends BasePage {
|
|||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget trailing(context) => Observer(builder: (_) {
|
||||
return sendTemplateViewModel.recipients.length > 1
|
||||
|
|
|
@ -12,6 +12,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
|||
{required this.alertTitle,
|
||||
this.paymentId,
|
||||
this.paymentIdValue,
|
||||
this.expirationTime,
|
||||
required this.amount,
|
||||
required this.amountValue,
|
||||
required this.fiatAmountValue,
|
||||
|
@ -28,11 +29,13 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
|||
this.alertLeftActionButtonTextColor,
|
||||
this.alertRightActionButtonTextColor,
|
||||
this.alertLeftActionButtonColor,
|
||||
this.alertRightActionButtonColor});
|
||||
this.alertRightActionButtonColor,
|
||||
this.onDispose});
|
||||
|
||||
final String alertTitle;
|
||||
final String? paymentId;
|
||||
final String? paymentIdValue;
|
||||
final String? expirationTime;
|
||||
final String amount;
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
|
@ -50,6 +53,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
|||
final Color? alertRightActionButtonTextColor;
|
||||
final Color? alertLeftActionButtonColor;
|
||||
final Color? alertRightActionButtonColor;
|
||||
final Function? onDispose;
|
||||
|
||||
@override
|
||||
String get titleText => alertTitle;
|
||||
|
@ -88,6 +92,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
|||
Widget content(BuildContext context) => ConfirmSendingAlertContent(
|
||||
paymentId: paymentId,
|
||||
paymentIdValue: paymentIdValue,
|
||||
expirationTime: expirationTime,
|
||||
amount: amount,
|
||||
amountValue: amountValue,
|
||||
fiatAmountValue: fiatAmountValue,
|
||||
|
@ -95,13 +100,15 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
|||
feeRate: feeRate,
|
||||
feeValue: feeValue,
|
||||
feeFiatAmount: feeFiatAmount,
|
||||
outputs: outputs);
|
||||
outputs: outputs,
|
||||
onDispose: onDispose);
|
||||
}
|
||||
|
||||
class ConfirmSendingAlertContent extends StatefulWidget {
|
||||
ConfirmSendingAlertContent(
|
||||
{this.paymentId,
|
||||
this.paymentIdValue,
|
||||
this.expirationTime,
|
||||
required this.amount,
|
||||
required this.amountValue,
|
||||
required this.fiatAmountValue,
|
||||
|
@ -109,10 +116,12 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
|||
this.feeRate,
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs});
|
||||
required this.outputs,
|
||||
required this.onDispose}) {}
|
||||
|
||||
final String? paymentId;
|
||||
final String? paymentIdValue;
|
||||
final String? expirationTime;
|
||||
final String amount;
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
|
@ -121,11 +130,13 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
|||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
final Function? onDispose;
|
||||
|
||||
@override
|
||||
ConfirmSendingAlertContentState createState() => ConfirmSendingAlertContentState(
|
||||
paymentId: paymentId,
|
||||
paymentIdValue: paymentIdValue,
|
||||
expirationTime: expirationTime,
|
||||
amount: amount,
|
||||
amountValue: amountValue,
|
||||
fiatAmountValue: fiatAmountValue,
|
||||
|
@ -133,13 +144,15 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
|||
feeRate: feeRate,
|
||||
feeValue: feeValue,
|
||||
feeFiatAmount: feeFiatAmount,
|
||||
outputs: outputs);
|
||||
outputs: outputs,
|
||||
onDispose: onDispose);
|
||||
}
|
||||
|
||||
class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> {
|
||||
ConfirmSendingAlertContentState(
|
||||
{this.paymentId,
|
||||
this.paymentIdValue,
|
||||
this.expirationTime,
|
||||
required this.amount,
|
||||
required this.amountValue,
|
||||
required this.fiatAmountValue,
|
||||
|
@ -147,7 +160,8 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
|||
this.feeRate,
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs})
|
||||
required this.outputs,
|
||||
this.onDispose})
|
||||
: recipientTitle = '' {
|
||||
recipientTitle = outputs.length > 1
|
||||
? S.current.transaction_details_recipient_address
|
||||
|
@ -156,6 +170,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
|||
|
||||
final String? paymentId;
|
||||
final String? paymentIdValue;
|
||||
final String? expirationTime;
|
||||
final String amount;
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
|
@ -164,6 +179,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
|||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
final Function? onDispose;
|
||||
|
||||
final double backgroundHeight = 160;
|
||||
final double thumbHeight = 72;
|
||||
|
@ -172,6 +188,12 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
|||
String recipientTitle;
|
||||
bool showScrollbar = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (onDispose != null) onDispose!();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
controller.addListener(() {
|
||||
|
@ -217,14 +239,18 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
|||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
paymentIdValue!,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
Container(
|
||||
width: 160,
|
||||
child: Text(
|
||||
paymentIdValue!,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
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(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
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,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:cake_wallet/routes.dart';
|
|||
import 'package:cake_wallet/src/widgets/setting_action_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/setting_actions.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/router.dart' as Router;
|
||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||
|
@ -10,7 +11,9 @@ import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
|||
final _settingsNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
class DesktopSettingsPage extends StatefulWidget {
|
||||
const DesktopSettingsPage({super.key});
|
||||
const DesktopSettingsPage(this.dashboardViewModel, {super.key});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
State<DesktopSettingsPage> createState() => _DesktopSettingsPageState();
|
||||
|
@ -51,6 +54,12 @@ class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
|
|||
padding: EdgeInsets.only(top: 0),
|
||||
itemBuilder: (_, index) {
|
||||
final item = SettingActions.desktopSettings[index];
|
||||
|
||||
if (!widget.dashboardViewModel.hasSilentPayments &&
|
||||
item.name(context) == S.of(context).silent_payments_settings) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
final isLastTile = index == itemCount - 1;
|
||||
return SettingActionButton(
|
||||
isLastTile: isLastTile,
|
||||
|
|
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());
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ class SettingActions {
|
|||
connectionSettingAction,
|
||||
walletSettingAction,
|
||||
addressBookSettingAction,
|
||||
silentPaymentsSettingAction,
|
||||
securityBackupSettingAction,
|
||||
privacySettingAction,
|
||||
displaySettingAction,
|
||||
|
|
|
@ -119,25 +119,27 @@ class ExceptionHandler {
|
|||
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(timeStamp) async {
|
||||
await showPopUp<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
builder: (context) {
|
||||
return AlertWithTwoActions(
|
||||
isDividerExist: true,
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: S.of(context).error_dialog_content,
|
||||
rightButtonText: S.of(context).send,
|
||||
leftButtonText: S.of(context).do_not_send,
|
||||
actionRightButton: () {
|
||||
Navigator.of(context).pop();
|
||||
_sendExceptionFile();
|
||||
},
|
||||
actionLeftButton: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (navigatorKey.currentContext != null) {
|
||||
await showPopUp<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
builder: (context) {
|
||||
return AlertWithTwoActions(
|
||||
isDividerExist: true,
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: S.of(context).error_dialog_content,
|
||||
rightButtonText: S.of(context).send,
|
||||
leftButtonText: S.of(context).do_not_send,
|
||||
actionRightButton: () {
|
||||
Navigator.of(context).pop();
|
||||
_sendExceptionFile();
|
||||
},
|
||||
actionLeftButton: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_hasError = false;
|
||||
},
|
||||
|
|
|
@ -10,10 +10,10 @@ class RouteAwareWidget extends StatefulWidget {
|
|||
this.popNextWidget});
|
||||
|
||||
final Widget child;
|
||||
final Function()? pushToWidget;
|
||||
final Function()? pushToNextWidget;
|
||||
final Function()? popWidget;
|
||||
final Function()? popNextWidget;
|
||||
final Function(BuildContext context)? pushToWidget;
|
||||
final Function(BuildContext context)? pushToNextWidget;
|
||||
final Function(BuildContext context)? popWidget;
|
||||
final Function(BuildContext context)? popNextWidget;
|
||||
|
||||
@override
|
||||
State<RouteAwareWidget> createState() => RouteAwareWidgetState();
|
||||
|
@ -35,28 +35,28 @@ class RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware {
|
|||
@override
|
||||
void didPush() {
|
||||
if (widget.pushToWidget != null) {
|
||||
widget.pushToWidget!();
|
||||
widget.pushToWidget!(context);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didPushNext() {
|
||||
if (widget.pushToNextWidget != null) {
|
||||
widget.pushToNextWidget!();
|
||||
widget.pushToNextWidget!(context);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didPop() {
|
||||
if (widget.popWidget != null) {
|
||||
widget.popWidget!();
|
||||
widget.popWidget!(context);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didPopNext() {
|
||||
if (widget.popNextWidget != null) {
|
||||
widget.popNextWidget!();
|
||||
widget.popNextWidget!(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
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';
|
||||
|
||||
part 'cake_features_view_model.g.dart';
|
||||
|
@ -6,11 +6,11 @@ part 'cake_features_view_model.g.dart';
|
|||
class CakeFeaturesViewModel = CakeFeaturesViewModelBase with _$CakeFeaturesViewModel;
|
||||
|
||||
abstract class CakeFeaturesViewModelBase with Store {
|
||||
final IoniaService _ioniaService;
|
||||
final CakePayService _cakePayService;
|
||||
|
||||
CakeFeaturesViewModelBase(this._ioniaService);
|
||||
CakeFeaturesViewModelBase(this._cakePayService);
|
||||
|
||||
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:device_display_brightness/device_display_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 DeviceDisplayBrightness.getBrightness();
|
||||
await DeviceDisplayBrightness.setBrightness(1.0);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue