mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Cw 78 ethereum (#862)
* Add initial flow for ethereum * Add initial create Eth wallet flow * Complete Ethereum wallet creation flow * Fix web3dart versioning issue * Add primary receive address extracted from private key * Implement open wallet functionality * Implement restore wallet from seed functionality * Fixate web3dart version as higher versions cause some issues * Add Initial Transaction priorities for eth Add estimated gas price * Rename priority value to tip * Re-order wallet types * Change ethereum node Fix connection issues * Fix estimating gas for priority * Add case for ethereum to fetch it's seeds * Add case for ethereum to request node * Fix Exchange screen initial pairs * Add initial send transaction flow * Add missing configure for ethereum class * Add Eth address initial setup * Fix Private key for Ethereum wallets * Change sign/send transaction flow * - Fix Conflicts with main - Remove unused function from Haven configure.dart * Add build command for ethereum package * Add missing Node list file to pubspec * - Fix balance display - Fix parsing of Ethereum amount - Add more Ethereum Nodes * - Fix extracting Ethereum Private key from seeds - Integrate signing/sending transaction with the send view model * - Update and Fix Conflicts with main * Add Balances for ERC20 tokens * Fix conflicts with main * Add erc20 abi json * Add send erc20 tokens initial function * add missing getHeightByDate in Haven * Allow contacts and wallets from the same tag * Add Shiba Inu icon * Add send ERC-20 tokens initial flow * Add missing import in generated file * Add initial approach for transaction sending for ERC-20 tokens * Refactor signing/sending transactions * Add initial flow for transactions subscription * Refactor signing/sending transactions * Add home settings icon * Fix conflicts with main * Initial flow for home settings * Add logic flow for adding erc20 tokens * Fix initial UI * Finalize UI for Tokens * Integrate UI with Ethereum flow * Add "Enable/Disable" feature for ERC20 tokens * Add initial Erc20 tokens * Add Sorting and Pin Native Token features * Fix price sorting * Sort tokens list as well when Sort criteria changes * - Improve sorting balances flow - Add initial add token from search bar flow * Fix Accounts Popup UI * Fix Pin native token * Fix Enabling/Disabling tokens Fix sorting by fiat once app is opened Improve token availability mechanism * Fix deleting token Fix renaming tokens * Fix issue with search * Add more tokens * - Fix scroll issue - Add ERC20 tokens placeholder image in picker * - Separate and organize default erc20 tokens - Fix scrolling - Add token placeholder images in picker - Sort disabled tokens alphabetically * Change BNB token initial availability * Fix Conflicts with main * Fix Conflicts with main * Add Verse ERC20 token to the initial tokens list * Add rename wallet to Ethereum * Integrate EtherScan API for fetching address transactions Generate Ethereum specific secrets in Ethereum package * Adjust transactions fiat price for ERC20 tokens * Free Up GitHub Actions Ubuntu Runner Disk Space * Free Up GitHub Actions Ubuntu Runner Disk space (trial 2) * Fix Transaction Fee display * Save transaction history * Enhance loading time for erc20 tokens transactions * Minor Fixes and Enhancements * Fix sending erc20 fix block explorer issue * Fix int overflow * Fix transaction amount conversions * Minor: `slow` -> `Slow` * Update build guide * Fix fetching fiat rate taking a lot of time by only fetching enabled tokens only and making the API calls in parallel not sequential * Update transactions on a periodic basis * For fee, use ETH spot price, not ERC-20 spot price * Add Etherscan History privacy option to enable/disable Etherscan API * Show estimated fee amounts in the send screen * fix send fiat fields parsing issue * Fix transactions estimated fee less than actual fee * handle balance sorting when balance is disabled Handle empty transactions list * Fix Delete Ethereum wallet Fix balance < 0.01 * Fix Decimal place for Ethereum amount Fix sending amount issue * Change words count * Remove balance hint and Full balance row from Ethereum wallets * support changing the asset type in send templates * Fix Templates for ERC tokens issues * Fix conflicts in send templates * Disable batch sending in Ethereum * Fix Fee calculation with different priorities * Fix Conflicts with main * Add offline error to ignored exceptions --------- Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>
This commit is contained in:
co-authored by
Justin Ehrenhofer
parent
4120394121
commit
3ce4000dcf
@@ -80,7 +80,7 @@ class CWBitcoin extends Bitcoin {
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||
.toList(),
|
||||
priority: priority != null ? priority as BitcoinTransactionPriority : null,
|
||||
priority: priority as BitcoinTransactionPriority,
|
||||
feeRate: feeRate);
|
||||
|
||||
@override
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
|
||||
class AddressValidator extends TextValidator {
|
||||
AddressValidator({required CryptoCurrency type})
|
||||
@@ -14,6 +15,9 @@ class AddressValidator extends TextValidator {
|
||||
length: getLength(type));
|
||||
|
||||
static String getPattern(CryptoCurrency type) {
|
||||
if (type is Erc20Token) {
|
||||
return '0x[0-9a-zA-Z]';
|
||||
}
|
||||
switch (type) {
|
||||
case CryptoCurrency.xmr:
|
||||
return '^4[0-9a-zA-Z]{94}\$|^8[0-9a-zA-Z]{94}\$|^[0-9a-zA-Z]{106}\$';
|
||||
@@ -56,6 +60,7 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.zrx:
|
||||
case CryptoCurrency.dydx:
|
||||
case CryptoCurrency.steth:
|
||||
case CryptoCurrency.shib:
|
||||
return '0x[0-9a-zA-Z]';
|
||||
case CryptoCurrency.xrp:
|
||||
return '^[0-9a-zA-Z]{34}\$|^X[0-9a-zA-Z]{46}\$';
|
||||
@@ -116,17 +121,14 @@ class AddressValidator extends TextValidator {
|
||||
}
|
||||
|
||||
static List<int>? getLength(CryptoCurrency type) {
|
||||
if (type is Erc20Token) {
|
||||
return [42];
|
||||
}
|
||||
switch (type) {
|
||||
case CryptoCurrency.xmr:
|
||||
return null;
|
||||
case CryptoCurrency.ada:
|
||||
return null;
|
||||
case CryptoCurrency.avaxc:
|
||||
return [42];
|
||||
case CryptoCurrency.bch:
|
||||
return [42];
|
||||
case CryptoCurrency.bnb:
|
||||
return [42];
|
||||
case CryptoCurrency.btc:
|
||||
return null;
|
||||
case CryptoCurrency.dash:
|
||||
@@ -166,6 +168,10 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.zrx:
|
||||
case CryptoCurrency.dydx:
|
||||
case CryptoCurrency.steth:
|
||||
case CryptoCurrency.shib:
|
||||
case CryptoCurrency.avaxc:
|
||||
case CryptoCurrency.bch:
|
||||
case CryptoCurrency.bnb:
|
||||
return [42];
|
||||
case CryptoCurrency.ltc:
|
||||
return [34, 43, 63];
|
||||
@@ -203,11 +209,8 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.xusd:
|
||||
return [98, 99, 106];
|
||||
case CryptoCurrency.btt:
|
||||
return [34];
|
||||
case CryptoCurrency.bttc:
|
||||
return [34];
|
||||
case CryptoCurrency.doge:
|
||||
return [34];
|
||||
case CryptoCurrency.firo:
|
||||
return [34];
|
||||
case CryptoCurrency.hbar:
|
||||
@@ -258,6 +261,8 @@ class AddressValidator extends TextValidator {
|
||||
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
|
||||
case CryptoCurrency.eth:
|
||||
return '0x[0-9a-zA-Z]{42}';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -240,6 +240,9 @@ class BackupService {
|
||||
data[PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets] as bool?;
|
||||
final shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
|
||||
data[PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings] as bool?;
|
||||
final sortBalanceTokensBy = data[PreferencesKey.sortBalanceBy] as int?;
|
||||
final pinNativeTokenAtTop = data[PreferencesKey.pinNativeTokenAtTop] as bool?;
|
||||
final useEtherscan = data[PreferencesKey.useEtherscan] as bool?;
|
||||
|
||||
await _sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName);
|
||||
|
||||
@@ -349,6 +352,15 @@ class BackupService {
|
||||
PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||
shouldRequireTOTP2FAForAllSecurityAndBackupSettings);
|
||||
|
||||
if (sortBalanceTokensBy != null)
|
||||
await _sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy);
|
||||
|
||||
if (pinNativeTokenAtTop != null)
|
||||
await _sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop);
|
||||
|
||||
if (useEtherscan != null)
|
||||
await _sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan);
|
||||
|
||||
await preferencesFile.delete();
|
||||
}
|
||||
|
||||
@@ -492,6 +504,12 @@ class BackupService {
|
||||
_sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets),
|
||||
PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings: _sharedPreferences
|
||||
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings),
|
||||
PreferencesKey.sortBalanceBy:
|
||||
_sharedPreferences.getInt(PreferencesKey.sortBalanceBy),
|
||||
PreferencesKey.pinNativeTokenAtTop:
|
||||
_sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop),
|
||||
PreferencesKey.useEtherscan:
|
||||
_sharedPreferences.getBool(PreferencesKey.useEtherscan),
|
||||
};
|
||||
|
||||
return json.encode(preferences);
|
||||
|
||||
@@ -5,21 +5,20 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
|
||||
const _fiatApiClearNetAuthority = 'fiat-api.cakewallet.com';
|
||||
const _fiatApiOnionAuthority = 'n4z7bdcmwk2oyddxvzaap3x2peqcplh3pzdy7tpkk5ejz5n4mhfvoxqd.onion';
|
||||
const _fiatApiPath = '/v2/rates';
|
||||
|
||||
Future<double> _fetchPrice(Map<String, dynamic> args) async {
|
||||
final crypto = args['crypto'] as CryptoCurrency;
|
||||
final fiat = args['fiat'] as FiatCurrency;
|
||||
final crypto = args['crypto'] as String;
|
||||
final fiat = args['fiat'] as String;
|
||||
final torOnly = args['torOnly'] as bool;
|
||||
|
||||
final Map<String, String> queryParams = {
|
||||
'interval_count': '1',
|
||||
'base': crypto.toString(),
|
||||
'quote': fiat.toString(),
|
||||
'key' : secrets.fiatApiKey,
|
||||
'base': crypto,
|
||||
'quote': fiat,
|
||||
'key': secrets.fiatApiKey,
|
||||
};
|
||||
|
||||
double price = 0.0;
|
||||
@@ -52,7 +51,11 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
|
||||
}
|
||||
|
||||
Future<double> _fetchPriceAsync(CryptoCurrency crypto, FiatCurrency fiat, bool torOnly) async =>
|
||||
compute(_fetchPrice, {'fiat': fiat, 'crypto': crypto, 'torOnly': torOnly});
|
||||
compute(_fetchPrice, {
|
||||
'fiat': fiat.toString(),
|
||||
'crypto': crypto.toString(),
|
||||
'torOnly': torOnly,
|
||||
});
|
||||
|
||||
class FiatConversionService {
|
||||
static Future<double> fetchPrice({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/haven/haven.dart';
|
||||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/entities/mnemonic_item.dart';
|
||||
@@ -25,6 +26,8 @@ class SeedValidator extends Validator<MnemonicItem> {
|
||||
return monero!.getMoneroWordList(language);
|
||||
case WalletType.haven:
|
||||
return haven!.getMoneroWordList(language);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.getEthereumWordList(language);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
+27
-6
@@ -7,6 +7,7 @@ import 'package:cake_wallet/core/yat_service.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/entities/receive_page_option.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_anypay.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_tip.dart';
|
||||
@@ -16,6 +17,8 @@ import 'package:cake_wallet/src/screens/buy/webview_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_dashboard_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||
@@ -40,6 +43,7 @@ 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/market_place_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
|
||||
@@ -70,6 +74,7 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_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:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/core/backup_service.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
@@ -239,9 +244,9 @@ Future setup({
|
||||
getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
|
||||
}
|
||||
|
||||
final isBitcoinBuyEnabled = (secrets.wyreSecretKey.isNotEmpty ?? false) &&
|
||||
(secrets.wyreApiKey.isNotEmpty ?? false) &&
|
||||
(secrets.wyreAccountId.isNotEmpty ?? false);
|
||||
final isBitcoinBuyEnabled = (secrets.wyreSecretKey.isNotEmpty) &&
|
||||
(secrets.wyreApiKey.isNotEmpty) &&
|
||||
(secrets.wyreAccountId.isNotEmpty);
|
||||
|
||||
final settingsStore = await SettingsStoreBase.load(
|
||||
nodeSource: _nodeSource,
|
||||
@@ -638,7 +643,7 @@ Future setup({
|
||||
});
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return PrivacySettingsViewModel(getIt.get<SettingsStore>());
|
||||
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() {
|
||||
@@ -745,6 +750,8 @@ Future setup({
|
||||
return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.createEthereumWalletService(_walletInfoSource);
|
||||
default:
|
||||
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
|
||||
}
|
||||
@@ -787,8 +794,8 @@ Future setup({
|
||||
transactionDetailsViewModel:
|
||||
getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
|
||||
|
||||
getIt.registerFactoryParam<NewWalletTypePage, void Function(BuildContext, WalletType), void>(
|
||||
(param1, _) => NewWalletTypePage(onTypeSelected: param1));
|
||||
getIt.registerFactoryParam<NewWalletTypePage, void Function(BuildContext, WalletType), bool?>(
|
||||
(param1, isCreate) => NewWalletTypePage(onTypeSelected: param1, isCreate: isCreate ?? true));
|
||||
|
||||
getIt.registerFactoryParam<PreSeedPage, WalletType, void>(
|
||||
(WalletType type, _) => PreSeedPage(type));
|
||||
@@ -1034,5 +1041,19 @@ Future setup({
|
||||
getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>(
|
||||
(type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactoryParam<HomeSettingsPage, BalanceViewModel, void>((balanceViewModel, _) =>
|
||||
HomeSettingsPage(getIt.get<HomeSettingsViewModel>(param1: balanceViewModel)));
|
||||
|
||||
getIt.registerFactoryParam<HomeSettingsViewModel, BalanceViewModel, void>(
|
||||
(balanceViewModel, _) => HomeSettingsViewModel(getIt.get<SettingsStore>(), balanceViewModel));
|
||||
|
||||
getIt.registerFactoryParam<EditTokenPage, HomeSettingsViewModel, Map<String, dynamic>>(
|
||||
(homeSettingsViewModel, arguments) => EditTokenPage(
|
||||
homeSettingsViewModel: homeSettingsViewModel,
|
||||
erc20token: arguments['token'] as Erc20Token?,
|
||||
initialContractAddress: arguments['contractAddress'] as String?,
|
||||
),
|
||||
);
|
||||
|
||||
_isSetupFinished = true;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
|
||||
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
|
||||
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
|
||||
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
|
||||
const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
|
||||
|
||||
Future defaultSettingsMigration(
|
||||
{required int version,
|
||||
@@ -157,6 +158,12 @@ Future defaultSettingsMigration(
|
||||
case 20:
|
||||
await migrateExchangeStatus(sharedPreferences);
|
||||
break;
|
||||
case 21:
|
||||
await addEthereumNodeList(nodes: nodes);
|
||||
await changeEthereumCurrentNodeToDefault(
|
||||
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -242,6 +249,12 @@ Node? getHavenDefaultNode({required Box<Node> nodes}) {
|
||||
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
|
||||
}
|
||||
|
||||
Node? getEthereumDefaultNode({required Box<Node> nodes}) {
|
||||
return nodes.values.firstWhereOrNull(
|
||||
(Node node) => node.uriRaw == ethereumDefaultNodeUri)
|
||||
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
|
||||
}
|
||||
|
||||
Node getMoneroDefaultNode({required Box<Node> nodes}) {
|
||||
final timeZone = DateTime.now().timeZoneOffset.inHours;
|
||||
var nodeUri = '';
|
||||
@@ -438,6 +451,8 @@ Future<void> checkCurrentNodes(
|
||||
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
||||
final currentHavenNodeId = sharedPreferences
|
||||
.getInt(PreferencesKey.currentHavenNodeIdKey);
|
||||
final currentEthereumNodeId = sharedPreferences
|
||||
.getInt(PreferencesKey.currentEthereumNodeIdKey);
|
||||
final currentMoneroNode = nodeSource.values.firstWhereOrNull(
|
||||
(node) => node.key == currentMoneroNodeId);
|
||||
final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull(
|
||||
@@ -446,6 +461,8 @@ Future<void> checkCurrentNodes(
|
||||
(node) => node.key == currentLitecoinElectrumSeverId);
|
||||
final currentHavenNodeServer = nodeSource.values.firstWhereOrNull(
|
||||
(node) => node.key == currentHavenNodeId);
|
||||
final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull(
|
||||
(node) => node.key == currentEthereumNodeId);
|
||||
|
||||
if (currentMoneroNode == null) {
|
||||
final newCakeWalletNode =
|
||||
@@ -479,6 +496,13 @@ Future<void> checkCurrentNodes(
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentHavenNodeIdKey, node.key as int);
|
||||
}
|
||||
|
||||
if (currentEthereumNodeServer == null) {
|
||||
final node = Node(uri: ethereumDefaultNodeUri, type: WalletType.ethereum);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentEthereumNodeIdKey, node.key as int);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> resetBitcoinElectrumServer(
|
||||
@@ -522,8 +546,26 @@ Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
|
||||
return;
|
||||
}
|
||||
|
||||
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled
|
||||
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled
|
||||
? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
|
||||
|
||||
|
||||
await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
|
||||
}
|
||||
|
||||
Future<void> addEthereumNodeList({required Box<Node> nodes}) async {
|
||||
final nodeList = await loadDefaultEthereumNodes();
|
||||
for (var node in nodeList) {
|
||||
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
|
||||
await nodes.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> changeEthereumCurrentNodeToDefault(
|
||||
{required SharedPreferences sharedPreferences,
|
||||
required Box<Node> nodes}) async {
|
||||
final node = getEthereumDefaultNode(nodes: nodes);
|
||||
final nodeId = node?.key as int? ?? 0;
|
||||
|
||||
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ class MainActions {
|
||||
switch (walletType) {
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.ethereum:
|
||||
if (viewModel.isEnabledBuyAction) {
|
||||
final uri = getIt.get<OnRamperBuyProvider>().requestUrl();
|
||||
if (DeviceInfo.instance.isMobile) {
|
||||
@@ -116,6 +117,7 @@ class MainActions {
|
||||
switch (walletType) {
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.ethereum:
|
||||
if (viewModel.isEnabledSellAction) {
|
||||
final moonPaySellProvider = MoonPaySellProvider();
|
||||
final uri = await moonPaySellProvider.requestUrl(
|
||||
|
||||
@@ -70,6 +70,22 @@ Future<List<Node>> loadDefaultHavenNodes() async {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadDefaultEthereumNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/ethereum_server_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.ethereum;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future resetToDefault(Box<Node> nodeSource) async {
|
||||
final moneroNodes = await loadDefaultNodes();
|
||||
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
|
||||
|
||||
@@ -5,6 +5,7 @@ class PreferencesKey {
|
||||
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
|
||||
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
|
||||
static const currentHavenNodeIdKey = 'current_node_id_xhv';
|
||||
static const currentEthereumNodeIdKey = 'current_node_id_eth';
|
||||
static const currentFiatCurrencyKey = 'current_fiat_currency';
|
||||
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
|
||||
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
||||
@@ -31,6 +32,7 @@ class PreferencesKey {
|
||||
static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin';
|
||||
static const havenTransactionPriority = 'current_fee_priority_haven';
|
||||
static const litecoinTransactionPriority = 'current_fee_priority_litecoin';
|
||||
static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
|
||||
static const shouldShowReceiveWarning = 'should_show_receive_warning';
|
||||
static const shouldShowYatPopup = 'should_show_yat_popup';
|
||||
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
|
||||
@@ -38,6 +40,9 @@ class PreferencesKey {
|
||||
static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds';
|
||||
static const lastPopupDate = 'last_popup_date';
|
||||
static const lastAppReviewDate = 'last_app_review_date';
|
||||
static const sortBalanceBy = 'sort_balance_by';
|
||||
static const pinNativeTokenAtTop = 'pin_native_token_at_top';
|
||||
static const useEtherscan = 'use_etherscan';
|
||||
|
||||
static String moneroWalletUpdateV1Key(String name) =>
|
||||
'${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/haven/haven.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
@@ -14,6 +15,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
|
||||
return bitcoin!.getLitecoinTransactionPriorities();
|
||||
case WalletType.haven:
|
||||
return haven!.getTransactionPriorities();
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.getTransactionPriorities();
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
enum SortBalanceBy {
|
||||
FiatBalance,
|
||||
GrossBalance,
|
||||
Alphabetical;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
switch (this) {
|
||||
case SortBalanceBy.FiatBalance:
|
||||
return S.current.fiat_balance;
|
||||
case SortBalanceBy.GrossBalance:
|
||||
return S.current.gross_balance;
|
||||
case SortBalanceBy.Alphabetical:
|
||||
return S.current.alphabetical;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,5 +55,5 @@ class Template extends HiveObject {
|
||||
|
||||
String get amount => amountRaw ?? '';
|
||||
|
||||
List<Template>? get additionalRecipients => additionalRecipientsRaw ?? null;
|
||||
List<Template>? get additionalRecipients => additionalRecipientsRaw;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
part of 'ethereum.dart';
|
||||
|
||||
class CWEthereum extends Ethereum {
|
||||
@override
|
||||
List<String> getEthereumWordList(String language) => EthereumMnemonics.englishWordlist;
|
||||
|
||||
WalletService createEthereumWalletService(Box<WalletInfo> walletInfoSource) =>
|
||||
EthereumWalletService(walletInfoSource);
|
||||
|
||||
@override
|
||||
WalletCredentials createEthereumNewWalletCredentials({
|
||||
required String name,
|
||||
WalletInfo? walletInfo,
|
||||
}) =>
|
||||
EthereumNewWalletCredentials(name: name, walletInfo: walletInfo);
|
||||
|
||||
@override
|
||||
WalletCredentials createEthereumRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String mnemonic,
|
||||
required String password,
|
||||
}) =>
|
||||
EthereumRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
|
||||
|
||||
@override
|
||||
String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;
|
||||
|
||||
@override
|
||||
TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium;
|
||||
|
||||
@override
|
||||
List<TransactionPriority> getTransactionPriorities() => EthereumTransactionPriority.all;
|
||||
|
||||
@override
|
||||
TransactionPriority deserializeEthereumTransactionPriority(int raw) =>
|
||||
EthereumTransactionPriority.deserialize(raw: raw);
|
||||
|
||||
Object createEthereumTransactionCredentials(
|
||||
List<Output> outputs, {
|
||||
required TransactionPriority priority,
|
||||
required CryptoCurrency currency,
|
||||
int? feeRate,
|
||||
}) =>
|
||||
EthereumTransactionCredentials(
|
||||
outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||
.toList(),
|
||||
priority: priority as EthereumTransactionPriority,
|
||||
currency: currency,
|
||||
feeRate: feeRate,
|
||||
);
|
||||
|
||||
Object createEthereumTransactionCredentialsRaw(
|
||||
List<OutputInfo> outputs, {
|
||||
TransactionPriority? priority,
|
||||
required CryptoCurrency currency,
|
||||
required int feeRate,
|
||||
}) =>
|
||||
EthereumTransactionCredentials(
|
||||
outputs,
|
||||
priority: priority as EthereumTransactionPriority?,
|
||||
currency: currency,
|
||||
feeRate: feeRate,
|
||||
);
|
||||
|
||||
@override
|
||||
int formatterEthereumParseAmount(String amount) => EthereumFormatter.parseEthereumAmount(amount);
|
||||
|
||||
@override
|
||||
double formatterEthereumAmountToDouble(
|
||||
{TransactionInfo? transaction, BigInt? amount, int exponent = 18}) {
|
||||
assert(transaction != null || amount != null);
|
||||
|
||||
if (transaction != null) {
|
||||
transaction as EthereumTransactionInfo;
|
||||
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
|
||||
} else {
|
||||
return (amount!) / BigInt.from(10).pow(exponent);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<Erc20Token> getERC20Currencies(WalletBase wallet) {
|
||||
final ethereumWallet = wallet as EthereumWallet;
|
||||
return ethereumWallet.erc20Currencies;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addErc20Token(WalletBase wallet, Erc20Token token) async =>
|
||||
await (wallet as EthereumWallet).addErc20Token(token);
|
||||
|
||||
@override
|
||||
Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token) async =>
|
||||
await (wallet as EthereumWallet).deleteErc20Token(token);
|
||||
|
||||
@override
|
||||
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
|
||||
final ethereumWallet = wallet as EthereumWallet;
|
||||
return await ethereumWallet.getErc20Token(contractAddress);
|
||||
}
|
||||
|
||||
@override
|
||||
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
|
||||
transaction as EthereumTransactionInfo;
|
||||
if (transaction.tokenSymbol == CryptoCurrency.eth.title) {
|
||||
return CryptoCurrency.eth;
|
||||
}
|
||||
|
||||
wallet as EthereumWallet;
|
||||
return wallet.erc20Currencies
|
||||
.firstWhere((element) => transaction.tokenSymbol == element.symbol);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) {
|
||||
(wallet as EthereumWallet).updateEtherscanUsageState(isEnabled);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -141,7 +141,7 @@ Future<void> main() async {
|
||||
transactionDescriptions: transactionDescriptions,
|
||||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 19);
|
||||
initialMigrationVersion: 21);
|
||||
runApp(App());
|
||||
}, (error, stackTrace) async {
|
||||
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace));
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
@@ -31,6 +32,20 @@ Future<void> startFiatRateUpdate(
|
||||
fiat: settingsStore.fiatCurrency,
|
||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||
}
|
||||
|
||||
if (appStore.wallet!.type == WalletType.ethereum) {
|
||||
final currencies =
|
||||
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
||||
|
||||
for (final currency in currencies) {
|
||||
() async {
|
||||
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
|
||||
crypto: currency,
|
||||
fiat: settingsStore.fiatCurrency,
|
||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||
}.call();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
@@ -97,6 +97,20 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
||||
crypto: wallet.currency,
|
||||
fiat: settingsStore.fiatCurrency,
|
||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||
|
||||
if (wallet.type == WalletType.ethereum) {
|
||||
final currencies =
|
||||
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
||||
|
||||
for (final currency in currencies) {
|
||||
() async {
|
||||
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
|
||||
crypto: currency,
|
||||
fiat: settingsStore.fiatCurrency,
|
||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||
}.call();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
+29
-8
@@ -10,6 +10,8 @@ import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/webview_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||
@@ -313,7 +315,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => getIt.get<SecurityBackupPage>());
|
||||
|
||||
|
||||
case Routes.privacyPage:
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
@@ -328,7 +330,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => getIt.get<OtherSettingsPage>());
|
||||
|
||||
|
||||
case Routes.newNode:
|
||||
final args = settings.arguments as Map<String, dynamic>?;
|
||||
return CupertinoPageRoute<void>(
|
||||
@@ -336,7 +338,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
param1: args?['editingNode'] as Node?,
|
||||
param2: args?['isSelected'] as bool?));
|
||||
|
||||
|
||||
|
||||
|
||||
case Routes.accountCreation:
|
||||
return CupertinoPageRoute<String>(
|
||||
@@ -466,7 +468,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => getIt.get<IoniaWelcomePage>(),
|
||||
);
|
||||
|
||||
|
||||
case Routes.ioniaLoginPage:
|
||||
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>());
|
||||
|
||||
@@ -480,7 +482,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
case Routes.ioniaBuyGiftCardPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
|
||||
|
||||
|
||||
case Routes.ioniaBuyGiftCardDetailPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
|
||||
@@ -497,7 +499,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
|
||||
case Routes.ioniaAccountPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountPage>());
|
||||
|
||||
|
||||
case Routes.ioniaAccountCardsPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountCardsPage>());
|
||||
|
||||
@@ -508,11 +510,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
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));
|
||||
@@ -584,6 +586,25 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
case Routes.modify2FAPage:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<Modify2FAPage>());
|
||||
|
||||
case Routes.homeSettings:
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<HomeSettingsPage>(param1: settings.arguments),
|
||||
);
|
||||
|
||||
case Routes.editToken:
|
||||
final args = settings.arguments as Map<String, dynamic>;
|
||||
|
||||
return CupertinoPageRoute<void>(
|
||||
settings: RouteSettings(name: Routes.editToken),
|
||||
builder: (_) => getIt.get<EditTokenPage>(
|
||||
param1: args['homeSettingsViewModel'],
|
||||
param2: {
|
||||
'token': args['token'],
|
||||
'contractAddress': args['contractAddress'],
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
default:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => Scaffold(
|
||||
|
||||
@@ -88,4 +88,6 @@ class Routes {
|
||||
static const setup_2faQRPage = '/setup_2fa_qr_page';
|
||||
static const totpAuthCodePage = '/totp_auth_code_page';
|
||||
static const modify2FAPage = '/modify_2fa_page';
|
||||
static const homeSettings = '/home_settings';
|
||||
static const editToken = '/edit_token';
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
||||
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
||||
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
|
||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||
|
||||
Image _newWalletImage(BuildContext context) => Image.asset(
|
||||
@@ -136,6 +137,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||
return litecoinIcon;
|
||||
case WalletType.haven:
|
||||
return havenIcon;
|
||||
case WalletType.ethereum:
|
||||
return ethereumIcon;
|
||||
default:
|
||||
return nonWalletTypeIcon;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/address_text_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/checkbox_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/view_model/dashboard/home_settings_view_model.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class EditTokenPage extends BasePage {
|
||||
EditTokenPage({
|
||||
Key? key,
|
||||
required this.homeSettingsViewModel,
|
||||
this.erc20token,
|
||||
this.initialContractAddress,
|
||||
}) : assert(erc20token == null || initialContractAddress == null);
|
||||
|
||||
final HomeSettingsViewModel homeSettingsViewModel;
|
||||
final Erc20Token? erc20token;
|
||||
final String? initialContractAddress;
|
||||
|
||||
@override
|
||||
String? get title => S.current.edit_token;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return EditTokenPageBody(
|
||||
homeSettingsViewModel: homeSettingsViewModel,
|
||||
erc20token: erc20token,
|
||||
initialContractAddress: initialContractAddress,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EditTokenPageBody extends StatefulWidget {
|
||||
const EditTokenPageBody({
|
||||
Key? key,
|
||||
required this.homeSettingsViewModel,
|
||||
this.erc20token,
|
||||
this.initialContractAddress,
|
||||
}) : super(key: key);
|
||||
|
||||
final HomeSettingsViewModel homeSettingsViewModel;
|
||||
final Erc20Token? erc20token;
|
||||
final String? initialContractAddress;
|
||||
|
||||
@override
|
||||
State<EditTokenPageBody> createState() => _EditTokenPageBodyState();
|
||||
}
|
||||
|
||||
class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
final TextEditingController _contractAddressController = TextEditingController();
|
||||
final TextEditingController _tokenNameController = TextEditingController();
|
||||
final TextEditingController _tokenSymbolController = TextEditingController();
|
||||
final TextEditingController _tokenDecimalController = TextEditingController();
|
||||
|
||||
final FocusNode _contractAddressFocusNode = FocusNode();
|
||||
final FocusNode _tokenNameFocusNode = FocusNode();
|
||||
final FocusNode _tokenSymbolFocusNode = FocusNode();
|
||||
final FocusNode _tokenDecimalFocusNode = FocusNode();
|
||||
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
bool _showDisclaimer = false;
|
||||
bool _disclaimerChecked = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.erc20token != null) {
|
||||
_contractAddressController.text = widget.erc20token!.contractAddress;
|
||||
_tokenNameController.text = widget.erc20token!.name;
|
||||
_tokenSymbolController.text = widget.erc20token!.symbol;
|
||||
_tokenDecimalController.text = widget.erc20token!.decimal.toString();
|
||||
}
|
||||
|
||||
if (widget.initialContractAddress != null) {
|
||||
_contractAddressController.text = widget.initialContractAddress!;
|
||||
_getTokenInfo();
|
||||
}
|
||||
|
||||
_contractAddressFocusNode.addListener(() {
|
||||
if (!_contractAddressFocusNode.hasFocus) {
|
||||
_getTokenInfo();
|
||||
}
|
||||
|
||||
final contractAddress = _contractAddressController.text;
|
||||
if (contractAddress.isNotEmpty && contractAddress != widget.erc20token?.contractAddress) {
|
||||
setState(() {
|
||||
_showDisclaimer = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 25),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 28),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset('assets/images/restore_keys.png'),
|
||||
const SizedBox(width: 24),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).warning,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
S.of(context).add_token_warning,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).primaryTextTheme.labelSmall!.color!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 50),
|
||||
_tokenForm(),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
if (_showDisclaimer) ...[
|
||||
CheckboxWidget(
|
||||
value: _disclaimerChecked,
|
||||
caption: S.of(context).add_token_disclaimer_check,
|
||||
onChanged: (value) {
|
||||
_disclaimerChecked = value;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: () async {
|
||||
if (widget.erc20token != null) {
|
||||
await widget.homeSettingsViewModel.deleteErc20Token(widget.erc20token!);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
text: widget.erc20token != null ? S.of(context).delete : S.of(context).cancel,
|
||||
color: Colors.red,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 20),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState!.validate() &&
|
||||
(!_showDisclaimer || _disclaimerChecked)) {
|
||||
await widget.homeSettingsViewModel.addErc20Token(Erc20Token(
|
||||
name: _tokenNameController.text,
|
||||
symbol: _tokenSymbolController.text,
|
||||
contractAddress: _contractAddressController.text,
|
||||
decimal: int.parse(_tokenDecimalController.text),
|
||||
));
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
text: S.of(context).save,
|
||||
color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _getTokenInfo() async {
|
||||
if (_contractAddressController.text.isNotEmpty) {
|
||||
final token =
|
||||
await widget.homeSettingsViewModel.getErc20Token(_contractAddressController.text);
|
||||
|
||||
if (token != null) {
|
||||
if (_tokenNameController.text.isEmpty) _tokenNameController.text = token.name;
|
||||
if (_tokenSymbolController.text.isEmpty) _tokenSymbolController.text = token.symbol;
|
||||
if (_tokenDecimalController.text.isEmpty)
|
||||
_tokenDecimalController.text = token.decimal.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pasteText() async {
|
||||
final value = await Clipboard.getData('text/plain');
|
||||
|
||||
if (value?.text?.isNotEmpty ?? false) {
|
||||
_contractAddressController.text = value!.text!;
|
||||
|
||||
_getTokenInfo();
|
||||
setState(() {
|
||||
_showDisclaimer = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _tokenForm() {
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
AddressTextField(
|
||||
controller: _contractAddressController,
|
||||
focusNode: _contractAddressFocusNode,
|
||||
placeholder: S.of(context).token_contract_address,
|
||||
options: [AddressTextFieldOption.paste],
|
||||
buttonColor: Theme.of(context).hintColor,
|
||||
validator: AddressValidator(type: widget.homeSettingsViewModel.nativeToken),
|
||||
onPushPasteButton: (_) {
|
||||
_pasteText();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
BaseTextFormField(
|
||||
controller: _tokenNameController,
|
||||
focusNode: _tokenNameFocusNode,
|
||||
onSubmit: (_) => FocusScope.of(context).requestFocus(_tokenSymbolFocusNode),
|
||||
textInputAction: TextInputAction.next,
|
||||
hintText: S.of(context).token_name,
|
||||
validator: (text) {
|
||||
if (text?.isNotEmpty ?? false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return S.of(context).field_required;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
BaseTextFormField(
|
||||
controller: _tokenSymbolController,
|
||||
focusNode: _tokenSymbolFocusNode,
|
||||
onSubmit: (_) => FocusScope.of(context).requestFocus(_tokenDecimalFocusNode),
|
||||
textInputAction: TextInputAction.next,
|
||||
hintText: S.of(context).token_symbol,
|
||||
validator: (text) {
|
||||
if (text?.isNotEmpty ?? false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return S.of(context).field_required;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
BaseTextFormField(
|
||||
controller: _tokenDecimalController,
|
||||
focusNode: _tokenDecimalFocusNode,
|
||||
textInputAction: TextInputAction.done,
|
||||
hintText: S.of(context).token_decimal,
|
||||
validator: (text) {
|
||||
if (text?.isEmpty ?? true) {
|
||||
return S.of(context).field_required;
|
||||
}
|
||||
if (int.tryParse(text!) == null) {
|
||||
return S.of(context).invalid_input;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/entities/sort_balance_types.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/settings/widgets/settings_picker_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class HomeSettingsPage extends BasePage {
|
||||
HomeSettingsPage(this._homeSettingsViewModel);
|
||||
|
||||
final HomeSettingsViewModel _homeSettingsViewModel;
|
||||
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
String? get title => S.current.home_screen_settings;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => SettingsPickerCell<SortBalanceBy>(
|
||||
title: S.current.sort_by,
|
||||
items: SortBalanceBy.values,
|
||||
selectedItem: _homeSettingsViewModel.sortBalanceBy,
|
||||
onItemSelected: _homeSettingsViewModel.setSortBalanceBy,
|
||||
),
|
||||
),
|
||||
Divider(color: Theme.of(context).primaryTextTheme.bodySmall!.decorationColor!),
|
||||
Observer(
|
||||
builder: (_) => SettingsSwitcherCell(
|
||||
title: S.of(context).pin_at_top(_homeSettingsViewModel.nativeToken.title),
|
||||
value: _homeSettingsViewModel.pinNativeToken,
|
||||
onValueChange: (_, bool value) {
|
||||
_homeSettingsViewModel.setPinNativeToken(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
Divider(color: Theme.of(context).primaryTextTheme.bodySmall!.decorationColor!),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 16),
|
||||
child: TextFormField(
|
||||
controller: _searchController,
|
||||
style: TextStyle(color: Theme.of(context).primaryTextTheme.titleLarge!.color!),
|
||||
decoration: InputDecoration(
|
||||
hintText: S.of(context).search_add_token,
|
||||
prefixIcon: Image.asset("assets/images/search_icon.png"),
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).accentTextTheme.displaySmall!.color!,
|
||||
alignLabelWithHint: false,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
borderSide: const BorderSide(color: Colors.transparent),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
borderSide: const BorderSide(color: Colors.transparent),
|
||||
),
|
||||
),
|
||||
onChanged: (String text) => _homeSettingsViewModel.changeSearchText(text),
|
||||
),
|
||||
),
|
||||
),
|
||||
RawMaterialButton(
|
||||
onPressed: () async {
|
||||
Navigator.pushNamed(context, Routes.editToken, arguments: {
|
||||
'homeSettingsViewModel': _homeSettingsViewModel,
|
||||
if (AddressValidator(type: _homeSettingsViewModel.nativeToken)
|
||||
.isValid(_searchController.text))
|
||||
'contractAddress': _searchController.text,
|
||||
});
|
||||
},
|
||||
elevation: 0,
|
||||
fillColor: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||
size: 22.0,
|
||||
),
|
||||
padding: EdgeInsets.all(12),
|
||||
shape: CircleBorder(),
|
||||
splashColor: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16, left: 16, right: 16),
|
||||
child: Observer(
|
||||
builder: (_) => ListView.builder(
|
||||
itemCount: _homeSettingsViewModel.tokens.length,
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 16),
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
final token = _homeSettingsViewModel.tokens.elementAt(index);
|
||||
|
||||
return SettingsSwitcherCell(
|
||||
title: "${token.name} "
|
||||
"(${token.symbol})",
|
||||
value: token.enabled,
|
||||
onValueChange: (_, bool value) {
|
||||
_homeSettingsViewModel.changeTokenAvailability(token, value);
|
||||
},
|
||||
onTap: (_) {
|
||||
Navigator.pushNamed(context, Routes.editToken, arguments: {
|
||||
'homeSettingsViewModel': _homeSettingsViewModel,
|
||||
'token': token,
|
||||
});
|
||||
},
|
||||
leading: token.iconPath != null
|
||||
? Container(
|
||||
child: Image.asset(
|
||||
token.iconPath!,
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
token.symbol.substring(0, min(token.symbol.length, 2)),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -27,15 +27,15 @@ class AddressPage extends BasePage {
|
||||
required this.addressListViewModel,
|
||||
required this.dashboardViewModel,
|
||||
required this.receiveOptionViewModel,
|
||||
}) : _cryptoAmountFocus = FocusNode(),
|
||||
_formKey = GlobalKey<FormState>(),
|
||||
_amountController = TextEditingController(){
|
||||
_amountController.addListener(() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
addressListViewModel.changeAmount(
|
||||
_amountController.text,
|
||||
);
|
||||
}
|
||||
}) : _cryptoAmountFocus = FocusNode(),
|
||||
_formKey = GlobalKey<FormState>(),
|
||||
_amountController = TextEditingController() {
|
||||
_amountController.addListener(() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
addressListViewModel.changeAmount(
|
||||
_amountController.text,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,15 +63,11 @@ class AddressPage extends BasePage {
|
||||
Widget? leading(BuildContext context) {
|
||||
final _backButton = Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
size: 16,
|
||||
);
|
||||
final _closeButton = currentTheme.type == ThemeType.dark
|
||||
? closeButtonImageDarkTheme
|
||||
: closeButtonImage;
|
||||
final _closeButton =
|
||||
currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
|
||||
|
||||
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
|
||||
|
||||
@@ -82,13 +78,10 @@ class AddressPage extends BasePage {
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: Semantics(
|
||||
label: !isMobileView
|
||||
? S.of(context).close
|
||||
: S.of(context).seed_alert_back,
|
||||
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
overlayColor: MaterialStateColor.resolveWith(
|
||||
(states) => Colors.transparent),
|
||||
overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent),
|
||||
),
|
||||
onPressed: () => onClose(context),
|
||||
child: !isMobileView ? _closeButton : _backButton,
|
||||
@@ -100,8 +93,7 @@ class AddressPage extends BasePage {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) =>
|
||||
PresentReceiveOptionPicker(
|
||||
Widget middle(BuildContext context) => PresentReceiveOptionPicker(
|
||||
receiveOptionViewModel: receiveOptionViewModel,
|
||||
hasWhiteBackground: currentTheme.type == ThemeType.light,
|
||||
);
|
||||
@@ -136,10 +128,7 @@ class AddressPage extends BasePage {
|
||||
icon: Icon(
|
||||
Icons.share,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -180,10 +169,7 @@ class AddressPage extends BasePage {
|
||||
tapOutsideToDismiss: true,
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.bodyLarge!
|
||||
.backgroundColor!,
|
||||
keyboardBarColor: Theme.of(context).accentTextTheme.bodyLarge!.backgroundColor!,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
@@ -205,62 +191,54 @@ class AddressPage extends BasePage {
|
||||
isLight: dashboardViewModel.settingsStore.currentTheme.type ==
|
||||
ThemeType.light))),
|
||||
Observer(builder: (_) {
|
||||
return addressListViewModel.hasAddressList
|
||||
? GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
|
||||
child: Container(
|
||||
height: 50,
|
||||
padding: EdgeInsets.only(left: 24, right: 12),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(25)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.titleMedium!
|
||||
.color!,
|
||||
width: 1),
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.titleLarge!
|
||||
.backgroundColor!),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) => Text(
|
||||
addressListViewModel.hasAccounts
|
||||
? S.of(context).accounts_subaddresses
|
||||
: S.of(context).addresses,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!),
|
||||
)),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: Text(S.of(context).electrum_address_disclaimer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displaySmall!
|
||||
.backgroundColor!));
|
||||
if (addressListViewModel.hasAddressList) {
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
|
||||
child: Container(
|
||||
height: 50,
|
||||
padding: EdgeInsets.only(left: 24, right: 12),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(25)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).textTheme.titleMedium!.color!, width: 1),
|
||||
color: Theme.of(context).textTheme.titleLarge!.backgroundColor!),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) => Text(
|
||||
addressListViewModel.hasAccounts
|
||||
? S.of(context).accounts_subaddresses
|
||||
: S.of(context).addresses,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme.displayMedium!
|
||||
.backgroundColor!),
|
||||
)),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color:
|
||||
Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (addressListViewModel.showElectrumAddressDisclaimer) {
|
||||
return Text(S.of(context).electrum_address_disclaimer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color:
|
||||
Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!));
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
@@ -20,51 +20,78 @@ class BalancePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||
!dashboardViewModel.balanceViewModel.isReversing,
|
||||
onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||
!dashboardViewModel.balanceViewModel.isReversing,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(height: 56),
|
||||
Container(
|
||||
onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||
!dashboardViewModel.balanceViewModel.isReversing,
|
||||
onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||
!dashboardViewModel.balanceViewModel.isReversing,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 56),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 24, bottom: 16),
|
||||
child: Observer(builder: (_) {
|
||||
return Text(dashboardViewModel.balanceViewModel.asset,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center);
|
||||
})),
|
||||
Observer(builder: (_) {
|
||||
if (dashboardViewModel.balanceViewModel.isShowCard && FeatureFlag.isCakePayEnabled) {
|
||||
return IntroducingCard(
|
||||
title: S.of(context).introducing_cake_pay,
|
||||
subTitle: S.of(context).cake_pay_learn_more,
|
||||
borderColor: settingsStore.currentTheme.type == ThemeType.bright
|
||||
? Color.fromRGBO(255, 255, 255, 0.2)
|
||||
: Colors.transparent,
|
||||
closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard);
|
||||
}
|
||||
return Container();
|
||||
}),
|
||||
Observer(builder: (_) {
|
||||
return ListView.separated(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)),
|
||||
itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length,
|
||||
itemBuilder: (__, index) {
|
||||
final balance =
|
||||
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
||||
return buildBalanceRow(context,
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
dashboardViewModel.balanceViewModel.asset,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (dashboardViewModel.balanceViewModel.isHomeScreenSettingsEnabled)
|
||||
InkWell(
|
||||
onTap: () => Navigator.pushNamed(context, Routes.homeSettings,
|
||||
arguments: dashboardViewModel.balanceViewModel),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Image.asset(
|
||||
'assets/images/home_screen_settings_icon.png',
|
||||
color:
|
||||
Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
if (dashboardViewModel.balanceViewModel.isShowCard &&
|
||||
FeatureFlag.isCakePayEnabled) {
|
||||
return IntroducingCard(
|
||||
title: S.of(context).introducing_cake_pay,
|
||||
subTitle: S.of(context).cake_pay_learn_more,
|
||||
borderColor: settingsStore.currentTheme.type == ThemeType.bright
|
||||
? Color.fromRGBO(255, 255, 255, 0.2)
|
||||
: Colors.transparent,
|
||||
closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard);
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return ListView.separated(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)),
|
||||
itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length,
|
||||
itemBuilder: (__, index) {
|
||||
final balance =
|
||||
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
||||
return buildBalanceRow(
|
||||
context,
|
||||
availableBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
||||
availableBalance: balance.availableBalance,
|
||||
@@ -75,45 +102,57 @@ class BalancePage extends StatelessWidget {
|
||||
additionalFiatBalance: balance.fiatAdditionalBalance,
|
||||
frozenBalance: balance.frozenBalance,
|
||||
frozenFiatBalance: balance.fiatFrozenBalance,
|
||||
currency: balance.formattedAssetTitle);
|
||||
});
|
||||
})
|
||||
])));
|
||||
currency: balance.formattedAssetTitle,
|
||||
hasAdditionalBalance:
|
||||
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBalanceRow(BuildContext context,
|
||||
{required String availableBalanceLabel,
|
||||
required String availableBalance,
|
||||
required String availableFiatBalance,
|
||||
required String additionalBalanceLabel,
|
||||
required String additionalBalance,
|
||||
required String additionalFiatBalance,
|
||||
required String frozenBalance,
|
||||
required String frozenFiatBalance,
|
||||
required String currency}) {
|
||||
Widget buildBalanceRow(
|
||||
BuildContext context, {
|
||||
required String availableBalanceLabel,
|
||||
required String availableBalance,
|
||||
required String availableFiatBalance,
|
||||
required String additionalBalanceLabel,
|
||||
required String additionalBalance,
|
||||
required String additionalFiatBalance,
|
||||
required String frozenBalance,
|
||||
required String frozenFiatBalance,
|
||||
required String currency,
|
||||
required bool hasAdditionalBalance,
|
||||
}) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
border: Border.all(
|
||||
color: settingsStore.currentTheme.type == ThemeType.bright
|
||||
? Color.fromRGBO(255, 255, 255, 0.2)
|
||||
: Colors.transparent,
|
||||
width: 1,
|
||||
),
|
||||
color: Theme.of(context).textTheme!.titleLarge!.backgroundColor!),
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
border: Border.all(
|
||||
color: settingsStore.currentTheme.type == ThemeType.bright
|
||||
? Color.fromRGBO(255, 255, 255, 0.2)
|
||||
: Colors.transparent,
|
||||
width: 1,
|
||||
),
|
||||
color: Theme.of(context).textTheme.titleLarge!.backgroundColor!,
|
||||
),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _showBalanceDescription(context),
|
||||
onTap: hasAdditionalBalance ? () => _showBalanceDescription(context) : null,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -129,19 +168,19 @@ class BalancePage extends StatelessWidget {
|
||||
.displaySmall!
|
||||
.backgroundColor!,
|
||||
height: 1)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Icon(Icons.help_outline,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displaySmall!
|
||||
.backgroundColor!),
|
||||
)
|
||||
if (hasAdditionalBalance)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Icon(Icons.help_outline,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displaySmall!
|
||||
.backgroundColor!),
|
||||
),
|
||||
],
|
||||
),SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
AutoSizeText(availableBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
@@ -154,9 +193,7 @@ class BalancePage extends StatelessWidget {
|
||||
height: 1),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.start),
|
||||
SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
Text('${availableFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
@@ -168,7 +205,6 @@ class BalancePage extends StatelessWidget {
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1)),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -177,97 +213,99 @@ class BalancePage extends StatelessWidget {
|
||||
fontSize: 28,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
color: Theme.of(context).accentTextTheme!.displayMedium!.backgroundColor!,
|
||||
height: 1)),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 26),
|
||||
if (frozenBalance.isNotEmpty)
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(S.current.frozen_balance,
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 26),
|
||||
Text(
|
||||
S.current.frozen_balance,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displaySmall!
|
||||
.backgroundColor!,
|
||||
height: 1)),
|
||||
SizedBox(height: 8),
|
||||
AutoSizeText(frozenBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
frozenFiatBalance,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1),
|
||||
),
|
||||
SizedBox(height: 24)
|
||||
]),
|
||||
Text('${additionalBalanceLabel}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displaySmall!
|
||||
.backgroundColor!,
|
||||
height: 1)),
|
||||
SizedBox(height: 8),
|
||||
AutoSizeText(additionalBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center),
|
||||
SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
'${additionalFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1),
|
||||
)
|
||||
])),
|
||||
color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
AutoSizeText(
|
||||
frozenBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
frozenFiatBalance,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (hasAdditionalBalance)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 24),
|
||||
Text(
|
||||
'${additionalBalanceLabel}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
AutoSizeText(
|
||||
additionalBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'${additionalFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,17 +19,18 @@ class MenuWidget extends StatefulWidget {
|
||||
|
||||
class MenuWidgetState extends State<MenuWidget> {
|
||||
MenuWidgetState()
|
||||
: this.menuWidth = 0,
|
||||
this.screenWidth = 0,
|
||||
this.screenHeight = 0,
|
||||
this.headerHeight = 120,
|
||||
this.tileHeight = 60,
|
||||
this.fromTopEdge = 50,
|
||||
this.fromBottomEdge = 25,
|
||||
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
|
||||
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
|
||||
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
|
||||
this.havenIcon = Image.asset('assets/images/haven_menu.png');
|
||||
: this.menuWidth = 0,
|
||||
this.screenWidth = 0,
|
||||
this.screenHeight = 0,
|
||||
this.headerHeight = 120,
|
||||
this.tileHeight = 60,
|
||||
this.fromTopEdge = 50,
|
||||
this.fromBottomEdge = 25,
|
||||
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
|
||||
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
|
||||
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
|
||||
this.havenIcon = Image.asset('assets/images/haven_menu.png'),
|
||||
this.ethereumIcon = Image.asset('assets/images/eth_icon.png');
|
||||
|
||||
final largeScreen = 731;
|
||||
|
||||
@@ -46,6 +47,7 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
Image bitcoinIcon;
|
||||
Image litecoinIcon;
|
||||
Image havenIcon;
|
||||
Image ethereumIcon;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -85,16 +87,14 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
|
||||
moneroIcon = Image.asset('assets/images/monero_menu.png',
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.accentTextTheme
|
||||
.labelSmall!
|
||||
.decorationColor!);
|
||||
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.accentTextTheme
|
||||
.labelSmall!
|
||||
.decorationColor!);
|
||||
litecoinIcon = Image.asset('assets/images/litecoin_menu.png');
|
||||
havenIcon = Image.asset('assets/images/haven_menu.png');
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
@@ -178,7 +178,7 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
index--;
|
||||
|
||||
final item = SettingActions.all[index];
|
||||
|
||||
|
||||
final isLastTile = index == itemCount - 1;
|
||||
|
||||
return SettingActionButton(
|
||||
@@ -215,6 +215,8 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
return litecoinIcon;
|
||||
case WalletType.haven:
|
||||
return havenIcon;
|
||||
case WalletType.ethereum:
|
||||
return ethereumIcon;
|
||||
default:
|
||||
throw Exception('No icon for ${type.toString()}');
|
||||
}
|
||||
|
||||
@@ -511,17 +511,16 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||
|
||||
void _presentPicker(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
builder: (_) => CurrencyPicker(
|
||||
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
|
||||
items: widget.currencies,
|
||||
hintText: S.of(context).search_currency,
|
||||
isMoneroWallet: _isMoneroWallet,
|
||||
isConvertFrom: widget.hasRefundAddress,
|
||||
onItemSelected: (Currency item) =>
|
||||
widget.onCurrencySelected != null
|
||||
? widget.onCurrencySelected(item as CryptoCurrency)
|
||||
: null),
|
||||
context: context);
|
||||
context: context,
|
||||
builder: (_) => CurrencyPicker(
|
||||
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
|
||||
items: widget.currencies,
|
||||
hintText: S.of(context).search_currency,
|
||||
isMoneroWallet: _isMoneroWallet,
|
||||
isConvertFrom: widget.hasRefundAddress,
|
||||
onItemSelected: (Currency item) => widget.onCurrencySelected(item as CryptoCurrency),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAmountPopup(BuildContext context, PaymentRequest paymentRequest) {
|
||||
|
||||
@@ -11,14 +11,17 @@ import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NewWalletTypePage extends BasePage {
|
||||
NewWalletTypePage({required this.onTypeSelected});
|
||||
NewWalletTypePage({required this.onTypeSelected, required this.isCreate});
|
||||
|
||||
final void Function(BuildContext, WalletType) onTypeSelected;
|
||||
final bool isCreate;
|
||||
|
||||
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
|
||||
final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png');
|
||||
|
||||
@override
|
||||
String get title => S.current.wallet_list_restore_wallet;
|
||||
String get title =>
|
||||
isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => WalletTypeForm(
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class RestoreWalletOptionsPage extends BasePage {
|
||||
RestoreWalletOptionsPage(
|
||||
{required this.type,
|
||||
required this.onRestoreFromSeed,
|
||||
required this.onRestoreFromKeys});
|
||||
|
||||
final WalletType type;
|
||||
final Function(BuildContext context) onRestoreFromSeed;
|
||||
final Function(BuildContext context) onRestoreFromKeys;
|
||||
|
||||
@override
|
||||
String get title => S.current.restore_restore_wallet;
|
||||
|
||||
final imageSeed = Image.asset('assets/images/restore_seed.png');
|
||||
final imageKeys = Image.asset('assets/images/restore_keys.png');
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
padding: EdgeInsets.all(24),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
RestoreButton(
|
||||
onPressed: () => onRestoreFromSeed(context),
|
||||
image: imageSeed,
|
||||
title: S.of(context).restore_title_from_seed,
|
||||
description: _fromSeedDescription(context)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: RestoreButton(
|
||||
onPressed: () => onRestoreFromKeys(context),
|
||||
image: imageKeys,
|
||||
title: _fromKeyTitle(context),
|
||||
description: _fromKeyDescription(context)),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
String _fromSeedDescription(BuildContext context) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return S.of(context).restore_description_from_seed;
|
||||
case WalletType.bitcoin:
|
||||
// TODO: Add transaction for bitcoin description.
|
||||
return S.of(context).restore_bitcoin_description_from_seed;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _fromKeyDescription(BuildContext context) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return S.of(context).restore_description_from_keys;
|
||||
case WalletType.bitcoin:
|
||||
// TODO: Add transaction for bitcoin description.
|
||||
return S.of(context).restore_bitcoin_description_from_keys;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _fromKeyTitle(BuildContext context) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return S.of(context).restore_title_from_keys;
|
||||
case WalletType.bitcoin:
|
||||
// TODO: Add transaction for bitcoin description.
|
||||
return S.of(context).restore_bitcoin_title_from_keys;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,7 @@ class PreSeedPage extends BasePage {
|
||||
PreSeedPage(this.type)
|
||||
: imageLight = Image.asset('assets/images/pre_seed_light.png'),
|
||||
imageDark = Image.asset('assets/images/pre_seed_dark.png'),
|
||||
wordsCount = type == WalletType.monero
|
||||
? 25
|
||||
: 24; // FIXME: Stupid fast implementation
|
||||
wordsCount = _wordsCount(type);
|
||||
|
||||
final Image imageDark;
|
||||
final Image imageLight;
|
||||
@@ -68,4 +66,15 @@ class PreSeedPage extends BasePage {
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
static int _wordsCount(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return 25;
|
||||
case WalletType.ethereum:
|
||||
return 12;
|
||||
default:
|
||||
return 24;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,115 +220,108 @@ class SendPage extends BasePage {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (sendViewModel.hasMultiRecipient)
|
||||
Container(
|
||||
height: 40,
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.only(left: 24),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
final templates = sendViewModel.templates;
|
||||
final itemCount = templates.length;
|
||||
Container(
|
||||
height: 40,
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.only(left: 24),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
final templates = sendViewModel.templates;
|
||||
final itemCount = templates.length;
|
||||
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
AddTemplateButton(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.sendTemplate),
|
||||
currentTemplatesLength: templates.length,
|
||||
),
|
||||
ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
final template = templates[index];
|
||||
return TemplateTile(
|
||||
key: UniqueKey(),
|
||||
to: template.name,
|
||||
hasMultipleRecipients:
|
||||
template.additionalRecipients !=
|
||||
null &&
|
||||
template.additionalRecipients!
|
||||
.length > 1,
|
||||
amount: template.isCurrencySelected
|
||||
? template.amount
|
||||
: template.amountFiat,
|
||||
from: template.isCurrencySelected
|
||||
? template.cryptoCurrency
|
||||
: template.fiatCurrency,
|
||||
onTap: () async {
|
||||
if (template.additionalRecipients !=
|
||||
null) {
|
||||
sendViewModel.clearOutputs();
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
AddTemplateButton(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.sendTemplate),
|
||||
currentTemplatesLength: templates.length,
|
||||
),
|
||||
ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
final template = templates[index];
|
||||
return TemplateTile(
|
||||
key: UniqueKey(),
|
||||
to: template.name,
|
||||
hasMultipleRecipients:
|
||||
template.additionalRecipients != null &&
|
||||
template.additionalRecipients!.length > 1,
|
||||
amount: template.isCurrencySelected
|
||||
? template.amount
|
||||
: template.amountFiat,
|
||||
from: template.isCurrencySelected
|
||||
? template.cryptoCurrency
|
||||
: template.fiatCurrency,
|
||||
onTap: () async {
|
||||
if (template.additionalRecipients?.isNotEmpty ?? false) {
|
||||
sendViewModel.clearOutputs();
|
||||
|
||||
template.additionalRecipients!
|
||||
.forEach((currentElement) async {
|
||||
int i = template
|
||||
.additionalRecipients!
|
||||
.indexOf(currentElement);
|
||||
for (int i = 0;i < template.additionalRecipients!.length;i++) {
|
||||
Output output;
|
||||
try {
|
||||
output = sendViewModel.outputs[i];
|
||||
} catch (e) {
|
||||
sendViewModel.addOutput();
|
||||
output = sendViewModel.outputs[i];
|
||||
}
|
||||
|
||||
Output output;
|
||||
try {
|
||||
output = sendViewModel.outputs[i];
|
||||
} catch (e) {
|
||||
sendViewModel.addOutput();
|
||||
output = sendViewModel.outputs[i];
|
||||
}
|
||||
|
||||
await _setInputsFromTemplate(
|
||||
context,
|
||||
output: output,
|
||||
template: currentElement);
|
||||
});
|
||||
} else {
|
||||
final output = _defineCurrentOutput();
|
||||
await _setInputsFromTemplate(
|
||||
context,
|
||||
output: output,
|
||||
template: template);
|
||||
await _setInputsFromTemplate(
|
||||
context,
|
||||
output: output,
|
||||
template: template.additionalRecipients![i],
|
||||
);
|
||||
}
|
||||
},
|
||||
onRemove: () {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle:
|
||||
S.of(context).template,
|
||||
alertContent: S
|
||||
.of(context)
|
||||
.confirm_delete_template,
|
||||
rightButtonText:
|
||||
S.of(context).delete,
|
||||
leftButtonText:
|
||||
S.of(context).cancel,
|
||||
actionRightButton: () {
|
||||
Navigator.of(dialogContext)
|
||||
.pop();
|
||||
sendViewModel
|
||||
.sendTemplateViewModel
|
||||
.removeTemplate(
|
||||
template: template);
|
||||
},
|
||||
actionLeftButton: () =>
|
||||
Navigator.of(dialogContext)
|
||||
.pop());
|
||||
},
|
||||
} else {
|
||||
final output = _defineCurrentOutput();
|
||||
await _setInputsFromTemplate(
|
||||
context,
|
||||
output: output,
|
||||
template: template,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
}
|
||||
},
|
||||
onRemove: () {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle:
|
||||
S.of(context).template,
|
||||
alertContent: S
|
||||
.of(context)
|
||||
.confirm_delete_template,
|
||||
rightButtonText:
|
||||
S.of(context).delete,
|
||||
leftButtonText:
|
||||
S.of(context).cancel,
|
||||
actionRightButton: () {
|
||||
Navigator.of(dialogContext)
|
||||
.pop();
|
||||
sendViewModel
|
||||
.sendTemplateViewModel
|
||||
.removeTemplate(
|
||||
template: template);
|
||||
},
|
||||
actionLeftButton: () =>
|
||||
Navigator.of(dialogContext)
|
||||
.pop());
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -350,7 +343,7 @@ class SendPage extends BasePage {
|
||||
.displaySmall!
|
||||
.decorationColor!,
|
||||
))),
|
||||
if (sendViewModel.hasMultiRecipient)
|
||||
if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: PrimaryButton(
|
||||
@@ -518,6 +511,7 @@ class SendPage extends BasePage {
|
||||
output.address = template.address;
|
||||
|
||||
if (template.isCurrencySelected) {
|
||||
sendViewModel.setSelectedCryptoCurrency(template.cryptoCurrency);
|
||||
output.setCryptoAmount(template.amount);
|
||||
} else {
|
||||
sendViewModel.setFiatCurrency(fiatFromTemplate);
|
||||
|
||||
@@ -67,8 +67,7 @@ class SendTemplatePage extends BasePage {
|
||||
controller: controller,
|
||||
itemCount: sendTemplateViewModel.recipients.length,
|
||||
itemBuilder: (_, index) {
|
||||
final template =
|
||||
sendTemplateViewModel.recipients[index];
|
||||
final template = sendTemplateViewModel.recipients[index];
|
||||
return SendTemplateCard(
|
||||
template: template,
|
||||
index: index,
|
||||
@@ -76,8 +75,7 @@ class SendTemplatePage extends BasePage {
|
||||
});
|
||||
})),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 10, left: 24, right: 24, bottom: 10),
|
||||
padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10),
|
||||
child: Container(
|
||||
height: 10,
|
||||
child: Observer(
|
||||
@@ -107,55 +105,42 @@ class SendTemplatePage extends BasePage {
|
||||
),
|
||||
),
|
||||
])),
|
||||
bottomSectionPadding:
|
||||
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: Column(children: [
|
||||
// if (sendViewModel.hasMultiRecipient)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: PrimaryButton(
|
||||
onPressed: () {
|
||||
sendTemplateViewModel.addRecipient();
|
||||
Future.delayed(const Duration(milliseconds: 250), () {
|
||||
controller.jumpToPage(
|
||||
sendTemplateViewModel.recipients.length - 1);
|
||||
});
|
||||
},
|
||||
text: S.of(context).add_receiver,
|
||||
color: Colors.transparent,
|
||||
textColor: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.displaySmall!
|
||||
.decorationColor!,
|
||||
isDottedBorder: true,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.displaySmall!
|
||||
.decorationColor!)),
|
||||
if (sendTemplateViewModel.hasMultiRecipient)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: PrimaryButton(
|
||||
onPressed: () {
|
||||
sendTemplateViewModel.addRecipient();
|
||||
Future.delayed(const Duration(milliseconds: 250), () {
|
||||
controller.jumpToPage(sendTemplateViewModel.recipients.length - 1);
|
||||
});
|
||||
},
|
||||
text: S.of(context).add_receiver,
|
||||
color: Colors.transparent,
|
||||
textColor: Theme.of(context).accentTextTheme.displaySmall!.decorationColor!,
|
||||
isDottedBorder: true,
|
||||
borderColor:
|
||||
Theme.of(context).primaryTextTheme.displaySmall!.decorationColor!)),
|
||||
PrimaryButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState != null &&
|
||||
_formKey.currentState!.validate()) {
|
||||
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
|
||||
final mainTemplate = sendTemplateViewModel.recipients[0];
|
||||
print(sendTemplateViewModel.recipients.map((element) =>
|
||||
element.toTemplate(
|
||||
cryptoCurrency:
|
||||
sendTemplateViewModel.cryptoCurrency.title,
|
||||
fiatCurrency:
|
||||
sendTemplateViewModel.fiatCurrency)));
|
||||
final additionalRecipients = sendTemplateViewModel.recipients
|
||||
.map((element) => element.toTemplate(
|
||||
cryptoCurrency: element.selectedCurrency.title,
|
||||
fiatCurrency: sendTemplateViewModel.fiatCurrency))
|
||||
.toList();
|
||||
|
||||
sendTemplateViewModel.addTemplate(
|
||||
isCurrencySelected: mainTemplate.isCurrencySelected,
|
||||
name: mainTemplate.name,
|
||||
address: mainTemplate.address,
|
||||
cryptoCurrency: mainTemplate.selectedCurrency.title,
|
||||
amount: mainTemplate.output.cryptoAmount,
|
||||
amountFiat: mainTemplate.output.fiatAmount,
|
||||
additionalRecipients: sendTemplateViewModel.recipients
|
||||
.map((element) => element.toTemplate(
|
||||
cryptoCurrency: sendTemplateViewModel
|
||||
.cryptoCurrency.title,
|
||||
fiatCurrency:
|
||||
sendTemplateViewModel.fiatCurrency))
|
||||
.toList());
|
||||
additionalRecipients: additionalRecipients);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,29 +4,53 @@ class PrefixCurrencyIcon extends StatelessWidget {
|
||||
PrefixCurrencyIcon({
|
||||
required this.isSelected,
|
||||
required this.title,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final bool isSelected;
|
||||
final String title;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(0, 6.0, 8.0, 0),
|
||||
child: Column(children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
color: isSelected ? Colors.green : Colors.transparent,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
color: isSelected ? Colors.green : Colors.transparent,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
if (onTap != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: Image.asset(
|
||||
'assets/images/arrow_bottom_purple_icon.png',
|
||||
color: Colors.white,
|
||||
height: 8,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
title + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Text(title + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
)),
|
||||
)
|
||||
]));
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
@@ -32,18 +35,14 @@ class SendCard extends StatefulWidget {
|
||||
|
||||
@override
|
||||
SendCardState createState() => SendCardState(
|
||||
output: output,
|
||||
sendViewModel: sendViewModel,
|
||||
initialPaymentRequest: initialPaymentRequest,
|
||||
);
|
||||
output: output,
|
||||
sendViewModel: sendViewModel,
|
||||
initialPaymentRequest: initialPaymentRequest,
|
||||
);
|
||||
}
|
||||
|
||||
class SendCardState extends State<SendCard>
|
||||
with AutomaticKeepAliveClientMixin<SendCard> {
|
||||
SendCardState({
|
||||
required this.output,
|
||||
required this.sendViewModel,
|
||||
this.initialPaymentRequest})
|
||||
class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<SendCard> {
|
||||
SendCardState({required this.output, required this.sendViewModel, this.initialPaymentRequest})
|
||||
: addressController = TextEditingController(),
|
||||
cryptoAmountController = TextEditingController(),
|
||||
fiatAmountController = TextEditingController(),
|
||||
@@ -100,40 +99,41 @@ class SendCardState extends State<SendCard>
|
||||
return Stack(
|
||||
children: [
|
||||
KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.bodyLarge!
|
||||
.backgroundColor!,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: cryptoAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
KeyboardActionsItem(
|
||||
focusNode: fiatAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
)
|
||||
]),
|
||||
child: Container(
|
||||
height: 0,
|
||||
color: Colors.transparent,
|
||||
)),
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).accentTextTheme.bodyLarge!.backgroundColor!,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: cryptoAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
KeyboardActionsItem(
|
||||
focusNode: fiatAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
height: 0,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: ResponsiveLayoutUtil.instance.isMobile ? BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||
Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.titleMedium!
|
||||
.decorationColor!,
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
||||
) : null,
|
||||
decoration: ResponsiveLayoutUtil.instance.isMobile
|
||||
? BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
24,
|
||||
@@ -142,7 +142,8 @@ class SendCardState extends State<SendCard>
|
||||
ResponsiveLayoutUtil.instance.isMobile ? 32 : 0,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Observer(builder: (_) => Column(
|
||||
child: Observer(
|
||||
builder: (_) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Observer(builder: (_) {
|
||||
@@ -164,25 +165,15 @@ class SendCardState extends State<SendCard>
|
||||
AddressTextFieldOption.qrCode,
|
||||
AddressTextFieldOption.addressBook
|
||||
],
|
||||
buttonColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
buttonColor: Theme.of(context).primaryTextTheme.headlineMedium!.color!,
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
color:
|
||||
Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
|
||||
onPushPasteButton: (context) async {
|
||||
output.resetParsedAddress();
|
||||
await output.fetchParsedAddress(context);
|
||||
@@ -197,170 +188,192 @@ class SendCardState extends State<SendCard>
|
||||
selectedCurrency: sendViewModel.currency,
|
||||
);
|
||||
}),
|
||||
if (output.isParsedAddress) Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: BaseTextFormField(
|
||||
controller: extractedAddressController,
|
||||
readOnly: true,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
validator: sendViewModel.addressValidator
|
||||
)
|
||||
),
|
||||
if (output.isParsedAddress)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: BaseTextFormField(
|
||||
controller: extractedAddressController,
|
||||
readOnly: true,
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
validator: sendViewModel.addressValidator)),
|
||||
Observer(
|
||||
builder: (_) => Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
sendViewModel.selectedCryptoCurrency.title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
)),
|
||||
sendViewModel.selectedCryptoCurrency.tag != null ? Padding(
|
||||
padding: const EdgeInsets.fromLTRB(3.0,0,3.0,0),
|
||||
child: Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(6))),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text( sendViewModel.selectedCryptoCurrency.tag!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.decorationColor!)),
|
||||
builder: (_) => Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
sendViewModel.hasMultipleTokens
|
||||
? Container(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
height: 32,
|
||||
child: InkWell(
|
||||
onTap: () => _presentPicker(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: Image.asset(
|
||||
'assets/images/arrow_bottom_purple_icon.png',
|
||||
color: Colors.white,
|
||||
height: 8,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
sendViewModel.selectedCryptoCurrency.title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
) : Container(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: Text(':',
|
||||
)
|
||||
: Text(
|
||||
sendViewModel.selectedCryptoCurrency.title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
focusNode: cryptoAmountFocus,
|
||||
controller: cryptoAmountController,
|
||||
keyboardType:
|
||||
TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
||||
],
|
||||
suffixIcon: SizedBox(
|
||||
width: prefixIconWidth,
|
||||
),
|
||||
hintText: '0.0000',
|
||||
borderColor: Colors.transparent,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
),
|
||||
sendViewModel.selectedCryptoCurrency.tag != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.fromLTRB(3.0, 0, 3.0, 0),
|
||||
child: Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
)),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text(
|
||||
sendViewModel.selectedCryptoCurrency.tag!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.decorationColor!),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: Text(
|
||||
':',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
focusNode: cryptoAmountFocus,
|
||||
controller: cryptoAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
||||
],
|
||||
suffixIcon: SizedBox(
|
||||
width: prefixIconWidth,
|
||||
),
|
||||
hintText: '0.0000',
|
||||
borderColor: Colors.transparent,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: output.sendAll
|
||||
? sendViewModel.allAmountValidator
|
||||
: sendViewModel.amountValidator,
|
||||
),
|
||||
if (!sendViewModel.isBatchSending)
|
||||
Positioned(
|
||||
top: 2,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: prefixIconWidth,
|
||||
height: prefixIconHeight,
|
||||
child: InkWell(
|
||||
onTap: () async => output.setSendAll(),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: output.sendAll
|
||||
? sendViewModel.allAmountValidator
|
||||
: sendViewModel
|
||||
.amountValidator),
|
||||
if (!sendViewModel.isBatchSending) Positioned(
|
||||
top: 2,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: prefixIconWidth,
|
||||
height: prefixIconHeight,
|
||||
child: InkWell(
|
||||
onTap: () async =>
|
||||
output.setSendAll(),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderRadius:
|
||||
BorderRadius.all(
|
||||
Radius.circular(6))),
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.of(context).all,
|
||||
textAlign:
|
||||
TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
color:
|
||||
Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.decorationColor!))),
|
||||
))))]),
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.of(context).all,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.decorationColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
)),
|
||||
Divider(height: 1,color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
|
||||
Observer(
|
||||
builder: (_) => Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
S.of(context).available_balance +
|
||||
':',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
)),
|
||||
Text(
|
||||
sendViewModel.balance,
|
||||
builder: (_) => Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
S.of(context).available_balance + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -368,10 +381,22 @@ class SendCardState extends State<SendCard>
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
sendViewModel.balance,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!sendViewModel.isFiatDisabled)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
@@ -379,171 +404,155 @@ class SendCardState extends State<SendCard>
|
||||
focusNode: fiatAmountFocus,
|
||||
controller: fiatAmountController,
|
||||
keyboardType:
|
||||
TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
||||
FilteringTextInputFormatter.deny(
|
||||
RegExp('[\\-|\\ ]'),
|
||||
)
|
||||
],
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.only(top: 9),
|
||||
child:
|
||||
Text(sendViewModel.fiat.title + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
)),
|
||||
child: Text(
|
||||
sendViewModel.fiat.title + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
hintText: '0.00',
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme.headlineSmall!.decorationColor!,
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
)),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: BaseTextFormField(
|
||||
controller: noteController,
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: null,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
hintText: S.of(context).note_optional,
|
||||
placeholderTextStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
color:
|
||||
Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => GestureDetector(
|
||||
onTap: () =>
|
||||
_setTransactionPriority(context),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S
|
||||
.of(context)
|
||||
.send_estimated_fee,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.w500,
|
||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||
color: Colors.white)),
|
||||
Container(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
output
|
||||
.estimatedFee
|
||||
.toString() +
|
||||
' ' +
|
||||
sendViewModel
|
||||
.selectedCryptoCurrency.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.w600,
|
||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||
color:
|
||||
Colors.white)),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.only(top: 5),
|
||||
child: sendViewModel.isFiatDisabled
|
||||
? const SizedBox(height: 14)
|
||||
: Text(output
|
||||
.estimatedFeeFiatAmount
|
||||
+ ' ' +
|
||||
sendViewModel
|
||||
.fiat.title,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.w600,
|
||||
color: Theme
|
||||
.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!))
|
||||
builder: (_) => GestureDetector(
|
||||
onTap: () => _setTransactionPriority(context),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.of(context).send_estimated_fee,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||
color: Colors.white),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
output.estimatedFee.toString() +
|
||||
' ' +
|
||||
sendViewModel.selectedCryptoCurrency.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 2,
|
||||
left: 5),
|
||||
child: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: sendViewModel.isFiatDisabled
|
||||
? const SizedBox(height: 14)
|
||||
: Text(
|
||||
output.estimatedFeeFiatAmount +
|
||||
' ' +
|
||||
sendViewModel.fiat.title,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 2, left: 5),
|
||||
child: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (sendViewModel.isElectrumWallet)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).coin_control,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
if (sendViewModel.isElectrumWallet) Padding(
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.unspentCoinsList),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).coin_control,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white)),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
))
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -552,10 +561,10 @@ class SendCardState extends State<SendCard>
|
||||
}
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
if (_effectsInstalled) {
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (output.address.isNotEmpty) {
|
||||
addressController.text = output.address;
|
||||
}
|
||||
@@ -664,16 +673,30 @@ class SendCardState extends State<SendCard>
|
||||
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
||||
|
||||
await showPopUp<void>(
|
||||
builder: (_) => Picker(
|
||||
items: items,
|
||||
displayItem: sendViewModel.displayFeeRate,
|
||||
selectedAtIndex: selectedItem,
|
||||
title: S.of(context).please_select,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
onItemSelected: (TransactionPriority priority) =>
|
||||
sendViewModel.setTransactionPriority(priority),
|
||||
),
|
||||
context: context);
|
||||
context: context,
|
||||
builder: (_) => Picker(
|
||||
items: items,
|
||||
displayItem: sendViewModel.displayFeeRate,
|
||||
selectedAtIndex: selectedItem,
|
||||
title: S.of(context).please_select,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
onItemSelected: (TransactionPriority priority) =>
|
||||
sendViewModel.setTransactionPriority(priority),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _presentPicker(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => CurrencyPicker(
|
||||
selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency),
|
||||
items: sendViewModel.currencies,
|
||||
hintText: S.of(context).search_currency,
|
||||
onItemSelected: (Currency cur) =>
|
||||
sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/send/template_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -35,161 +39,140 @@ class SendTemplateCard extends StatelessWidget {
|
||||
_setEffects(context);
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
|
||||
child: Column(children: <Widget>[
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
|
||||
child: Column(children: <Widget>[
|
||||
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
if (index == 0)
|
||||
BaseTextFormField(
|
||||
controller: _nameController,
|
||||
hintText: sendTemplateViewModel.recipients.length > 1
|
||||
? S.of(context).template_name
|
||||
: S.of(context).send_name,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: sendTemplateViewModel.templateValidator),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: AddressTextField(
|
||||
selectedCurrency: sendTemplateViewModel.cryptoCurrency,
|
||||
controller: _addressController,
|
||||
onURIScanned: (uri) {
|
||||
final paymentRequest = PaymentRequest.fromUri(uri);
|
||||
_addressController.text = paymentRequest.address;
|
||||
_cryptoAmountController.text = paymentRequest.amount;
|
||||
},
|
||||
options: [
|
||||
AddressTextFieldOption.paste,
|
||||
AddressTextFieldOption.qrCode,
|
||||
AddressTextFieldOption.addressBook
|
||||
],
|
||||
onPushPasteButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
onPushAddressBookButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
buttonColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
validator: sendTemplateViewModel.addressValidator)),
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: AddressTextField(
|
||||
selectedCurrency: sendTemplateViewModel.cryptoCurrency,
|
||||
controller: _addressController,
|
||||
onURIScanned: (uri) {
|
||||
final paymentRequest = PaymentRequest.fromUri(uri);
|
||||
_addressController.text = paymentRequest.address;
|
||||
_cryptoAmountController.text = paymentRequest.amount;
|
||||
},
|
||||
options: [
|
||||
AddressTextFieldOption.paste,
|
||||
AddressTextFieldOption.qrCode,
|
||||
AddressTextFieldOption.addressBook
|
||||
],
|
||||
onPushPasteButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
onPushAddressBookButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
buttonColor: Theme.of(context).primaryTextTheme.headlineMedium!.color!,
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||
),
|
||||
validator: sendTemplateViewModel.addressValidator,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) {
|
||||
template.selectCurrency();
|
||||
}
|
||||
},
|
||||
child: BaseTextFormField(
|
||||
focusNode: _cryptoAmountFocus,
|
||||
controller: _cryptoAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(
|
||||
RegExp('[\\-|\\ ]'))
|
||||
],
|
||||
prefixIcon: Observer(
|
||||
builder: (_) => PrefixCurrencyIcon(
|
||||
title: sendTemplateViewModel
|
||||
.cryptoCurrency.title,
|
||||
isSelected: template.isCurrencySelected)),
|
||||
hintText: '0.0000',
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: sendTemplateViewModel.amountValidator))),
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) {
|
||||
template.selectCurrency();
|
||||
}
|
||||
},
|
||||
child: BaseTextFormField(
|
||||
focusNode: _cryptoAmountFocus,
|
||||
controller: _cryptoAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
|
||||
prefixIcon: Observer(
|
||||
builder: (_) => PrefixCurrencyIcon(
|
||||
title: template.selectedCurrency.title,
|
||||
isSelected: template.isCurrencySelected,
|
||||
onTap: sendTemplateViewModel.walletCurrencies.length > 1
|
||||
? () => _presentPicker(context)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
hintText: '0.0000',
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: sendTemplateViewModel.amountValidator,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) {
|
||||
template.selectFiat();
|
||||
}
|
||||
},
|
||||
child: BaseTextFormField(
|
||||
focusNode: _fiatAmountFocus,
|
||||
controller: _fiatAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(
|
||||
RegExp('[\\-|\\ ]'))
|
||||
],
|
||||
prefixIcon: Observer(
|
||||
builder: (_) => PrefixCurrencyIcon(
|
||||
title: sendTemplateViewModel.fiatCurrency,
|
||||
isSelected: template.isFiatSelected)),
|
||||
hintText: '0.00',
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14))))
|
||||
]))
|
||||
]));
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) {
|
||||
template.selectFiat();
|
||||
}
|
||||
},
|
||||
child: BaseTextFormField(
|
||||
focusNode: _fiatAmountFocus,
|
||||
controller: _fiatAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
|
||||
prefixIcon: Observer(
|
||||
builder: (_) => PrefixCurrencyIcon(
|
||||
title: sendTemplateViewModel.fiatCurrency,
|
||||
isSelected: template.isFiatSelected)),
|
||||
hintText: '0.00',
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
@@ -264,4 +247,16 @@ class SendTemplateCard extends StatelessWidget {
|
||||
|
||||
_effectsInstalled = true;
|
||||
}
|
||||
|
||||
void _presentPicker(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => CurrencyPicker(
|
||||
selectedAtIndex: sendTemplateViewModel.walletCurrencies.indexOf(template.selectedCurrency),
|
||||
items: sendTemplateViewModel.walletCurrencies,
|
||||
hintText: S.of(context).search_currency,
|
||||
onItemSelected: (Currency cur) => template.changeSelectedCurrency(cur as CryptoCurrency),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ class PrivacyPage extends BasePage {
|
||||
title: S.current.exchange,
|
||||
items: ExchangeApiMode.all,
|
||||
selectedItem: _privacySettingsViewModel.exchangeStatus,
|
||||
onItemSelected: (ExchangeApiMode mode) => _privacySettingsViewModel.setExchangeApiMode(mode),
|
||||
onItemSelected: (ExchangeApiMode mode) =>
|
||||
_privacySettingsViewModel.setExchangeApiMode(mode),
|
||||
),
|
||||
),
|
||||
SettingsSwitcherCell(
|
||||
@@ -68,6 +69,13 @@ class PrivacyPage extends BasePage {
|
||||
onValueChange: (BuildContext _, bool value) {
|
||||
_privacySettingsViewModel.setDisableSell(value);
|
||||
}),
|
||||
if (_privacySettingsViewModel.canUseEtherscan)
|
||||
SettingsSwitcherCell(
|
||||
title: S.current.etherscan_history,
|
||||
value: _privacySettingsViewModel.useEtherscan,
|
||||
onValueChange: (BuildContext _, bool value) {
|
||||
_privacySettingsViewModel.setUseEtherscan(value);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -3,14 +3,23 @@ import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_switch.dart';
|
||||
|
||||
class SettingsSwitcherCell extends StandardListRow {
|
||||
SettingsSwitcherCell(
|
||||
{required String title, required this.value, this.onValueChange})
|
||||
: super(title: title, isSelected: false);
|
||||
SettingsSwitcherCell({
|
||||
required String title,
|
||||
required this.value,
|
||||
this.onValueChange,
|
||||
Decoration? decoration,
|
||||
this.leading,
|
||||
void Function(BuildContext context)? onTap,
|
||||
}) : super(title: title, isSelected: false, decoration: decoration, onTap: onTap);
|
||||
|
||||
final bool value;
|
||||
final void Function(BuildContext context, bool value)? onValueChange;
|
||||
final Widget? leading;
|
||||
|
||||
@override
|
||||
Widget buildTrailing(BuildContext context) => StandardSwitch(
|
||||
value: value, onTaped: () => onValueChange?.call(context, !value));
|
||||
Widget buildTrailing(BuildContext context) =>
|
||||
StandardSwitch(value: value, onTaped: () => onValueChange?.call(context, !value));
|
||||
|
||||
@override
|
||||
Widget? buildLeading(BuildContext context) => leading;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
||||
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
|
||||
final scrollController = ScrollController();
|
||||
final double tileHeight = 60;
|
||||
Flushbar<void>? _progressBar;
|
||||
@@ -230,6 +231,8 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||
return litecoinIcon;
|
||||
case WalletType.haven:
|
||||
return havenIcon;
|
||||
case WalletType.ethereum:
|
||||
return ethereumIcon;
|
||||
default:
|
||||
return nonWalletTypeIcon;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CheckboxWidget extends StatefulWidget {
|
||||
CheckboxWidget({
|
||||
required this.value,
|
||||
required this.caption,
|
||||
required this.onChanged});
|
||||
CheckboxWidget({required this.value, required this.caption, required this.onChanged});
|
||||
|
||||
final bool value;
|
||||
final String caption;
|
||||
@@ -26,55 +21,45 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
value = !value;
|
||||
onChanged(value);
|
||||
setState(() {});
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 16,
|
||||
width: 16,
|
||||
height: 24.0,
|
||||
width: 24.0,
|
||||
margin: EdgeInsets.only(right: 10.0),
|
||||
decoration: BoxDecoration(
|
||||
color: value
|
||||
? Palette.blueCraiola
|
||||
: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleMedium!
|
||||
.decorationColor!,
|
||||
borderRadius: BorderRadius.all(Radius.circular(2)),
|
||||
border: Border.all(
|
||||
color: value
|
||||
? Palette.blueCraiola
|
||||
: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.labelSmall!
|
||||
.color!,
|
||||
width: 1)),
|
||||
child: value
|
||||
? Center(
|
||||
child: Icon(
|
||||
Icons.done,
|
||||
color: Colors.white,
|
||||
size: 14,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).primaryTextTheme.bodySmall!.color!,
|
||||
width: 1.0,
|
||||
),
|
||||
)
|
||||
: Offstage(),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(8.0),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: value
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Colors.blue,
|
||||
size: 20.0,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
caption,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme!.titleLarge!.color!,
|
||||
fontSize: 18,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w500,
|
||||
decoration: TextDecoration.none
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14.0,
|
||||
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -82,4 +67,4 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+49
-44
@@ -1,5 +1,7 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -145,8 +147,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.color!,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
@@ -163,8 +164,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
),
|
||||
Divider(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
@@ -194,8 +194,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
fontFamily: 'Lato',
|
||||
decoration: TextDecoration.none,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.titleLarge!
|
||||
.primaryTextTheme.titleLarge!
|
||||
.color!,
|
||||
),
|
||||
),
|
||||
@@ -217,8 +216,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
Widget itemsList() {
|
||||
return Container(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.backgroundColor!,
|
||||
child: widget.isGridView
|
||||
? GridView.builder(
|
||||
@@ -240,8 +238,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
separatorBuilder: (context, index) => widget.isSeparated
|
||||
? Divider(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.backgroundColor!,
|
||||
height: 1,
|
||||
)
|
||||
@@ -254,15 +251,9 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
|
||||
Widget buildItem(int index) {
|
||||
final item = filteredItems[index];
|
||||
final tag = item is Currency ? item.tag : null;
|
||||
|
||||
final icon = item is Currency && item.iconPath != null
|
||||
? Image.asset(
|
||||
item.iconPath!,
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
)
|
||||
: null;
|
||||
final tag = item is Currency ? item.tag : null;
|
||||
final icon = _getItemIcon(item);
|
||||
|
||||
final image = images.isNotEmpty ? filteredImages[index] : icon;
|
||||
|
||||
@@ -274,8 +265,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
child: Container(
|
||||
height: 55,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.color!,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
@@ -298,8 +288,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.titleLarge!
|
||||
.primaryTextTheme.titleLarge!
|
||||
.color!,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
@@ -318,8 +307,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.bodyMedium!
|
||||
.textTheme.bodyMedium!
|
||||
.color!),
|
||||
),
|
||||
),
|
||||
@@ -327,8 +315,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.bodyMedium!
|
||||
.textTheme.bodyMedium!
|
||||
.decorationColor!,
|
||||
),
|
||||
),
|
||||
@@ -345,15 +332,9 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
|
||||
Widget buildSelectedItem(int index) {
|
||||
final item = items[index];
|
||||
final tag = item is Currency ? item.tag : null;
|
||||
|
||||
final icon = item is Currency && item.iconPath != null
|
||||
? Image.asset(
|
||||
item.iconPath!,
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
)
|
||||
: null;
|
||||
final tag = item is Currency ? item.tag : null;
|
||||
final icon = _getItemIcon(item);
|
||||
|
||||
final image = images.isNotEmpty ? images[index] : icon;
|
||||
|
||||
@@ -364,8 +345,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
child: Container(
|
||||
height: 55,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.color!,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
@@ -388,8 +368,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.titleLarge!
|
||||
.primaryTextTheme.titleLarge!
|
||||
.color!,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
@@ -408,8 +387,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.bodyMedium!
|
||||
.textTheme.bodyMedium!
|
||||
.color!),
|
||||
),
|
||||
),
|
||||
@@ -417,8 +395,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.bodyMedium!
|
||||
.textTheme.bodyMedium!
|
||||
.decorationColor!,
|
||||
),
|
||||
),
|
||||
@@ -429,12 +406,40 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
),
|
||||
Icon(Icons.check_circle,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.bodyLarge!
|
||||
.accentTextTheme.bodyLarge!
|
||||
.color!),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _getItemIcon(Item item) {
|
||||
if (item is Currency) {
|
||||
if (item.iconPath != null) {
|
||||
return Image.asset(
|
||||
item.iconPath!,
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
item.name.substring(0, min(item.name.length, 2)).toUpperCase(),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class StandardListRow extends StatelessWidget {
|
||||
StandardListRow(
|
||||
{required this.title, required this.isSelected, this.onTap});
|
||||
{required this.title, required this.isSelected, this.onTap, this.decoration});
|
||||
|
||||
final String title;
|
||||
final bool isSelected;
|
||||
final void Function(BuildContext context)? onTap;
|
||||
final Decoration? decoration;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -19,9 +20,11 @@ class StandardListRow extends StatelessWidget {
|
||||
return InkWell(
|
||||
onTap: () => onTap?.call(context),
|
||||
child: Container(
|
||||
color: _backgroundColor(context),
|
||||
height: 56,
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
decoration: decoration ?? BoxDecoration(
|
||||
color: _backgroundColor(context),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
@@ -54,7 +57,7 @@ class StandardListRow extends StatelessWidget {
|
||||
|
||||
Color titleColor(BuildContext context) => isSelected
|
||||
? Palette.blueCraiola
|
||||
: Theme.of(context).primaryTextTheme!.titleLarge!.color!;
|
||||
: Theme.of(context).primaryTextTheme.titleLarge!.color!;
|
||||
|
||||
Color _backgroundColor(BuildContext context) {
|
||||
return Theme.of(context).colorScheme.background;
|
||||
@@ -89,7 +92,7 @@ class StandardListSeparator extends StatelessWidget {
|
||||
child: Container(
|
||||
height: height,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.primaryTextTheme
|
||||
.titleLarge
|
||||
?.backgroundColor));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/entities/sort_balance_types.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/themes/theme_list.dart';
|
||||
@@ -66,10 +68,14 @@ abstract class SettingsStoreBase with Store {
|
||||
required bool initialShouldRequireTOTP2FAForAddingContacts,
|
||||
required bool initialShouldRequireTOTP2FAForCreatingNewWallets,
|
||||
required bool initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||
required this.sortBalanceBy,
|
||||
required this.pinNativeTokenAtTop,
|
||||
required this.useEtherscan,
|
||||
TransactionPriority? initialBitcoinTransactionPriority,
|
||||
TransactionPriority? initialMoneroTransactionPriority,
|
||||
TransactionPriority? initialHavenTransactionPriority,
|
||||
TransactionPriority? initialLitecoinTransactionPriority})
|
||||
TransactionPriority? initialLitecoinTransactionPriority,
|
||||
TransactionPriority? initialEthereumTransactionPriority})
|
||||
: nodes = ObservableMap<WalletType, Node>.of(nodes),
|
||||
_sharedPreferences = sharedPreferences,
|
||||
fiatCurrency = initialFiatCurrency,
|
||||
@@ -120,6 +126,10 @@ abstract class SettingsStoreBase with Store {
|
||||
priority[WalletType.litecoin] = initialLitecoinTransactionPriority;
|
||||
}
|
||||
|
||||
if (initialEthereumTransactionPriority != null) {
|
||||
priority[WalletType.ethereum] = initialEthereumTransactionPriority;
|
||||
}
|
||||
|
||||
reaction(
|
||||
(_) => fiatCurrency,
|
||||
(FiatCurrency fiatCurrency) => sharedPreferences.setString(
|
||||
@@ -145,6 +155,9 @@ abstract class SettingsStoreBase with Store {
|
||||
case WalletType.haven:
|
||||
key = PreferencesKey.havenTransactionPriority;
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
key = PreferencesKey.ethereumTransactionPriority;
|
||||
break;
|
||||
default:
|
||||
key = null;
|
||||
}
|
||||
@@ -279,6 +292,21 @@ abstract class SettingsStoreBase with Store {
|
||||
(ExchangeApiMode mode) =>
|
||||
sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, mode.serialize()));
|
||||
|
||||
reaction(
|
||||
(_) => sortBalanceBy,
|
||||
(SortBalanceBy sortBalanceBy) =>
|
||||
_sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceBy.index));
|
||||
|
||||
reaction(
|
||||
(_) => pinNativeTokenAtTop,
|
||||
(bool pinNativeTokenAtTop) =>
|
||||
_sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop));
|
||||
|
||||
reaction(
|
||||
(_) => useEtherscan,
|
||||
(bool useEtherscan) =>
|
||||
_sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan));
|
||||
|
||||
this.nodes.observe((change) {
|
||||
if (change.newValue != null && change.key != null) {
|
||||
_saveCurrentNode(change.newValue!, change.key!);
|
||||
@@ -385,6 +413,15 @@ abstract class SettingsStoreBase with Store {
|
||||
@observable
|
||||
ObservableMap<WalletType, TransactionPriority> priority;
|
||||
|
||||
@observable
|
||||
SortBalanceBy sortBalanceBy;
|
||||
|
||||
@observable
|
||||
bool pinNativeTokenAtTop;
|
||||
|
||||
@observable
|
||||
bool useEtherscan;
|
||||
|
||||
String appVersion;
|
||||
|
||||
String deviceName;
|
||||
@@ -429,6 +466,7 @@ abstract class SettingsStoreBase with Store {
|
||||
|
||||
TransactionPriority? havenTransactionPriority;
|
||||
TransactionPriority? litecoinTransactionPriority;
|
||||
TransactionPriority? ethereumTransactionPriority;
|
||||
|
||||
if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
|
||||
havenTransactionPriority = monero?.deserializeMoneroTransactionPriority(
|
||||
@@ -438,11 +476,16 @@ abstract class SettingsStoreBase with Store {
|
||||
litecoinTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
|
||||
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!);
|
||||
}
|
||||
if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
|
||||
ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
|
||||
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
|
||||
}
|
||||
|
||||
moneroTransactionPriority ??= monero?.getDefaultTransactionPriority();
|
||||
bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
|
||||
havenTransactionPriority ??= monero?.getDefaultTransactionPriority();
|
||||
litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
|
||||
ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
|
||||
|
||||
final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
|
||||
@@ -502,6 +545,12 @@ abstract class SettingsStoreBase with Store {
|
||||
final pinCodeTimeOutDuration = timeOutDuration != null
|
||||
? PinCodeRequiredDuration.deserialize(raw: timeOutDuration)
|
||||
: defaultPinCodeTimeOutDuration;
|
||||
final sortBalanceBy =
|
||||
SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0];
|
||||
final pinNativeTokenAtTop =
|
||||
sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
|
||||
final useEtherscan =
|
||||
sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
|
||||
|
||||
// If no value
|
||||
if (pinLength == null || pinLength == 0) {
|
||||
@@ -516,10 +565,12 @@ abstract class SettingsStoreBase with Store {
|
||||
final litecoinElectrumServerId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
||||
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
|
||||
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
|
||||
final moneroNode = nodeSource.get(nodeId);
|
||||
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
||||
final havenNode = nodeSource.get(havenNodeId);
|
||||
final ethereumNode = nodeSource.get(ethereumNodeId);
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final deviceName = await _getDeviceName() ?? '';
|
||||
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
|
||||
@@ -542,6 +593,10 @@ abstract class SettingsStoreBase with Store {
|
||||
nodes[WalletType.haven] = havenNode;
|
||||
}
|
||||
|
||||
if (ethereumNode != null) {
|
||||
nodes[WalletType.ethereum] = ethereumNode;
|
||||
}
|
||||
|
||||
return SettingsStore(
|
||||
sharedPreferences: sharedPreferences,
|
||||
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
|
||||
@@ -567,6 +622,9 @@ abstract class SettingsStoreBase with Store {
|
||||
initialPinLength: pinLength,
|
||||
pinTimeOutDuration: pinCodeTimeOutDuration,
|
||||
initialLanguageCode: savedLanguageCode,
|
||||
sortBalanceBy: sortBalanceBy,
|
||||
pinNativeTokenAtTop: pinNativeTokenAtTop,
|
||||
useEtherscan: useEtherscan,
|
||||
initialMoneroTransactionPriority: moneroTransactionPriority,
|
||||
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
||||
initialHavenTransactionPriority: havenTransactionPriority,
|
||||
@@ -582,6 +640,7 @@ abstract class SettingsStoreBase with Store {
|
||||
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
|
||||
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||
initialEthereumTransactionPriority: ethereumTransactionPriority,
|
||||
shouldShowYatPopup: shouldShowYatPopup);
|
||||
}
|
||||
|
||||
@@ -608,6 +667,11 @@ abstract class SettingsStoreBase with Store {
|
||||
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ??
|
||||
priority[WalletType.litecoin]!;
|
||||
}
|
||||
if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
|
||||
priority[WalletType.ethereum] = ethereum?.deserializeEthereumTransactionPriority(
|
||||
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ??
|
||||
priority[WalletType.ethereum]!;
|
||||
}
|
||||
|
||||
balanceDisplayMode = BalanceDisplayMode.deserialize(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
|
||||
@@ -616,7 +680,7 @@ abstract class SettingsStoreBase with Store {
|
||||
shouldSaveRecipientAddress;
|
||||
totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey;
|
||||
useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA;
|
||||
|
||||
|
||||
numberOfFailedTokenTrials =
|
||||
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
|
||||
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
|
||||
@@ -677,6 +741,10 @@ abstract class SettingsStoreBase with Store {
|
||||
languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
|
||||
shouldShowYatPopup =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
|
||||
sortBalanceBy = SortBalanceBy
|
||||
.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index];
|
||||
pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
|
||||
useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
|
||||
|
||||
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||
final bitcoinElectrumServerId =
|
||||
@@ -684,10 +752,12 @@ abstract class SettingsStoreBase with Store {
|
||||
final litecoinElectrumServerId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
||||
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
|
||||
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
|
||||
final moneroNode = nodeSource.get(nodeId);
|
||||
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
||||
final havenNode = nodeSource.get(havenNodeId);
|
||||
final ethereumNode = nodeSource.get(ethereumNodeId);
|
||||
|
||||
if (moneroNode != null) {
|
||||
nodes[WalletType.monero] = moneroNode;
|
||||
@@ -704,6 +774,10 @@ abstract class SettingsStoreBase with Store {
|
||||
if (havenNode != null) {
|
||||
nodes[WalletType.haven] = havenNode;
|
||||
}
|
||||
|
||||
if (ethereumNode != null) {
|
||||
nodes[WalletType.ethereum] = ethereumNode;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveCurrentNode(Node node, WalletType walletType) async {
|
||||
@@ -722,6 +796,9 @@ abstract class SettingsStoreBase with Store {
|
||||
case WalletType.haven:
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, node.key as int);
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ class ExceptionHandler {
|
||||
"Connection closed before full header was received",
|
||||
"Connection terminated during handshake",
|
||||
"PERMISSION_NOT_GRANTED",
|
||||
"Failed host lookup: ",
|
||||
];
|
||||
|
||||
static Future<void> _addDeviceInfo(File file) async {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:cake_wallet/entities/contact_base.dart';
|
||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
@@ -57,11 +58,15 @@ abstract class ContactListViewModelBase with Store {
|
||||
|
||||
@computed
|
||||
List<ContactRecord> get contactsToShow => contacts
|
||||
.where((element) => _currency == null || element.type == _currency)
|
||||
.where((element) => _isValidForCurrency(element))
|
||||
.toList();
|
||||
|
||||
@computed
|
||||
List<WalletContact> get walletContactsToShow => walletContacts
|
||||
.where((element) => _currency == null || element.type == _currency)
|
||||
.where((element) => _isValidForCurrency(element))
|
||||
.toList();
|
||||
|
||||
bool _isValidForCurrency(ContactBase element) {
|
||||
return _currency == null || element.type == _currency || element.type.title == _currency!.tag;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/sort_balance_types.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
@@ -79,6 +80,15 @@ abstract class BalanceViewModelBase with Store {
|
||||
@computed
|
||||
bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled;
|
||||
|
||||
@computed
|
||||
bool get isHomeScreenSettingsEnabled => wallet.type == WalletType.ethereum;
|
||||
|
||||
@computed
|
||||
SortBalanceBy get sortBalanceBy => settingsStore.sortBalanceBy;
|
||||
|
||||
@computed
|
||||
bool get pinNativeToken => settingsStore.pinNativeTokenAtTop;
|
||||
|
||||
@computed
|
||||
String get asset {
|
||||
final typeFormatted = walletTypeToString(appStore.wallet!.type);
|
||||
@@ -109,6 +119,7 @@ abstract class BalanceViewModelBase with Store {
|
||||
switch(wallet.type) {
|
||||
case WalletType.monero:
|
||||
case WalletType.haven:
|
||||
case WalletType.ethereum:
|
||||
return S.current.xmr_available_balance;
|
||||
default:
|
||||
return S.current.confirmed;
|
||||
@@ -120,6 +131,7 @@ abstract class BalanceViewModelBase with Store {
|
||||
switch(wallet.type) {
|
||||
case WalletType.monero:
|
||||
case WalletType.haven:
|
||||
case WalletType.ethereum:
|
||||
return S.current.xmr_full_balance;
|
||||
default:
|
||||
return S.current.unconfirmed;
|
||||
@@ -262,32 +274,58 @@ abstract class BalanceViewModelBase with Store {
|
||||
});
|
||||
}
|
||||
|
||||
@computed
|
||||
bool get hasAdditionalBalance => wallet.type != WalletType.ethereum;
|
||||
|
||||
@computed
|
||||
List<BalanceRecord> get formattedBalances {
|
||||
final balance = balances.values.toList();
|
||||
|
||||
balance.sort((BalanceRecord a, BalanceRecord b) {
|
||||
if (b.asset == CryptoCurrency.xhv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (b.asset == CryptoCurrency.xusd) {
|
||||
if (a.asset == CryptoCurrency.xhv) {
|
||||
return -1;
|
||||
if (wallet.currency == CryptoCurrency.xhv) {
|
||||
if (b.asset == CryptoCurrency.xhv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
if (b.asset == CryptoCurrency.xusd) {
|
||||
if (a.asset == CryptoCurrency.xhv) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (b.asset == CryptoCurrency.xbtc) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (b.asset == CryptoCurrency.xeur) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (b.asset == CryptoCurrency.xbtc) {
|
||||
return 1;
|
||||
if (pinNativeToken) {
|
||||
if (b.asset == wallet.currency) return 1;
|
||||
if (a.asset == wallet.currency) return -1;
|
||||
}
|
||||
|
||||
if (b.asset == CryptoCurrency.xeur) {
|
||||
return 1;
|
||||
}
|
||||
switch (sortBalanceBy) {
|
||||
case SortBalanceBy.FiatBalance:
|
||||
final aFiatBalance = _getFiatBalance(
|
||||
price: fiatConvertationStore.prices[a.asset] ?? 0, cryptoAmount: a.availableBalance);
|
||||
final bFiatBalance = _getFiatBalance(
|
||||
price: fiatConvertationStore.prices[b.asset] ?? 0, cryptoAmount: b.availableBalance);
|
||||
|
||||
return 0;
|
||||
return (double.tryParse(bFiatBalance) ?? 0)
|
||||
.compareTo((double.tryParse(aFiatBalance)) ?? 0);
|
||||
case SortBalanceBy.GrossBalance:
|
||||
return (double.tryParse(b.availableBalance) ?? 0)
|
||||
.compareTo(double.tryParse(a.availableBalance) ?? 0);
|
||||
case SortBalanceBy.Alphabetical:
|
||||
return a.asset.title.compareTo(b.asset.title);
|
||||
}
|
||||
});
|
||||
|
||||
return balance;
|
||||
@@ -335,7 +373,7 @@ abstract class BalanceViewModelBase with Store {
|
||||
}
|
||||
|
||||
String _getFiatBalance({required double price, String? cryptoAmount}) {
|
||||
if (cryptoAmount == null || cryptoAmount.isEmpty) {
|
||||
if (cryptoAmount == null || cryptoAmount.isEmpty || double.tryParse(cryptoAmount) == null) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/sort_balance_types.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'home_settings_view_model.g.dart';
|
||||
|
||||
class HomeSettingsViewModel = HomeSettingsViewModelBase with _$HomeSettingsViewModel;
|
||||
|
||||
abstract class HomeSettingsViewModelBase with Store {
|
||||
HomeSettingsViewModelBase(this._settingsStore, this._balanceViewModel)
|
||||
: tokens = ObservableSet<Erc20Token>() {
|
||||
_updateTokensList();
|
||||
}
|
||||
|
||||
final SettingsStore _settingsStore;
|
||||
final BalanceViewModel _balanceViewModel;
|
||||
|
||||
final ObservableSet<Erc20Token> tokens;
|
||||
|
||||
@observable
|
||||
String searchText = '';
|
||||
|
||||
@computed
|
||||
SortBalanceBy get sortBalanceBy => _settingsStore.sortBalanceBy;
|
||||
|
||||
@action
|
||||
void setSortBalanceBy(SortBalanceBy value) {
|
||||
_settingsStore.sortBalanceBy = value;
|
||||
_updateTokensList();
|
||||
}
|
||||
|
||||
@computed
|
||||
bool get pinNativeToken => _settingsStore.pinNativeTokenAtTop;
|
||||
|
||||
@action
|
||||
void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value;
|
||||
|
||||
Future<void> addErc20Token(Erc20Token token) async {
|
||||
await ethereum!.addErc20Token(_balanceViewModel.wallet, token);
|
||||
_updateTokensList();
|
||||
_updateFiatPrices(token);
|
||||
}
|
||||
|
||||
Future<void> deleteErc20Token(Erc20Token token) async {
|
||||
await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token);
|
||||
_updateTokensList();
|
||||
}
|
||||
|
||||
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
|
||||
await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
|
||||
|
||||
CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
|
||||
|
||||
void _updateFiatPrices(Erc20Token token) async {
|
||||
try {
|
||||
_balanceViewModel.fiatConvertationStore.prices[token] =
|
||||
await FiatConversionService.fetchPrice(
|
||||
crypto: token,
|
||||
fiat: _settingsStore.fiatCurrency,
|
||||
torOnly: _settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void changeTokenAvailability(Erc20Token token, bool value) async {
|
||||
token.enabled = value;
|
||||
ethereum!.addErc20Token(_balanceViewModel.wallet, token);
|
||||
_refreshTokensList();
|
||||
}
|
||||
|
||||
@action
|
||||
void _updateTokensList() {
|
||||
int _sortFunc(Erc20Token e1, Erc20Token e2) {
|
||||
int index1 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e1);
|
||||
int index2 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e2);
|
||||
|
||||
if (e1.enabled && !e2.enabled) {
|
||||
return -1;
|
||||
} else if (e2.enabled && !e1.enabled) {
|
||||
return 1;
|
||||
} else if (!e1.enabled && !e2.enabled) { // if both are disabled then sort alphabetically
|
||||
return e1.name.compareTo(e2.name);
|
||||
}
|
||||
|
||||
return index1.compareTo(index2);
|
||||
}
|
||||
|
||||
tokens.clear();
|
||||
|
||||
tokens.addAll(ethereum!
|
||||
.getERC20Currencies(_balanceViewModel.wallet)
|
||||
.where((element) => _matchesSearchText(element))
|
||||
.toList()
|
||||
..sort(_sortFunc));
|
||||
}
|
||||
|
||||
@action
|
||||
void _refreshTokensList() {
|
||||
final _tokens = Set.of(tokens);
|
||||
tokens.clear();
|
||||
tokens.addAll(_tokens);
|
||||
}
|
||||
|
||||
@action
|
||||
void changeSearchText(String text) {
|
||||
searchText = text;
|
||||
_updateTokensList();
|
||||
}
|
||||
|
||||
bool _matchesSearchText(Erc20Token asset) {
|
||||
return searchText.isEmpty ||
|
||||
asset.fullName!.toLowerCase().contains(searchText.toLowerCase()) ||
|
||||
asset.title.toLowerCase().contains(searchText.toLowerCase()) ||
|
||||
asset.contractAddress == searchText;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
@@ -84,6 +85,13 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||
cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount),
|
||||
price: price);
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||
final price = balanceViewModel.fiatConvertationStore.prices[asset];
|
||||
amount = calculateFiatAmountRaw(
|
||||
cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
|
||||
price: price);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -699,6 +699,10 @@ abstract class ExchangeViewModelBase with Store {
|
||||
depositCurrency = CryptoCurrency.xhv;
|
||||
receiveCurrency = CryptoCurrency.btc;
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
depositCurrency = CryptoCurrency.eth;
|
||||
receiveCurrency = CryptoCurrency.xmr;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ abstract class NodeListViewModelBase with Store {
|
||||
case WalletType.haven:
|
||||
node = getHavenDefaultNode(nodes: _nodeSource)!;
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
node = getEthereumDefaultNode(nodes: _nodeSource)!;
|
||||
break;
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/haven/haven.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
@@ -90,6 +91,9 @@ abstract class OutputBase with Store {
|
||||
case WalletType.haven:
|
||||
_amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount);
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
_amount = ethereum!.formatterEthereumParseAmount(_cryptoAmount);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -123,6 +127,10 @@ abstract class OutputBase with Store {
|
||||
if (_wallet.type == WalletType.haven) {
|
||||
return haven!.formatterMoneroAmountToDouble(amount: fee);
|
||||
}
|
||||
|
||||
if (_wallet.type == WalletType.ethereum) {
|
||||
return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee));
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
@@ -133,8 +141,9 @@ abstract class OutputBase with Store {
|
||||
@computed
|
||||
String get estimatedFeeFiatAmount {
|
||||
try {
|
||||
final currency = _wallet.type == WalletType.ethereum ? _wallet.currency : cryptoCurrencyHandler();
|
||||
final fiat = calculateFiatAmountRaw(
|
||||
price: _fiatConversationStore.prices[cryptoCurrencyHandler()]!,
|
||||
price: _fiatConversationStore.prices[currency]!,
|
||||
cryptoAmount: estimatedFee);
|
||||
return fiat;
|
||||
} catch (_) {
|
||||
@@ -228,6 +237,9 @@ abstract class OutputBase with Store {
|
||||
case WalletType.haven:
|
||||
maximumFractionDigits = 12;
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
maximumFractionDigits = 12;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,7 @@ import 'package:cake_wallet/store/settings_store.dart';
|
||||
|
||||
part 'send_template_view_model.g.dart';
|
||||
|
||||
class SendTemplateViewModel = SendTemplateViewModelBase
|
||||
with _$SendTemplateViewModel;
|
||||
class SendTemplateViewModel = SendTemplateViewModelBase with _$SendTemplateViewModel;
|
||||
|
||||
abstract class SendTemplateViewModelBase with Store {
|
||||
final WalletBase _wallet;
|
||||
@@ -22,8 +21,8 @@ abstract class SendTemplateViewModelBase with Store {
|
||||
final SendTemplateStore _sendTemplateStore;
|
||||
final FiatConversionStore _fiatConversationStore;
|
||||
|
||||
SendTemplateViewModelBase(this._wallet, this._settingsStore,
|
||||
this._sendTemplateStore, this._fiatConversationStore)
|
||||
SendTemplateViewModelBase(
|
||||
this._wallet, this._settingsStore, this._sendTemplateStore, this._fiatConversationStore)
|
||||
: recipients = ObservableList<TemplateViewModel>() {
|
||||
addRecipient();
|
||||
}
|
||||
@@ -33,7 +32,6 @@ abstract class SendTemplateViewModelBase with Store {
|
||||
@action
|
||||
void addRecipient() {
|
||||
recipients.add(TemplateViewModel(
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
wallet: _wallet,
|
||||
settingsStore: _settingsStore,
|
||||
fiatConversationStore: _fiatConversationStore));
|
||||
@@ -47,11 +45,13 @@ abstract class SendTemplateViewModelBase with Store {
|
||||
AmountValidator get amountValidator =>
|
||||
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
|
||||
|
||||
AddressValidator get addressValidator =>
|
||||
AddressValidator(type: _wallet.currency);
|
||||
AddressValidator get addressValidator => AddressValidator(type: _wallet.currency);
|
||||
|
||||
TemplateValidator get templateValidator => TemplateValidator();
|
||||
|
||||
bool get hasMultiRecipient =>
|
||||
_wallet.type != WalletType.haven && _wallet.type != WalletType.ethereum;
|
||||
|
||||
@computed
|
||||
CryptoCurrency get cryptoCurrency => _wallet.currency;
|
||||
|
||||
@@ -68,6 +68,7 @@ abstract class SendTemplateViewModelBase with Store {
|
||||
void addTemplate(
|
||||
{required String name,
|
||||
required bool isCurrencySelected,
|
||||
required String cryptoCurrency,
|
||||
required String address,
|
||||
required String amount,
|
||||
required String amountFiat,
|
||||
@@ -76,7 +77,7 @@ abstract class SendTemplateViewModelBase with Store {
|
||||
name: name,
|
||||
isCurrencySelected: isCurrencySelected,
|
||||
address: address,
|
||||
cryptoCurrency: cryptoCurrency.title,
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
fiatCurrency: fiatCurrency,
|
||||
amount: amount,
|
||||
amountFiat: amountFiat,
|
||||
@@ -89,4 +90,7 @@ abstract class SendTemplateViewModelBase with Store {
|
||||
_sendTemplateStore.remove(template: template);
|
||||
updateTemplate();
|
||||
}
|
||||
|
||||
@computed
|
||||
List<CryptoCurrency> get walletCurrencies => _wallet.balance.keys.toList();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
@@ -45,6 +46,7 @@ abstract class SendViewModelBase with Store {
|
||||
: state = InitialExecutionState(),
|
||||
currencies = _wallet.balance.keys.toList(),
|
||||
selectedCryptoCurrency = _wallet.currency,
|
||||
hasMultipleTokens = _wallet.type == WalletType.ethereum,
|
||||
outputs = ObservableList<Output>(),
|
||||
fiatFromSettings = _settingsStore.fiatCurrency {
|
||||
final priority = _settingsStore.priority[_wallet.type];
|
||||
@@ -105,8 +107,11 @@ abstract class SendViewModelBase with Store {
|
||||
String get pendingTransactionFeeFiatAmount {
|
||||
try {
|
||||
if (pendingTransaction != null) {
|
||||
final currency = walletType == WalletType.ethereum
|
||||
? _wallet.currency
|
||||
: selectedCryptoCurrency;
|
||||
final fiat = calculateFiatAmount(
|
||||
price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
|
||||
price: _fiatConversationStore.prices[currency]!,
|
||||
cryptoAmount: pendingTransaction!.feeFormatted);
|
||||
return fiat;
|
||||
} else {
|
||||
@@ -131,14 +136,14 @@ abstract class SendViewModelBase with Store {
|
||||
|
||||
CryptoCurrency get currency => _wallet.currency;
|
||||
|
||||
Validator get amountValidator =>
|
||||
Validator<String> get amountValidator =>
|
||||
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
|
||||
|
||||
Validator get allAmountValidator => AllAmountValidator();
|
||||
Validator<String> get allAmountValidator => AllAmountValidator();
|
||||
|
||||
Validator get addressValidator => AddressValidator(type: selectedCryptoCurrency);
|
||||
Validator<String> get addressValidator => AddressValidator(type: selectedCryptoCurrency);
|
||||
|
||||
Validator get textValidator => TextValidator();
|
||||
Validator<String> get textValidator => TextValidator();
|
||||
|
||||
final FiatCurrency fiatFromSettings;
|
||||
|
||||
@@ -146,7 +151,7 @@ abstract class SendViewModelBase with Store {
|
||||
PendingTransaction? pendingTransaction;
|
||||
|
||||
@computed
|
||||
String get balance => balanceViewModel.availableBalance;
|
||||
String get balance => _wallet.balance[selectedCryptoCurrency]!.formattedAvailableBalance;
|
||||
|
||||
@computed
|
||||
bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
|
||||
@@ -176,10 +181,9 @@ abstract class SendViewModelBase with Store {
|
||||
|
||||
List<CryptoCurrency> currencies;
|
||||
|
||||
bool get hasMultiRecipient => _wallet.type != WalletType.haven;
|
||||
|
||||
bool get hasYat => outputs
|
||||
.any((out) => out.isParsedAddress && out.parsedAddress.parseFrom == ParseFrom.yatRecord);
|
||||
bool get hasYat => outputs.any((out) =>
|
||||
out.isParsedAddress &&
|
||||
out.parsedAddress.parseFrom == ParseFrom.yatRecord);
|
||||
|
||||
WalletType get walletType => _wallet.type;
|
||||
|
||||
@@ -198,6 +202,7 @@ abstract class SendViewModelBase with Store {
|
||||
final ContactListViewModel contactListViewModel;
|
||||
final FiatConversionStore _fiatConversationStore;
|
||||
final Box<TransactionDescription> transactionDescriptionBox;
|
||||
final bool hasMultipleTokens;
|
||||
|
||||
@computed
|
||||
List<ContactRecord> get contactsToShow => contactListViewModel.contacts
|
||||
@@ -351,6 +356,15 @@ abstract class SendViewModelBase with Store {
|
||||
|
||||
return haven!.createHavenTransactionCreationCredentials(
|
||||
outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title);
|
||||
case WalletType.ethereum:
|
||||
final priority = _settingsStore.priority[_wallet.type];
|
||||
|
||||
if (priority == null) {
|
||||
throw Exception('Priority is null for wallet type: ${_wallet.type}');
|
||||
}
|
||||
|
||||
return ethereum!.createEthereumTransactionCredentials(
|
||||
outputs, priority: priority, currency: selectedCryptoCurrency);
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${_wallet.type}');
|
||||
}
|
||||
@@ -369,11 +383,22 @@ abstract class SendViewModelBase with Store {
|
||||
}
|
||||
|
||||
bool _isEqualCurrency(String currency) =>
|
||||
currency.toLowerCase() == _wallet.currency.title.toLowerCase();
|
||||
_wallet.balance.keys.any((e) => currency.toLowerCase() == e.title.toLowerCase());
|
||||
|
||||
@action
|
||||
void onClose() => _settingsStore.fiatCurrency = fiatFromSettings;
|
||||
|
||||
@action
|
||||
void setFiatCurrency(FiatCurrency fiat) => _settingsStore.fiatCurrency = fiat;
|
||||
void setFiatCurrency(FiatCurrency fiat) =>
|
||||
_settingsStore.fiatCurrency = fiat;
|
||||
|
||||
@action
|
||||
void setSelectedCryptoCurrency(String cryptoCurrency) {
|
||||
try {
|
||||
selectedCryptoCurrency = _wallet.balance.keys
|
||||
.firstWhere((e) => cryptoCurrency.toLowerCase() == e.title.toLowerCase());
|
||||
} catch (e) {
|
||||
selectedCryptoCurrency = _wallet.currency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,23 +11,20 @@ part 'template_view_model.g.dart';
|
||||
class TemplateViewModel = TemplateViewModelBase with _$TemplateViewModel;
|
||||
|
||||
abstract class TemplateViewModelBase with Store {
|
||||
final CryptoCurrency cryptoCurrency;
|
||||
final WalletBase _wallet;
|
||||
final SettingsStore _settingsStore;
|
||||
final FiatConversionStore _fiatConversationStore;
|
||||
|
||||
TemplateViewModelBase(
|
||||
{required this.cryptoCurrency,
|
||||
required WalletBase wallet,
|
||||
required SettingsStore settingsStore,
|
||||
required FiatConversionStore fiatConversationStore})
|
||||
: _wallet = wallet,
|
||||
TemplateViewModelBase({
|
||||
required WalletBase wallet,
|
||||
required SettingsStore settingsStore,
|
||||
required FiatConversionStore fiatConversationStore,
|
||||
}) : _wallet = wallet,
|
||||
_settingsStore = settingsStore,
|
||||
_fiatConversationStore = fiatConversationStore,
|
||||
output = Output(wallet, settingsStore, fiatConversationStore,
|
||||
() => wallet.currency) {
|
||||
output = Output(
|
||||
_wallet, _settingsStore, _fiatConversationStore, () => cryptoCurrency);
|
||||
_currency = wallet.currency,
|
||||
output = Output(wallet, settingsStore, fiatConversationStore, () => wallet.currency) {
|
||||
output = Output(_wallet, _settingsStore, _fiatConversationStore, () => _currency);
|
||||
}
|
||||
|
||||
@observable
|
||||
@@ -39,6 +36,9 @@ abstract class TemplateViewModelBase with Store {
|
||||
@observable
|
||||
String address = '';
|
||||
|
||||
@observable
|
||||
CryptoCurrency _currency;
|
||||
|
||||
@observable
|
||||
bool isCurrencySelected = true;
|
||||
|
||||
@@ -66,8 +66,7 @@ abstract class TemplateViewModelBase with Store {
|
||||
output.reset();
|
||||
}
|
||||
|
||||
Template toTemplate(
|
||||
{required String cryptoCurrency, required String fiatCurrency}) {
|
||||
Template toTemplate({required String cryptoCurrency, required String fiatCurrency}) {
|
||||
return Template(
|
||||
isCurrencySelectedRaw: isCurrencySelected,
|
||||
nameRaw: name,
|
||||
@@ -77,4 +76,13 @@ abstract class TemplateViewModelBase with Store {
|
||||
amountRaw: output.cryptoAmount,
|
||||
amountFiatRaw: output.fiatAmount);
|
||||
}
|
||||
|
||||
@action
|
||||
void changeSelectedCurrency(CryptoCurrency currency) {
|
||||
isCurrencySelected = true;
|
||||
_currency = currency;
|
||||
}
|
||||
|
||||
@computed
|
||||
CryptoCurrency get selectedCurrency => _currency;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||
|
||||
@@ -8,9 +11,10 @@ part 'privacy_settings_view_model.g.dart';
|
||||
class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel;
|
||||
|
||||
abstract class PrivacySettingsViewModelBase with Store {
|
||||
PrivacySettingsViewModelBase(this._settingsStore);
|
||||
PrivacySettingsViewModelBase(this._settingsStore, this._wallet);
|
||||
|
||||
final SettingsStore _settingsStore;
|
||||
final WalletBase _wallet;
|
||||
|
||||
@computed
|
||||
ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
|
||||
@@ -30,8 +34,14 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||
@computed
|
||||
bool get disableSell => _settingsStore.disableSell;
|
||||
|
||||
@computed
|
||||
bool get useEtherscan => _settingsStore.useEtherscan;
|
||||
|
||||
bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
|
||||
|
||||
@action
|
||||
void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value;
|
||||
void setShouldSaveRecipientAddress(bool value) =>
|
||||
_settingsStore.shouldSaveRecipientAddress = value;
|
||||
|
||||
@action
|
||||
void setExchangeApiMode(ExchangeApiMode value) => _settingsStore.exchangeStatus = value;
|
||||
@@ -48,4 +58,9 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||
@action
|
||||
void setDisableSell(bool value) => _settingsStore.disableSell = value;
|
||||
|
||||
@action
|
||||
void setUseEtherscan(bool value) {
|
||||
_settingsStore.useEtherscan = value;
|
||||
ethereum!.updateEtherscanUsageState(_wallet, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/src/intl/date_format.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
@@ -27,105 +28,27 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
required this.wallet,
|
||||
required this.settingsStore})
|
||||
: items = [],
|
||||
isRecipientAddressShown = false,
|
||||
showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
|
||||
isRecipientAddressShown = false,
|
||||
showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
|
||||
final dateFormat = DateFormatter.withCurrentLocal();
|
||||
final tx = transactionInfo;
|
||||
|
||||
if (wallet.type == WalletType.monero) {
|
||||
final key = tx.additionalInfo['key'] as String?;
|
||||
final accountIndex = tx.additionalInfo['accountIndex'] as int;
|
||||
final addressIndex = tx.additionalInfo['addressIndex'] as int;
|
||||
final feeFormatted = tx.feeFormatted();
|
||||
final _items = [
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date,
|
||||
value: dateFormat.format(tx.date)),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_amount,
|
||||
value: tx.amountFormatted()),
|
||||
if (feeFormatted != null)
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_fee, value: feeFormatted),
|
||||
if (key?.isNotEmpty ?? false)
|
||||
StandartListItem(title: S.current.transaction_key, value: key!)
|
||||
];
|
||||
|
||||
if (tx.direction == TransactionDirection.incoming &&
|
||||
accountIndex != null &&
|
||||
addressIndex != null) {
|
||||
try {
|
||||
final address = monero!.getTransactionAddress(wallet, accountIndex, addressIndex);
|
||||
final label = monero!.getSubaddressLabel(wallet, accountIndex, addressIndex);
|
||||
|
||||
if (address?.isNotEmpty ?? false) {
|
||||
isRecipientAddressShown = true;
|
||||
_items.add(
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
value: address));
|
||||
}
|
||||
|
||||
if (label?.isNotEmpty ?? false) {
|
||||
_items.add(
|
||||
StandartListItem(
|
||||
title: S.current.address_label,
|
||||
value: label)
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.bitcoin
|
||||
|| wallet.type == WalletType.litecoin) {
|
||||
final _items = [
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date,
|
||||
value: dateFormat.format(tx.date)),
|
||||
StandartListItem(
|
||||
title: S.current.confirmations,
|
||||
value: tx.confirmations.toString()),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_amount,
|
||||
value: tx.amountFormatted()),
|
||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_fee,
|
||||
value: tx.feeFormatted()!),
|
||||
];
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.haven) {
|
||||
items.addAll([
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date,
|
||||
value: dateFormat.format(tx.date)),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_amount,
|
||||
value: tx.amountFormatted()),
|
||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||
]);
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
_addMoneroListItems(tx, dateFormat);
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
_addElectrumListItems(tx, dateFormat);
|
||||
break;
|
||||
case WalletType.haven:
|
||||
_addHavenListItems(tx, dateFormat);
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
_addEthereumListItems(tx, dateFormat);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (showRecipientAddress && !isRecipientAddressShown) {
|
||||
@@ -136,10 +59,9 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
|
||||
if (recipientAddress?.isNotEmpty ?? false) {
|
||||
items.add(StandartListItem(
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
value: recipientAddress!));
|
||||
title: S.current.transaction_details_recipient_address, value: recipientAddress!));
|
||||
}
|
||||
} catch(_) {
|
||||
} catch (_) {
|
||||
// FIX-ME: Unhandled exception
|
||||
}
|
||||
}
|
||||
@@ -192,6 +114,8 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
return 'https://blockchair.com/litecoin/transaction/${txId}';
|
||||
case WalletType.haven:
|
||||
return 'https://explorer.havenprotocol.org/search?value=${txId}';
|
||||
case WalletType.ethereum:
|
||||
return 'https://etherscan.io/tx/${txId}';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@@ -207,8 +131,92 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
return S.current.view_transaction_on + 'Blockchair.com';
|
||||
case WalletType.haven:
|
||||
return S.current.view_transaction_on + 'explorer.havenprotocol.org';
|
||||
case WalletType.ethereum:
|
||||
return S.current.view_transaction_on + 'etherscan.io';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
void _addMoneroListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||
final key = tx.additionalInfo['key'] as String?;
|
||||
final accountIndex = tx.additionalInfo['accountIndex'] as int;
|
||||
final addressIndex = tx.additionalInfo['addressIndex'] as int;
|
||||
final feeFormatted = tx.feeFormatted();
|
||||
final _items = [
|
||||
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||
if (feeFormatted != null)
|
||||
StandartListItem(title: S.current.transaction_details_fee, value: feeFormatted),
|
||||
if (key?.isNotEmpty ?? false) StandartListItem(title: S.current.transaction_key, value: key!),
|
||||
];
|
||||
|
||||
if (tx.direction == TransactionDirection.incoming) {
|
||||
try {
|
||||
final address = monero!.getTransactionAddress(wallet, accountIndex, addressIndex);
|
||||
final label = monero!.getSubaddressLabel(wallet, accountIndex, addressIndex);
|
||||
|
||||
if (address.isNotEmpty) {
|
||||
isRecipientAddressShown = true;
|
||||
_items.add(StandartListItem(
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
value: address,
|
||||
));
|
||||
}
|
||||
|
||||
if (label.isNotEmpty) {
|
||||
_items.add(StandartListItem(title: S.current.address_label, value: label));
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
void _addElectrumListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||
final _items = [
|
||||
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||
StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
|
||||
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||
];
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
void _addHavenListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||
items.addAll([
|
||||
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||
]);
|
||||
}
|
||||
|
||||
void _addEthereumListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||
final _items = [
|
||||
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||
StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
|
||||
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||
];
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||
@@ -93,6 +94,22 @@ class LitecoinURI extends PaymentURI {
|
||||
}
|
||||
}
|
||||
|
||||
class EthereumURI extends PaymentURI {
|
||||
EthereumURI({required String amount, required String address})
|
||||
: super(amount: amount, address: address);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'ethereum:' + address;
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class WalletAddressListViewModelBase with Store {
|
||||
WalletAddressListViewModelBase({
|
||||
required AppStore appStore,
|
||||
@@ -151,6 +168,10 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||
return LitecoinURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (_wallet.type == WalletType.ethereum) {
|
||||
return EthereumURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
|
||||
@@ -202,6 +223,12 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||
addressList.addAll(bitcoinAddresses);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.ethereum) {
|
||||
final primaryAddress = ethereum!.getAddress(wallet);
|
||||
|
||||
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
|
||||
}
|
||||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
@@ -226,6 +253,10 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||
@computed
|
||||
bool get hasAddressList => _wallet.type == WalletType.monero || _wallet.type == WalletType.haven;
|
||||
|
||||
@computed
|
||||
bool get showElectrumAddressDisclaimer =>
|
||||
_wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin;
|
||||
|
||||
@observable
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ class WalletKeysViewModel = WalletKeysViewModelBase with _$WalletKeysViewModel;
|
||||
abstract class WalletKeysViewModelBase with Store {
|
||||
WalletKeysViewModelBase(this._appStore)
|
||||
: title = _appStore.wallet!.type == WalletType.bitcoin ||
|
||||
_appStore.wallet!.type == WalletType.litecoin
|
||||
_appStore.wallet!.type == WalletType.litecoin ||
|
||||
_appStore.wallet!.type == WalletType.ethereum
|
||||
? S.current.wallet_seed
|
||||
: S.current.wallet_keys,
|
||||
_restoreHeight = _appStore.wallet!.walletInfo.restoreHeight,
|
||||
@@ -89,7 +90,8 @@ abstract class WalletKeysViewModelBase with Store {
|
||||
}
|
||||
|
||||
if (_appStore.wallet!.type == WalletType.bitcoin ||
|
||||
_appStore.wallet!.type == WalletType.litecoin) {
|
||||
_appStore.wallet!.type == WalletType.litecoin ||
|
||||
_appStore.wallet!.type == WalletType.ethereum) {
|
||||
items.addAll([
|
||||
StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed),
|
||||
]);
|
||||
@@ -116,6 +118,8 @@ abstract class WalletKeysViewModelBase with Store {
|
||||
return 'litecoin-wallet';
|
||||
case WalletType.haven:
|
||||
return 'haven-wallet';
|
||||
case WalletType.ethereum:
|
||||
return 'ethereum-wallet';
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
@@ -42,6 +43,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
||||
case WalletType.haven:
|
||||
return haven!.createHavenNewWalletCredentials(
|
||||
name: name, language: options as String);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.createEthereumNewWalletCredentials(name: name);
|
||||
default:
|
||||
throw Exception('Unexpected type: ${type.toString()}');;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/mnemonic_length.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
@@ -85,6 +86,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||
height: height,
|
||||
mnemonic: seed,
|
||||
password: password);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
mnemonic: seed,
|
||||
password: password);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user