mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 20:39:51 +00:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
b4fcec3a01
9 changed files with 277 additions and 335 deletions
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
|
@ -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
|
||||
|
|
|
@ -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<String, dynamic>;
|
||||
|
||||
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<String>) {
|
||||
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<Uint8List> _exportKeychainDumpV1(String password,
|
||||
{required String nonce, String keychainSalt = secrets.backupKeychainSalt}) async =>
|
||||
throw Exception('Deprecated');
|
||||
|
||||
Future<Uint8List> 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<String> _excludedPrefsKeys = [
|
||||
PreferencesKey.currentPinLength,
|
||||
PreferencesKey.showCameraConsent,
|
||||
PreferencesKey.lastSeenAppVersion,
|
||||
PreferencesKey.failedTotpTokenTrials,
|
||||
];
|
||||
|
||||
Future<String> exportPreferencesJSON() async {
|
||||
final preferences = <String, dynamic>{
|
||||
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 = <String, dynamic>{};
|
||||
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<Uint8List> _encryptV1(Uint8List data, String secretKeySource, String nonceBase64) async =>
|
||||
throw Exception('Deprecated');
|
||||
|
||||
Future<Uint8List> _decryptV1(Uint8List data, String secretKeySource, String nonceBase64,
|
||||
{int macLength = 16}) async {
|
||||
final secretKeyHash = await Cryptography.instance.sha256().hash(utf8.encode(secretKeySource));
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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<String, List<FilterItem>> filterItems;
|
||||
final Function()? onClose;
|
||||
|
||||
@override
|
||||
_FilterWidgetState createState() => _FilterWidgetState();
|
||||
}
|
||||
|
||||
class _FilterWidgetState extends State<FilterWidget> {
|
||||
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<CakeMenuTheme>()!.backgroundColor,
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(24.0),
|
||||
child: Text(
|
||||
S.of(context).filter_by,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
sectionDivider,
|
||||
ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: filterItems.length,
|
||||
separatorBuilder: (context, _) => sectionDivider,
|
||||
itemBuilder: (_, index1) {
|
||||
final title = filterItems.keys.elementAt(index1);
|
||||
final section = filterItems.values.elementAt(index1);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20, left: 24, right: 24),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none),
|
||||
),
|
||||
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<CakeMenuTheme>()!.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<TransactionTradeTheme>()!
|
||||
.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<CakeTextTheme>()!.secondaryTextColor),
|
||||
),
|
||||
),
|
||||
child: DropdownFilterList(
|
||||
items: item.items,
|
||||
caption: item.caption,
|
||||
selectedItem: item.selectedItem,
|
||||
onItemSelected: item.onItemSelected,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
final content = Observer(
|
||||
builder: (_) => StandardCheckbox(
|
||||
value: item.value(),
|
||||
caption: item.caption,
|
||||
gradientBackground: true,
|
||||
borderColor: Theme.of(context).dividerColor,
|
||||
iconColor: Colors.white,
|
||||
onChanged: (value) => item.onChanged(),
|
||||
));
|
||||
return FilterTile(child: content);
|
||||
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<CakeTextTheme>()!.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,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: <Widget>[
|
||||
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),
|
||||
|
|
|
@ -38,6 +38,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
|||
static const fourPinLength = 4;
|
||||
final _gridViewKey = GlobalKey();
|
||||
final _key = GlobalKey<ScaffoldState>();
|
||||
late final FocusNode _focusNode;
|
||||
|
||||
int pinLength;
|
||||
String pin;
|
||||
|
@ -54,7 +55,17 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
|||
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<T extends PinCodeWidget> extends State<T> {
|
|||
);
|
||||
|
||||
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<T extends PinCodeWidget> extends State<T> {
|
|||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||
Spacer(flex: 8),
|
||||
Container(
|
||||
width: 180,
|
||||
|
@ -162,7 +172,9 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
|||
shape: BoxShape.circle,
|
||||
color: isFilled
|
||||
? Theme.of(context).extension<CakeTextTheme>()!.titleColor
|
||||
: Theme.of(context).extension<PinCodeTheme>()!.indicatorsColor
|
||||
: Theme.of(context)
|
||||
.extension<PinCodeTheme>()!
|
||||
.indicatorsColor
|
||||
.withOpacity(0.25),
|
||||
));
|
||||
}),
|
||||
|
@ -225,7 +237,8 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
|||
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<T extends PinCodeWidget> extends State<T> {
|
|||
style: TextStyle(
|
||||
fontSize: 25.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.titleColor)),
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1629,6 +1629,7 @@ abstract class SecureStorage {
|
|||
Future<void> delete({required String key});
|
||||
// Legacy
|
||||
Future<String?> readNoIOptions({required String key});
|
||||
Future<Map<String, String>> readAll();
|
||||
}""";
|
||||
const defaultSecureStorage = """
|
||||
class DefaultSecureStorage extends SecureStorage {
|
||||
|
@ -1667,6 +1668,11 @@ class DefaultSecureStorage extends SecureStorage {
|
|||
iOptions: useNoIOptions ? IOSOptions() : null,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> readAll() async {
|
||||
return await _secureStorage.readAll();
|
||||
}
|
||||
}""";
|
||||
const fakeSecureStorage = """
|
||||
class FakeSecureStorage extends SecureStorage {
|
||||
|
@ -1678,6 +1684,8 @@ class FakeSecureStorage extends SecureStorage {
|
|||
Future<void> delete({required String key}) async {}
|
||||
@override
|
||||
Future<String?> readNoIOptions({required String key}) async => null;
|
||||
@override
|
||||
Future<Map<String, String>> readAll() async => {};
|
||||
}""";
|
||||
final outputFile = File(secureStoragePath);
|
||||
final header = hasFlutterSecureStorage
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue