CW-438 add nano (#1015)

* 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 [skip ci]

* - 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 [skip ci]

* 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 [skip ci]

* 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` [skip-ci]

* initial changes

* more base config stuff

* config changes

* successfully builds!

* save

* successfully add nano wallet

* save

* seed generation

* receive screen + node screen working

* tx history working and fiat fixes

* balance working

* derivation updates

* nano-unfinished

* sends working

* remove fees from send screen, send and receive transactions working

* fixes + auto receive incoming txs

* fix for scanning QR codes

* save

* update translations

* fixes

* more fixes

* more strings

* small fix

* fix github actions workflow

* potential fix

* potential fix

* ci/cd fix

* change rep working

* seed generation fixes

* fixes

* save

* change rep screen functional

* save

* banano changes

* fixes, start adding ui for PoW

* pow node changes

* update translations

* fix

* account changing barely working

* save

* disable account generation

* small fix

* save

* UI work

* save

* fixes after merge main

* fixes

* remove monero stuff, work on derivation ui

* lots of fixes + finish up seed derivation

* last minute fixes

* node related fixes

* more fixes

* small fix

* more fixes

* fixes

* pretty big refactor for pow, still some bugs

* finally works!

* get transactions after send

* fix

* merge conflict fixes

* save

* fix pow node showing up twice

* done

* initial changes

* small fix

* more merge fixes

* fixes

* more fixes

* fix

* save

* fix manage pow nodes setting appearing on other wallets

* fix contact bug

* fixes

* fiat fixes

* save

* save

* save

* save

* updates

* cleanup

* restore fix

* fixes

* remove deprecated alert

* fix

* small fix

* remove outdated warning

* electrum restore fixes

* fixes

* fixes

* fix

* derivation fixes

* nano fixes pt.1

* nano fixes pt.2

* bip39 fixes

* pownode refactor

* nodes pages fixes

* observer fix

* ssl fix

* remove old references

* remove unused imports

* code cleanup

* small fix

* small potential fix

* save

* undo all bitcoin related changes

* remove dead code

* review fixes

* more fixes

* fix

* fix

* review fix

* small fix

* nano derivation and nanoutil fixes

* exchange nano fix

* nano review fixes pt.1

* nano fixes pt.2

* nano fixes pt.3

* remove old imports + stop using dynamic in di

* nanoutil fixes

* add nano.dart to gitignore, configure fixes

* review fixes, getnanowalletservice removed

* fix settings screen, add changeRep to configure.dart, other minor fixes

* remove manage_pow_nodes_page, key derivation edge case handled

* remove old refs

* more small fixes

* Generic Enhancements/Minor fixes

* review fixes

* hopefully final fixes

* review fixes

* node connection fixes

---------

Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>
Co-authored-by: fossephate <fosse@book.local>
This commit is contained in:
Matthew Fosse
2023-10-05 04:09:07 +03:00
committed by GitHub
co-authored by OmarHatem Justin Ehrenhofer fossephate
parent 6233422643
commit 4c60b178be
134 changed files with 8171 additions and 1055 deletions
+1 -1
View File
@@ -161,4 +161,4 @@ class CWBitcoin extends Bitcoin {
@override
TransactionPriority getLitecoinTransactionPrioritySlow()
=> LitecoinTransactionPriority.slow;
}
}
@@ -27,6 +27,8 @@ class OnRamperBuyProvider {
return "LTC_LITECOIN";
case CryptoCurrency.xmr:
return "XMR_MONERO";
case CryptoCurrency.nano:
return "XNO_NANO";
default:
return _wallet.currency.title;
}
+4
View File
@@ -28,6 +28,8 @@ class AddressValidator extends TextValidator {
return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$';
case CryptoCurrency.nano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.banano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.usdc:
case CryptoCurrency.usdcpoly:
case CryptoCurrency.ape:
@@ -177,6 +179,8 @@ class AddressValidator extends TextValidator {
return [34, 43, 63];
case CryptoCurrency.nano:
return [64, 65];
case CryptoCurrency.banano:
return [64, 65];
case CryptoCurrency.sc:
return [76];
case CryptoCurrency.sol:
+4
View File
@@ -5,6 +5,7 @@ import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/entities/mnemonic_item.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/utils/language_list.dart';
class SeedValidator extends Validator<MnemonicItem> {
@@ -28,6 +29,9 @@ class SeedValidator extends Validator<MnemonicItem> {
return haven!.getMoneroWordList(language);
case WalletType.ethereum:
return ethereum!.getEthereumWordList(language);
case WalletType.nano:
case WalletType.banano:
return nano!.getNanoWordList(language);
default:
return [];
}
+12 -25
View File
@@ -39,15 +39,11 @@ class WalletCreationService {
bool exists(String name) {
final walletName = name.toLowerCase();
return walletInfoSource
.values
.any((walletInfo) => walletInfo.name.toLowerCase() == walletName);
return walletInfoSource.values.any((walletInfo) => walletInfo.name.toLowerCase() == walletName);
}
bool typeExists(WalletType type) {
return walletInfoSource
.values
.any((walletInfo) => walletInfo.type == type);
return walletInfoSource.values.any((walletInfo) => walletInfo.type == type);
}
void checkIfExists(String name) {
@@ -60,15 +56,12 @@ class WalletCreationService {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
final wallet = await _service!.create(credentials);
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.create(credentials);
if (wallet.type == WalletType.monero) {
await sharedPreferences
.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
await sharedPreferences.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
}
return wallet;
@@ -78,15 +71,12 @@ class WalletCreationService {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.restoreFromKeys(credentials);
if (wallet.type == WalletType.monero) {
await sharedPreferences
.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
await sharedPreferences.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
}
return wallet;
@@ -96,15 +86,12 @@ class WalletCreationService {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.restoreFromSeed(credentials);
if (wallet.type == WalletType.monero) {
await sharedPreferences
.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
await sharedPreferences.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
}
return wallet;
+82 -12
View File
@@ -13,6 +13,7 @@ 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/nano/nano.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';
@@ -26,8 +27,13 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet
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/nano/nano_change_rep_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
@@ -73,6 +79,9 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
@@ -83,7 +92,9 @@ 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:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cake_wallet/core/backup_service.dart';
import 'package:cw_core/wallet_service.dart';
@@ -206,6 +217,7 @@ import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/qr_view_data.dart';
import 'package:cake_wallet/nano/nano.dart' as nanoNano;
import 'core/totp_request_details.dart';
@@ -214,6 +226,7 @@ final getIt = GetIt.instance;
var _isSetupFinished = false;
late Box<WalletInfo> _walletInfoSource;
late Box<Node> _nodeSource;
late Box<Node> _powNodeSource;
late Box<Contact> _contactSource;
late Box<Trade> _tradesSource;
late Box<Template> _templates;
@@ -226,6 +239,7 @@ late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
Future<void> setup({
required Box<WalletInfo> walletInfoSource,
required Box<Node> nodeSource,
required Box<Node> powNodeSource,
required Box<Contact> contactSource,
required Box<Trade> tradesSource,
required Box<Template> templates,
@@ -237,6 +251,7 @@ Future<void> setup({
}) async {
_walletInfoSource = walletInfoSource;
_nodeSource = nodeSource;
_powNodeSource = powNodeSource;
_contactSource = contactSource;
_tradesSource = tradesSource;
_templates = templates;
@@ -259,6 +274,7 @@ Future<void> setup({
final settingsStore = await SettingsStoreBase.load(
nodeSource: _nodeSource,
powNodeSource: _powNodeSource,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
// Enforce darkTheme on platforms other than mobile till the design for other themes is completed
initialTheme: ResponsiveLayoutUtil.instance.isMobile && DeviceInfo.instance.isMobile
@@ -271,6 +287,7 @@ Future<void> setup({
}
getIt.registerFactory<Box<Node>>(() => _nodeSource);
getIt.registerFactory<Box<Node>>(() => _powNodeSource, instanceName: Node.boxName + "pow");
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
getIt.registerSingleton(AuthenticationStore());
@@ -401,7 +418,7 @@ Future<void> setup({
}
if (appStore.wallet != null) {
authStore.allowed();
if (appStore.wallet!.type == WalletType.ethereum) {
getIt.get<Web3WalletService>().init();
}
@@ -449,7 +466,7 @@ Future<void> setup({
}, instanceName: 'login');
getIt.registerSingleton<BottomSheetService>(BottomSheetServiceImpl());
final appStore = getIt.get<AppStore>();
getIt.registerLazySingleton<WalletConnectKeyService>(() => KeyServiceImpl(appStore.wallet!));
@@ -612,20 +629,30 @@ Future<void> setup({
editingWallet: editingWallet);
});
getIt.registerFactory(() {
getIt.registerFactory<NanoAccountListViewModel>(() {
final wallet = getIt.get<AppStore>().wallet!;
if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
return NanoAccountListViewModel(wallet);
}
throw Exception(
'Unexpected wallet type: ${wallet.type} for generate Nano/Banano AccountListViewModel');
});
getIt.registerFactory<MoneroAccountListViewModel>(() {
final wallet = getIt.get<AppStore>().wallet!;
if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
return MoneroAccountListViewModel(wallet);
}
throw Exception(
'Unexpected wallet type: ${wallet.type} for generate MoneroAccountListViewModel');
'Unexpected wallet type: ${wallet.type} for generate Nano/Monero AccountListViewModel');
});
getIt.registerFactory(
() => MoneroAccountListPage(accountListViewModel: getIt.get<MoneroAccountListViewModel>()));
getIt.registerFactory(
() => NanoAccountListPage(accountListViewModel: getIt.get<NanoAccountListViewModel>()));
/*getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
@@ -653,6 +680,18 @@ Future<void> setup({
moneroAccountCreationViewModel:
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
getIt.registerFactoryParam<NanoAccountEditOrCreateViewModel, NanoAccount?, void>(
(NanoAccount? account, _) =>
NanoAccountEditOrCreateViewModel(nano!.getAccountList(getIt.get<AppStore>().wallet!),
// banano?.getAccountList(getIt.get<AppStore>().wallet!),
wallet: getIt.get<AppStore>().wallet!,
accountListItem: account));
getIt.registerFactoryParam<NanoAccountEditOrCreatePage, NanoAccount?, void>(
(NanoAccount? account, _) => NanoAccountEditOrCreatePage(
nanoAccountCreationViewModel:
getIt.get<NanoAccountEditOrCreateViewModel>(param1: account)));
getIt.registerFactory(() {
return DisplaySettingsViewModel(getIt.get<SettingsStore>());
});
@@ -696,6 +735,11 @@ Future<void> setup({
return NodeListViewModel(_nodeSource, appStore);
});
getIt.registerFactory(() {
final appStore = getIt.get<AppStore>();
return PowNodeListViewModel(_powNodeSource, appStore);
});
getIt.registerFactory(
() => ConnectionSyncPage(getIt.get<DashboardViewModel>(), getIt.get<Web3WalletService>()),
);
@@ -709,13 +753,23 @@ Future<void> setup({
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, void>((WalletType? type, _) =>
NodeCreateOrEditViewModel(
_nodeSource, type ?? getIt.get<AppStore>().wallet!.type, getIt.get<SettingsStore>()));
getIt.registerFactory(() => NanoChangeRepPage(getIt.get<AppStore>().wallet!));
getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, bool?>(
(WalletType? type, bool? isPow) => NodeCreateOrEditViewModel(
(isPow ?? false) ? _powNodeSource : _nodeSource,
type ?? getIt.get<AppStore>().wallet!.type,
getIt.get<SettingsStore>()));
getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>(
(Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(),
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: false),
editingNode: editingNode,
isSelected: isSelected));
getIt.registerFactoryParam<PowNodeCreateOrEditPage, Node?, bool?>(
(Node? editingNode, bool? isSelected) => PowNodeCreateOrEditPage(
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: true),
editingNode: editingNode,
isSelected: isSelected));
@@ -771,6 +825,8 @@ Future<void> setup({
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.ethereum:
return ethereum!.createEthereumWalletService(_walletInfoSource);
case WalletType.nano:
return nano!.createNanoWalletService(_walletInfoSource);
default:
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
}
@@ -798,6 +854,15 @@ Future<void> setup({
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>(
(type, _) => WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, List<DerivationInfo>, void>(
(derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
getIt.registerFactoryParam<WalletRestoreChooseDerivationPage, List<DerivationInfo>, void>(
(credentials, _) =>
WalletRestoreChooseDerivationPage(getIt.get<WalletRestoreChooseDerivationViewModel>(
param1: credentials,
)));
getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) {
final wallet = getIt.get<AppStore>().wallet!;
@@ -911,8 +976,8 @@ Future<void> setup({
getIt.registerFactory(() => YatService());
getIt.registerFactory(() => AddressResolver(
yatService: getIt.get<YatService>(), wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory(() =>
AddressResolver(yatService: getIt.get<YatService>(), wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
(QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
@@ -1077,7 +1142,12 @@ Future<void> setup({
),
);
getIt.registerFactory<ManageNodesPage>(() => ManageNodesPage(getIt.get<NodeListViewModel>()));
getIt.registerFactoryParam<ManageNodesPage, bool, void>((bool isPow, _) {
if (isPow) {
return ManageNodesPage(isPow, powNodeListViewModel: getIt.get<PowNodeListViewModel>());
}
return ManageNodesPage(isPow, nodeListViewModel: getIt.get<NodeListViewModel>());
});
_isSetupFinished = true;
}
+165 -136
View File
@@ -26,12 +26,15 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
const nanoDefaultNodeUri = 'rpc.nano.to';
const nanoDefaultPowNodeUri = 'rpc.nano.to';
Future<void> defaultSettingsMigration(
{required int version,
required SharedPreferences sharedPreferences,
required FlutterSecureStorage secureStorage,
required Box<Node> nodes,
required Box<Node> powNodes,
required Box<WalletInfo> walletInfoSource,
required Box<Trade> tradeSource,
required Box<Contact> contactSource}) async {
@@ -40,41 +43,36 @@ Future<void> defaultSettingsMigration(
}
// check current nodes for nullability regardless of the version
await checkCurrentNodes(nodes, sharedPreferences);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
final isNewInstall =
sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
await _validateWalletInfoBoxData(walletInfoSource);
final isNewInstall = sharedPreferences
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
await sharedPreferences.setBool(PreferencesKey.isNewInstall, isNewInstall);
await sharedPreferences.setBool(
PreferencesKey.isNewInstall, isNewInstall);
final currentVersion =
sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ?? 0;
final currentVersion = sharedPreferences
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ??
0;
if (currentVersion >= version) {
return;
}
final migrationVersionsLength = version - currentVersion;
final migrationVersions = List<int>.generate(
migrationVersionsLength, (i) => currentVersion + (i + 1));
final migrationVersions =
List<int>.generate(migrationVersionsLength, (i) => currentVersion + (i + 1));
await Future.forEach(migrationVersions, (int version) async {
try {
switch (version) {
case 1:
await sharedPreferences.setString(
PreferencesKey.currentFiatCurrencyKey,
FiatCurrency.usd.toString());
await sharedPreferences.setInt(
PreferencesKey.currentTransactionPriorityKeyLegacy,
PreferencesKey.currentFiatCurrencyKey, FiatCurrency.usd.toString());
await sharedPreferences.setInt(PreferencesKey.currentTransactionPriorityKeyLegacy,
monero!.getDefaultTransactionPriority().raw);
await sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey,
BalanceDisplayMode.availableBalance.raw);
PreferencesKey.currentBalanceDisplayModeKey, BalanceDisplayMode.availableBalance.raw);
await sharedPreferences.setBool('save_recipient_address', true);
await resetToDefault(nodes);
await changeMoneroCurrentNodeToDefault(
@@ -83,14 +81,12 @@ Future<void> defaultSettingsMigration(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeHavenCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 2:
await replaceNodesMigration(nodes: nodes);
await replaceDefaultNode(
sharedPreferences: sharedPreferences, nodes: nodes);
await replaceDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 3:
@@ -124,7 +120,7 @@ Future<void> defaultSettingsMigration(
break;
case 12:
await checkCurrentNodes(nodes, sharedPreferences);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
break;
case 13:
@@ -135,14 +131,13 @@ Future<void> defaultSettingsMigration(
await addLitecoinElectrumServerList(nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await checkCurrentNodes(nodes, sharedPreferences);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
break;
case 16:
await addHavenNodeList(nodes: nodes);
await changeHavenCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await checkCurrentNodes(nodes, sharedPreferences);
await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
break;
case 17:
@@ -164,6 +159,13 @@ Future<void> defaultSettingsMigration(
await changeEthereumCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 22:
await addNanoNodeList(nodes: nodes);
await addNanoPowNodeList(nodes: nodes);
await changeNanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
await changeNanoCurrentPowNodeToDefault(
sharedPreferences: sharedPreferences, nodes: powNodes);
break;
default:
break;
@@ -176,8 +178,7 @@ Future<void> defaultSettingsMigration(
}
});
await sharedPreferences.setInt(
PreferencesKey.currentDefaultSettingsMigrationVersion, version);
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
}
Future<void> _validateWalletInfoBoxData(Box<WalletInfo> walletInfoSource) async {
@@ -247,8 +248,8 @@ Future<void> validateBitcoinSavedTransactionPriority(SharedPreferences sharedPre
final int? savedBitcoinPriority =
sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority);
if (!bitcoin!.getTransactionPriorities().any((element) => element.raw == savedBitcoinPriority)) {
await sharedPreferences.setInt(
PreferencesKey.bitcoinTransactionPriority, bitcoin!.getMediumTransactionPriority().serialize());
await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
bitcoin!.getMediumTransactionPriority().serialize());
}
}
@@ -265,10 +266,9 @@ Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
final replaceNodes = <String, Node>{
'eu-node.cakewallet.io:18081':
Node(uri: 'xmr-node-eu.cakewallet.com:18081', type: WalletType.monero),
'node.cakewallet.io:18081': Node(
uri: 'xmr-node-usa-east.cakewallet.com:18081', type: WalletType.monero),
'node.xmr.ru:13666':
Node(uri: 'node.monero.net:18081', type: WalletType.monero)
'node.cakewallet.io:18081':
Node(uri: 'xmr-node-usa-east.cakewallet.com:18081', type: WalletType.monero),
'node.xmr.ru:13666': Node(uri: 'node.monero.net:18081', type: WalletType.monero)
};
nodes.values.forEach((Node node) async {
@@ -284,8 +284,7 @@ Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
}
Future<void> changeMoneroCurrentNodeToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getMoneroDefaultNode(nodes: nodes);
final nodeId = node.key as int? ?? 0; // 0 - England
@@ -293,27 +292,35 @@ Future<void> changeMoneroCurrentNodeToDefault(
}
Node? getBitcoinDefaultElectrumServer({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull(
(Node node) => node.uriRaw == cakeWalletBitcoinElectrumUri)
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
return nodes.values
.firstWhereOrNull((Node node) => node.uriRaw == cakeWalletBitcoinElectrumUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
}
Node? getLitecoinDefaultElectrumServer({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull(
(Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri)
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.litecoin);
return nodes.values
.firstWhereOrNull((Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.litecoin);
}
Node? getHavenDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull(
(Node node) => node.uriRaw == havenDefaultNodeUri)
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == havenDefaultNodeUri) ??
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);
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == ethereumDefaultNodeUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
}
Node? getNanoDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultNodeUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano);
}
Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultPowNodeUri) ??
nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano));
}
Node getMoneroDefaultNode({required Box<Node> nodes}) {
@@ -329,16 +336,14 @@ Node getMoneroDefaultNode({required Box<Node> nodes}) {
}
try {
return nodes.values
.firstWhere((Node node) => node.uriRaw == nodeUri);
} catch(_) {
return nodes.values.firstWhere((Node node) => node.uriRaw == nodeUri);
} catch (_) {
return nodes.values.first;
}
}
Future<void> changeBitcoinCurrentElectrumServerToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final server = getBitcoinDefaultElectrumServer(nodes: nodes);
final serverId = server?.key as int? ?? 0;
@@ -346,8 +351,7 @@ Future<void> changeBitcoinCurrentElectrumServerToDefault(
}
Future<void> changeLitecoinCurrentElectrumServerToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final server = getLitecoinDefaultElectrumServer(nodes: nodes);
final serverId = server?.key as int? ?? 0;
@@ -355,8 +359,7 @@ Future<void> changeLitecoinCurrentElectrumServerToDefault(
}
Future<void> changeHavenCurrentNodeToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getHavenDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
@@ -364,25 +367,21 @@ Future<void> changeHavenCurrentNodeToDefault(
}
Future<void> replaceDefaultNode(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
const nodesForReplace = <String>[
'xmr-node-uk.cakewallet.com:18081',
'eu-node.cakewallet.io:18081',
'node.cakewallet.io:18081'
];
final currentNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentNode =
nodes.values.firstWhereOrNull((Node node) => node.key == currentNodeId);
final needToReplace =
currentNode == null ? true : nodesForReplace.contains(currentNode.uriRaw);
final currentNode = nodes.values.firstWhereOrNull((Node node) => node.key == currentNodeId);
final needToReplace = currentNode == null ? true : nodesForReplace.contains(currentNode.uriRaw);
if (!needToReplace) {
return;
}
await changeMoneroCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
}
Future<void> updateNodeTypes({required Box<Node> nodes}) async {
@@ -421,14 +420,11 @@ Future<void> addHavenNodeList({required Box<Node> nodes}) async {
}
}
Future<void> addAddressesForMoneroWallets(
Box<WalletInfo> walletInfoSource) async {
final moneroWalletsInfo =
walletInfoSource.values.where((info) => info.type == WalletType.monero);
Future<void> addAddressesForMoneroWallets(Box<WalletInfo> walletInfoSource) async {
final moneroWalletsInfo = walletInfoSource.values.where((info) => info.type == WalletType.monero);
moneroWalletsInfo.forEach((info) async {
try {
final walletPath =
await pathForWallet(name: info.name, type: WalletType.monero);
final walletPath = await pathForWallet(name: info.name, type: WalletType.monero);
final addressFilePath = '$walletPath.address.txt';
final addressFile = File(addressFilePath);
@@ -449,8 +445,7 @@ Future<void> updateDisplayModes(SharedPreferences sharedPreferences) async {
final currentBalanceDisplayMode =
sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey) ?? -1;
final balanceDisplayMode = currentBalanceDisplayMode < 2 ? 3 : 2;
await sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
await sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
}
Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async {
@@ -464,10 +459,9 @@ Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async {
await secureStorage.write(key: key, value: password);
}
Future<void> changeTransactionPriorityAndFeeRateKeys(
SharedPreferences sharedPreferences) async {
final legacyTransactionPriority = sharedPreferences
.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy)!;
Future<void> changeTransactionPriorityAndFeeRateKeys(SharedPreferences sharedPreferences) async {
final legacyTransactionPriority =
sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy)!;
await sharedPreferences.setInt(
PreferencesKey.moneroTransactionPriority, legacyTransactionPriority);
await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
@@ -477,10 +471,8 @@ Future<void> changeTransactionPriorityAndFeeRateKeys(
Future<void> changeDefaultMoneroNode(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
const cakeWalletMoneroNodeUriPattern = '.cakewallet.com';
final currentMoneroNodeId =
sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentMoneroNode =
nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
final needToReplaceCurrentMoneroNode =
currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
@@ -491,78 +483,87 @@ Future<void> changeDefaultMoneroNode(
}
});
final newCakeWalletNode =
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode);
if (needToReplaceCurrentMoneroNode) {
await sharedPreferences.setInt(
PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
}
}
Future<void> checkCurrentNodes(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
final currentMoneroNodeId =
sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentBitcoinElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final currentLitecoinElectrumSeverId = sharedPreferences
.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(
(node) => node.key == currentBitcoinElectrumSeverId);
final currentLitecoinElectrumServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentLitecoinElectrumSeverId);
final currentHavenNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentHavenNodeId);
final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentEthereumNodeId);
final currentLitecoinElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentMoneroNode =
nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
final currentBitcoinElectrumServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinElectrumSeverId);
final currentLitecoinElectrumServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentLitecoinElectrumSeverId);
final currentHavenNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId);
final currentEthereumNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId);
final currentNanoNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
final currentNanoPowNodeServer =
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
if (currentMoneroNode == null) {
final newCakeWalletNode =
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode);
await sharedPreferences.setInt(
PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
}
if (currentBitcoinElectrumServer == null) {
final cakeWalletElectrum =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
final cakeWalletElectrum = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey,
cakeWalletElectrum.key as int);
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
}
if (currentLitecoinElectrumServer == null) {
final cakeWalletElectrum =
Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
final cakeWalletElectrum = Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt(
PreferencesKey.currentLitecoinElectrumSererIdKey,
cakeWalletElectrum.key as int);
PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
}
if (currentHavenNodeServer == null) {
final node = Node(uri: havenDefaultNodeUri, type: WalletType.haven);
await nodeSource.add(node);
await sharedPreferences.setInt(
PreferencesKey.currentHavenNodeIdKey, node.key as int);
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);
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
}
if (currentNanoNodeServer == null) {
final node = Node(uri: nanoDefaultNodeUri, useSSL: true, type: WalletType.nano);
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
}
if (currentNanoPowNodeServer == null) {
Node? node = powNodeSource.values
.firstWhereOrNull((node) => node.uri.toString() == nanoDefaultPowNodeUri);
if (node == null) {
node = Node(uri: nanoDefaultPowNodeUri, useSSL: true, type: WalletType.nano);
await powNodeSource.add(node);
}
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
}
}
@@ -570,31 +571,27 @@ Future<void> resetBitcoinElectrumServer(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
final currentElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final oldElectrumServer = nodeSource.values.firstWhereOrNull(
(node) => node.uri.toString().contains('electrumx.cakewallet.com'));
var cakeWalletNode = nodeSource.values.firstWhereOrNull(
(node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
final oldElectrumServer = nodeSource.values
.firstWhereOrNull((node) => node.uri.toString().contains('electrumx.cakewallet.com'));
var cakeWalletNode = nodeSource.values
.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
if (cakeWalletNode == null) {
cakeWalletNode =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
cakeWalletNode = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
await nodeSource.add(cakeWalletNode);
}
if (currentElectrumSeverId == oldElectrumServer?.key) {
await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey,
cakeWalletNode.key as int);
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletNode.key as int);
}
await oldElectrumServer?.delete();
}
Future<void> changeDefaultHavenNode(
Box<Node> nodeSource) async {
Future<void> changeDefaultHavenNode(Box<Node> nodeSource) async {
const previousHavenDefaultNodeUri = 'vault.havenprotocol.org:443';
final havenNodes = nodeSource.values.where(
(node) => node.uriRaw == previousHavenDefaultNodeUri);
final havenNodes = nodeSource.values.where((node) => node.uriRaw == previousHavenDefaultNodeUri);
havenNodes.forEach((node) async {
node.uriRaw = havenDefaultNodeUri;
await node.save();
@@ -607,8 +604,8 @@ Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
return;
}
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled
? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey,
isExchangeDisabled ? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
}
@@ -623,10 +620,42 @@ Future<void> addEthereumNodeList({required Box<Node> nodes}) async {
}
Future<void> changeEthereumCurrentNodeToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{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);
}
Future<void> addNanoNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultNanoNodes();
for (var node in nodeList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> addNanoPowNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultNanoPowNodes();
for (var node in nodeList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> changeNanoCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getNanoDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, nodeId);
}
Future<void> changeNanoCurrentPowNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getNanoDefaultPowNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId);
}
+4 -2
View File
@@ -52,6 +52,8 @@ class MainActions {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.nano:
case WalletType.banano:
switch (defaultBuyProvider) {
case BuyProviderType.AskEachTime:
Navigator.pushNamed(context, Routes.buy);
@@ -73,7 +75,7 @@ class MainActions {
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).buy,
alertContent: S.of(context).buy_alert_content,
alertContent: S.of(context).unsupported_asset,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
@@ -143,7 +145,7 @@ class MainActions {
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).sell,
alertContent: S.of(context).sell_alert_content,
alertContent: S.of(context).unsupported_asset,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
},
+50 -9
View File
@@ -21,13 +21,12 @@ Future<List<Node>> loadDefaultNodes() async {
}
Future<List<Node>> loadBitcoinElectrumServerList() async {
final serverListRaw =
await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
final serverListRaw = await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
final loadedServerList = loadYaml(serverListRaw) as YamlList;
final serverList = <Node>[];
for (final raw in loadedServerList) {
if (raw is Map) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.bitcoin;
serverList.add(node);
@@ -38,8 +37,7 @@ Future<List<Node>> loadBitcoinElectrumServerList() async {
}
Future<List<Node>> loadLitecoinElectrumServerList() async {
final serverListRaw =
await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
final serverListRaw = await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
final loadedServerList = loadYaml(serverListRaw) as YamlList;
final serverList = <Node>[];
@@ -66,7 +64,7 @@ Future<List<Node>> loadDefaultHavenNodes() async {
nodes.add(node);
}
}
return nodes;
}
@@ -86,17 +84,60 @@ Future<List<Node>> loadDefaultEthereumNodes() async {
return nodes;
}
Future<List<Node>> loadDefaultNanoNodes() async {
final nodesRaw = await rootBundle.loadString('assets/nano_node_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.nano;
nodes.add(node);
}
}
return nodes;
}
Future<List<Node>> loadDefaultNanoPowNodes() async {
final powNodesRaw = await rootBundle.loadString('assets/nano_pow_node_list.yml');
final loadedPowNodes = loadYaml(powNodesRaw) as YamlList;
final nodes = <Node>[];
for (final raw in loadedPowNodes) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.nano;
nodes.add(node);
}
}
return nodes;
}
Future resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
final havenNodes = await loadDefaultHavenNodes();
final nodes =
moneroNodes +
final ethereumNodes = await loadDefaultEthereumNodes();
final nanoNodes = await loadDefaultNanoNodes();
final nodes = moneroNodes +
bitcoinElectrumServerList +
litecoinElectrumServerList +
havenNodes;
havenNodes +
ethereumNodes +
nanoNodes;
await nodeSource.clear();
await nodeSource.addAll(nodes);
}
Future resetPowToDefault(Box<Node> powNodeSource) async {
final nanoPowNodes = await loadDefaultNanoPowNodes();
final nodes = nanoPowNodes;
await powNodeSource.clear();
await powNodeSource.addAll(nodes);
}
+4
View File
@@ -6,6 +6,10 @@ class PreferencesKey {
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
static const currentHavenNodeIdKey = 'current_node_id_xhv';
static const currentEthereumNodeIdKey = 'current_node_id_eth';
static const currentNanoNodeIdKey = 'current_node_id_nano';
static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
static const currentBananoNodeIdKey = 'current_node_id_banano';
static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow';
static const currentFiatCurrencyKey = 'current_fiat_currency';
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
+4 -1
View File
@@ -17,8 +17,11 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
return haven!.getTransactionPriorities();
case WalletType.ethereum:
return ethereum!.getTransactionPriorities();
// no such thing for nano/banano:
case WalletType.nano:
case WalletType.banano:
return [];
default:
return [];
}
}
+12 -3
View File
@@ -97,6 +97,10 @@ Future<void> initializeAppConfigs() async {
CakeHive.registerAdapter(WalletInfoAdapter());
}
if (!Hive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(DerivationTypeAdapter());
}
if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(WalletTypeAdapter());
}
@@ -128,6 +132,7 @@ Future<void> initializeAppConfigs() async {
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await CakeHive.openBox<Contact>(Contact.boxName);
final nodes = await CakeHive.openBox<Node>(Node.boxName);
final powNodes = await CakeHive.openBox<Node>(Node.boxName + "pow");// must be different from Node.boxName
final transactionDescriptions = await CakeHive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
@@ -142,6 +147,7 @@ Future<void> initializeAppConfigs() async {
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
powNodes: powNodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
@@ -153,12 +159,13 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 21);
}
initialMigrationVersion: 22);
}
Future<void> initialSetup(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes,
required Box<Node> powNodes,
required Box<WalletInfo> walletInfoSource,
required Box<Contact> contactSource,
required Box<Trade> tradesSource,
@@ -179,10 +186,12 @@ Future<void> initialSetup(
walletInfoSource: walletInfoSource,
contactSource: contactSource,
tradeSource: tradesSource,
nodes: nodes);
nodes: nodes,
powNodes: powNodes);
await setup(
walletInfoSource: walletInfoSource,
nodeSource: nodes,
powNodeSource: powNodes,
contactSource: contactSource,
tradesSource: tradesSource,
templates: templates,
+499
View File
@@ -0,0 +1,499 @@
part of 'nano.dart';
class CWNanoAccountList extends NanoAccountList {
CWNanoAccountList(this._wallet);
final Object _wallet;
@override
@computed
ObservableList<NanoAccount> get accounts {
final nanoWallet = _wallet as NanoWallet;
final accounts = nanoWallet.walletAddresses.accountList.accounts
.map((acc) => NanoAccount(id: acc.id, label: acc.label, balance: acc.balance))
.toList();
return ObservableList<NanoAccount>.of(accounts);
}
@override
void update(Object wallet) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.accountList.update(null);
}
@override
void refresh(Object wallet) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.accountList.refresh();
}
@override
Future<List<NanoAccount>> getAll(Object wallet) async {
final nanoWallet = wallet as NanoWallet;
return (await nanoWallet.walletAddresses.accountList.getAll())
.map((acc) => NanoAccount(id: acc.id, label: acc.label, balance: acc.balance))
.toList();
}
@override
Future<void> addAccount(Object wallet, {required String label}) async {
final nanoWallet = wallet as NanoWallet;
await nanoWallet.walletAddresses.accountList.addAccount(label: label);
}
@override
Future<void> setLabelAccount(Object wallet,
{required int accountIndex, required String label}) async {
final nanoWallet = wallet as NanoWallet;
await nanoWallet.walletAddresses.accountList
.setLabelAccount(accountIndex: accountIndex, label: label);
}
}
class CWNano extends Nano {
@override
NanoAccountList getAccountList(Object wallet) {
return CWNanoAccountList(wallet);
}
@override
Account getCurrentAccount(Object wallet) {
final nanoWallet = wallet as NanoWallet;
final acc = nanoWallet.walletAddresses.account;
return Account(id: acc!.id, label: acc.label, balance: acc.balance);
}
@override
void setCurrentAccount(Object wallet, int id, String label, String? balance) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.account = NanoAccount(id: id, label: label, balance: balance);
nanoWallet.regenerateAddress();
}
@override
List<String> getNanoWordList(String language) {
return NanoMnemomics.WORDLIST;
}
@override
WalletService createNanoWalletService(Box<WalletInfo> walletInfoSource) {
return NanoWalletService(walletInfoSource);
}
@override
Map<String, String> getKeys(Object wallet) {
final nanoWallet = wallet as NanoWallet;
final keys = nanoWallet.keys;
return <String, String>{
"seedKey": keys.seedKey,
};
}
@override
WalletCredentials createNanoNewWalletCredentials({
required String name,
String? password,
}) =>
NanoNewWalletCredentials(
name: name,
password: password,
);
@override
WalletCredentials createNanoRestoreWalletFromSeedCredentials({
required String name,
required String password,
required String mnemonic,
DerivationType? derivationType,
}) {
if (derivationType == null) {
// figure out the derivation type as best we can, otherwise set it to "unknown"
if (mnemonic.split(" ").length == 12) {
derivationType = DerivationType.bip39;
} else {
derivationType = DerivationType.unknown;
}
}
return NanoRestoreWalletFromSeedCredentials(
name: name,
password: password,
mnemonic: mnemonic,
derivationType: derivationType,
);
}
@override
WalletCredentials createNanoRestoreWalletFromKeysCredentials({
required String name,
required String password,
required String seedKey,
DerivationType? derivationType,
}) {
if (derivationType == null) {
// figure out the derivation type as best we can, otherwise set it to "unknown"
if (seedKey.length == 64) {
derivationType = DerivationType.nano;
} else {
derivationType = DerivationType.unknown;
}
}
return NanoRestoreWalletFromKeysCredentials(
name: name,
password: password,
seedKey: seedKey,
derivationType: derivationType,
);
}
@override
Object createNanoTransactionCredentials(List<Output> outputs) {
return NanoTransactionCredentials(
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(),
);
}
@override
Future<void> changeRep(Object wallet, String address) async {
return (wallet as NanoWallet).changeRep(address);
}
@override
Future<void> updateTransactions(Object wallet) async {
return (wallet as NanoWallet).updateTransactions();
}
@override
BigInt getTransactionAmountRaw(TransactionInfo transactionInfo) {
return (transactionInfo as NanoTransactionInfo).amountRaw;
}
}
class CWNanoUtil extends NanoUtil {
// standard:
@override
String seedToPrivate(String seed, int index) {
return ND.NanoKeys.seedToPrivate(seed, index);
}
@override
String seedToAddress(String seed, int index) {
return ND.NanoAccounts.createAccount(
ND.NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index)));
}
@override
String seedToMnemonic(String seed) {
return NanoMnemomics.seedToMnemonic(seed).join(" ");
}
@override
Future<String> mnemonicToSeed(String mnemonic) async {
return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
}
@override
String privateKeyToPublic(String privateKey) {
// return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
return ND.NanoKeys.createPublicKey(privateKey);
}
@override
String addressToPublicKey(String publicAddress) {
return ND.NanoAccounts.extractPublicKey(publicAddress);
}
// universal:
@override
String privateKeyToAddress(String privateKey) {
return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, privateKeyToPublic(privateKey));
}
@override
String publicKeyToAddress(String publicKey) {
return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, publicKey);
}
// standard + hd:
@override
bool isValidSeed(String seed) {
// Ensure seed is 64 or 128 characters long
if (seed.length != 64 && seed.length != 128) {
return false;
}
// Ensure seed only contains hex characters, 0-9;A-F
return ND.NanoHelpers.isHexString(seed);
}
// hd:
@override
Future<String> hdMnemonicListToSeed(List<String> words) async {
// if (words.length != 24) {
// throw Exception('Expected a 24-word list, got a ${words.length} list');
// }
final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic'));
final Pbkdf2 hasher = Pbkdf2(iterations: 2048);
final String seed = await hasher.sha512(words.join(' '), salt);
return seed;
}
@override
Future<String> hdSeedToPrivate(String seed, int index) async {
List<int> seedBytes = hex.decode(seed);
KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
return hex.encode(data.key);
}
@override
Future<String> hdSeedToAddress(String seed, int index) async {
return ND.NanoAccounts.createAccount(
ND.NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
}
@override
Future<String> uniSeedToAddress(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToAddress(seed, index));
} else if (type == "hd") {
return hdSeedToAddress(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
@override
Future<String> uniSeedToPrivate(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToPrivate(seed, index));
} else if (type == "hd") {
return hdSeedToPrivate(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
@override
bool isValidBip39Seed(String seed) {
// Ensure seed is 128 characters long
if (seed.length != 128) {
return false;
}
// Ensure seed only contains hex characters, 0-9;A-F
return ND.NanoHelpers.isHexString(seed);
}
// number util:
static const int maxDecimalDigits = 6; // Max digits after decimal
BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
BigInt rawPerXMR = BigInt.parse("1000000000000");
BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
// static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000");
/// Convert raw to ban and return as BigDecimal
///
/// @param raw 100000000000000000000000000000
/// @return Decimal value 1.000000000000000000000000000000
///
@override
Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur) {
rawPerCur ??= rawPerNano;
final Decimal amount = Decimal.parse(raw.toString());
final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal();
return result;
}
@override
String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) {
Decimal bigger = input.shift(digits);
bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05
bigger = bigger.shift(-digits);
return bigger.toString();
}
/// Return raw as a NANO amount.
///
/// @param raw 100000000000000000000000000000
/// @returns 1
///
@override
String getRawAsUsableString(String? raw, BigInt rawPerCur) {
final String res =
truncateDecimal(getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9);
if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") {
return "0";
}
if (!res.contains(".")) {
return res;
}
final String numAmount = res.split(".")[0];
String decAmount = res.split(".")[1];
// truncate:
if (decAmount.length > maxDecimalDigits) {
decAmount = decAmount.substring(0, maxDecimalDigits);
// remove trailing zeros:
decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => '');
if (decAmount.isEmpty) {
return numAmount;
}
}
return "$numAmount.$decAmount";
}
@override
String getRawAccuracy(String? raw, BigInt rawPerCur) {
final String rawString = getRawAsUsableString(raw, rawPerCur);
final String rawDecimalString = getRawAsDecimal(raw, rawPerCur).toString();
if (raw == null || raw.isEmpty || raw == "0") {
return "";
}
if (rawString != rawDecimalString) {
return "~";
}
return "";
}
/// Return readable string amount as raw string
/// @param amount 1.01
/// @returns 101000000000000000000000000000
///
@override
String getAmountAsRaw(String amount, BigInt rawPerCur) {
final Decimal asDecimal = Decimal.parse(amount);
final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
return (asDecimal * rawDecimal).toString();
}
@override
Future<AccountInfoResponse?> getInfoFromSeedOrMnemonic(
DerivationType derivationType, {
String? seedKey,
String? mnemonic,
required Node node,
}) async {
NanoClient nanoClient = NanoClient();
nanoClient.connect(node);
late String publicAddress;
if (seedKey != null) {
if (derivationType == DerivationType.bip39) {
publicAddress = await hdSeedToAddress(seedKey, 0);
} else if (derivationType == DerivationType.nano) {
publicAddress = await seedToAddress(seedKey, 0);
}
}
if (derivationType == DerivationType.bip39) {
if (mnemonic != null) {
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
publicAddress = await hdSeedToAddress(seedKey, 0);
}
}
if (derivationType == DerivationType.nano) {
if (mnemonic != null) {
seedKey = await mnemonicToSeed(mnemonic);
publicAddress = await seedToAddress(seedKey, 0);
}
}
AccountInfoResponse? accountInfo = await nanoClient.getAccountInfo(publicAddress);
if (accountInfo == null) {
accountInfo = AccountInfoResponse(frontier: "", balance: "0", representative: "", confirmationHeight: 0);
}
accountInfo.address = publicAddress;
return accountInfo;
}
@override
Future<List<DerivationType>> compareDerivationMethods({
String? mnemonic,
String? privateKey,
required Node node,
}) async {
String? seedKey = privateKey;
if (mnemonic?.split(' ').length == 12) {
return [DerivationType.bip39];
}
if (seedKey?.length == 128) {
return [DerivationType.bip39];
} else if (seedKey?.length == 64) {
return [DerivationType.nano];
}
late String publicAddressStandard;
late String publicAddressBip39;
try {
NanoClient nanoClient = NanoClient();
nanoClient.connect(node);
if (mnemonic != null) {
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
seedKey = await mnemonicToSeed(mnemonic);
publicAddressStandard = await seedToAddress(seedKey, 0);
} else if (seedKey != null) {
try {
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
} catch (e) {
return [DerivationType.nano];
}
try {
publicAddressStandard = await seedToAddress(seedKey, 0);
} catch (e) {
return [DerivationType.bip39];
}
}
// check if account has a history:
AccountInfoResponse? bip39Info;
AccountInfoResponse? standardInfo;
try {
bip39Info = await nanoClient.getAccountInfo(publicAddressBip39);
} catch (e) {
bip39Info = null;
}
try {
standardInfo = await nanoClient.getAccountInfo(publicAddressStandard);
} catch (e) {
standardInfo = null;
}
// one of these is *probably* null:
if (bip39Info == null && standardInfo != null) {
return [DerivationType.nano];
} else if (standardInfo == null && bip39Info != null) {
return [DerivationType.bip39];
}
// we don't know for sure:
return [DerivationType.nano, DerivationType.bip39];
} catch (e) {
return [DerivationType.unknown];
}
}
}
@@ -13,4 +13,11 @@ void startOnCurrentNodeChangeReaction(AppStore appStore) {
print(e.toString());
}
});
appStore.settingsStore.powNodes.observe((change) async {
try {
await appStore.wallet!.connectToPowNode(node: change.newValue!);
} catch (e) {
print(e.toString());
}
});
}
+8 -1
View File
@@ -58,16 +58,23 @@ void startCurrentWalletChangeReaction(
}
final node = settingsStore.getCurrentNode(wallet.type);
startWalletSyncStatusChangeReaction(wallet, fiatConversionStore);
startCheckConnectionReaction(wallet, settingsStore);
await getIt.get<SharedPreferences>().setString(PreferencesKey.currentWalletName, wallet.name);
await getIt
.get<SharedPreferences>()
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
if (wallet.type == WalletType.monero) {
_setAutoGenerateSubaddressStatus(wallet, settingsStore);
}
await wallet.connectToNode(node: node);
if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
final powNode = settingsStore.getCurrentPowNode(wallet.type);
await wallet.connectToPowNode(node: powNode);
}
if (wallet.type == WalletType.haven) {
await updateHavenRate(fiatConversionStore);
@@ -101,7 +108,7 @@ void startCurrentWalletChangeReaction(
if (wallet.type == WalletType.ethereum) {
final currencies =
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
for (final currency in currencies) {
() async {
+125 -141
View File
@@ -12,11 +12,15 @@ 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/dashboard/edit_token_page.dart';
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_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';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
@@ -51,6 +55,8 @@ import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dar
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
@@ -111,36 +117,34 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.newWalletFromWelcome:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
if (availableWalletTypes.length == 1) {
Navigator.of(context.context).pushNamed(Routes.newWallet, arguments: availableWalletTypes.first);
} else {
Navigator.of(context.context).pushNamed(Routes.newWalletType);
}
}),
builder: (_) =>
getIt.get<SetupPinCodePage>(param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
if (availableWalletTypes.length == 1) {
Navigator.of(context.context)
.pushNamed(Routes.newWallet, arguments: availableWalletTypes.first);
} else {
Navigator.of(context.context).pushNamed(Routes.newWalletType);
}
}),
fullscreenDialog: true);
case Routes.newWalletType:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) =>
Navigator.of(context)
.pushNamed(Routes.newWallet, arguments: type)));
Navigator.of(context).pushNamed(Routes.newWallet, arguments: type)));
case Routes.newWallet:
final type = settings.arguments as WalletType;
final walletNewVM = getIt.get<WalletNewVM>(param1: type);
return CupertinoPageRoute<void>(
builder: (_) => NewWalletPage(walletNewVM));
return CupertinoPageRoute<void>(builder: (_) => NewWalletPage(walletNewVM));
case Routes.setupPin:
Function(PinCodeState<PinCodeWidget>, String)? callback;
if (settings.arguments is Function(PinCodeState<PinCodeWidget>, String)) {
callback =
settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
callback = settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
}
return CupertinoPageRoute<void>(
@@ -150,8 +154,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) =>
Navigator.of(context)
.pushNamed(Routes.restoreWallet, arguments: type),
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
param2: false));
case Routes.restoreOptions:
@@ -166,66 +169,62 @@ Route<dynamic> createRoute(RouteSettings settings) {
if (isNewInstall) {
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
if (isSingleCoin) {
return Navigator.of(context.context)
.pushNamed(Routes.restoreWallet, arguments: availableWalletTypes.first);
}
return Navigator.pushNamed(
context.context, Routes.restoreWalletType);
return Navigator.pushNamed(context.context, Routes.restoreWalletType);
}),
fullscreenDialog: true);
} else if (isSingleCoin) {
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestorePage>(
param1: availableWalletTypes.first
));
builder: (_) => getIt.get<WalletRestorePage>(param1: availableWalletTypes.first));
} else {
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) =>
Navigator.of(context)
.pushNamed(Routes.restoreWallet, arguments: type),
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
param2: false));
}
case Routes.seed:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) =>
getIt.get<WalletSeedPage>(param1: settings.arguments as bool));
builder: (_) => getIt.get<WalletSeedPage>(param1: settings.arguments as bool));
case Routes.restoreWallet:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestorePage>(
param1: settings.arguments as WalletType));
builder: (_) => getIt.get<WalletRestorePage>(param1: settings.arguments as WalletType));
case Routes.restoreWalletChooseDerivation:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestoreChooseDerivationPage>(
param1: settings.arguments as List<DerivationInfo>));
case Routes.sweepingWalletPage:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SweepingWalletPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SweepingWalletPage>());
case Routes.dashboard:
return CupertinoPageRoute<void>(
settings: settings,
builder: (_) => getIt.get<DashboardPage>());
settings: settings, builder: (_) => getIt.get<DashboardPage>());
case Routes.send:
final initialPaymentRequest = settings.arguments as PaymentRequest?;
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<SendPage>(
param1: initialPaymentRequest,
));
fullscreenDialog: true,
builder: (_) => getIt.get<SendPage>(
param1: initialPaymentRequest,
));
case Routes.sendTemplate:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<SendTemplatePage>());
fullscreenDialog: true, builder: (_) => getIt.get<SendTemplatePage>());
case Routes.receive:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ReceivePage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ReceivePage>());
case Routes.addressPage:
return CupertinoPageRoute<void>(
@@ -234,20 +233,21 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.transactionDetails:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<TransactionDetailsPage>(
param1: settings.arguments as TransactionInfo));
builder: (_) =>
getIt.get<TransactionDetailsPage>(param1: settings.arguments as TransactionInfo));
case Routes.newSubaddress:
return CupertinoPageRoute<void>(
builder: (_) =>
getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
builder: (_) => getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
case Routes.disclaimer:
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage());
case Routes.readDisclaimer:
return CupertinoPageRoute<void>(
builder: (_) => DisclaimerPage(isReadOnly: true));
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage(isReadOnly: true));
case Routes.changeRep:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
case Routes.seedLanguage:
final args = settings.arguments as List<dynamic>;
@@ -256,8 +256,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(builder: (_) {
return SeedLanguage(
onConfirm: (context, lang) => Navigator.of(context)
.popAndPushNamed(redirectRoute, arguments: [type, lang]));
onConfirm: (context, lang) =>
Navigator.of(context).popAndPushNamed(redirectRoute, arguments: [type, lang]));
});
case Routes.walletList:
@@ -267,15 +267,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.walletEdit:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<WalletEditPage>(
param1: settings.arguments as List<dynamic>));
builder: (_) => getIt.get<WalletEditPage>(param1: settings.arguments as List<dynamic>));
case Routes.auth:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished,
param2: true));
param1: settings.arguments as OnAuthenticationFinished, param2: true));
case Routes.totpAuthCodePage:
final args = settings.arguments as TotpAuthArgumentsModel;
@@ -290,9 +288,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(
builder: (context) => WillPopScope(
child: getIt.get<AuthPage>(instanceName: 'login'),
onWillPop: () async =>
// FIX-ME: Additional check does it works correctly
(await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ??
onWillPop: () async =>
// FIX-ME: Additional check does it works correctly
(await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ??
false),
),
fullscreenDialog: true);
@@ -302,53 +300,54 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true,
builder: (_) => WillPopScope(
child: getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished,
param2: false),
param1: settings.arguments as OnAuthenticationFinished, param2: false),
onWillPop: () async => false));
case Routes.connectionSync:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<ConnectionSyncPage>());
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());
case Routes.securityBackupPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<SecurityBackupPage>());
fullscreenDialog: true, builder: (_) => getIt.get<SecurityBackupPage>());
case Routes.privacyPage:
case Routes.privacyPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<PrivacyPage>());
fullscreenDialog: true, builder: (_) => getIt.get<PrivacyPage>());
case Routes.displaySettingsPage:
case Routes.displaySettingsPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<DisplaySettingsPage>());
fullscreenDialog: true, builder: (_) => getIt.get<DisplaySettingsPage>());
case Routes.otherSettingsPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<OtherSettingsPage>());
fullscreenDialog: true, builder: (_) => getIt.get<OtherSettingsPage>());
case Routes.newNode:
final args = settings.arguments as Map<String, dynamic>?;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NodeCreateOrEditPage>(
param1: args?['editingNode'] as Node?,
param2: args?['isSelected'] as bool?));
param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
case Routes.newPowNode:
final args = settings.arguments as Map<String, dynamic>?;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<PowNodeCreateOrEditPage>(
param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
case Routes.accountCreation:
return CupertinoPageRoute<String>(
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
param1: settings.arguments as AccountListItem?));
case Routes.nanoAccountCreation:
return CupertinoPageRoute<String>(
builder: (_) =>
getIt.get<NanoAccountEditOrCreatePage>(param1: settings.arguments as NanoAccount?));
case Routes.addressBook:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<ContactListPage>());
fullscreenDialog: true, builder: (_) => getIt.get<ContactListPage>());
case Routes.pickerAddressBook:
final selectedCurrency = settings.arguments as CryptoCurrency?;
@@ -357,31 +356,26 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.addressBookAddContact:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ContactPage>(
param1: settings.arguments as ContactRecord?));
builder: (_) => getIt.get<ContactPage>(param1: settings.arguments as ContactRecord?));
case Routes.showKeys:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletKeysPage>(), fullscreenDialog: true);
case Routes.exchangeTrade:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ExchangeTradePage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTradePage>());
case Routes.exchangeConfirm:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<ExchangeConfirmPage>());
return MaterialPageRoute<void>(builder: (_) => getIt.get<ExchangeConfirmPage>());
case Routes.tradeDetails:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) =>
getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
builder: (_) => getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
case Routes.orderDetails:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
builder: (_) => getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
case Routes.buy:
return MaterialPageRoute<void>(builder: (_) => getIt.get<BuyOptionsPage>());
@@ -390,18 +384,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
final args = settings.arguments as List;
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) =>
getIt.get<BuyWebViewPage>(param1: args));
fullscreenDialog: true, builder: (_) => getIt.get<BuyWebViewPage>(param1: args));
case Routes.exchange:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<ExchangePage>());
fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>());
case Routes.exchangeTemplate:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ExchangeTemplatePage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());
case Routes.rescan:
return MaterialPageRoute<void>(builder: (_) => getIt.get<RescanPage>());
@@ -411,51 +401,41 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.preSeed:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
builder: (_) => getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
case Routes.backup:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>());
case Routes.editBackupPassword:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<EditBackupPasswordPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<EditBackupPasswordPage>());
case Routes.restoreFromBackup:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<RestoreFromBackupPage>());
fullscreenDialog: true, builder: (_) => getIt.get<RestoreFromBackupPage>());
case Routes.support:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SupportPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SupportPage>());
case Routes.supportLiveChat:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SupportChatPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SupportChatPage>());
case Routes.supportOtherLinks:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<SupportOtherLinksPage>());
fullscreenDialog: true, builder: (_) => getIt.get<SupportOtherLinksPage>());
case Routes.unspentCoinsList:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<UnspentCoinsListPage>());
return MaterialPageRoute<void>(builder: (_) => getIt.get<UnspentCoinsListPage>());
case Routes.unspentCoinsDetails:
final args = settings.arguments as List;
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<UnspentCoinsDetailsPage>(
param1: args));
builder: (_) => getIt.get<UnspentCoinsDetailsPage>(param1: args));
case Routes.fullscreenQR:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<FullscreenQRPage>(
builder: (_) => getIt.get<FullscreenQRPage>(
param1: settings.arguments as QrViewData,
));
@@ -466,26 +446,27 @@ Route<dynamic> createRoute(RouteSettings settings) {
);
case Routes.ioniaLoginPage:
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaLoginPage>());
case Routes.ioniaCreateAccountPage:
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaCreateAccountPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCreateAccountPage>());
case Routes.ioniaManageCardsPage:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaManageCardsPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaManageCardsPage>());
case Routes.ioniaBuyGiftCardPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
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));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
case Routes.ioniaVerifyIoniaOtpPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaVerifyIoniaOtp>(param1: args));
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaVerifyIoniaOtp>(param1: args));
case Routes.ioniaDebitCardPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaDebitCardPage>());
@@ -501,57 +482,60 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.ioniaCustomTipPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaCustomTipPage>(param1: args));
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomTipPage>(param1: args));
case Routes.ioniaGiftCardDetailPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
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));
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));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args));
case Routes.ioniaPaymentStatusPage:
final args = settings.arguments as List;
final paymentInfo = args.first as IoniaAnyPayPaymentInfo;
final commitedInfo = args[1] as AnyPayPaymentCommittedInfo;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaPaymentStatusPage>(
param1: paymentInfo,
param2: commitedInfo));
return CupertinoPageRoute<void>(
builder: (_) =>
getIt.get<IoniaPaymentStatusPage>(param1: paymentInfo, param2: commitedInfo));
case Routes.webViewPage:
final args = settings.arguments as List;
final title = args.first as String;
final url = args[1] as Uri;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<WebViewPage>(
param1: title,
param2: url));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<WebViewPage>(param1: title, param2: url));
case Routes.advancedPrivacySettings:
final type = settings.arguments as WalletType;
return CupertinoPageRoute<void>(
builder: (_) => AdvancedPrivacySettingsPage(
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type),
));
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
));
case Routes.anonPayInvoicePage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
case Routes.anonPayReceivePage:
final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData));
final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData));
case Routes.anonPayDetailsPage:
final anonInvoiceViewData = settings.arguments as AnonpayInvoiceInfo;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData));
case Routes.desktop_actions:
return PageRouteBuilder(
@@ -560,12 +544,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
);
case Routes.desktop_settings_page:
return CupertinoPageRoute<void>(
builder: (_) => DesktopSettingsPage());
return CupertinoPageRoute<void>(builder: (_) => DesktopSettingsPage());
case Routes.empty_no_route:
return MaterialPageRoute<void>(
builder: (_) => SizedBox.shrink());
return MaterialPageRoute<void>(builder: (_) => SizedBox.shrink());
case Routes.transactionsPage:
return CupertinoPageRoute<void>(
@@ -602,12 +584,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
);
case Routes.manageNodes:
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>());
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: false));
case Routes.managePowNodes:
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: true));
default:
return MaterialPageRoute<void>(
builder: (_) => Scaffold(
body: Center(
child: Text(S.current.router_no_route(settings.name ?? 'No route')))));
body: Center(child: Text(S.current.router_no_route(settings.name ?? 'No route')))));
}
}
+6
View File
@@ -6,6 +6,7 @@ class Routes {
static const seed = '/seed';
static const restoreOptions = '/restore_options';
static const restoreWalletFromSeedKeys = '/restore_wallet_from_seeds_keys';
static const restoreWalletChooseDerivation = '/restore_wallet_choose_derivation';
static const dashboard = '/dashboard';
static const send = '/send';
static const transactionDetails = '/transaction_info';
@@ -14,13 +15,16 @@ class Routes {
static const walletEdit = '/walletEdit';
static const disclaimer = '/disclaimer';
static const readDisclaimer = '/read_disclaimer';
static const changeRep = '/change_representative';
static const seedLanguage = '/seed_language';
static const walletList = '/view_model.wallet_list';
static const auth = '/auth';
static const newNode = '/new_node_list';
static const newPowNode = '/new_pow_node_list';
static const login = '/login';
static const splash = '/splash';
static const accountCreation = '/account_new';
static const nanoAccountCreation = '/nano_account_new';
static const addressBook = '/address_book';
static const pickerAddressBook = '/picker_address_book';
static const addressBookAddContact = '/address_book_add_contact';
@@ -92,4 +96,6 @@ class Routes {
static const homeSettings = '/home_settings';
static const editToken = '/edit_token';
static const manageNodes = '/manage_nodes';
static const managePowNodes = '/manage_pow_nodes';
}
@@ -295,29 +295,6 @@ class _DashboardPageView extends BasePage {
);
_isEffectsInstalled = true;
autorun(
(_) async {
if (!dashboardViewModel.isOutdatedElectrumWallet) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
if (context.mounted) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_description,
buttonText: S.of(context).understand,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
},
);
_showReleaseNotesPopup(context);
var needToPresentYat = false;
@@ -78,23 +78,6 @@ class DesktopDashboardPage extends StatelessWidget {
}
_isEffectsInstalled = true;
autorun((_) async {
if (!dashboardViewModel.isOutdatedElectrumWallet) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_description,
buttonText: S.of(context).understand,
buttonAction: () => Navigator.of(context).pop());
});
});
var needToPresentYat = false;
var isInactive = false;
@@ -33,6 +33,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
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 nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
Image _newWalletImage(BuildContext context) => Image.asset(
@@ -141,6 +143,10 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return havenIcon;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.banano:
return bananoIcon;
default:
return nonWalletTypeIcon;
}
@@ -120,31 +120,6 @@ class AddressPage extends BasePage {
Widget body(BuildContext context) {
_setEffects(context);
autorun((_) async {
if (!dashboardViewModel.isOutdatedElectrumWallet ||
!dashboardViewModel.settingsStore.shouldShowReceiveWarning) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
if (context.mounted) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_receive_warning,
leftButtonText: S.of(context).understand,
actionLeftButton: () => Navigator.of(context).pop(),
rightButtonText: S.of(context).do_not_show_me,
actionRightButton: () {
dashboardViewModel.settingsStore.setShouldShowReceiveWarning(false);
Navigator.of(context).pop();
});
});
}
});
return KeyboardActions(
autoScroll: false,
disableScroll: true,
@@ -18,18 +18,21 @@ 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.ethereumIcon = Image.asset('assets/images/eth_icon.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'),
this.nanoIcon = Image.asset('assets/images/nano_icon.png'),
this.bananoIcon = Image.asset('assets/images/nano_icon.png');
final largeScreen = 731;
@@ -47,6 +50,9 @@ class MenuWidgetState extends State<MenuWidget> {
Image litecoinIcon;
Image havenIcon;
Image ethereumIcon;
Image nanoIcon;
Image bananoIcon;
@override
void initState() {
@@ -206,6 +212,10 @@ class MenuWidgetState extends State<MenuWidget> {
return havenIcon;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.banano:
return bananoIcon;
default:
throw Exception('No icon for ${type.toString()}');
}
@@ -0,0 +1,103 @@
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_nano/nano_wallet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
class NanoChangeRepPage extends BasePage {
NanoChangeRepPage(WalletBase wallet)
: _wallet = wallet,
_addressController = TextEditingController() {
_addressController.text = (wallet as NanoWallet).representative;
}
final TextEditingController _addressController;
final WalletBase _wallet;
@override
String get title => S.current.change_rep;
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0),
content: Container(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _addressController,
hintText: S.of(context).node_address,
validator: AddressValidator(type: CryptoCurrency.nano),
),
)
],
),
],
),
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_rep,
alertContent: S.of(context).change_rep_message,
rightButtonText: S.of(context).change,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.pop(context, true),
actionLeftButton: () => Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
try {
await nano!.changeRep(_wallet, _addressController.text);
Navigator.of(context).pop();
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: e.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.pop(context));
});
throw e;
}
}
},
text: S.of(context).change,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
)),
],
)),
));
}
}
@@ -0,0 +1,66 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/monero_account_label_validator.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
class NanoAccountEditOrCreatePage extends BasePage {
NanoAccountEditOrCreatePage({required this.nanoAccountCreationViewModel})
: _formKey = GlobalKey<FormState>(),
_textController = TextEditingController() {
_textController.addListener(() => nanoAccountCreationViewModel.label = _textController.text);
_textController.text = nanoAccountCreationViewModel.label;
}
final NanoAccountEditOrCreateViewModel nanoAccountCreationViewModel;
@override
String get title => S.current.account;
final GlobalKey<FormState> _formKey;
final TextEditingController _textController;
@override
Widget body(BuildContext context) => Form(
key: _formKey,
child: Container(
padding: EdgeInsets.all(24.0),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: BaseTextFormField(
controller: _textController,
hintText: S.of(context).account,
validator: MoneroLabelValidator(),
))),
Observer(
builder: (_) => LoadingPrimaryButton(
onPressed: () async {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
return;
}
await nanoAccountCreationViewModel.save();
Navigator.of(context).pop(_textController.text);
},
text: nanoAccountCreationViewModel.isEdit
? S.of(context).rename
: S.of(context).add,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isLoading: nanoAccountCreationViewModel.state is IsExecutingState,
isDisabled: nanoAccountCreationViewModel.label?.isEmpty ?? true,
))
],
),
),
);
}
@@ -0,0 +1,92 @@
import 'package:cake_wallet/src/widgets/picker_inner_wrapper_widget.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/monero_accounts/widgets/account_tile.dart';
class NanoAccountListPage extends StatelessWidget {
NanoAccountListPage({required this.accountListViewModel});
final NanoAccountListViewModel accountListViewModel;
final ScrollController controller = ScrollController();
@override
Widget build(BuildContext context) {
double itemHeight = 80;
double buttonHeight = 62;
return Observer(builder: (_) {
final accounts = accountListViewModel.accounts;
return PickerInnerWrapperWidget(
title: S.of(context).choose_account,
itemsHeight: (itemHeight * accounts.length) + buttonHeight,
children: [
Expanded(
child: Scrollbar(
controller: controller,
child: ListView.separated(
padding: EdgeInsets.zero,
controller: controller,
separatorBuilder: (context, index) => const VerticalSectionDivider(),
itemCount: accounts.length,
itemBuilder: (context, index) {
final account = accounts[index];
return AccountTile(
isCurrent: account.isSelected,
accountName: account.label,
accountBalance: account.balance ?? '0.00',
currency: accountListViewModel.currency.toString(),
onTap: () {
if (account.isSelected) {
return;
}
accountListViewModel.select(account);
Navigator.of(context).pop();
},
onEdit: () async => await Navigator.of(context)
.pushNamed(Routes.nanoAccountCreation, arguments: account));
},
),
)),
GestureDetector(
onTap: () async => await Navigator.of(context).pushNamed(Routes.nanoAccountCreation),
child: Container(
height: buttonHeight,
color: Theme.of(context).cardColor,
padding: EdgeInsets.symmetric(horizontal: 24),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.add,
color: Colors.white,
),
Padding(
padding: EdgeInsets.only(left: 5),
child: Text(
S.of(context).create_new_account,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Colors.white,
decoration: TextDecoration.none,
),
),
)
],
),
),
),
)
],
);
});
}
}
@@ -0,0 +1,96 @@
import 'package:cake_wallet/themes/extensions/account_list_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:cake_wallet/generated/i18n.dart';
class AccountTile extends StatelessWidget {
AccountTile(
{required this.isCurrent,
required this.accountName,
this.accountBalance,
required this.currency,
required this.onTap,
required this.onEdit});
final bool isCurrent;
final String accountName;
final String? accountBalance;
final String currency;
final Function() onTap;
final Function() onEdit;
@override
Widget build(BuildContext context) {
final color = isCurrent
? Theme.of(context).extension<AccountListTheme>()!.currentAccountBackgroundColor
: Theme.of(context).extension<AccountListTheme>()!.tilesBackgroundColor;
final textColor = isCurrent
? Theme.of(context).extension<AccountListTheme>()!.currentAccountTextColor
: Theme.of(context).extension<AccountListTheme>()!.tilesTextColor;
final Widget cell = GestureDetector(
onTap: onTap,
child: Container(
height: 77,
width: double.infinity,
padding: EdgeInsets.only(left: 24, right: 24),
color: color,
child: Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.spaceBetween,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Container(
child: Text(
accountName,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: textColor,
decoration: TextDecoration.none,
),
),
),
if (accountBalance != null)
Container(
child: Text(
'${accountBalance.toString()} $currency',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).textTheme.headlineMedium!.color!,
decoration: TextDecoration.none,
),
),
),
],
),
),
);
// return cell;
return Slidable(
key: Key(accountName),
child: cell,
endActionPane: _actionPane(context)
);
}
ActionPane _actionPane(BuildContext context) => ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.3,
children: [
SlidableAction(
onPressed: (_) => onEdit.call(),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
icon: Icons.edit,
label: S.of(context).edit,
),
],
);
}
@@ -0,0 +1,196 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
class PowNodeCreateOrEditPage extends BasePage {
PowNodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
: _formKey = GlobalKey<FormState>(),
_addressController = TextEditingController(),
_portController = TextEditingController(),
_loginController = TextEditingController(),
_passwordController = TextEditingController() {
reaction((_) => nodeCreateOrEditViewModel.address, (String address) {
if (address != _addressController.text) {
_addressController.text = address;
}
});
reaction((_) => nodeCreateOrEditViewModel.port, (String port) {
if (port != _portController.text) {
_portController.text = port;
}
});
if (nodeCreateOrEditViewModel.hasAuthCredentials) {
reaction((_) => nodeCreateOrEditViewModel.login, (String login) {
if (login != _loginController.text) {
_loginController.text = login;
}
});
reaction((_) => nodeCreateOrEditViewModel.password, (String password) {
if (password != _passwordController.text) {
_passwordController.text = password;
}
});
}
_addressController.addListener(
() => nodeCreateOrEditViewModel.address = _addressController.text);
_portController.addListener(
() => nodeCreateOrEditViewModel.port = _portController.text);
_loginController.addListener(
() => nodeCreateOrEditViewModel.login = _loginController.text);
_passwordController.addListener(
() => nodeCreateOrEditViewModel.password = _passwordController.text);
}
final GlobalKey<FormState> _formKey;
final TextEditingController _addressController;
final TextEditingController _portController;
final TextEditingController _loginController;
final TextEditingController _passwordController;
@override
String get title => editingNode != null ? S.current.edit_node : S.current.node_new;
@override
Widget trailing(BuildContext context) => IconButton(
onPressed: () async {
await nodeCreateOrEditViewModel.scanQRCodeForNewNode();
},
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
icon: Image.asset(
'assets/images/qr_code_icon.png',
),
);
final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
final Node? editingNode;
final bool? isSelected;
@override
Widget body(BuildContext context) {
reaction((_) => nodeCreateOrEditViewModel.connectionState,
(ExecutionState state) {
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) =>
AlertWithOneAction(
alertTitle: S.of(context).new_node_testing,
alertContent: state.payload as bool
? S.of(context).node_connection_successful
: S.of(context).node_connection_failed,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()));
});
}
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
});
}
});
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0),
content: NodeForm(
formKey: _formKey,
nodeViewModel: nodeCreateOrEditViewModel,
editingNode: editingNode,
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle:
S.of(context).remove_node,
alertContent: S
.of(context)
.remove_node_message,
rightButtonText:
S.of(context).remove,
leftButtonText:
S.of(context).cancel,
actionRightButton: () =>
Navigator.pop(context, true),
actionLeftButton: () =>
Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
await editingNode!.delete();
Navigator.of(context).pop();
}
},
text: S.of(context).delete,
isDisabled: !nodeCreateOrEditViewModel.isReady ||
(isSelected ?? false),
color: Palette.red,
textColor: Colors.white),
)),
Flexible(
child: Container(
padding: EdgeInsets.only(left: 8.0),
child: PrimaryButton(
onPressed: () async {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
return;
}
await nodeCreateOrEditViewModel.save(
editingNode: editingNode, saveAsCurrent: isSelected ?? false);
Navigator.of(context).pop();
},
text: S.of(context).save,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isDisabled: (!nodeCreateOrEditViewModel.isReady)||
(nodeCreateOrEditViewModel
.connectionState is IsExecutingState),
),
)),
],
)),
));
}
}
@@ -11,10 +11,12 @@ class NodeListRow extends StandardListRow {
{required String title,
required this.node,
required void Function(BuildContext context) onTap,
required bool isSelected})
required bool isSelected,
required this.isPow})
: super(title: title, onTap: onTap, isSelected: isSelected);
final Node node;
final bool isPow;
@override
Widget buildLeading(BuildContext context) {
@@ -33,7 +35,7 @@ class NodeListRow extends StandardListRow {
@override
Widget buildTrailing(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.of(context).pushNamed(Routes.newNode,
onTap: () => Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode,
arguments: {'editingNode': node, 'isSelected': isSelected}),
child: Container(
padding: EdgeInsets.all(10),
+18 -6
View File
@@ -1,7 +1,9 @@
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/share_util.dart';
@@ -22,7 +24,6 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
class ReceivePage extends BasePage {
ReceivePage({required this.addressListViewModel})
@@ -99,7 +100,9 @@ class ReceivePage extends BasePage {
@override
Widget body(BuildContext context) {
return (addressListViewModel.type == WalletType.monero ||
addressListViewModel.type == WalletType.haven)
addressListViewModel.type == WalletType.haven ||
addressListViewModel.type == WalletType.nano ||
addressListViewModel.type == WalletType.banano)
? KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
@@ -137,9 +140,18 @@ class ReceivePage extends BasePage {
if (item is WalletAccountListHeader) {
cell = HeaderTile(
onTap: () async => await showPopUp<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>()),
onTap: () async {
if (addressListViewModel.type == WalletType.monero ||
addressListViewModel.type == WalletType.haven) {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>());
} else {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<NanoAccountListPage>());
}
},
title: S.of(context).accounts,
icon: Icon(
Icons.arrow_forward_ios,
@@ -0,0 +1,129 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
class WalletRestoreChooseDerivationPage extends BasePage {
WalletRestoreChooseDerivationPage(this.walletRestoreChooseDerivationViewModel) {}
@override
Widget middle(BuildContext context) => Observer(
builder: (_) => Text(
S.current.choose_derivation,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: titleColor(context)),
));
final WalletRestoreChooseDerivationViewModel walletRestoreChooseDerivationViewModel;
DerivationType derivationType = DerivationType.unknown;
@override
Widget body(BuildContext context) {
return Observer(
builder: (_) => FutureBuilder<List<DerivationInfo>>(
future: walletRestoreChooseDerivationViewModel.derivations,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('Error! No derivations available!'));
} else {
return ListView.separated(
shrinkWrap: true,
separatorBuilder: (_, __) => SizedBox(),
itemCount: snapshot.data!.length,
itemBuilder: (__, index) {
final derivation = snapshot.data![index];
return Card(
margin: const EdgeInsets.all(8),
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
child: InkWell(
borderRadius: BorderRadius.circular(15),
onTap: () async {
Navigator.pop(context, derivation);
},
child: ListTile(
contentPadding: EdgeInsets.all(16),
title: Center(
child: Text(
"${derivation.description ?? derivation.derivationType.toString().split('.').last}",
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w800,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (derivation.derivationPath != null)
Text(
derivation.derivationPath!,
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
),
),
Text(
derivation.address,
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
),
),
Text(
"${S.current.confirmed}: ${derivation.balance}",
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
),
),
Text(
"${S.current.transactions}: ${derivation.height}",
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
),
),
],
),
),
),
);
},
);
}
},
),
);
}
}
@@ -13,6 +13,7 @@ import 'package:flutter/services.dart';
class WalletRestoreFromKeysFrom extends StatefulWidget {
WalletRestoreFromKeysFrom({
required this.walletRestoreViewModel,
required this.onPrivateKeyChange,
required this.displayPrivateKeyField,
required this.onHeightOrDateEntered,
Key? key,
@@ -20,6 +21,7 @@ class WalletRestoreFromKeysFrom extends StatefulWidget {
final Function(bool) onHeightOrDateEntered;
final WalletRestoreViewModel walletRestoreViewModel;
final void Function(String)? onPrivateKeyChange;
final bool displayPrivateKeyField;
@override
@@ -54,6 +56,7 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
if (privateKeyController.text.isNotEmpty) {
widget.onHeightOrDateEntered(true);
}
widget.onPrivateKeyChange?.call(privateKeyController.text);
});
}
@@ -62,8 +65,8 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
nameController.dispose();
addressController.dispose();
viewKeyController.dispose();
spendKeyController.dispose();
privateKeyController.dispose();
spendKeyController.dispose();
super.dispose();
}
+151 -19
View File
@@ -1,6 +1,12 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/nano_account_info_response.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
@@ -69,6 +75,12 @@ class WalletRestorePage extends BasePage {
_pages.add(WalletRestoreFromKeysFrom(
key: walletRestoreFromKeysFormKey,
walletRestoreViewModel: walletRestoreViewModel,
onPrivateKeyChange: (String seed) {
if (walletRestoreViewModel.type == WalletType.nano ||
walletRestoreViewModel.type == WalletType.banano) {
walletRestoreViewModel.isButtonEnabled = _isValidSeedKey();
}
},
displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey,
onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value));
break;
@@ -97,6 +109,8 @@ class WalletRestorePage extends BasePage {
final GlobalKey<WalletRestoreFromSeedFormState> walletRestoreFromSeedFormKey;
final GlobalKey<WalletRestoreFromKeysFromState> walletRestoreFromKeysFormKey;
final FocusNode _blockHeightFocusNode;
DerivationType derivationType = DerivationType.unknown;
String? derivationPath = null;
@override
Widget body(BuildContext context) {
@@ -185,7 +199,9 @@ class WalletRestorePage extends BasePage {
child: Observer(
builder: (context) {
return LoadingPrimaryButton(
onPressed: _confirmForm,
onPressed: () async {
await _confirmForm(context);
},
text: S.of(context).restore_recover,
color: Theme.of(context)
.extension<WalletListTheme>()!
@@ -217,18 +233,34 @@ class WalletRestorePage extends BasePage {
return false;
}
if ((walletRestoreViewModel.type == WalletType.bitcoin ||
walletRestoreViewModel.type == WalletType.litecoin) &&
if ((walletRestoreViewModel.type == WalletType.litecoin) &&
(seedWords.length != WalletRestoreViewModelBase.electrumSeedMnemonicLength &&
seedWords.length != WalletRestoreViewModelBase.electrumShortSeedMnemonicLength)) {
return false;
}
// bip39:
const validSeedLengths = [12, 18, 24];
if (walletRestoreViewModel.type == WalletType.bitcoin &&
!(validSeedLengths.contains(seedWords.length))) {
return false;
}
final words =
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet();
return seedWords.toSet().difference(words).toSet().isEmpty;
}
bool _isValidSeedKey() {
final seedKey = walletRestoreFromKeysFormKey.currentState!.privateKeyController.text;
if (seedKey.length != 64 && seedKey.length != 128) {
return false;
}
return true;
}
Map<String, dynamic> _credentials() {
final credentials = <String, dynamic>{};
@@ -243,10 +275,12 @@ class WalletRestorePage extends BasePage {
credentials['name'] =
walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text;
} else {
} else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) {
if (walletRestoreViewModel.hasRestoreFromPrivateKey) {
credentials['private_key'] =
walletRestoreFromKeysFormKey.currentState!.privateKeyController.text;
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
} else {
credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text;
credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
@@ -254,31 +288,81 @@ class WalletRestorePage extends BasePage {
walletRestoreFromKeysFormKey.currentState!.spendKeyController.text;
credentials['height'] =
walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height;
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
}
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
}
credentials['derivationType'] = this.derivationType;
credentials['derivationPath'] = this.derivationPath;
credentials['walletType'] = walletRestoreViewModel.type;
return credentials;
}
void _confirmForm() {
Future<List<DerivationInfo>> getDerivationInfo(dynamic credentials) async {
var list = <DerivationInfo>[];
var walletType = credentials["walletType"] as WalletType;
var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType);
switch (walletType) {
case WalletType.nano:
String? mnemonic = credentials['seed'] as String?;
String? seedKey = credentials['private_key'] as String?;
AccountInfoResponse? bip39Info = await nanoUtil!.getInfoFromSeedOrMnemonic(
DerivationType.bip39,
mnemonic: mnemonic,
seedKey: seedKey,
node: node);
AccountInfoResponse? standardInfo = await nanoUtil!.getInfoFromSeedOrMnemonic(
DerivationType.nano,
mnemonic: mnemonic,
seedKey: seedKey,
node: node,
);
if (standardInfo?.balance != null) {
list.add(DerivationInfo(
derivationType: DerivationType.nano,
balance: nanoUtil!.getRawAsUsableString(standardInfo!.balance, nanoUtil!.rawPerNano),
address: standardInfo.address!,
height: standardInfo.confirmationHeight,
));
}
if (bip39Info?.balance != null) {
list.add(DerivationInfo(
derivationType: DerivationType.bip39,
balance: nanoUtil!.getRawAsUsableString(bip39Info!.balance, nanoUtil!.rawPerNano),
address: bip39Info.address!,
height: bip39Info.confirmationHeight,
));
}
break;
default:
break;
}
return list;
}
Future<void> _confirmForm(BuildContext context) async {
// Dismissing all visible keyboard to provide context for navigation
FocusManager.instance.primaryFocus?.unfocus();
final formContext = walletRestoreViewModel.mode == WalletRestoreMode.seed
? walletRestoreFromSeedFormKey.currentContext
: walletRestoreFromKeysFormKey.currentContext;
final formKey = walletRestoreViewModel.mode == WalletRestoreMode.seed
? walletRestoreFromSeedFormKey.currentState!.formKey
: walletRestoreFromKeysFormKey.currentState!.formKey;
late BuildContext? formContext;
late GlobalKey<FormState>? formKey;
late String name;
if (walletRestoreViewModel.mode == WalletRestoreMode.seed) {
formContext = walletRestoreFromSeedFormKey.currentContext;
formKey = walletRestoreFromSeedFormKey.currentState!.formKey;
name = walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text;
} else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) {
formContext = walletRestoreFromKeysFormKey.currentContext;
formKey = walletRestoreFromKeysFormKey.currentState!.formKey;
name = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text;
}
final name = walletRestoreViewModel.mode == WalletRestoreMode.seed
? walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text
: walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text;
if (!formKey.currentState!.validate()) {
if (!formKey!.currentState!.validate()) {
return;
}
@@ -287,6 +371,54 @@ class WalletRestorePage extends BasePage {
return;
}
walletRestoreViewModel.state = IsExecutingState();
List<DerivationType> derivationTypes =
await walletRestoreViewModel.getDerivationTypes(_credentials());
if (derivationTypes[0] == DerivationType.unknown || derivationTypes.length > 1) {
// push screen to choose the derivation type:
List<DerivationInfo> derivations = await getDerivationInfo(_credentials());
int derivationsWithHistory = 0;
int derivationWithHistoryIndex = 0;
for (int i = 0; i < derivations.length; i++) {
if (derivations[i].height > 0) {
derivationsWithHistory++;
derivationWithHistoryIndex = i;
}
}
DerivationInfo? derivationInfo;
if (derivationsWithHistory > 1) {
derivationInfo = await Navigator.of(context).pushNamed(Routes.restoreWalletChooseDerivation,
arguments: derivations) as DerivationInfo?;
} else if (derivationsWithHistory == 1) {
derivationInfo = derivations[derivationWithHistoryIndex];
} else if (derivationsWithHistory == 0) {
// default derivation:
derivationInfo = DerivationInfo(
derivationType: derivationTypes[0],
derivationPath: "m/0'/1",
height: 0,
);
}
if (derivationInfo == null) {
walletRestoreViewModel.state = InitialExecutionState();
return;
}
this.derivationType = derivationInfo.derivationType;
this.derivationPath = derivationInfo.derivationPath;
} else {
// electrum derivation:
this.derivationType = derivationTypes[0];
this.derivationPath = "m/0'/1";
}
walletRestoreViewModel.state = InitialExecutionState();
walletRestoreViewModel.create(options: _credentials());
}
@@ -8,26 +8,26 @@ import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
import 'package:flutter/scheduler.dart';
class ConfirmSendingAlert extends BaseAlertDialog {
ConfirmSendingAlert({
required this.alertTitle,
this.paymentId,
this.paymentIdValue,
required this.amount,
required this.amountValue,
required this.fiatAmountValue,
required this.fee,
required this.feeValue,
required this.feeFiatAmount,
required this.outputs,
required this.leftButtonText,
required this.rightButtonText,
required this.actionLeftButton,
required this.actionRightButton,
this.alertBarrierDismissible = true,
this.alertLeftActionButtonTextColor,
this.alertRightActionButtonTextColor,
this.alertLeftActionButtonColor,
this.alertRightActionButtonColor});
ConfirmSendingAlert(
{required this.alertTitle,
this.paymentId,
this.paymentIdValue,
required this.amount,
required this.amountValue,
required this.fiatAmountValue,
required this.fee,
required this.feeValue,
required this.feeFiatAmount,
required this.outputs,
required this.leftButtonText,
required this.rightButtonText,
required this.actionLeftButton,
required this.actionRightButton,
this.alertBarrierDismissible = true,
this.alertLeftActionButtonTextColor,
this.alertRightActionButtonTextColor,
this.alertLeftActionButtonColor,
this.alertRightActionButtonColor});
final String alertTitle;
final String? paymentId;
@@ -92,21 +92,20 @@ class ConfirmSendingAlert extends BaseAlertDialog {
fee: fee,
feeValue: feeValue,
feeFiatAmount: feeFiatAmount,
outputs: outputs
);
outputs: outputs);
}
class ConfirmSendingAlertContent extends StatefulWidget {
ConfirmSendingAlertContent({
this.paymentId,
this.paymentIdValue,
required this.amount,
required this.amountValue,
required this.fiatAmountValue,
required this.fee,
required this.feeValue,
required this.feeFiatAmount,
required this.outputs});
ConfirmSendingAlertContent(
{this.paymentId,
this.paymentIdValue,
required this.amount,
required this.amountValue,
required this.fiatAmountValue,
required this.fee,
required this.feeValue,
required this.feeFiatAmount,
required this.outputs});
final String? paymentId;
final String? paymentIdValue;
@@ -120,29 +119,28 @@ class ConfirmSendingAlertContent extends StatefulWidget {
@override
ConfirmSendingAlertContentState createState() => ConfirmSendingAlertContentState(
paymentId: paymentId,
paymentIdValue: paymentIdValue,
amount: amount,
amountValue: amountValue,
fiatAmountValue: fiatAmountValue,
fee: fee,
feeValue: feeValue,
feeFiatAmount: feeFiatAmount,
outputs: outputs
);
paymentId: paymentId,
paymentIdValue: paymentIdValue,
amount: amount,
amountValue: amountValue,
fiatAmountValue: fiatAmountValue,
fee: fee,
feeValue: feeValue,
feeFiatAmount: feeFiatAmount,
outputs: outputs);
}
class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> {
ConfirmSendingAlertContentState({
this.paymentId,
this.paymentIdValue,
required this.amount,
required this.amountValue,
required this.fiatAmountValue,
required this.fee,
required this.feeValue,
required this.feeFiatAmount,
required this.outputs})
ConfirmSendingAlertContentState(
{this.paymentId,
this.paymentIdValue,
required this.amount,
required this.amountValue,
required this.fiatAmountValue,
required this.fee,
required this.feeValue,
required this.feeFiatAmount,
required this.outputs})
: recipientTitle = '' {
recipientTitle = outputs.length > 1
? S.current.transaction_details_recipient_address
@@ -170,8 +168,9 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
Widget build(BuildContext context) {
controller.addListener(() {
fromTop = controller.hasClients
? (controller.offset / controller.position.maxScrollExtent *
(backgroundHeight - thumbHeight))
? (controller.offset /
controller.position.maxScrollExtent *
(backgroundHeight - thumbHeight))
: 0;
setState(() {});
});
@@ -182,94 +181,92 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
});
});
return Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
Container(
height: 200,
child: SingleChildScrollView(
controller: controller,
child: Column(
children: <Widget>[
if (paymentIdValue != null && paymentId != null)
Padding(
padding: EdgeInsets.only(bottom: 32),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
paymentId!,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
return Stack(alignment: Alignment.center, clipBehavior: Clip.none, children: [
Container(
height: 200,
child: SingleChildScrollView(
controller: controller,
child: Column(
children: <Widget>[
if (paymentIdValue != null && paymentId != null)
Padding(
padding: EdgeInsets.only(bottom: 32),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
paymentId!,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
paymentIdValue!,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
paymentIdValue!,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
],
)
],
),
],
)
],
),
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
amount,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
amount,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
amountValue,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
amountValue,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
Text(
fiatAmountValue,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
],
)
],
),
),
Text(
fiatAmountValue,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
],
)
],
),
if (feeValue.isNotEmpty && feeValue != "0")
Padding(
padding: EdgeInsets.only(top: 16),
child: Row(
@@ -313,103 +310,97 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
],
)
],
)
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Column(
children: [
Text(
'$recipientTitle:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
)),
Padding(
padding: EdgeInsets.only(top: 16),
child: Column(
children: [
Text(
'$recipientTitle:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
outputs.length > 1
? ListView.builder(
padding: EdgeInsets.only(top: 0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: outputs.length,
itemBuilder: (context, index) {
final item = outputs[index];
final _address = item.isParsedAddress
? item.extractedAddress
: item.address;
final _amount =
item.cryptoAmount.replaceAll(',', '.');
),
outputs.length > 1
? ListView.builder(
padding: EdgeInsets.only(top: 0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: outputs.length,
itemBuilder: (context, index) {
final item = outputs[index];
final _address =
item.isParsedAddress ? item.extractedAddress : item.address;
final _amount = item.cryptoAmount.replaceAll(',', '.');
return Column(
children: [
if (item.isParsedAddress) Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
item.parsedAddress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
),
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
_address,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
),
Padding(
padding: EdgeInsets.only(top: 8),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
_amount,
return Column(
children: [
if (item.isParsedAddress)
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
item.parsedAddress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10,
fontSize: 14,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
],
)
)
],
);
})
: Column(
children: [
if (outputs.first.isParsedAddress) Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
outputs.first.parsedAddress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
),
)),
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
_address,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)),
Padding(
padding: EdgeInsets.only(top: 8),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
_amount,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
],
))
],
);
})
: Column(children: [
if (outputs.first.isParsedAddress)
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
outputs.first.parsedAddress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)),
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
@@ -423,24 +414,19 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
),
]
)
],
),
)
],
)
)
),
if (showScrollbar) CakeScrollbar(
backgroundHeight: backgroundHeight,
thumbHeight: thumbHeight,
fromTop: fromTop,
rightOffset: -15
)
]
);
)),
])
],
),
)
],
))),
if (showScrollbar)
CakeScrollbar(
backgroundHeight: backgroundHeight,
thumbHeight: thumbHeight,
fromTop: fromTop,
rightOffset: -15)
]);
}
}
}
+100 -76
View File
@@ -167,8 +167,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
buttonColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintStyle: TextStyle(
@@ -196,7 +198,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
child: BaseTextFormField(
controller: extractedAddressController,
readOnly: true,
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
borderColor: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldBorderColor,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
validator: sendViewModel.addressValidator)),
@@ -251,7 +255,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
child: Container(
height: 32,
decoration: BoxDecoration(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonColor,
borderRadius: BorderRadius.all(
Radius.circular(6),
)),
@@ -263,7 +269,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor),
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor),
),
),
),
@@ -304,7 +312,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor,
fontWeight: FontWeight.w500,
fontSize: 14),
validator: output.sendAll
@@ -322,7 +332,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
onTap: () async => output.setSendAll(),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonColor,
borderRadius: BorderRadius.all(
Radius.circular(6),
),
@@ -334,7 +346,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor,
),
),
),
@@ -364,7 +378,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor),
),
),
Text(
@@ -372,7 +388,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor),
)
],
),
@@ -403,11 +421,13 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
),
),
hintText: '0.00',
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
color:
Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
fontWeight: FontWeight.w500,
fontSize: 14),
),
@@ -418,7 +438,8 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
controller: noteController,
keyboardType: TextInputType.multiline,
maxLines: null,
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintText: S.of(context).note_optional,
@@ -429,73 +450,76 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
),
),
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: 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: 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).extension<SendPageTheme>()!.textFieldHintColor,
),
),
),
],
),
Padding(
padding: EdgeInsets.only(top: 2, left: 5),
child: Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
),
)
],
if (sendViewModel.hasFees)
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: 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: 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)
.extension<SendPageTheme>()!
.textFieldHintColor,
),
),
),
],
),
Padding(
padding: EdgeInsets.only(top: 2, left: 5),
child: Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
),
)
],
),
)
],
),
),
),
),
),
if (sendViewModel.hasCoinControl)
Padding(
padding: EdgeInsets.only(top: 6),
@@ -70,6 +70,21 @@ class ConnectionSyncPage extends BasePage {
handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes),
),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Observer(
builder: (context) {
if (!dashboardViewModel.hasPowNodes) return const SizedBox();
return Column(
children: [
SettingsCellWithArrow(
title: S.current.manage_pow_nodes,
handler: (context) => Navigator.of(context).pushNamed(Routes.managePowNodes),
),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
],
);
},
),
if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[
WalletConnectTile(
onTap: () async {
@@ -6,13 +6,17 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class ManageNodesPage extends BasePage {
ManageNodesPage(this.nodeListViewModel);
ManageNodesPage(this.isPow, {this.nodeListViewModel, this.powNodeListViewModel})
: assert((isPow && powNodeListViewModel != null) || (!isPow && nodeListViewModel != null));
final NodeListViewModel nodeListViewModel;
final NodeListViewModel? nodeListViewModel;
final PowNodeListViewModel? powNodeListViewModel;
final bool isPow;
@override
String get title => S.current.manage_nodes;
@@ -34,7 +38,8 @@ class ManageNodesPage extends BasePage {
SizedBox(height: 20),
Observer(
builder: (BuildContext context) {
int itemsCount = nodeListViewModel.nodes.length;
int itemsCount =
nodeListViewModel?.nodes.length ?? powNodeListViewModel!.nodes.length;
return Flexible(
child: SectionStandardList(
sectionCount: 1,
@@ -43,12 +48,19 @@ class ManageNodesPage extends BasePage {
itemBuilder: (_, index) {
return Observer(
builder: (context) {
final node = nodeListViewModel.nodes[index];
final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex;
final node =
nodeListViewModel?.nodes[index] ?? powNodeListViewModel!.nodes[index];
late bool isSelected;
if (isPow) {
isSelected = node.keyIndex == powNodeListViewModel!.currentNode.keyIndex;
} else {
isSelected = node.keyIndex == nodeListViewModel!.currentNode.keyIndex;
}
final nodeListRow = NodeListRow(
title: node.uriRaw,
node: node,
isSelected: isSelected,
isPow: false,
onTap: (_) async {
if (isSelected) {
return;
@@ -59,12 +71,17 @@ class ManageNodesPage extends BasePage {
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: nodeListViewModel.getAlertContent(node.uriRaw),
alertContent: nodeListViewModel?.getAlertContent(node.uriRaw) ??
powNodeListViewModel!.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () async {
await nodeListViewModel.setAsCurrent(node);
if (isPow) {
await powNodeListViewModel!.setAsCurrent(node);
} else {
await nodeListViewModel!.setAsCurrent(node);
}
Navigator.of(context).pop();
},
);
@@ -28,13 +28,20 @@ class OtherSettingsPage extends BasePage {
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
),
if (!_otherSettingsViewModel.changeRepresentativeEnabled)
SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
),
if (_otherSettingsViewModel.changeRepresentativeEnabled)
SettingsCellWithArrow(
title: S.current.change_rep,
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.changeRep),
),
SettingsPickerCell(
title: S.current.default_buy_provider,
items: BuyProviderType.values,
@@ -50,7 +57,7 @@ class OtherSettingsPage extends BasePage {
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Spacer(),
SettingsVersionCell(
title: S.of(context).version(_otherSettingsViewModel.currentVersion))
title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
],
),
);
@@ -48,6 +48,7 @@ class WalletListBodyState extends State<WalletListBody> {
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 nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final scrollController = ScrollController();
final double tileHeight = 60;
Flushbar<void>? _progressBar;
@@ -242,6 +243,8 @@ class WalletListBodyState extends State<WalletListBody> {
return havenIcon;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.nano:
return nanoIcon;
default:
return nonWalletTypeIcon;
}
+1 -1
View File
@@ -36,7 +36,7 @@ class WelcomePage extends BasePage {
return S.of(context).haven_app_wallet_text;
}
return S.of(context).first_wallet_text;
return S.of(context).new_first_wallet_text;
}
@override
+55
View File
@@ -64,6 +64,7 @@ abstract class SettingsStoreBase with Store {
required this.appVersion,
required this.deviceName,
required Map<WalletType, Node> nodes,
required Map<WalletType, Node> powNodes,
required this.shouldShowYatPopup,
required this.isBitcoinBuyEnabled,
required this.actionlistDisplayMode,
@@ -86,6 +87,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? initialLitecoinTransactionPriority,
TransactionPriority? initialEthereumTransactionPriority})
: nodes = ObservableMap<WalletType, Node>.of(nodes),
powNodes = ObservableMap<WalletType, Node>.of(powNodes),
_sharedPreferences = sharedPreferences,
_backgroundTasks = backgroundTasks,
fiatCurrency = initialFiatCurrency,
@@ -347,6 +349,12 @@ abstract class SettingsStoreBase with Store {
_saveCurrentNode(change.newValue!, change.key!);
}
});
this.powNodes.observe((change) {
if (change.newValue != null && change.key != null) {
_saveCurrentPowNode(change.newValue!, change.key!);
}
});
}
static const defaultPinLength = 4;
@@ -473,6 +481,7 @@ abstract class SettingsStoreBase with Store {
final BackgroundTasks _backgroundTasks;
ObservableMap<WalletType, Node> nodes;
ObservableMap<WalletType, Node> powNodes;
Node getCurrentNode(WalletType walletType) {
final node = nodes[walletType];
@@ -484,6 +493,16 @@ abstract class SettingsStoreBase with Store {
return node;
}
Node getCurrentPowNode(WalletType walletType) {
final node = powNodes[walletType];
if (node == null) {
throw Exception('No pow node found for wallet type: ${walletType.toString()}');
}
return node;
}
bool isBitcoinBuyEnabled;
bool get shouldShowReceiveWarning =>
@@ -494,6 +513,7 @@ abstract class SettingsStoreBase with Store {
static Future<SettingsStore> load(
{required Box<Node> nodeSource,
required Box<Node> powNodeSource,
required bool isBitcoinBuyEnabled,
FiatCurrency initialFiatCurrency = FiatCurrency.usd,
BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance,
@@ -611,11 +631,15 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
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 nanoNode = nodeSource.get(nanoNodeId);
final nanoPowNode = powNodeSource.get(nanoPowNodeId);
final packageInfo = await PackageInfo.fromPlatform();
final deviceName = await _getDeviceName() ?? '';
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
@@ -626,6 +650,7 @@ abstract class SettingsStoreBase with Store {
? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
: defaultAutoGenerateSubaddressStatus;
final nodes = <WalletType, Node>{};
final powNodes = <WalletType, Node>{};
if (moneroNode != null) {
nodes[WalletType.monero] = moneroNode;
@@ -647,6 +672,13 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.ethereum] = ethereumNode;
}
if (nanoNode != null) {
nodes[WalletType.nano] = nanoNode;
}
if (nanoPowNode != null) {
powNodes[WalletType.nano] = nanoPowNode;
}
final savedSyncMode = SyncMode.all.firstWhere((element) {
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
});
@@ -656,6 +688,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
nodes: nodes,
powNodes: powNodes,
appVersion: packageInfo.version,
deviceName: deviceName,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
@@ -818,11 +851,14 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
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 nanoNode = nodeSource.get(nanoNodeId);
if (moneroNode != null) {
nodes[WalletType.monero] = moneroNode;
@@ -843,6 +879,10 @@ abstract class SettingsStoreBase with Store {
if (ethereumNode != null) {
nodes[WalletType.ethereum] = ethereumNode;
}
if (nanoNode != null) {
nodes[WalletType.nano] = nanoNode;
}
}
Future<void> _saveCurrentNode(Node node, WalletType walletType) async {
@@ -864,6 +904,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.ethereum:
await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
break;
case WalletType.nano:
await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
break;
default:
break;
}
@@ -871,6 +914,18 @@ abstract class SettingsStoreBase with Store {
nodes[walletType] = node;
}
Future<void> _saveCurrentPowNode(Node node, WalletType walletType) async {
switch (walletType) {
case WalletType.nano:
await _sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
break;
default:
break;
}
powNodes[walletType] = node;
}
static Future<String?> _getDeviceName() async {
String? deviceName = '';
final deviceInfoPlugin = DeviceInfoPlugin();
+12 -3
View File
@@ -1,3 +1,5 @@
import 'package:cake_wallet/nano/nano.dart';
class PaymentRequest {
PaymentRequest(this.address, this.amount, this.note, this.scheme);
@@ -10,11 +12,18 @@ class PaymentRequest {
if (uri != null) {
address = uri.path;
amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? "";
note = uri.queryParameters['tx_description']
?? uri.queryParameters['message'] ?? "";
note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? "";
scheme = uri.scheme;
}
if (nano != null) {
if (address.contains("nano")) {
amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerNano);
} else if (address.contains("ban")) {
amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerBanano);
}
}
return PaymentRequest(address, amount, note, scheme);
}
@@ -22,4 +31,4 @@ class PaymentRequest {
final String amount;
final String note;
final String scheme;
}
}
@@ -15,12 +15,11 @@ import 'package:collection/collection.dart';
part 'contact_list_view_model.g.dart';
class ContactListViewModel = ContactListViewModelBase
with _$ContactListViewModel;
class ContactListViewModel = ContactListViewModelBase with _$ContactListViewModel;
abstract class ContactListViewModelBase with Store {
ContactListViewModelBase(this.contactSource, this.walletInfoSource,
this._currency, this.settingsStore)
ContactListViewModelBase(
this.contactSource, this.walletInfoSource, this._currency, this.settingsStore)
: contacts = ObservableList<ContactRecord>(),
walletContacts = [],
isAutoGenerateEnabled =
@@ -48,6 +47,12 @@ abstract class ContactListViewModelBase with Store {
walletTypeToCryptoCurrency(info.type),
));
});
} else if (info.address != null) {
walletContacts.add(WalletContact(
info.address,
info.name,
walletTypeToCryptoCurrency(info.type),
));
}
});
@@ -78,14 +83,12 @@ abstract class ContactListViewModelBase with Store {
Future<void> delete(ContactRecord contact) async => contact.original.delete();
@computed
List<ContactRecord> get contactsToShow => contacts
.where((element) => _isValidForCurrency(element))
.toList();
List<ContactRecord> get contactsToShow =>
contacts.where((element) => _isValidForCurrency(element)).toList();
@computed
List<WalletContact> get walletContactsToShow => walletContacts
.where((element) => _isValidForCurrency(element))
.toList();
List<WalletContact> get walletContactsToShow =>
walletContacts.where((element) => _isValidForCurrency(element)).toList();
bool _isValidForCurrency(ContactBase element) {
return _currency == null || element.type == _currency || element.type.title == _currency!.tag;
@@ -47,74 +47,71 @@ abstract class DashboardViewModelBase with Store {
required this.yatStore,
required this.ordersStore,
required this.anonpayTransactionsStore})
: isOutdatedElectrumWallet = false,
hasSellAction = false,
hasBuyAction = false,
hasExchangeAction = false,
isShowFirstYatIntroduction = false,
isShowSecondYatIntroduction = false,
isShowThirdYatIntroduction = false,
filterItems = {
S.current.transactions: [
FilterItem(
value: () => transactionFilterStore.displayAll,
caption: S.current.all_transactions,
onChanged: transactionFilterStore.toggleAll),
FilterItem(
value: () => transactionFilterStore.displayIncoming,
caption: S.current.incoming,
onChanged: transactionFilterStore.toggleIncoming),
FilterItem(
value: () => transactionFilterStore.displayOutgoing,
caption: S.current.outgoing,
onChanged: transactionFilterStore.toggleOutgoing),
// FilterItem(
// value: () => false,
// caption: S.current.transactions_by_date,
// onChanged: null),
],
S.current.trades: [
FilterItem(
value: () => tradeFilterStore.displayAllTrades,
caption: S.current.all_trades,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.all)),
FilterItem(
value: () => tradeFilterStore.displayChangeNow,
caption: ExchangeProviderDescription.changeNow.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
FilterItem(
value: () => tradeFilterStore.displaySideShift,
caption: ExchangeProviderDescription.sideShift.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
FilterItem(
value: () => tradeFilterStore.displaySimpleSwap,
caption: ExchangeProviderDescription.simpleSwap.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
FilterItem(
value: () => tradeFilterStore.displayTrocador,
caption: ExchangeProviderDescription.trocador.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
FilterItem(
value: () => tradeFilterStore.displayExolix,
caption: ExchangeProviderDescription.exolix.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
]
},
subname = '',
name = appStore.wallet!.name,
type = appStore.wallet!.type,
transactions = ObservableList<TransactionListItem>(),
wallet = appStore.wallet! {
: hasSellAction = false,
hasBuyAction = false,
hasExchangeAction = false,
isShowFirstYatIntroduction = false,
isShowSecondYatIntroduction = false,
isShowThirdYatIntroduction = false,
filterItems = {
S.current.transactions: [
FilterItem(
value: () => transactionFilterStore.displayAll,
caption: S.current.all_transactions,
onChanged: transactionFilterStore.toggleAll),
FilterItem(
value: () => transactionFilterStore.displayIncoming,
caption: S.current.incoming,
onChanged:transactionFilterStore.toggleIncoming),
FilterItem(
value: () => transactionFilterStore.displayOutgoing,
caption: S.current.outgoing,
onChanged: transactionFilterStore.toggleOutgoing),
// FilterItem(
// value: () => false,
// caption: S.current.transactions_by_date,
// onChanged: null),
],
S.current.trades: [
FilterItem(
value: () => tradeFilterStore.displayAllTrades,
caption: S.current.all_trades,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.all)),
FilterItem(
value: () => tradeFilterStore.displayChangeNow,
caption: ExchangeProviderDescription.changeNow.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
FilterItem(
value: () => tradeFilterStore.displaySideShift,
caption: ExchangeProviderDescription.sideShift.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
FilterItem(
value: () => tradeFilterStore.displaySimpleSwap,
caption: ExchangeProviderDescription.simpleSwap.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
FilterItem(
value: () => tradeFilterStore.displayTrocador,
caption: ExchangeProviderDescription.trocador.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
FilterItem(
value: () => tradeFilterStore.displayExolix,
caption: ExchangeProviderDescription.exolix.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
]
},
subname = '',
name = appStore.wallet!.name,
type = appStore.wallet!.type,
transactions = ObservableList<TransactionListItem>(),
wallet = appStore.wallet! {
name = wallet.name;
type = wallet.type;
isOutdatedElectrumWallet =
wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24;
isShowFirstYatIntroduction = false;
isShowSecondYatIntroduction = false;
isShowThirdYatIntroduction = false;
@@ -151,6 +148,11 @@ abstract class DashboardViewModelBase with Store {
settingsStore: appStore.settingsStore)));
}
// TODO: nano sub-account generation is disabled:
// if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
// subname = nano!.getCurrentAccount(_wallet).label;
// }
reaction((_) => appStore.wallet, _onWalletChange);
connectMapToListWithTransform(
@@ -315,12 +317,16 @@ abstract class DashboardViewModelBase with Store {
ReactionDisposer? _onMoneroBalanceChangeReaction;
@observable
bool isOutdatedElectrumWallet;
@computed
bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano;
Future<void> reconnect() async {
final node = appStore.settingsStore.getCurrentNode(wallet.type);
await wallet.connectToNode(node: node);
if (hasPowNodes) {
final powNode = settingsStore.getCurrentPowNode(wallet.type);
await wallet.connectToPowNode(node: powNode);
}
}
@action
@@ -333,8 +339,6 @@ abstract class DashboardViewModelBase with Store {
this.wallet = wallet;
type = wallet.type;
name = wallet.name;
isOutdatedElectrumWallet =
wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24;
updateActions();
if (wallet.type == WalletType.monero) {
@@ -2,6 +2,7 @@ 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:cake_wallet/nano/nano.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cake_wallet/store/settings_store.dart';
@@ -16,9 +17,7 @@ import 'package:cw_core/wallet_type.dart';
class TransactionListItem extends ActionListItem with Keyable {
TransactionListItem(
{required this.transaction,
required this.balanceViewModel,
required this.settingsStore});
{required this.transaction, required this.balanceViewModel, required this.settingsStore});
final TransactionInfo transaction;
final BalanceViewModel balanceViewModel;
@@ -34,10 +33,9 @@ class TransactionListItem extends ActionListItem with Keyable {
dynamic get keyIndex => transaction.id;
String get formattedCryptoAmount {
return displayMode == BalanceDisplayMode.hiddenBalance
? '---'
: transaction.amountFormatted();
return displayMode == BalanceDisplayMode.hiddenBalance ? '---' : transaction.amountFormatted();
}
String get formattedTitle {
if (transaction.direction == TransactionDirection.incoming) {
return S.current.received;
@@ -57,33 +55,47 @@ class TransactionListItem extends ActionListItem with Keyable {
if (transaction.direction == TransactionDirection.incoming) {
if (balanceViewModel.wallet.type == WalletType.monero ||
balanceViewModel.wallet.type == WalletType.haven) {
return formattedPendingStatus;
}
return formattedPendingStatus;
}
return transaction.isPending ? S.current.pending : '';
}
return transaction.isPending ? S.current.pending : '';
}
String get formattedFiatAmount {
var amount = '';
switch(balanceViewModel.wallet.type) {
switch (balanceViewModel.wallet.type) {
case WalletType.monero:
amount = calculateFiatAmountRaw(
cryptoAmount: monero!.formatterMoneroAmountToDouble(amount: transaction.amount),
price: price);
cryptoAmount: monero!.formatterMoneroAmountToDouble(amount: transaction.amount),
price: price);
break;
case WalletType.bitcoin:
case WalletType.litecoin:
amount = calculateFiatAmountRaw(
cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount),
price: price);
cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount),
price: price);
break;
case WalletType.haven:
final asset = haven!.assetOfTransaction(transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount),
price: price);
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;
case WalletType.nano:
amount = calculateFiatAmountRaw(
cryptoAmount: nanoUtil!
.getRawAsDecimal(nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)
.toDouble(),
price: price);
break;
case WalletType.ethereum:
final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
@@ -86,13 +86,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
super(appStore: appStore) {
_useTorOnly = _settingsStore.exchangeStatus == ExchangeApiMode.torOnly;
_setProviders();
const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano];
const excludeDepositCurrencies = [CryptoCurrency.btt];
const excludeReceiveCurrencies = [
CryptoCurrency.xlm,
CryptoCurrency.xrp,
CryptoCurrency.bnb,
CryptoCurrency.btt,
CryptoCurrency.nano
CryptoCurrency.btt
];
_initialPairBasedOnWallet();
@@ -703,6 +702,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.eth;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.nano:
depositCurrency = CryptoCurrency.nano;
receiveCurrency = CryptoCurrency.xmr;
break;
default:
break;
}
@@ -0,0 +1,58 @@
import 'package:cake_wallet/nano/nano.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/execution_state.dart';
part 'nano_account_edit_or_create_view_model.g.dart';
class NanoAccountEditOrCreateViewModel = NanoAccountEditOrCreateViewModelBase
with _$NanoAccountEditOrCreateViewModel;
abstract class NanoAccountEditOrCreateViewModelBase with Store {
NanoAccountEditOrCreateViewModelBase(this._nanoAccountList,
{required WalletBase wallet, NanoAccount? accountListItem})
: state = InitialExecutionState(),
isEdit = accountListItem != null,
label = accountListItem?.label ?? '',
_accountListItem = accountListItem,
_wallet = wallet;
final bool isEdit;
@observable
ExecutionState state;
@observable
String label;
final NanoAccountList _nanoAccountList;
final NanoAccount? _accountListItem;
final WalletBase _wallet;
Future<void> save() async {
if (_wallet.type == WalletType.nano) {
await saveNano();
}
}
Future<void> saveNano() async {
try {
state = IsExecutingState();
if (_accountListItem != null) {
await _nanoAccountList.setLabelAccount(_wallet,
accountIndex: _accountListItem!.id, label: label);
} else {
await _nanoAccountList.addAccount(_wallet, label: label);
}
await _wallet.save();
state = ExecutedSuccessfullyState();
} catch (e) {
state = FailureState(e.toString());
}
}
}
@@ -0,0 +1,54 @@
import 'package:cake_wallet/nano/nano.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/nano_account.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_base.dart';
part 'nano_account_list_view_model.g.dart';
class NanoAccountListViewModel = NanoAccountListViewModelBase with _$NanoAccountListViewModel;
abstract class NanoAccountListViewModelBase with Store {
NanoAccountListViewModelBase(this._wallet) : scrollOffsetFromTop = 0;
@observable
double scrollOffsetFromTop;
@action
void setScrollOffsetFromTop(double scrollOffsetFromTop) {
this.scrollOffsetFromTop = scrollOffsetFromTop;
}
CryptoCurrency get currency => _wallet.currency;
@computed
List<NanoAccount> get accounts {
if (_wallet.type == WalletType.nano) {
return nano!
.getAccountList(_wallet)
.accounts
.map((acc) => NanoAccount(
label: acc.label,
id: acc.id,
isSelected: acc.id == nano?.getCurrentAccount(_wallet).id,
))
.toList();
}
throw Exception('Unexpected wallet type: ${_wallet.type}');
}
final WalletBase _wallet;
void select(NanoAccount item) {
if (_wallet.type == WalletType.nano) {
nano!.setCurrentAccount(
_wallet,
item.id,
item.label,
item.balance,
);
}
}
}
@@ -66,6 +66,9 @@ abstract class NodeListViewModelBase with Store {
case WalletType.ethereum:
node = getEthereumDefaultNode(nodes: _nodeSource)!;
break;
case WalletType.nano:
node = getNanoDefaultNode(nodes: _nodeSource)!;
break;
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
}
@@ -0,0 +1,79 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/node.dart';
import 'package:cake_wallet/entities/node_list.dart';
import 'package:cake_wallet/entities/default_settings_migration.dart';
import 'package:cw_core/wallet_type.dart';
part 'pow_node_list_view_model.g.dart';
class PowNodeListViewModel = PowNodeListViewModelBase with _$PowNodeListViewModel;
abstract class PowNodeListViewModelBase with Store {
PowNodeListViewModelBase(this._nodeSource, this._appStore)
: nodes = ObservableList<Node>(),
settingsStore = _appStore.settingsStore {
_bindNodes();
reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
_bindNodes();
});
}
@computed
Node get currentNode {
final node = settingsStore.powNodes[_appStore.wallet!.type];
if (node == null) {
throw Exception('No node for wallet type: ${_appStore.wallet!.type}');
}
return node;
}
String getAlertContent(String uri) =>
S.current.change_current_node(uri) +
'${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + S.current.orbot_running_alert : ''}';
final ObservableList<Node> nodes;
final SettingsStore settingsStore;
final Box<Node> _nodeSource;
final AppStore _appStore;
Future<void> reset() async {
await resetPowToDefault(_nodeSource);
Node node;
switch (_appStore.wallet!.type) {
case WalletType.nano:
node = getNanoDefaultPowNode(nodes: _nodeSource)!;
break;
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
}
await setAsCurrent(node);
}
@action
Future<void> delete(Node node) async => node.delete();
Future<void> setAsCurrent(Node node) async =>
settingsStore.powNodes[_appStore.wallet!.type] = node;
@action
void _bindNodes() {
nodes.clear();
_nodeSource.bindToList(
nodes,
filter: (val) => val.type == _appStore.wallet!.type,
initialFire: true,
);
}
}
+27 -14
View File
@@ -1,10 +1,13 @@
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/di.dart';
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/nano/nano.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.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';
@@ -16,7 +19,6 @@ import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cw_core/sync_status.dart';
@@ -62,7 +64,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
final priority = _settingsStore.priority[wallet.type];
final priorities = priorityForWalletType(wallet.type);
if (!priorityForWalletType(wallet.type).contains(priority)) {
if (!priorityForWalletType(wallet.type).contains(priority) && priorities.isNotEmpty) {
_settingsStore.priority[wallet.type] = priorities.first;
}
@@ -99,15 +101,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
@computed
String get pendingTransactionFiatAmount {
if (pendingTransaction == null) {
return '0.00';
}
try {
if (pendingTransaction != null) {
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
cryptoAmount: pendingTransaction!.amountFormatted);
return fiat;
} else {
return '0.00';
}
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
cryptoAmount: pendingTransaction!.amountFormatted);
return fiat;
} catch (_) {
return '0.00';
}
@@ -191,6 +193,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
bool get isElectrumWallet =>
wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin;
@computed
bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano;
@observable
CryptoCurrency selectedCryptoCurrency;
@@ -316,6 +321,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
state = TransactionCommitting();
await pendingTransaction!.commit();
if (walletType == WalletType.nano) {
nano!.updateTransactions(wallet);
}
if (pendingTransaction!.id.isNotEmpty) {
_settingsStore.shouldSaveRecipientAddress
? await transactionDescriptionBox.add(TransactionDescription(
@@ -380,6 +389,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return ethereum!.createEthereumTransactionCredentials(outputs,
priority: priority, currency: selectedCryptoCurrency);
case WalletType.nano:
return nano!.createNanoTransactionCredentials(
outputs,
);
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}
@@ -25,7 +25,7 @@ abstract class OtherSettingsViewModelBase with Store {
final priority = _settingsStore.priority[_wallet.type];
final priorities = priorityForWalletType(_wallet.type);
if (!priorities.contains(priority)) {
if (!priorities.contains(priority) && priorities.isNotEmpty) {
_settingsStore.priority[_wallet.type] = priorities.first;
}
}
@@ -50,6 +50,14 @@ abstract class OtherSettingsViewModelBase with Store {
}
@computed
bool get changeRepresentativeEnabled {
if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
return true;
}
return false;
}
BuyProviderType get buyProviderType { return _settingsStore.defaultBuyProvider; }
String getDisplayPriority(dynamic priority) {
@@ -47,6 +47,9 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.ethereum:
_addEthereumListItems(tx, dateFormat);
break;
case WalletType.nano:
_addNanoListItems(tx, dateFormat);
break;
default:
break;
}
@@ -116,6 +119,10 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://explorer.havenprotocol.org/search?value=${txId}';
case WalletType.ethereum:
return 'https://etherscan.io/tx/${txId}';
case WalletType.nano:
return 'https://nanolooker.com/block/${txId}';
case WalletType.banano:
return 'https://bananolooker.com/block/${txId}';
default:
return '';
}
@@ -133,6 +140,10 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'explorer.havenprotocol.org';
case WalletType.ethereum:
return S.current.view_transaction_on + 'etherscan.io';
case WalletType.nano:
return S.current.view_transaction_on + 'nanolooker.com';
case WalletType.banano:
return S.current.view_transaction_on + 'bananolooker.com';
default:
return '';
}
@@ -221,4 +232,18 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
void _addNanoListItems(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 > 0).toString()),
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
];
items.addAll(_items);
}
}
@@ -107,6 +107,22 @@ class EthereumURI extends PaymentURI {
}
}
class NanoURI extends PaymentURI {
NanoURI({required String amount, required String address})
: super(amount: amount, address: address);
@override
String toString() {
var base = 'nano:' + address;
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
}
return base;
}
}
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({
required AppStore appStore,
@@ -176,6 +192,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return EthereumURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.nano) {
return NanoURI(amount: amount, address: address.address);
}
throw Exception('Unexpected type: ${type.toString()}');
}
@@ -252,7 +272,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}
@computed
bool get hasAddressList => wallet.type == WalletType.monero || wallet.type == WalletType.haven;
bool get hasAddressList =>
wallet.type == WalletType.monero ||
wallet.type == WalletType.haven;/* ||
wallet.type == WalletType.nano ||
wallet.type == WalletType.banano;*/// TODO: nano accounts are disabled for now
@computed
bool get showElectrumAddressDisclaimer =>
@@ -269,11 +293,16 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
void _init() {
_baseItems = [];
if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
if (wallet.type == WalletType.monero ||
wallet.type == WalletType.haven /*||
wallet.type == WalletType.nano ||
wallet.type == WalletType.banano*/) {
_baseItems.add(WalletAccountListHeader());
}
_baseItems.add(WalletAddressListHeader());
if (wallet.type != WalletType.nano && wallet.type != WalletType.banano) {
_baseItems.add(WalletAddressListHeader());
}
}
@action
+26 -23
View File
@@ -35,18 +35,16 @@ abstract class WalletCreationVMBase with Store {
final Box<WalletInfo> _walletInfoSource;
final AppStore _appStore;
bool nameExists(String name)
=> walletCreationService.exists(name);
bool nameExists(String name) => walletCreationService.exists(name);
bool typeExists(WalletType type)
=> walletCreationService.typeExists(type);
bool typeExists(WalletType type) => walletCreationService.typeExists(type);
Future<void> create({dynamic options, RestoredWallet? restoreWallet}) async {
final type = restoreWallet?.type ?? this.type;
try {
state = IsExecutingState();
if (name.isEmpty) {
name = await generateName();
name = await generateName();
}
walletCreationService.checkIfExists(name);
@@ -55,17 +53,21 @@ abstract class WalletCreationVMBase with Store {
final credentials = restoreWallet != null
? getCredentialsFromRestoredWallet(options, restoreWallet)
: getCredentials(options);
final walletInfo = WalletInfo.external(
id: WalletBase.idFor(name, type),
name: name,
type: type,
isRecovery: isRecovery,
restoreHeight: credentials.height ?? 0,
date: DateTime.now(),
path: path,
dirPath: dirPath,
address: '',
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven);
id: WalletBase.idFor(name, type),
name: name,
type: type,
isRecovery: isRecovery,
restoreHeight: credentials.height ?? 0,
date: DateTime.now(),
path: path,
dirPath: dirPath,
address: '',
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
derivationPath: credentials.derivationPath,
derivationType: credentials.derivationType,
);
credentials.walletInfo = walletInfo;
final wallet = restoreWallet != null
? await processFromRestoredWallet(credentials, restoreWallet)
@@ -80,15 +82,16 @@ abstract class WalletCreationVMBase with Store {
state = FailureState(e.toString());
}
}
WalletCredentials getCredentials(dynamic options) =>
WalletCredentials getCredentials(dynamic options) => throw UnimplementedError();
Future<WalletBase> process(WalletCredentials credentials) => throw UnimplementedError();
WalletCredentials getCredentialsFromRestoredWallet(
dynamic options, RestoredWallet restoreWallet) =>
throw UnimplementedError();
Future<WalletBase> process(WalletCredentials credentials) =>
throw UnimplementedError();
WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) =>
throw UnimplementedError();
Future<WalletBase> processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) =>
Future<WalletBase> processFromRestoredWallet(
WalletCredentials credentials, RestoredWallet restoreWallet) =>
throw UnimplementedError();
}
@@ -1,3 +1,4 @@
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
@@ -104,6 +105,22 @@ abstract class WalletKeysViewModelBase with Store {
StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
]);
}
if (_appStore.wallet!.type == WalletType.nano || _appStore.wallet!.type == WalletType.banano) {
// we don't necessarily have the seed phrase for nano / banano:
if (_appStore.wallet!.seed != null) {
items.addAll([
StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
]);
}
// we always have the hex version of the seed:
items.addAll([
if (_appStore.wallet!.privateKey != null)
StandartListItem(title: S.current.spend_key_private, value: _appStore.wallet!.privateKey!),
]);
}
}
Future<int?> _currentHeight() async {
@@ -128,6 +145,10 @@ abstract class WalletKeysViewModelBase with Store {
return 'haven-wallet';
case WalletType.ethereum:
return 'ethereum-wallet';
case WalletType.nano:
return 'nano-wallet';
case WalletType.banano:
return 'banano-wallet';
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
}
+3
View File
@@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
@@ -45,6 +46,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
name: name, language: options as String);
case WalletType.ethereum:
return ethereum!.createEthereumNewWalletCredentials(name: name);
case WalletType.nano:
return nano!.createNanoNewWalletCredentials(name: name);
default:
throw Exception('Unexpected type: ${type.toString()}');;
}
@@ -0,0 +1,23 @@
import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
part 'wallet_restore_choose_derivation_view_model.g.dart';
class WalletRestoreChooseDerivationViewModel = WalletRestoreChooseDerivationViewModelBase
with _$WalletRestoreChooseDerivationViewModel;
abstract class WalletRestoreChooseDerivationViewModelBase with Store {
WalletRestoreChooseDerivationViewModelBase({required this.derivationInfos})
: mode = WalletRestoreMode.seed {}
@observable
List<DerivationInfo> derivationInfos;
Future<List<DerivationInfo>> get derivations async {
return derivationInfos;
}
@observable
WalletRestoreMode mode;
}
+84 -41
View File
@@ -1,4 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@@ -16,25 +18,34 @@ import 'package:cake_wallet/view_model/restore/restore_mode.dart';
part 'wallet_restore_view_model.g.dart';
class WalletRestoreViewModel = WalletRestoreViewModelBase
with _$WalletRestoreViewModel;
class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreViewModel;
abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource,
{required WalletType type})
: availableModes =
(type == WalletType.monero || type == WalletType.haven || type == WalletType.ethereum)
? WalletRestoreMode.values
: [WalletRestoreMode.seed],
hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
: hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven,
hasRestoreFromPrivateKey = type == WalletType.ethereum,
hasRestoreFromPrivateKey =
type == WalletType.ethereum || type == WalletType.nano || type == WalletType.banano,
isButtonEnabled = false,
mode = WalletRestoreMode.seed,
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) {
isButtonEnabled =
!hasSeedLanguageSelector && !hasBlockchainHeightLanguageSelector;
switch (type) {
case WalletType.monero:
case WalletType.haven:
case WalletType.ethereum:
availableModes = WalletRestoreMode.values;
break;
case WalletType.nano:
case WalletType.banano:
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
break;
default:
availableModes = [WalletRestoreMode.seed];
break;
}
isButtonEnabled = !hasSeedLanguageSelector && !hasBlockchainHeightLanguageSelector;
walletCreationService.changeWalletType(type: type);
}
@@ -42,7 +53,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
static const electrumSeedMnemonicLength = 24;
static const electrumShortSeedMnemonicLength = 12;
final List<WalletRestoreMode> availableModes;
late List<WalletRestoreMode> availableModes;
final bool hasSeedLanguageSelector;
final bool hasBlockchainHeightLanguageSelector;
final bool hasRestoreFromPrivateKey;
@@ -58,38 +69,37 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final password = generateWalletPassword();
final height = options['height'] as int? ?? 0;
name = options['name'] as String;
DerivationType? derivationType = options["derivationType"] as DerivationType?;
String? derivationPath = options["derivationPath"] as String?;
if (mode == WalletRestoreMode.seed) {
final seed = options['seed'] as String;
switch (type) {
case WalletType.monero:
return monero!.createMoneroRestoreWalletFromSeedCredentials(
name: name,
height: height,
mnemonic: seed,
password: password);
name: name, height: height, mnemonic: seed, password: password);
case WalletType.bitcoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password);
password: password,
);
case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password);
name: name, mnemonic: seed, password: password);
case WalletType.haven:
return haven!.createHavenRestoreWalletFromSeedCredentials(
name: name,
height: height,
mnemonic: seed,
password: password);
name: name, height: height, mnemonic: seed, password: password);
case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password);
name: name, mnemonic: seed, password: password);
case WalletType.nano:
return nano!.createNanoRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
derivationType: derivationType,
);
default:
break;
}
@@ -100,40 +110,73 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final spendKey = options['spendKey'] as String?;
final address = options['address'] as String?;
if (type == WalletType.monero) {
return monero!.createMoneroRestoreWalletFromKeysCredentials(
switch (type) {
case WalletType.monero:
return monero!.createMoneroRestoreWalletFromKeysCredentials(
name: name,
height: height,
spendKey: spendKey!,
viewKey: viewKey!,
address: address!,
password: password,
language: 'English');
}
language: 'English',
);
if (type == WalletType.haven) {
return haven!.createHavenRestoreWalletFromKeysCredentials(
case WalletType.haven:
return haven!.createHavenRestoreWalletFromKeysCredentials(
name: name,
height: height,
spendKey: spendKey!,
viewKey: viewKey!,
address: address!,
password: password,
language: 'English');
}
language: 'English',
);
if (type == WalletType.ethereum) {
return ethereum!.createEthereumRestoreWalletFromPrivateKey(
name: name,
privateKey: options['private_key'] as String,
password: password,
);
case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromPrivateKey(
name: name,
privateKey: options['private_key'] as String,
password: password,
);
case WalletType.nano:
return nano!.createNanoRestoreWalletFromKeysCredentials(
name: name,
password: password,
seedKey: options['private_key'] as String,
derivationType: options["derivationType"] as DerivationType,
);
default:
break;
}
}
throw Exception('Unexpected type: ${type.toString()}');
}
Future<List<DerivationType>> getDerivationTypes(dynamic options) async {
final seedKey = options['private_key'] as String?;
final mnemonic = options['seed'] as String?;
WalletType walletType = options['walletType'] as WalletType;
var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType);
switch (type) {
case WalletType.nano:
return nanoUtil!.compareDerivationMethods(
mnemonic: mnemonic,
privateKey: seedKey,
node: node,
);
default:
break;
}
// throw Exception('Unexpected type: ${type.toString()}');
return [DerivationType.def];
}
@override
Future<WalletBase> process(WalletCredentials credentials) async {
if (mode == WalletRestoreMode.keys) {