diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 223dc48df..f0a9d7be5 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -162,7 +162,8 @@ class $BackupService { final data = json.decode(preferencesFile.readAsStringSync()) as Map; - try { // shouldn't throw an error but just in case, so it doesn't stop the backup restore + try { + // shouldn't throw an error but just in case, so it doesn't stop the backup restore for (var entry in data.entries) { String key = entry.key; dynamic value = entry.value; @@ -180,7 +181,8 @@ class $BackupService { await sharedPreferences.setStringList(key, value); } else { if (kDebugMode) { - printV('Skipping individual save for key "$key": Unsupported type (${value.runtimeType}). Value: $value'); + printV( + 'Skipping individual save for key "$key": Unsupported type (${value.runtimeType}). Value: $value'); } } } @@ -263,15 +265,23 @@ class $BackupService { {String keychainSalt = secrets.backupKeychainSalt}) async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final wallets = await Future.wait(walletInfoSource.values.map((walletInfo) async { - return { - 'name': walletInfo.name, - 'type': walletInfo.type.toString(), - 'password': await keyService.getWalletPassword(walletName: walletInfo.name) - }; + try { + return { + 'name': walletInfo.name, + 'type': walletInfo.type.toString(), + 'password': await keyService.getWalletPassword(walletName: walletInfo.name) + }; + } catch (e) { + return {'name': walletInfo.name, 'type': walletInfo.type.toString(), 'password': ''}; + } })); final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword); final backupPassword = await _secureStorage.read(key: backupPasswordKey); - final data = utf8.encode(json.encode({'wallets': wallets, backupPasswordKey: backupPassword})); + final data = utf8.encode(json.encode({ + 'wallets': wallets, + backupPasswordKey: backupPassword, + '_all': await _secureStorage.readAll() + })); final encrypted = await _encryptV2(Uint8List.fromList(data), '$keychainSalt$password'); return encrypted; diff --git a/lib/di.dart b/lib/di.dart index 7303c1ac8..b06183cde 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -33,6 +33,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; +import 'package:cake_wallet/haven/cw_haven.dart'; import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; import 'package:cake_wallet/src/screens/settings/background_sync_page.dart'; @@ -1144,8 +1145,9 @@ Future setup({ return zano!.createZanoWalletService(_walletInfoSource); case WalletType.decred: return decred!.createDecredWalletService(_walletInfoSource, _unspentCoinsInfoSource); - case WalletType.none: case WalletType.haven: + return HavenWalletService(_walletInfoSource); + case WalletType.none: throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); } }); diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index e1c4470b4..d09751604 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -41,8 +41,8 @@ class TradeState extends EnumerableItem with Serializable { static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging'); static const sending = TradeState(raw: 'sending', title: 'Sending'); static const success = TradeState(raw: 'success', title: 'Success'); - static TradeState deserialize({required String raw}) { + static TradeState deserialize({required String raw}) { switch (raw) { case '1': return unpaid; @@ -138,7 +138,7 @@ class TradeState extends EnumerableItem with Serializable { case 'awaiting': return awaiting; default: - throw Exception('Unexpected token: $raw in TradeState deserialize'); + return TradeState(raw: raw, title: raw); } } diff --git a/lib/haven/cw_haven.dart b/lib/haven/cw_haven.dart index c54e47eb4..b69e733c3 100644 --- a/lib/haven/cw_haven.dart +++ b/lib/haven/cw_haven.dart @@ -1,348 +1,78 @@ -part of 'haven.dart'; +import 'dart:io'; -class CWHavenAccountList extends HavenAccountList { - CWHavenAccountList(this._wallet); +import 'package:cw_core/balance.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:hive/hive.dart'; - final Object _wallet; +class HavenWalletService extends WalletService { + final Box walletInfoSource; + + HavenWalletService(this.walletInfoSource); @override - @computed - ObservableList get accounts { - final havenWallet = _wallet as HavenWallet; - final accounts = havenWallet.walletAddresses.accountList.accounts - .map((acc) => Account(id: acc.id, label: acc.label)) - .toList(); - return ObservableList.of(accounts); - } + WalletType getType() => WalletType.haven; @override - void update(Object wallet) { - final havenWallet = wallet as HavenWallet; - havenWallet.walletAddresses.accountList.update(); - } + Future remove(String wallet) async { + final path = await pathForWalletDir(name: wallet, type: WalletType.haven); - @override - void refresh(Object wallet) { - final havenWallet = wallet as HavenWallet; - havenWallet.walletAddresses.accountList.refresh(); - } + final file = Directory(path); + final isExist = file.existsSync(); - @override - List getAll(Object wallet) { - final havenWallet = wallet as HavenWallet; - return havenWallet.walletAddresses.accountList - .getAll() - .map((acc) => Account(id: acc.id, label: acc.label)) - .toList(); - } - - @override - Future addAccount(Object wallet, {required String label}) async { - final havenWallet = wallet as HavenWallet; - await havenWallet.walletAddresses.accountList.addAccount(label: label); - } - - @override - Future setLabelAccount(Object wallet, - {required int accountIndex, required String label}) async { - final havenWallet = wallet as HavenWallet; - await havenWallet.walletAddresses.accountList - .setLabelAccount(accountIndex: accountIndex, label: label); - } -} - -class CWHavenSubaddressList extends MoneroSubaddressList { - CWHavenSubaddressList(this._wallet); - - final Object _wallet; - - @override - @computed - ObservableList get subaddresses { - final havenWallet = _wallet as HavenWallet; - final subAddresses = havenWallet.walletAddresses.subaddressList.subaddresses - .map((sub) => Subaddress(id: sub.id, address: sub.address, label: sub.label)) - .toList(); - return ObservableList.of(subAddresses); - } - - @override - void update(Object wallet, {required int accountIndex}) { - final havenWallet = wallet as HavenWallet; - havenWallet.walletAddresses.subaddressList.update(accountIndex: accountIndex); - } - - @override - void refresh(Object wallet, {required int accountIndex}) { - final havenWallet = wallet as HavenWallet; - havenWallet.walletAddresses.subaddressList.refresh(accountIndex: accountIndex); - } - - @override - List getAll(Object wallet) { - final havenWallet = wallet as HavenWallet; - return havenWallet.walletAddresses.subaddressList - .getAll() - .map((sub) => Subaddress(id: sub.id, label: sub.label, address: sub.address)) - .toList(); - } - - @override - Future addSubaddress(Object wallet, - {required int accountIndex, required String label}) async { - final havenWallet = wallet as HavenWallet; - await havenWallet.walletAddresses.subaddressList - .addSubaddress(accountIndex: accountIndex, label: label); - } - - @override - Future setLabelSubaddress(Object wallet, - {required int accountIndex, required int addressIndex, required String label}) async { - final havenWallet = wallet as HavenWallet; - await havenWallet.walletAddresses.subaddressList - .setLabelSubaddress(accountIndex: accountIndex, addressIndex: addressIndex, label: label); - } -} - -class CWHavenWalletDetails extends HavenWalletDetails { - CWHavenWalletDetails(this._wallet); - - final Object _wallet; - - @computed - @override - Account get account { - final havenWallet = _wallet as HavenWallet; - final acc = havenWallet.walletAddresses.account as monero_account.Account; - return Account(id: acc.id, label: acc.label); - } - - @computed - @override - HavenBalance get balance { - final havenWallet = _wallet as HavenWallet; - final balance = havenWallet.balance; - throw Exception('Unimplemented'); - //return HavenBalance( - // fullBalance: balance.fullBalance, - // unlockedBalance: balance.unlockedBalance); - } -} - -class CWHaven extends Haven { - @override - HavenAccountList getAccountList(Object wallet) { - return CWHavenAccountList(wallet); - } - - @override - MoneroSubaddressList getSubaddressList(Object wallet) { - return CWHavenSubaddressList(wallet); - } - - @override - TransactionHistoryBase getTransactionHistory(Object wallet) { - final havenWallet = wallet as HavenWallet; - return havenWallet.transactionHistory; - } - - @override - HavenWalletDetails getMoneroWalletDetails(Object wallet) { - return CWHavenWalletDetails(wallet); - } - - @override - int getHeightByDate({required DateTime date}) => getHavenHeightByDate(date: date); - - @override - Future getCurrentHeight() => getHavenCurrentHeight(); - - @override - TransactionPriority getDefaultTransactionPriority() { - return MoneroTransactionPriority.automatic; - } - - @override - TransactionPriority deserializeMoneroTransactionPriority({required int raw}) { - return MoneroTransactionPriority.deserialize(raw: raw); - } - - @override - List getTransactionPriorities() { - return MoneroTransactionPriority.all; - } - - @override - List getMoneroWordList(String language) { - switch (language.toLowerCase()) { - case 'english': - return EnglishMnemonics.words; - case 'chinese (simplified)': - return ChineseSimplifiedMnemonics.words; - case 'dutch': - return DutchMnemonics.words; - case 'german': - return GermanMnemonics.words; - case 'japanese': - return JapaneseMnemonics.words; - case 'portuguese': - return PortugueseMnemonics.words; - case 'russian': - return RussianMnemonics.words; - case 'spanish': - return SpanishMnemonics.words; - case 'french': - return FrenchMnemonics.words; - case 'italian': - return ItalianMnemonics.words; - default: - return EnglishMnemonics.words; + if (isExist) { + await file.delete(recursive: true); } + + final walletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(wallet, getType())); + await walletInfoSource.delete(walletInfo.key); } @override - WalletCredentials createHavenRestoreWalletFromKeysCredentials( - {required String name, - required String spendKey, - required String viewKey, - required String address, - required String password, - required String language, - required int height}) { - return HavenRestoreWalletFromKeysCredentials( - name: name, - spendKey: spendKey, - viewKey: viewKey, - address: address, - password: password, - language: language, - height: height); + Future, TransactionInfo>> create( + WalletCredentials credentials, + {bool? isTestnet}) { + throw UnimplementedError(); } @override - WalletCredentials createHavenRestoreWalletFromSeedCredentials( - {required String name, - required String password, - required int height, - required String mnemonic}) { - return HavenRestoreWalletFromSeedCredentials( - name: name, password: password, height: height, mnemonic: mnemonic); + Future isWalletExit(String name) { + throw UnimplementedError(); } @override - WalletCredentials createHavenNewWalletCredentials( - {required String name, required String language, String? password}) { - return HavenNewWalletCredentials(name: name, password: password, language: language); + Future, TransactionInfo>> openWallet( + String name, String password) { + throw UnimplementedError(); } @override - Map getKeys(Object wallet) { - final havenWallet = wallet as HavenWallet; - final keys = havenWallet.keys; - return { - 'privateSpendKey': keys.privateSpendKey, - 'privateViewKey': keys.privateViewKey, - 'publicSpendKey': keys.publicSpendKey, - 'publicViewKey': keys.publicViewKey - }; + Future rename(String currentName, String password, String newName) { + throw UnimplementedError(); } @override - Object createHavenTransactionCreationCredentials( - {required List outputs, - required TransactionPriority priority, - required String assetType}) { - return HavenTransactionCreationCredentials( - outputs: outputs - .map((out) => OutputInfo( - fiatAmount: out.fiatAmount, - cryptoAmount: out.cryptoAmount, - address: out.address, - note: out.note, - sendAll: out.sendAll, - extractedAddress: out.extractedAddress, - isParsedAddress: out.isParsedAddress, - formattedCryptoAmount: out.formattedCryptoAmount)) - .toList(), - priority: priority as MoneroTransactionPriority, - assetType: assetType); + Future, TransactionInfo>> + restoreFromHardwareWallet(WalletCredentials credentials) { + throw UnimplementedError(); } @override - String formatterMoneroAmountToString({required int amount}) { - return moneroAmountToString(amount: amount); + Future, TransactionInfo>> + restoreFromKeys(WalletCredentials credentials, {bool? isTestnet}) { + throw UnimplementedError(); } @override - double formatterMoneroAmountToDouble({required int amount}) { - return moneroAmountToDouble(amount: amount); + Future, TransactionInfo>> + restoreFromSeed(WalletCredentials credentials, {bool? isTestnet}) { + throw UnimplementedError(); } - - @override - int formatterMoneroParseAmount({required String amount}) { - return moneroParseAmount(amount: amount); - } - - @override - Account getCurrentAccount(Object wallet) { - final havenWallet = wallet as HavenWallet; - final acc = havenWallet.walletAddresses.account as monero_account.Account; - return Account(id: acc.id, label: acc.label); - } - - @override - void setCurrentAccount(Object wallet, int id, String label) { - final havenWallet = wallet as HavenWallet; - havenWallet.walletAddresses.account = monero_account.Account(id: id, label: label); - } - - @override - void onStartup() { - monero_wallet_api.onStartup(); - } - - @override - int getTransactionInfoAccountId(TransactionInfo tx) { - final havenTransactionInfo = tx as HavenTransactionInfo; - return havenTransactionInfo.accountIndex; - } - - @override - Future backupHavenSeeds(Box havenSeedStore) async { - final walletInfoSource = await CakeHive.openBox(WalletInfo.boxName); - final wallets = walletInfoSource.values - .where((element) => element.type == WalletType.haven); - for (var w in wallets) { - final walletService = HavenWalletService(walletInfoSource); - final flutterSecureStorage = secureStorageShared; - final keyService = KeyService(flutterSecureStorage); - final password = await keyService.getWalletPassword(walletName: w.name); - final wallet = await walletService.openWallet(w.name, password); - await havenSeedStore.add(HavenSeedStore(id: wallet.id, seed: wallet.seed)); - wallet.close(); - } - await havenSeedStore.flush(); - } - - @override - WalletService createHavenWalletService(Box walletInfoSource) { - return HavenWalletService(walletInfoSource); - } - - @override - String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) { - final havenWallet = wallet as HavenWallet; - return havenWallet.getTransactionAddress(accountIndex, addressIndex); - } - - @override - CryptoCurrency assetOfTransaction(TransactionInfo tx) { - final transaction = tx as HavenTransactionInfo; - final asset = CryptoCurrency.fromString(transaction.assetType); - return asset; - } - - @override - List getAssetRate() => - getRate().map((rate) => AssetRate(rate.getAssetType(), rate.getRate())).toList(); } diff --git a/lib/src/screens/buy/buy_sell_page.dart b/lib/src/screens/buy/buy_sell_page.dart index 48334f439..df81c60ce 100644 --- a/lib/src/screens/buy/buy_sell_page.dart +++ b/lib/src/screens/buy/buy_sell_page.dart @@ -490,11 +490,19 @@ class BuySellPage extends BasePage { return DesktopExchangeCardsSection( firstExchangeCard: fiatExchangeCard, secondExchangeCard: cryptoExchangeCard, + onBuyTap: () => null, + onSellTap: () => + buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null, + isBuySellOption: true, ); } else { return DesktopExchangeCardsSection( firstExchangeCard: cryptoExchangeCard, secondExchangeCard: fiatExchangeCard, + onBuyTap: () => + !buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null, + onSellTap: () => null, + isBuySellOption: true, ); } }, diff --git a/lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart b/lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart index 0a97d7bad..5bc07091b 100644 --- a/lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart +++ b/lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart @@ -1,15 +1,22 @@ +import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart'; import 'package:flutter/material.dart'; class DesktopExchangeCardsSection extends StatelessWidget { - final Widget firstExchangeCard; - final Widget secondExchangeCard; - const DesktopExchangeCardsSection({ Key? key, required this.firstExchangeCard, required this.secondExchangeCard, + this.isBuySellOption = false, + this.onBuyTap, + this.onSellTap, }) : super(key: key); + final Widget firstExchangeCard; + final Widget secondExchangeCard; + final bool isBuySellOption; + final VoidCallback? onBuyTap; + final VoidCallback? onSellTap; + @override Widget build(BuildContext context) { return FocusTraversalGroup( @@ -18,7 +25,18 @@ class DesktopExchangeCardsSection extends StatelessWidget { children: [ Padding( padding: EdgeInsets.only(top: 55, left: 24, right: 24), - child: firstExchangeCard, + child: Column( + children: [ + if (isBuySellOption) + Column( + children: [ + const SizedBox(height: 16), + BuySellOptionButtons(onBuyTap: onBuyTap, onSellTap: onSellTap), + ], + ), + firstExchangeCard, + ], + ), ), Padding( padding: EdgeInsets.only(top: 29, left: 24, right: 24), diff --git a/lib/src/screens/pin_code/pin_code_widget.dart b/lib/src/screens/pin_code/pin_code_widget.dart index f6249576d..410349336 100644 --- a/lib/src/screens/pin_code/pin_code_widget.dart +++ b/lib/src/screens/pin_code/pin_code_widget.dart @@ -38,6 +38,7 @@ class PinCodeState extends State { static const fourPinLength = 4; final _gridViewKey = GlobalKey(); final _key = GlobalKey(); + late final FocusNode _focusNode; int pinLength; String pin; @@ -54,7 +55,17 @@ class PinCodeState extends State { pin = ''; title = S.current.enter_your_pin; _aspectRatio = 0; - WidgetsBinding.instance.addPostFrameCallback(_afterLayout); + _focusNode = FocusNode(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _focusNode.requestFocus(); + _afterLayout(_); + }); + } + + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); } void setTitle(String title) => setState(() => this.title = title); @@ -120,8 +131,8 @@ class PinCodeState extends State { ); return KeyboardListener( - focusNode: FocusNode(), - autofocus: true, + focusNode: _focusNode, + autofocus: false, onKeyEvent: (keyEvent) { if (keyEvent is KeyDownEvent) { if (keyEvent.logicalKey.keyLabel == "Backspace") { @@ -144,8 +155,7 @@ class PinCodeState extends State { style: TextStyle( fontSize: 20, fontWeight: FontWeight.w500, - color: - Theme.of(context).extension()!.titleColor)), + color: Theme.of(context).extension()!.titleColor)), Spacer(flex: 8), Container( width: 180, @@ -162,7 +172,9 @@ class PinCodeState extends State { shape: BoxShape.circle, color: isFilled ? Theme.of(context).extension()!.titleColor - : Theme.of(context).extension()!.indicatorsColor + : Theme.of(context) + .extension()! + .indicatorsColor .withOpacity(0.25), )); }), @@ -225,7 +237,8 @@ class PinCodeState extends State { child: TextButton( onPressed: () => _pop(), style: TextButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: + Theme.of(context).colorScheme.background, shape: CircleBorder(), ), child: deleteIconImage, @@ -250,7 +263,9 @@ class PinCodeState extends State { style: TextStyle( fontSize: 25.0, fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.titleColor)), + color: Theme.of(context) + .extension()! + .titleColor)), ), ); }), diff --git a/tool/configure.dart b/tool/configure.dart index da2e13a8d..48f2af596 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -1680,6 +1680,7 @@ abstract class SecureStorage { Future delete({required String key}); // Legacy Future readNoIOptions({required String key}); + Future> readAll(); }"""; const defaultSecureStorage = """ class DefaultSecureStorage extends SecureStorage { @@ -1718,6 +1719,11 @@ class DefaultSecureStorage extends SecureStorage { iOptions: useNoIOptions ? IOSOptions() : null, ); } + + @override + Future> readAll() async { + return await _secureStorage.readAll(); + } }"""; const fakeSecureStorage = """ class FakeSecureStorage extends SecureStorage { @@ -1729,6 +1735,8 @@ class FakeSecureStorage extends SecureStorage { Future delete({required String key}) async {} @override Future readNoIOptions({required String key}) async => null; + @override + Future> readAll() async => {}; }"""; final outputFile = File(secureStoragePath); final header = hasFlutterSecureStorage