diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 18ad16e4b..272f7bbee 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,3 +11,4 @@ Please include a summary of the changes and which issue is fixed / feature is ad - [ ] Format code - [ ] Look for code duplication - [ ] Clear naming for variables and methods +- [ ] Manual tests in accessibility mode (TalkBack on Android) passed diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 2af037d44..385598060 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:cake_wallet/core/secure_storage.dart'; import 'package:cake_wallet/entities/get_encryption_key.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; @@ -164,6 +163,31 @@ 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 + for (var entry in data.entries) { + String key = entry.key; + dynamic value = entry.value; + + // Check the type of the value and save accordingly + if (value is String) { + await sharedPreferences.setString(key, value); + } else if (value is int) { + await sharedPreferences.setInt(key, value); + } else if (value is double) { + await sharedPreferences.setDouble(key, value); + } else if (value is bool) { + await sharedPreferences.setBool(key, value); + } else if (value is List) { + await sharedPreferences.setStringList(key, value); + } else { + if (kDebugMode) { + printV('Skipping individual save for key "$key": Unsupported type (${value.runtimeType}). Value: $value'); + } + } + } + } catch (_) {} + String currentWalletName = data[PreferencesKey.currentWalletName] as String; int currentWalletType = data[PreferencesKey.currentWalletType] as int; @@ -175,151 +199,10 @@ class $BackupService { currentWalletType = serializeToInt(correctWallets.first.type); } - final currentNodeId = data[PreferencesKey.currentNodeIdKey] as int?; - final currentBalanceDisplayMode = data[PreferencesKey.currentBalanceDisplayModeKey] as int?; - final currentFiatCurrency = data[PreferencesKey.currentFiatCurrencyKey] as String?; - final shouldSaveRecipientAddress = data[PreferencesKey.shouldSaveRecipientAddressKey] as bool?; - final isAppSecure = data[PreferencesKey.isAppSecureKey] as bool?; - final disableTradeOption = data[PreferencesKey.disableTradeOption] as bool?; - final currentTransactionPriorityKeyLegacy = - data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?; - final currentBitcoinElectrumSererId = - data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int?; - final currentLanguageCode = data[PreferencesKey.currentLanguageCode] as String?; - final displayActionListMode = data[PreferencesKey.displayActionListModeKey] as int?; - final fiatApiMode = data[PreferencesKey.currentFiatApiModeKey] as int?; - final currentTheme = data[PreferencesKey.currentTheme] as int?; - final exchangeStatus = data[PreferencesKey.exchangeStatusKey] as int?; - final currentDefaultSettingsMigrationVersion = - data[PreferencesKey.currentDefaultSettingsMigrationVersion] as int?; - final moneroTransactionPriority = data[PreferencesKey.moneroTransactionPriority] as int?; - final bitcoinTransactionPriority = data[PreferencesKey.bitcoinTransactionPriority] as int?; - final sortBalanceTokensBy = data[PreferencesKey.sortBalanceBy] as int?; - final pinNativeTokenAtTop = data[PreferencesKey.pinNativeTokenAtTop] as bool?; - final useEtherscan = data[PreferencesKey.useEtherscan] as bool?; - final defaultNanoRep = data[PreferencesKey.defaultNanoRep] as String?; - final defaultBananoRep = data[PreferencesKey.defaultBananoRep] as String?; - final lookupsTwitter = data[PreferencesKey.lookupsTwitter] as bool?; - final lookupsMastodon = data[PreferencesKey.lookupsMastodon] as bool?; - final lookupsYatService = data[PreferencesKey.lookupsYatService] as bool?; - final lookupsUnstoppableDomains = data[PreferencesKey.lookupsUnstoppableDomains] as bool?; - final lookupsOpenAlias = data[PreferencesKey.lookupsOpenAlias] as bool?; - final lookupsENS = data[PreferencesKey.lookupsENS] as bool?; - final lookupsWellKnown = data[PreferencesKey.lookupsWellKnown] as bool?; - final syncAll = data[PreferencesKey.syncAllKey] as bool?; - final syncMode = data[PreferencesKey.syncModeKey] as int?; - final autoGenerateSubaddressStatus = - data[PreferencesKey.autoGenerateSubaddressStatusKey] as int?; - - await sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName); - - if (currentNodeId != null) - await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, currentNodeId); - - if (currentBalanceDisplayMode != null) - await sharedPreferences.setInt( - PreferencesKey.currentBalanceDisplayModeKey, currentBalanceDisplayMode); - - await sharedPreferences.setInt(PreferencesKey.currentWalletType, currentWalletType); - - if (currentFiatCurrency != null) - await sharedPreferences.setString( - PreferencesKey.currentFiatCurrencyKey, currentFiatCurrency); - - if (shouldSaveRecipientAddress != null) - await sharedPreferences.setBool( - PreferencesKey.shouldSaveRecipientAddressKey, shouldSaveRecipientAddress); - - if (isAppSecure != null) - await sharedPreferences.setBool(PreferencesKey.isAppSecureKey, isAppSecure); - - if (disableTradeOption != null) - await sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption); - - if (currentTransactionPriorityKeyLegacy != null) - await sharedPreferences.setInt( - PreferencesKey.currentTransactionPriorityKeyLegacy, currentTransactionPriorityKeyLegacy); - - if (currentBitcoinElectrumSererId != null) - await sharedPreferences.setInt( - PreferencesKey.currentBitcoinElectrumSererIdKey, currentBitcoinElectrumSererId); - - if (currentLanguageCode != null) - await sharedPreferences.setString(PreferencesKey.currentLanguageCode, currentLanguageCode); - - if (displayActionListMode != null) - await sharedPreferences.setInt( - PreferencesKey.displayActionListModeKey, displayActionListMode); - - if (fiatApiMode != null) - await sharedPreferences.setInt(PreferencesKey.currentFiatApiModeKey, fiatApiMode); - if (autoGenerateSubaddressStatus != null) - await sharedPreferences.setInt( - PreferencesKey.autoGenerateSubaddressStatusKey, autoGenerateSubaddressStatus); - - if (currentTheme != null && DeviceInfo.instance.isMobile) { - await sharedPreferences.setInt(PreferencesKey.currentTheme, currentTheme); - // enforce dark theme on desktop platforms until the design is ready: - } else if (DeviceInfo.instance.isDesktop) { + if (DeviceInfo.instance.isDesktop) { await sharedPreferences.setInt(PreferencesKey.currentTheme, ThemeList.darkTheme.raw); } - if (exchangeStatus != null) - await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, exchangeStatus); - - if (currentDefaultSettingsMigrationVersion != null) - await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, - currentDefaultSettingsMigrationVersion); - - if (moneroTransactionPriority != null) - await sharedPreferences.setInt( - PreferencesKey.moneroTransactionPriority, moneroTransactionPriority); - - if (bitcoinTransactionPriority != null) - await sharedPreferences.setInt( - PreferencesKey.bitcoinTransactionPriority, bitcoinTransactionPriority); - - if (sortBalanceTokensBy != null) - await sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy); - - if (pinNativeTokenAtTop != null) - await sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop); - - if (useEtherscan != null) - await sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan); - - if (defaultNanoRep != null) - await sharedPreferences.setString(PreferencesKey.defaultNanoRep, defaultNanoRep); - - if (defaultBananoRep != null) - await sharedPreferences.setString(PreferencesKey.defaultBananoRep, defaultBananoRep); - - if (syncAll != null) await sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll); - if (lookupsTwitter != null) - await sharedPreferences.setBool(PreferencesKey.lookupsTwitter, lookupsTwitter); - - if (lookupsMastodon != null) - await sharedPreferences.setBool(PreferencesKey.lookupsMastodon, lookupsMastodon); - - if (lookupsYatService != null) - await sharedPreferences.setBool(PreferencesKey.lookupsYatService, lookupsYatService); - - if (lookupsUnstoppableDomains != null) - await sharedPreferences.setBool( - PreferencesKey.lookupsUnstoppableDomains, lookupsUnstoppableDomains); - - if (lookupsOpenAlias != null) - await sharedPreferences.setBool(PreferencesKey.lookupsOpenAlias, lookupsOpenAlias); - - if (lookupsENS != null) await sharedPreferences.setBool(PreferencesKey.lookupsENS, lookupsENS); - - if (lookupsWellKnown != null) - await sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, lookupsWellKnown); - - if (syncAll != null) await sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll); - - if (syncMode != null) await sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode); - await preferencesFile.delete(); } @@ -378,83 +261,45 @@ class $BackupService { await keyService.saveWalletPassword(walletName: name, password: password); } - @Deprecated('Use v2 instead') - Future _exportKeychainDumpV1(String password, - {required String nonce, String keychainSalt = secrets.backupKeychainSalt}) async => - throw Exception('Deprecated'); - Future exportKeychainDumpV2(String password, {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})); + json.encode({'wallets': wallets, backupPasswordKey: backupPassword, '_all': await _secureStorage.readAll()})); final encrypted = await _encryptV2(Uint8List.fromList(data), '$keychainSalt$password'); return encrypted; } + static const List _excludedPrefsKeys = [ + PreferencesKey.currentPinLength, + PreferencesKey.showCameraConsent, + PreferencesKey.lastSeenAppVersion, + PreferencesKey.failedTotpTokenTrials, + ]; + Future exportPreferencesJSON() async { - final preferences = { - PreferencesKey.currentWalletName: - sharedPreferences.getString(PreferencesKey.currentWalletName), - PreferencesKey.currentNodeIdKey: sharedPreferences.getInt(PreferencesKey.currentNodeIdKey), - PreferencesKey.currentBalanceDisplayModeKey: - sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey), - PreferencesKey.currentWalletType: sharedPreferences.getInt(PreferencesKey.currentWalletType), - PreferencesKey.currentFiatCurrencyKey: - sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey), - PreferencesKey.shouldSaveRecipientAddressKey: - sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey), - PreferencesKey.disableTradeOption: sharedPreferences.getBool(PreferencesKey.disableTradeOption), - PreferencesKey.currentTransactionPriorityKeyLegacy: - sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy), - PreferencesKey.currentBitcoinElectrumSererIdKey: - sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey), - PreferencesKey.currentLanguageCode: - sharedPreferences.getString(PreferencesKey.currentLanguageCode), - PreferencesKey.displayActionListModeKey: - sharedPreferences.getInt(PreferencesKey.displayActionListModeKey), - PreferencesKey.currentTheme: sharedPreferences.getInt(PreferencesKey.currentTheme), - PreferencesKey.exchangeStatusKey: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey), - PreferencesKey.currentDefaultSettingsMigrationVersion: - sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion), - PreferencesKey.bitcoinTransactionPriority: - sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority), - PreferencesKey.moneroTransactionPriority: - sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority), - PreferencesKey.currentFiatApiModeKey: - sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey), - PreferencesKey.sortBalanceBy: sharedPreferences.getInt(PreferencesKey.sortBalanceBy), - PreferencesKey.pinNativeTokenAtTop: - sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop), - PreferencesKey.useEtherscan: sharedPreferences.getBool(PreferencesKey.useEtherscan), - PreferencesKey.defaultNanoRep: sharedPreferences.getString(PreferencesKey.defaultNanoRep), - PreferencesKey.defaultBananoRep: - sharedPreferences.getString(PreferencesKey.defaultBananoRep), - PreferencesKey.lookupsTwitter: sharedPreferences.getBool(PreferencesKey.lookupsTwitter), - PreferencesKey.lookupsMastodon: sharedPreferences.getBool(PreferencesKey.lookupsMastodon), - PreferencesKey.lookupsYatService: - sharedPreferences.getBool(PreferencesKey.lookupsYatService), - PreferencesKey.lookupsUnstoppableDomains: - sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains), - PreferencesKey.lookupsOpenAlias: sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias), - PreferencesKey.lookupsENS: sharedPreferences.getBool(PreferencesKey.lookupsENS), - PreferencesKey.lookupsWellKnown: - sharedPreferences.getBool(PreferencesKey.lookupsWellKnown), - PreferencesKey.syncModeKey: sharedPreferences.getInt(PreferencesKey.syncModeKey), - PreferencesKey.syncAllKey: sharedPreferences.getBool(PreferencesKey.syncAllKey), - PreferencesKey.autoGenerateSubaddressStatusKey: - sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey), - }; + final preferences = {}; + sharedPreferences.getKeys().forEach((key) => preferences[key] = sharedPreferences.get(key)); + + _excludedPrefsKeys.forEach((key) => preferences.remove(key)); return json.encode(preferences); } @@ -466,10 +311,6 @@ class $BackupService { return Uint8List.fromList(bytes); } - @Deprecated('Use v2 instead') - Future _encryptV1(Uint8List data, String secretKeySource, String nonceBase64) async => - throw Exception('Deprecated'); - Future _decryptV1(Uint8List data, String secretKeySource, String nonceBase64, {int macLength = 16}) async { final secretKeyHash = await Cryptography.instance.sha256().hash(utf8.encode(secretKeySource)); 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/dashboard/widgets/filter_widget.dart b/lib/src/screens/dashboard/widgets/filter_widget.dart index eaf00a1de..81f29b81c 100644 --- a/lib/src/screens/dashboard/widgets/filter_widget.dart +++ b/lib/src/screens/dashboard/widgets/filter_widget.dart @@ -1,123 +1,178 @@ +import 'package:cake_wallet/src/widgets/alert_background.dart'; +import 'package:cake_wallet/src/widgets/alert_close_button.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/filter_tile.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:cake_wallet/themes/extensions/menu_theme.dart'; -import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item.dart'; -import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item_widget.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/view_model/dashboard/filter_item.dart'; import 'package:flutter/material.dart'; -import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; - -//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; -class FilterWidget extends StatelessWidget { - FilterWidget({required this.filterItems}); +class FilterWidget extends StatefulWidget { + const FilterWidget({required this.filterItems, this.onClose, Key? key}) : super(key: key); final Map> filterItems; + final Function()? onClose; + + @override + _FilterWidgetState createState() => _FilterWidgetState(); +} + +class _FilterWidgetState extends State { + final ScrollController _scrollController = ScrollController(); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { - const sectionDivider = const HorizontalSectionDivider(); - return PickerWrapperWidget( - children: [ - Padding( - padding: EdgeInsets.only(left: 24, right: 24, top: 24), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(24)), - child: Container( - color: Theme.of(context).extension()!.backgroundColor, - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: EdgeInsets.all(24.0), - child: Text( - S.of(context).filter_by, - style: TextStyle( - color: - Theme.of(context).extension()!.detailsTitlesColor, - fontSize: 16, - fontFamily: 'Lato', - decoration: TextDecoration.none, - ), - ), - ), - sectionDivider, - ListView.separated( - padding: EdgeInsets.zero, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: filterItems.length, - separatorBuilder: (context, _) => sectionDivider, - itemBuilder: (_, index1) { - final title = filterItems.keys.elementAt(index1); - final section = filterItems.values.elementAt(index1); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(top: 20, left: 24, right: 24), - child: Text( - title, - style: TextStyle( - color: Theme.of(context).extension()!.titleColor, - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.bold, - decoration: TextDecoration.none), - ), + return AlertBackground( + child: Column( + children: [ + const Expanded(child: SizedBox()), + Expanded( + flex: responsiveLayoutUtil.shouldRenderTabletUI ? 16 : 8, + child: LayoutBuilder( + builder: (context, constraints) { + double availableHeight = constraints.maxHeight; + return _buildFilterContent(context, availableHeight); + }, + ), + ), + Expanded( + child: AlertCloseButton( + key: const ValueKey('filter_wrapper_close_button_key'), + isPositioned: false, + onTap: widget.onClose, + ), + ), + const SizedBox(height: 24), + ], + ), + ); + } + + Widget _buildFilterContent(BuildContext context, double availableHeight) { + const sectionDivider = HorizontalSectionDivider(); + + const double totalHeaderHeight = 73; + const double filterTileMinHeight = 40; + double availableHeightForItems = availableHeight - totalHeaderHeight; + + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 24, right: 24, top: 24), + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Container( + color: Theme.of(context).extension()!.backgroundColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(24.0), + child: Text( + S.of(context).filter_by, + style: TextStyle( + color: Theme.of(context) + .extension()! + .detailsTitlesColor, + fontSize: 16, + fontFamily: 'Lato', + decoration: TextDecoration.none, ), - ListView.builder( - padding: EdgeInsets.symmetric(horizontal: 28.0), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), + ), + ), + sectionDivider, + ListView.separated( + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.filterItems.length, + separatorBuilder: (context, _) => sectionDivider, + itemBuilder: (_, index1) { + final title = widget.filterItems.keys.elementAt(index1); + final section = widget.filterItems.values.elementAt(index1); + + final double itemHeight = + availableHeightForItems / widget.filterItems.length; + + final isSectionScrollable = + (itemHeight < (section.length * filterTileMinHeight)); + + final Widget sectionListView = ListView.builder( + controller: isSectionScrollable ? _scrollController : null, + padding: const EdgeInsets.symmetric(horizontal: 28.0), + shrinkWrap: isSectionScrollable ? false : true, + physics: isSectionScrollable + ? const BouncingScrollPhysics() + : const NeverScrollableScrollPhysics(), itemCount: section.length, itemBuilder: (_, index2) { final item = section[index2]; - - if (item is DropdownFilterItem) { - return Padding( - padding: EdgeInsets.fromLTRB(8, 0, 8, 16), - child: Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 1.0, - color: Theme.of(context).extension()!.secondaryTextColor), - ), - ), - child: DropdownFilterList( - items: item.items, - caption: item.caption, - selectedItem: item.selectedItem, - onItemSelected: item.onItemSelected, - ), - ), - ); - } final content = Observer( - builder: (_) => StandardCheckbox( - value: item.value(), - caption: item.caption, - gradientBackground: true, - borderColor: Theme.of(context).dividerColor, - iconColor: Colors.white, - onChanged: (value) => item.onChanged(), - )); - return FilterTile(child: content); + builder: (_) => StandardCheckbox( + value: item.value(), + caption: item.caption, + gradientBackground: true, + borderColor: Theme.of(context).dividerColor, + iconColor: Colors.white, + onChanged: (value) => item.onChanged(), + ), + ); + return FilterTile( + child: content, + ); }, - ) - ], - ); - }, + ); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 20, left: 24, right: 24), + child: Text( + title, + style: TextStyle( + color: Theme.of(context).extension()!.titleColor, + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + Container( + height: isSectionScrollable ? itemHeight - totalHeaderHeight : null, + child: isSectionScrollable + ? Scrollbar( + controller: _scrollController, + thumbVisibility: true, + child: sectionListView, + ) + : sectionListView, + ), + ], + ); + }, + ), + ], ), - ]), + ), ), ), - ) - ], + ], + ), ); } } 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/lib/src/widgets/alert_close_button.dart b/lib/src/widgets/alert_close_button.dart index 6ef0bdaa5..30e37ef20 100644 --- a/lib/src/widgets/alert_close_button.dart +++ b/lib/src/widgets/alert_close_button.dart @@ -7,6 +7,7 @@ class AlertCloseButton extends StatelessWidget { this.image, this.bottom, this.onTap, + this.isPositioned = true, super.key, }); @@ -14,6 +15,7 @@ class AlertCloseButton extends StatelessWidget { final Image? image; final double? bottom; + final bool isPositioned; final closeButton = Image.asset( 'assets/images/close.png', @@ -22,24 +24,18 @@ class AlertCloseButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Positioned( - bottom: bottom ?? 60, - child: GestureDetector( + final button = GestureDetector( onTap: onTap ?? () => Navigator.of(context).pop(), child: Semantics( - label: S.of(context).close, - button: true, - enabled: true, - child: Container( - height: 42, - width: 42, - decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle), - child: Center( - child: image ?? closeButton, - ), - ), - ), - ), - ); + label: S.of(context).close, + button: true, + enabled: true, + child: Container( + height: 42, + width: 42, + decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle), + child: Center(child: image ?? closeButton)))); + + return isPositioned ? Positioned(bottom: bottom ?? 60, child: button) : button; } } diff --git a/lib/view_model/restore_from_backup_view_model.dart b/lib/view_model/restore_from_backup_view_model.dart index a74beeaea..388782cb0 100644 --- a/lib/view_model/restore_from_backup_view_model.dart +++ b/lib/view_model/restore_from_backup_view_model.dart @@ -50,7 +50,7 @@ abstract class RestoreFromBackupViewModelBase with Store { state = FailureState('This is not a valid backup file, please make sure you selected the correct backup file'); } else { state = FailureState('Failed to restore backup, please try again'); - ExceptionHandler.onError(FlutterErrorDetails(exception: e, stack: s, silent: true)); + ExceptionHandler.onError(FlutterErrorDetails(exception: e, stack: s)); } } diff --git a/tool/configure.dart b/tool/configure.dart index 4711373b7..2a29195c6 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -1629,6 +1629,7 @@ abstract class SecureStorage { Future delete({required String key}); // Legacy Future readNoIOptions({required String key}); + Future> readAll(); }"""; const defaultSecureStorage = """ class DefaultSecureStorage extends SecureStorage { @@ -1667,6 +1668,11 @@ class DefaultSecureStorage extends SecureStorage { iOptions: useNoIOptions ? IOSOptions() : null, ); } + + @override + Future> readAll() async { + return await _secureStorage.readAll(); + } }"""; const fakeSecureStorage = """ class FakeSecureStorage extends SecureStorage { @@ -1678,6 +1684,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