New Settings (#3065)

* Update display_settings_page.dart

* Update settings_choices_cell.dart

* Update settings_theme_choice.dart

* Convert display_settings_page.dart to NewListSection

* Cleanup [skip ci]

* Minor

* Minor [skip ci]

* Add SizedBox to scroll view

* Upgrade receive_top_bar.dart with gradient and optional child content

* Adjust animations

* Add modal_page_wrapper.dart

* Add option to hide AppBar for BasePage [skip ci]

* New settings all final (#3067)

* Conflict

* ModalPageWrapper improved abstraction

* display_settings_page.dart minor update

* connection_sync_page.dart redesign

* Conflict

* manage_nodes_page.dart initial redesign

* other settings initial

* Conflict

* add optional subtitle to standard_list.dart

* add connection_sync_view_model.dart

* Add missing images

* Conflict

* Update list item widgets

* Conflict

* Conflict

* Conflict

* minor updates

* Conflict

* Fixes + move Mweb to privacy page

* Conflict

* Remove observer from modal_page_wrapper.dart

* Conflict

* Conflict

* Refactor settings_page.dart with suggestions

* Suggestion fixes

* Conflicts + fix About page

* Fix sync bar conflict

* Update Connections page title [skip ci]

* Trigger CI

* Fix Lightning and Payjoin log export

* Update MWEB node page

* Add FeatureFlag to AutomaticNodeSwitching

* Fix address text on privacy_page.dart

* Move WalletConnect from connection_sync_page.dart to settings_page.dart

* Add WalletConnect icon

* Fix settings icons

* Make text strings use S.of(context) and fix About string in settings_page.dart

* Display settings page fixes

* modal_page_wrapper.dart minor suggestions

* Missingimage

* Add spacing to list_item_selector_widget.dart

* Fix error on manage_nodes_page.dart
This commit is contained in:
tuxsudo
2026-03-14 14:15:42 +01:00
committed by GitHub
parent aa18ea3960
commit 3815fc30c6
80 changed files with 1957 additions and 1339 deletions
+16 -17
View File
@@ -47,6 +47,8 @@ abstract class BasePage extends StatelessWidget {
AppBarStyle get appBarStyle => AppBarStyle.regular;
bool get hideAppBar => false;
Widget Function(BuildContext, Widget)? get rootWrapper => null;
MaterialThemeBase get currentTheme => getIt.get<ThemeStore>().currentTheme;
@@ -191,24 +193,21 @@ abstract class BasePage extends StatelessWidget {
: null,
// color: Colors.grey[200],
),
child: Padding(
padding: const EdgeInsets.only(top:6.0),
child: Scaffold(
key: _scaffoldKey,
backgroundColor: pageBackgroundColor(context),
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
extendBodyBehindAppBar: extendBodyBehindAppBar,
endDrawer: endDrawer,
appBar: appBar(context),
body: SafeArea(
left: false,
right: false,
top: false,
bottom: Platform.isAndroid,
child: body(context),
),
floatingActionButton: floatingActionButton(context),
child: Scaffold(
key: _scaffoldKey,
backgroundColor: pageBackgroundColor(context),
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
extendBodyBehindAppBar: extendBodyBehindAppBar,
endDrawer: endDrawer,
appBar: hideAppBar ? null : appBar(context),
body: SafeArea(
left: false,
right: false,
top: false,
bottom: Platform.isAndroid,
child: body(context),
),
floatingActionButton: floatingActionButton(context),
),
);
},
@@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/new_ui_entities/list_item/list_Item_checkbo
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_text_field.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -140,6 +141,7 @@ class NodeFormState extends State<NodeForm> {
initialValue: vm.socksProxyAddress,
validator: SocksProxyNodeAddressValidator(),
),
if(FeatureFlag.isAutomaticNodeSwitchingEnabled)
ListItemCheckbox(
keyValue: vm.autoSwitchingUIKey,
label: S.current.enable_for_auto_switching,
@@ -7,11 +7,12 @@ import 'package:flutter/material.dart';
class NodeListRow extends StandardListRow {
NodeListRow(
{required String title,
String? subtitle,
required this.node,
required void Function(BuildContext context) onTap,
required bool isSelected,
required this.isPow})
: super(title: title, onTap: onTap, isSelected: isSelected);
: super(title: title, onTap: onTap, isSelected: isSelected, subtitle: subtitle);
final Node node;
final bool isPow;
@@ -1,119 +1,219 @@
import 'dart:io';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_selector.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_toggle.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/new-ui/widgets/modal_header.dart';
import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/wallet_connect_button.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/settings/connection_sync_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class ConnectionSyncPage extends BasePage {
ConnectionSyncPage(this.dashboardViewModel);
ConnectionSyncPage(this._connectionSyncViewModel);
@override
String get title => S.current.connection_sync;
bool get hideAppBar => true;
final DashboardViewModel dashboardViewModel;
final ConnectionSyncViewModel _connectionSyncViewModel;
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
return ModalPageWrapper(
topBar: ModalTopBar(
title: "",
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: () => Navigator.of(context).pop()),
header: ModalHeader(
iconPath: "assets/new-ui/settings_row_icons/connections.svg",
message: S.of(context).connections_desc,
title: S.of(context).connections),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
SettingsCellWithArrow(
title: S.current.reconnect,
handler: (context) => _presentReconnectAlert(context),
),
if (dashboardViewModel.hasRescan) ...[
SettingsCellWithArrow(
title: dashboardViewModel.hasSilentPayments
? S.current.silent_payments_scanning
: S.current.rescan,
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
),
],
SettingsCellWithArrow(
title: S.current.manage_nodes,
handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes),
),
if (Platform.isAndroid && FeatureFlag.isBackgroundSyncEnabled) ...[
SettingsCellWithArrow(
title: S.current.background_sync,
handler: (context) => Navigator.of(context).pushNamed(Routes.backgroundSync),
),
],
Observer(
builder: (context) {
if (!dashboardViewModel.hasPowNodes) return const SizedBox();
builder: (context) =>
NewListSections(
sections: {
"": [
if (FeatureFlag.isInAppTorEnabled)
ListItemToggle(
keyValue: "enable_builtin_tor",
label: S.of(context).enable_builtin_tor,
value: _connectionSyncViewModel.builtinTor,
onChanged: (val) {
_connectionSyncViewModel.setBuiltinTor(val, context);
}),
ListItemToggle(
keyValue: "disable_automatic_exchange_status_updates",
label: S.of(context).disable_automatic_exchange_status_updates,
value: _connectionSyncViewModel.disableAutomaticExchangeStatusUpdates,
onChanged: (val) {
_connectionSyncViewModel.setDisableAutomaticExchangeStatusUpdates(val);
}),
if (_connectionSyncViewModel.canUseBlinkProtection)
ListItemToggle(
keyValue: "can_use_blink_protection",
label: S.of(context).use_blink_protection,
value: _connectionSyncViewModel.useBlinkProtection,
onChanged: (val) {
_connectionSyncViewModel.setUseBlinkProtection(val);
}),
if (_connectionSyncViewModel.canUseEtherscan)
ListItemToggle(
keyValue: "can_use_etherscan",
label: S.of(context).etherscan_history,
value: _connectionSyncViewModel.useEtherscan,
onChanged: (val) {
_connectionSyncViewModel.setUseEtherscan(val);
}),
if (_connectionSyncViewModel.canUsePolygonScan)
ListItemToggle(
keyValue: "can_use_polygonscan",
label: S.of(context).polygonscan_history,
value: _connectionSyncViewModel.usePolygonScan,
onChanged: (val) {
_connectionSyncViewModel.setUsePolygonScan(val);
}),
if (_connectionSyncViewModel.canUseBaseScan)
ListItemToggle(
keyValue: "can_use_basescan",
label: S.of(context).basescan_history,
value: _connectionSyncViewModel.canUseBaseScan,
onChanged: (val) {
_connectionSyncViewModel.setUseBaseScan(val);
}),
if (_connectionSyncViewModel.canUseArbiScan)
ListItemToggle(
keyValue: "can_use_arbiscan",
label: S.of(context).arbiscan_history,
value: _connectionSyncViewModel.useArbiScan,
onChanged: (val) {
_connectionSyncViewModel.setUsePolygonScan(val);
}),
if (_connectionSyncViewModel.canUseBscScan)
ListItemToggle(
keyValue: "can_use_bscscan",
label: S.of(context).bscscan_history,
value: _connectionSyncViewModel.useBscScan,
onChanged: (val) {
_connectionSyncViewModel.setUseBscScan(val);
}),
if (_connectionSyncViewModel.canUseTronGrid)
ListItemToggle(
keyValue: "can_use_trongrid",
label: S.of(context).trongrid_history,
value: _connectionSyncViewModel.useTronGrid,
onChanged: (val) {
_connectionSyncViewModel.setUsePolygonScan(val);
}),
if (_connectionSyncViewModel.canUseMempoolFeeAPI)
ListItemToggle(
keyValue: "enable_mempool_api",
label: S.of(context).enable_mempool_api,
value: _connectionSyncViewModel.useMempoolFeeAPI,
onChanged: (bool isEnabled) async {
if (!isEnabled) {
final bool confirmation = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).warning,
alertContent: S.of(context).disable_fee_api_warning,
rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
false;
if (confirmation) {
_connectionSyncViewModel.setUseMempoolFeeAPI(isEnabled);
}
return;
}
return Column(
children: [
SettingsCellWithArrow(
title: S.current.manage_pow_nodes,
handler: (context) => Navigator.of(context).pushNamed(Routes.managePowNodes),
),
],
);
},
_connectionSyncViewModel.setUseMempoolFeeAPI(isEnabled);
}),
if (Platform.isAndroid && FeatureFlag.isBackgroundSyncEnabled)
ListItemRegularRow(
keyValue: "background_sync",
label: S.of(context).background_sync,
onTap: () => Navigator.of(context).pushNamed(Routes.backgroundSync)
),
if (_connectionSyncViewModel.hasPowNodes)
ListItemRegularRow(
keyValue: "manage_pow_nodes",
label: S.of(context).manage_pow_nodes,
onTap: () => Navigator.of(context).pushNamed(Routes.managePowNodes),
),
ListItemSelector(
keyValue: "fiat_api",
label: S.of(context).fiat_api,
options: [_connectionSyncViewModel.fiatApiMode.title],
onTap: () async {
final items = FiatApiMode.all;
final selectedAtIndex =
items.indexOf(_connectionSyncViewModel.fiatApiMode);
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedAtIndex,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: (FiatApiMode fiatApiMode) {
_connectionSyncViewModel.setFiatMode(fiatApiMode);
},
isSeparated: false,
),
);
}),
ListItemSelector(
keyValue: "swap",
label: S.of(context).swap,
options: [_connectionSyncViewModel.exchangeStatus.title],
onTap: () async {
final items = ExchangeApiMode.all;
final selectedAtIndex =
items.indexOf(_connectionSyncViewModel.exchangeStatus);
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedAtIndex,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: (ExchangeApiMode mode) {
_connectionSyncViewModel.setExchangeApiMode(mode);
},
isSeparated: false,
),
);
}),
ListItemRegularRow(
keyValue: "domain_lookups",
label: S.of(context).domain_looks_up,
onTap: () => Navigator.of(context).pushNamed(Routes.domainLookupsPage)
),
],
}
)
),
if (isWalletConnectCompatibleChain(dashboardViewModel.wallet.type) &&
!dashboardViewModel.wallet.isHardwareWallet) ...[ // ToDo: Remove this line once WalletConnect is implemented
WalletConnectTile(
onTap: () => Navigator.of(context).pushNamed(Routes.walletConnectConnectionsListing),
),
],
if (FeatureFlag.isInAppTorEnabled)
Observer(builder: (context) {
return SettingsSwitcherCell(
leading: Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: Colors.red.shade100,
borderRadius: BorderRadius.circular(3),
),
child: Text(
'Alpha',
style: TextStyle(
color: Colors.red.shade700,
fontSize: 8,
fontWeight: FontWeight.bold,
),
),
),
title: S.current.enable_builtin_tor,
value: dashboardViewModel.builtinTor,
onValueChange: (_, bool value) => dashboardViewModel.setBuiltinTor(value, context),
);
}),
],
),
);
}
Future<void> _presentReconnectAlert(BuildContext context) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).reconnection,
alertContent: S.of(context).reconnect_alert_text,
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () async {
Navigator.of(context).pop();
await dashboardViewModel.reconnect();
},
actionLeftButton: () => Navigator.of(context).pop());
},
);
}
}
@@ -1,14 +1,18 @@
import 'package:cake_wallet/entities/bitcoin_amount_display_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_selector.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_toggle.dart';
import 'package:cake_wallet/entities/sync_status_display_mode.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_theme_choice.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
@@ -19,7 +23,6 @@ import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:image_picker/image_picker.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class DisplaySettingsPage extends StatelessWidget {
DisplaySettingsPage(this._displaySettingsViewModel);
@@ -29,172 +32,228 @@ class DisplaySettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).colorScheme.surface,
child: SafeArea(
top: false,
child: Column(
children: [
ModalTopBar(title: S.of(context).display_settings,leadingIcon: Icon(Icons.arrow_back_ios_new),onLeadingPressed: () => Navigator.of(context).pop()),
Expanded(
child: SingleChildScrollView(
controller: ModalScrollController.of(context),
physics: ClampingScrollPhysics(),
child: Observer(builder: (_) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SettingsSwitcherCell(
title: S.of(context).apps,
value: _displaySettingsViewModel.shouldShowMarketPlaceInDashboard,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShouldShowMarketPlaceInDashbaord(value);
},
),
SettingsSwitcherCell(
title: S.of(context).show_address_book_popup,
value: _displaySettingsViewModel.showAddressBookPopup,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShowAddressBookPopup(value);
},
),
if (_displaySettingsViewModel.showZcashCardSetting)
SettingsSwitcherCell(
title: S.of(context).show_zcash_card,
value: _displaySettingsViewModel.showZcashCard,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShowZcashCard(value);
},
),
SettingsPickerCell<SyncStatusDisplayMode>(
title: S.current.sync_status_display_mode,
items: SyncStatusDisplayMode.values.toList(),
selectedItem: _displaySettingsViewModel.syncStatusDisplayMode,
onItemSelected: (SyncStatusDisplayMode mode) =>
_displaySettingsViewModel.setSyncStatusDisplayMode(mode),
displayItem: (SyncStatusDisplayMode mode) => mode.title,
isGridView: false,
),
if (_displaySettingsViewModel.showDisplayAmountsInSatoshiSetting)
SettingsPickerCell<BitcoinAmountDisplayMode>(
title: S.of(context).bitcoin_amount_display,
items: BitcoinAmountDisplayMode.all,
selectedItem: _displaySettingsViewModel.displayAmountsInSatoshi,
onItemSelected: _displaySettingsViewModel.setDisplayAmountsInSatoshi,
displayItem: (mode) => mode.title,
isGridView: false,
),
//if (!isHaven) it does not work correctly
if (!_displaySettingsViewModel.disabledFiatApiMode)
SettingsPickerCell<FiatCurrency>(
title: S.of(context).settings_currency,
searchHintText: S.of(context).search_currency,
items: FiatCurrency.all,
selectedItem: _displaySettingsViewModel.fiatCurrency,
onItemSelected: (FiatCurrency currency) =>
_displaySettingsViewModel.setFiatCurrency(currency),
images: FiatCurrency.all
.map((e) => Image.asset("assets/images/flags/${e.countryCode}.png"))
.toList(),
isGridView: true,
matchingCriteria: (FiatCurrency currency, String searchText) {
return currency.title.toLowerCase().contains(searchText) ||
currency.fullName.toLowerCase().contains(searchText);
},
),
SettingsPickerCell<String>(
title: S.of(context).settings_change_language,
searchHintText: S.of(context).search_language,
items: LanguageService.list.keys.toList(),
displayItem: (dynamic code) {
return LanguageService.list[code] ?? '';
},
selectedItem: _displaySettingsViewModel.languageCode,
onItemSelected: _displaySettingsViewModel.onLanguageSelected,
images: LanguageService.list.keys
.map((e) => Image.asset(
"assets/images/flags/${LanguageService.localeCountryCode[e]}.png"))
.toList(),
matchingCriteria: (String code, String searchText) {
return LanguageService.list[code]?.toLowerCase().contains(searchText) ?? false;
},
),
if (FeatureFlag.customBackgroundEnabled)
StandardListRow(
title: "Custom background",
isSelected: false,
onTap: (_) => _pickImage(context),
),
if (responsiveLayoutUtil.shouldRenderMobileUI && DeviceInfo.instance.isMobile) ...[
SizedBox(height: 24),
Padding(
padding: const EdgeInsets.only(left: 24),
child: Text(
S.current.appearance,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
height: 22 / 14,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
SizedBox(height: 12),
Container(
margin: EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
SettingsChoicesCell(
ChoicesListItem<ThemeMode>(
title: "",
items: ThemeMode.values,
selectedItem: _displaySettingsViewModel.themeMode,
onItemSelected: (ThemeMode themeMode) =>
_displaySettingsViewModel.setThemeMode(themeMode),
displayItem: (ThemeMode themeMode) {
return themeMode.name[0].toUpperCase() +
themeMode.name.substring(1).toLowerCase();
},
),
useGenericColor: false,
padding: EdgeInsets.all(12),
),
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Semantics(
label: S.of(context).color_theme,
child: SettingsThemeChoicesCell(_displaySettingsViewModel),
),
],
),
),
],
),
),
],
],
),
);
}),
),
),
],
),
return ModalPageWrapper(
topBar: ModalTopBar(
title: S.of(context).display,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: () => Navigator.of(context).pop(),
),
);
content: Column(
spacing: 16,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (responsiveLayoutUtil.shouldRenderMobileUI &&
DeviceInfo.instance.isMobile) ...[
Padding(
padding: const EdgeInsets.only(left: 14, top: 14),
child: Text(
S.of(context).appearance,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
height: 0.2,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
Container(
decoration: ShapeDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(18))),
child: Column(
children: [
SettingsChoicesCell(
ChoicesListItem<ThemeMode>(
title: "",
items: ThemeMode.values,
selectedItem: _displaySettingsViewModel.themeMode,
onItemSelected: (ThemeMode themeMode) =>
_displaySettingsViewModel.setThemeMode(themeMode),
displayItem: (ThemeMode themeMode) {
return themeMode.name[0].toUpperCase() +
themeMode.name.substring(1).toLowerCase();
},
),
useGenericColor: false,
padding: EdgeInsets.all(14),
),
Container(
decoration: ShapeDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(18))),
child: Column(
children: [
Semantics(
label: S.of(context).color_theme,
child: SettingsThemeChoicesCell(_displaySettingsViewModel),
),
],
),
),
],
),
),
],
Observer(
builder: (_) => NewListSections(
sections: {
"": [
ListItemToggle(
keyValue: "apps",
label: S.of(context).apps,
value: _displaySettingsViewModel.shouldShowMarketPlaceInDashboard,
onChanged: (val) {
_displaySettingsViewModel.setShouldShowMarketPlaceInDashbaord(val);
}),
ListItemToggle(
keyValue: "display_settings_show_address_book_popup",
label: S.of(context).show_address_book_popup,
value: _displaySettingsViewModel.showAddressBookPopup,
onChanged: (val) {
_displaySettingsViewModel.setShowAddressBookPopup(val);
}),
ListItemToggle(
keyValue: "display_settings_disable_buy_button",
label: S.of(context).disable_buy,
value: _displaySettingsViewModel.disableTradeOption,
onChanged: (val) {
_displaySettingsViewModel.setDisableTradeOption(val);
}),
if (_displaySettingsViewModel.showZcashCardSetting)
ListItemToggle(
keyValue: "display_settings_show_zcashcard",
label: S.of(context).show_zcash_card,
value: _displaySettingsViewModel.showZcashCard,
onChanged: (val) {
_displaySettingsViewModel.setShowZcashCard(val);
}),
ListItemSelector(
keyValue: "display_settings_sync_status_display",
label: S.of(context).sync_status_display_mode,
options: [_displaySettingsViewModel.syncStatusDisplayMode.title],
onTap: () async {
final items = SyncStatusDisplayMode.values.toList();
final selectedAtIndex =
items.indexOf(_displaySettingsViewModel.syncStatusDisplayMode);
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedAtIndex,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: (SyncStatusDisplayMode mode) {
_displaySettingsViewModel.setSyncStatusDisplayMode(mode);
},
displayItem: (SyncStatusDisplayMode mode) => mode.title,
isSeparated: false,
),
);
}),
if (_displaySettingsViewModel.showDisplayAmountsInSatoshiSetting)
ListItemRegularRow(
keyValue: "display_settings_bitcoin_amount_display",
label: S.of(context).bitcoin_amount_display,
trailingText: _displaySettingsViewModel.displayAmountsInSatoshi.title,
onTap: () async {
final items = BitcoinAmountDisplayMode.all;
final selectedAtIndex =
items.indexOf(_displaySettingsViewModel.displayAmountsInSatoshi);
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedAtIndex,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: _displaySettingsViewModel.setDisplayAmountsInSatoshi,
displayItem: (BitcoinAmountDisplayMode mode) => mode.title,
isSeparated: false,
),
);
}),
if (!_displaySettingsViewModel.disabledFiatApiMode)
ListItemSelector(
keyValue: "display_settings_fiat_currency",
label: S.of(context).settings_currency,
options: [_displaySettingsViewModel.fiatCurrency.title],
onTap: () async {
final items = FiatCurrency.all;
final selectedAtIndex =
items.indexOf(_displaySettingsViewModel.fiatCurrency);
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedAtIndex,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: (FiatCurrency currency) {
_displaySettingsViewModel.setFiatCurrency(currency);
},
images: FiatCurrency.all
.map((e) => Image.asset("assets/images/flags/${e.countryCode}.png"))
.toList(),
hintText: S.of(context).search_currency,
isGridView: true,
matchingCriteria: (FiatCurrency currency, String searchText) {
return currency.title.toLowerCase().contains(searchText) ||
currency.fullName.toLowerCase().contains(searchText);
},
isSeparated: false,
),
);
}),
ListItemSelector(
keyValue: "display_settings_language",
label: S.of(context).settings_change_language,
options: [LanguageService.list[_displaySettingsViewModel.languageCode] ?? ''],
onTap: () async {
final items = LanguageService.list.keys.toList();
final selectedAtIndex =
items.indexOf(_displaySettingsViewModel.languageCode);
await showPopUp<void>(
context: context,
builder: (_) => Picker(
displayItem: (dynamic code) {
return LanguageService.list[code] ?? '';
},
items: items,
selectedAtIndex: selectedAtIndex,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: _displaySettingsViewModel.onLanguageSelected,
images: LanguageService.list.keys
.map((e) => Image.asset(
"assets/images/flags/${LanguageService.localeCountryCode[e]}.png"))
.toList(),
hintText: S.of(context).search_language,
matchingCriteria: (String code, String searchText) {
return LanguageService.list[code]?.toLowerCase().contains(searchText) ?? false;
},
isSeparated: true,
),
);
}),
],
},
),
),
if (FeatureFlag.customBackgroundEnabled)
StandardListRow(
title: "Custom background",
isSelected: false,
onTap: (_) => _pickImage(context),
),
],
),
);
}
// Function to pick an image from the gallery
@@ -1,17 +1,17 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/connection_sync_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class DomainLookupsPage extends BasePage {
DomainLookupsPage(this._privacySettingsViewModel);
DomainLookupsPage(this._connectionsSyncViewModel);
@override
String get title => S.current.domain_looks_up;
final PrivacySettingsViewModel _privacySettingsViewModel;
final ConnectionSyncViewModel _connectionsSyncViewModel;
@override
Widget body(BuildContext context) {
@@ -23,36 +23,36 @@ class DomainLookupsPage extends BasePage {
children: [
SettingsSwitcherCell(
title: 'Twitter',
value: _privacySettingsViewModel.lookupTwitter,
onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsTwitter(value)),
value: _connectionsSyncViewModel.lookupTwitter,
onValueChange: (_, bool value) => _connectionsSyncViewModel.setLookupsTwitter(value)),
SettingsSwitcherCell(
title: 'Mastodon',
value: _privacySettingsViewModel.looksUpMastodon,
onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsMastodon(value)),
value: _connectionsSyncViewModel.looksUpMastodon,
onValueChange: (_, bool value) => _connectionsSyncViewModel.setLookupsMastodon(value)),
SettingsSwitcherCell(
title: 'Yat service',
value: _privacySettingsViewModel.looksUpYatService,
onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsYatService(value)),
value: _connectionsSyncViewModel.looksUpYatService,
onValueChange: (_, bool value) => _connectionsSyncViewModel.setLookupsYatService(value)),
SettingsSwitcherCell(
title: 'Unstoppable Domains',
value: _privacySettingsViewModel.looksUpUnstoppableDomains,
onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsUnstoppableDomains(value)),
value: _connectionsSyncViewModel.looksUpUnstoppableDomains,
onValueChange: (_, bool value) => _connectionsSyncViewModel.setLookupsUnstoppableDomains(value)),
SettingsSwitcherCell(
title: 'OpenAlias',
value: _privacySettingsViewModel.looksUpOpenAlias,
onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsOpenAlias(value)),
value: _connectionsSyncViewModel.looksUpOpenAlias,
onValueChange: (_, bool value) => _connectionsSyncViewModel.setLookupsOpenAlias(value)),
SettingsSwitcherCell(
title: 'Ethereum Name Service',
value: _privacySettingsViewModel.looksUpENS,
onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsENS(value)),
value: _connectionsSyncViewModel.looksUpENS,
onValueChange: (_, bool value) => _connectionsSyncViewModel.setLookupsENS(value)),
SettingsSwitcherCell(
title: '.well-known',
value: _privacySettingsViewModel.looksUpWellKnown,
onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsWellKnown(value)),
value: _connectionsSyncViewModel.looksUpWellKnown,
onValueChange: (_, bool value) => _connectionsSyncViewModel.setLookupsWellKnown(value)),
SettingsSwitcherCell(
title: 'Zano Aliases',
value: _privacySettingsViewModel.lookupsZanoAlias,
onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsZanoAlias(value)),
value: _connectionsSyncViewModel.lookupsZanoAlias,
onValueChange: (_, bool value) => _connectionsSyncViewModel.setLookupsZanoAlias(value)),
//if (!isHaven) it does not work correctly
],
+81 -42
View File
@@ -1,4 +1,8 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/modal_header.dart';
import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart';
@@ -7,6 +11,7 @@ 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/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.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';
@@ -14,30 +19,46 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class ManageNodesPage extends BasePage {
ManageNodesPage(this.isPow, {this.nodeListViewModel, this.powNodeListViewModel})
ManageNodesPage(this.isPow, {this.nodeListViewModel, this.powNodeListViewModel, this.dashboardViewModel})
: assert((isPow && powNodeListViewModel != null) || (!isPow && nodeListViewModel != null)),
assert(powNodeListViewModel == null || nodeListViewModel == null);
final DashboardViewModel? dashboardViewModel;
final NodeListViewModel? nodeListViewModel;
final PowNodeListViewModel? powNodeListViewModel;
final bool isPow;
@override
bool get hideAppBar => true;
@override
String get title => S.current.manage_nodes;
@override
Widget body(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 10),
child: Column(
return ModalPageWrapper(
horizontalPadding: 0,
topBar: ModalTopBar(
title: S.of(context).manage_nodes,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: () => Navigator.of(context).pop(),
trailingWidget: Row(
spacing: 8,
children: [
if (dashboardViewModel?.hasRescan ?? true)
ModernButton(
size: 36,
icon: Icon(Icons.history),
onPressed: () => Navigator.of(context).pushNamed(Routes.rescan)),
ModernButton(
size: 36,
icon: Icon(Icons.add),
onPressed: () => Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode))
],
),
),
content: Column(
children: [
Semantics(
button: true,
child: NodeHeaderListRow(
title: S.of(context).add_new_node,
onTap: (_) async => await Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode),
),
),
if (FeatureFlag.isAutomaticNodeSwitchingEnabled)
Observer(
builder: (_) => SettingsSwitcherCell(
@@ -60,32 +81,34 @@ class ManageNodesPage extends BasePage {
builder: (BuildContext context) {
int itemsCount =
nodeListViewModel?.nodes.length ?? powNodeListViewModel!.nodes.length;
return Flexible(
child: SectionStandardList(
scrollController: ModalScrollController.of(context),
sectionCount: 1,
dividerPadding: EdgeInsets.symmetric(horizontal: 24),
itemCounter: (int sectionIndex) => itemsCount,
itemBuilder: (_, index) {
return Observer(
builder: (context) {
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: isPow,
onTap: (_) async {
if (isSelected) {
return;
}
return SectionStandardList(
scrollController: ModalScrollController.of(context),
sectionCount: 1,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
dividerPadding: EdgeInsets.symmetric(horizontal: 24),
itemCounter: (int sectionIndex) => itemsCount,
itemBuilder: (_, index) {
return Observer(
builder: (context) {
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,
subtitle: node.label!,
node: node,
isSelected: isSelected,
isPow: isPow,
onTap: (_) async {
if (isSelected) {
return;
}
await showPopUp<void>(
context: context,
@@ -110,11 +133,10 @@ class ManageNodesPage extends BasePage {
);
},
);
return nodeListRow;
},
);
},
),
return nodeListRow;
},
);
},
);
},
),
@@ -122,4 +144,21 @@ class ManageNodesPage extends BasePage {
),
);
}
Future<void> _presentReconnectAlert(BuildContext context) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).reconnection,
alertContent: S.of(context).reconnect_alert_text,
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () async {
Navigator.of(context).pop();
await dashboardViewModel!.reconnect();
},
actionLeftButton: () => Navigator.of(context).pop());
},
);
}
}
+46 -38
View File
@@ -1,56 +1,64 @@
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/core/node_address_validator.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_text_field.dart';
import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/settings/mweb_settings_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class MwebNodePage extends BasePage {
MwebNodePage(this.mwebSettingsViewModelBase)
: _nodeUriController = TextEditingController(text: mwebSettingsViewModelBase.mwebNodeUri),
super();
class MwebNodePage extends StatefulWidget {
const MwebNodePage(this.mwebSettingsViewModelBase, {super.key});
final MwebSettingsViewModelBase mwebSettingsViewModelBase;
final TextEditingController _nodeUriController;
@override
String get title => S.current.litecoin_mweb_node;
State<MwebNodePage> createState() => _MwebNodePageState();
}
class _MwebNodePageState extends State<MwebNodePage> {
late final TextEditingController _nodeUriController =
TextEditingController(text: widget.mwebSettingsViewModelBase.mwebNodeUri);
@override
Widget body(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(controller: _nodeUriController),
)
],
),
void dispose() {
_nodeUriController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ModalPageWrapper(
topBar: ModalTopBar(
title: S.current.litecoin_mweb_settings,
onLeadingPressed: Navigator.of(context).pop,
leadingIcon: Icon(Icons.arrow_back_ios_new)),
content: Container(
child: NewListSections(
controllers: {
widget.mwebSettingsViewModelBase.mwebNodeUri: _nodeUriController,
},
sections: {
'main': [
ListItemTextField(
keyValue: widget.mwebSettingsViewModelBase.mwebNodeUri,
label: S.current.node_address,
validator: NodePathValidator(),
),
]
}),
),
Positioned(
child: Observer(
builder: (_) => LoadingPrimaryButton(
onPressed: () => save(context),
text: S.of(context).save,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
),
bottom: 24,
left: 24,
right: 24,
)
],
bottomContent: LoadingPrimaryButton(
onPressed: () => save(context),
text: S.of(context).save,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
);
}
void save(BuildContext context) {
mwebSettingsViewModelBase.setMwebNodeUri(_nodeUriController.text);
widget.mwebSettingsViewModelBase.setMwebNodeUri(_nodeUriController.text);
Navigator.pop(context);
}
}
+199 -154
View File
@@ -1,21 +1,24 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_selector.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
import 'package:cake_wallet/src/screens/settings/widgets/setting_priority_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/db/sqlite.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class OtherSettingsPage extends BasePage {
OtherSettingsPage(this._otherSettingsViewModel) {
@@ -25,164 +28,206 @@ class OtherSettingsPage extends BasePage {
}
@override
String get title => S.current.other_settings;
bool get hideAppBar => true;
final OtherSettingsViewModel _otherSettingsViewModel;
@override
Widget body(BuildContext context) => Observer(
builder: (_) => Container(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
if (_otherSettingsViewModel.displayTransactionPriority)
_otherSettingsViewModel.walletType == WalletType.bitcoin
? SettingsPriorityPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
customValue: _otherSettingsViewModel.customBitcoinFeeRate,
maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
)
: 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: (context) => Navigator.of(context).pushNamed(Routes.changeRep),
),
if (_otherSettingsViewModel.changeHardwareWalletTypeEnabled)
SettingsCellWithArrow(
title: "Hardware wallet manufacturer",
handler: (context) => Navigator.of(context)
.pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: {
"showUnavailable": false,
"availableHardwareWalletTypes": [
HardwareWalletType.cupcake,
HardwareWalletType.coldcard,
HardwareWalletType.seedsigner,
],
"onSelect": (BuildContext context, HardwareWalletType hwType) async {
await _otherSettingsViewModel.onHardwareWalletTypeChanged(hwType);
Navigator.pop(context);
Widget body(BuildContext context) {
return ModalPageWrapper(
topBar: ModalTopBar(
title: S.of(context).other,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: () => Navigator.of(context).pop(),
),
// header: ModalHeader(
// iconPath: "assets/new-ui/settings_row_icons/other.svg",
// message: "Other settings",
// title: S.of(context).other_settings),
content: NewListSections(
sections: {
"": [
if (_otherSettingsViewModel.displayTransactionPriority)
_otherSettingsViewModel.walletType == WalletType.bitcoin
? ListItemSelector(
keyValue: "fee_priority",
label: S.of(context).settings_fee_priority,
options: [_otherSettingsViewModel.transactionPriority.title],
onTap: () async {
final items = priorityForWalletType(_otherSettingsViewModel.walletType);
var selectedAtIndex = items.indexOf(_otherSettingsViewModel.transactionPriority);
double sliderValue = _otherSettingsViewModel.customBitcoinFeeRate ?? 0.0;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Picker(
items: items,
displayItem: (TransactionPriority item) => _otherSettingsViewModel.getDisplayBitcoinPriority(item, sliderValue.round()),
selectedAtIndex: selectedAtIndex,
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
headerEnabled: false,
closeOnItemSelected: false,
mainAxisAlignment: MainAxisAlignment.center,
sliderValue: sliderValue,
onSliderChanged: (double newValue) =>
setState(() => sliderValue = newValue),
onItemSelected: (TransactionPriority priority) {
setState(() => selectedAtIndex = items.indexOf(priority));
_otherSettingsViewModel.onDisplayBitcoinPrioritySelected.call(priority, sliderValue);
},
}),
),
if (_otherSettingsViewModel.walletType == WalletType.bitcoin) ...[
SettingsCellWithArrow(
title: S.of(context).export_lightning_logs,
handler: onExportLNLog,
),
SettingsCellWithArrow(
title: S.of(context).export_payjoin_logs,
handler: onExportPJLog,
),
],
SettingsCellWithArrow(
title: S.current.settings_terms_and_conditions,
handler: (context) => Navigator.of(context).pushNamed(Routes.readDisclaimer),
),
if (FeatureFlag.hasDevOptions &&
_otherSettingsViewModel.walletType == WalletType.monero)
SettingsCellWithArrow(
title: '[dev] monero background sync',
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devMoneroBackgroundSync),
),
if (FeatureFlag.hasDevOptions &&
[WalletType.monero, WalletType.wownero, WalletType.zano]
.contains(_otherSettingsViewModel.walletType))
SettingsCellWithArrow(
title: '[dev] xmr call profiler',
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler),
),
if (FeatureFlag.hasDevOptions &&
[WalletType.monero].contains(_otherSettingsViewModel.walletType))
SettingsCellWithArrow(
title: '[dev] xmr wallet cache debug',
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devMoneroWalletCacheDebug),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] shared preferences',
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devSharedPreferences),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] secure storage preferences',
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devSecurePreferences),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] background sync logs',
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devBackgroundSyncLogs),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] socket health logs',
handler: (context) => Navigator.of(context).pushNamed(Routes.devSocketHealthLogs),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] network requests logs',
handler: (context) => Navigator.of(context).pushNamed(Routes.devNetworkRequests),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] exchange provider logs',
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] *QR tools',
handler: (context) => Navigator.of(context).pushNamed(Routes.devQRTools),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] exchange provider logs',
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] browse sqlite db',
handler: (BuildContext context) async {
final data = await dumpDb();
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => JsonExplorerPage(data: data, title: 'sqlite db')),
);
}
),
],
);
},
);
},
);
_otherSettingsViewModel.onDisplayBitcoinPrioritySelected.call(items[selectedAtIndex], sliderValue);
}
)
: ListItemSelector(
keyValue: "fee_priority",
label: S.of(context).settings_fee_priority,
options: [_otherSettingsViewModel.transactionPriority.title],
onTap: () async {
final selectedAtIndex = priorityForWalletType(_otherSettingsViewModel.walletType).indexOf(_otherSettingsViewModel.transactionPriority,);
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedAtIndex: selectedAtIndex,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: (TransactionPriority item) => _otherSettingsViewModel.onDisplayBitcoinPrioritySelected.call(item, 0),
isSeparated: false,
),
),
);
},
),
if(_otherSettingsViewModel.changeRepresentativeEnabled)
ListItemRegularRow(
keyValue: "change_rep",
label: S.of(context).change_rep,
onTap: () => Navigator.of(context).pushNamed(Routes.changeRep)
),
if (_otherSettingsViewModel.changeHardwareWalletTypeEnabled)
ListItemRegularRow(
keyValue: "hardware_wallet_manufacturer",
label: "Hardware wallet manufacturer",
onTap: () => Navigator.of(context)
.pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: {
"showUnavailable": false,
"availableHardwareWalletTypes": [
HardwareWalletType.cupcake,
HardwareWalletType.coldcard,
HardwareWalletType.seedsigner,
],
"onSelect": (BuildContext context, HardwareWalletType hwType) async {
await _otherSettingsViewModel.onHardwareWalletTypeChanged(hwType);
Navigator.pop(context);
},
}),
),
ListItemRegularRow(
keyValue: "security_backup_page_sign_and_verify",
label: S.of(context).sign_verify_title,
onTap: () {
Navigator.of(context).pushNamed(Routes.signPage);
}),
],
if(FeatureFlag.hasDevOptions)
"dev": [
if(_otherSettingsViewModel.walletType == WalletType.bitcoin) ...[
ListItemRegularRow(
keyValue: "export_lightning_logs",
label: S.of(context).export_lightning_logs,
onTap: () => onExportLNLog(context)
),
SettingsVersionCell(
title: S.of(context).version(_otherSettingsViewModel.currentVersion),
ListItemRegularRow(
keyValue: "export_payjoin_logs",
label: S.of(context).export_payjoin_logs,
onTap: () => onExportPJLog(context)
),
],
),
),
);
if (FeatureFlag.hasDevOptions &&
_otherSettingsViewModel.walletType == WalletType.monero)
ListItemRegularRow(
keyValue: "[dev] monero background sync",
label: "[dev] monero background sync",
onTap: () => Navigator.of(context).pushNamed(Routes.devMoneroBackgroundSync)
),
if (FeatureFlag.hasDevOptions &&
[WalletType.monero, WalletType.wownero, WalletType.zano]
.contains(_otherSettingsViewModel.walletType))
ListItemRegularRow(
keyValue: "[dev] xmr call profiler",
label: "[dev] xmr call profiler",
onTap: () => Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler)
),
if (FeatureFlag.hasDevOptions &&
[WalletType.monero].contains(_otherSettingsViewModel.walletType))
ListItemRegularRow(
keyValue: '[dev] xmr wallet cache debug',
label: '[dev] xmr wallet cache debug',
onTap: () => Navigator.of(context).pushNamed(Routes.devMoneroWalletCacheDebug)
),
ListItemRegularRow(
keyValue: '[dev] shared preferences',
label: '[dev] shared preferences',
onTap: () => Navigator.of(context).pushNamed(Routes.devSharedPreferences)
),
ListItemRegularRow(
keyValue: '[dev] secure storage preferences',
label: '[dev] secure storage preferences',
onTap: () => Navigator.of(context).pushNamed(Routes.devSecurePreferences)
),
ListItemRegularRow(
keyValue: '[dev] background sync logs',
label: '[dev] background sync logs',
onTap: () => Navigator.of(context).pushNamed(Routes.devBackgroundSyncLogs)
),
ListItemRegularRow(
keyValue: '[dev] socket health logs',
label: '[dev] socket health logs',
onTap: () => Navigator.of(context).pushNamed(Routes.devSocketHealthLogs)
),
ListItemRegularRow(
keyValue: '[dev] network requests logs',
label: '[dev] network requests logs',
onTap: () => Navigator.of(context).pushNamed(Routes.devNetworkRequests)
),
ListItemRegularRow(
keyValue: '[dev] exchange provider logs',
label: '[dev] exchange provider logs',
onTap: () => Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs)
),
ListItemRegularRow(
keyValue: '[dev] *QR tools',
label: '[dev] *QR tools',
onTap: () => Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs)
),
ListItemRegularRow(
keyValue: '[dev] browse sqlite db',
label: '[dev] browse sqlite db',
onTap: () async {
final data = await dumpDb();
Navigator.of(context).push(
MaterialPageRoute(builder: (context) =>
JsonExplorerPage(data: data, title: 'sqlite db')),
);
}
),
]
}
),
);
}
Future<void> onExportLNLog(BuildContext context) async {
final file = await _otherSettingsViewModel.getLightningLog();
+88 -192
View File
@@ -1,15 +1,12 @@
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_toggle.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/modal_header.dart';
import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -18,195 +15,94 @@ class PrivacyPage extends BasePage {
PrivacyPage(this._privacySettingsViewModel);
@override
String get title => S.current.privacy_settings;
bool get hideAppBar => true;
final PrivacySettingsViewModel _privacySettingsViewModel;
@override
Widget body(BuildContext context) {
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(top: 10),
child: Observer(builder: (_) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SettingsChoicesCell(
ChoicesListItem<FiatApiMode>(
title: S.current.fiat_api,
items: FiatApiMode.all,
selectedItem: _privacySettingsViewModel.fiatApiMode,
onItemSelected: (FiatApiMode fiatApiMode) =>
_privacySettingsViewModel.setFiatMode(fiatApiMode),
return ModalPageWrapper(
topBar: ModalTopBar(
title: "",
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: () => Navigator.of(context).pop(),
),
header: ModalHeader(
iconPath: "assets/new-ui/settings_row_icons/privacy.svg",
message: S.of(context).privacy_desc,
title: S.of(context).privacy),
content: SingleChildScrollView(
child: Container(
child: Observer(builder: (_) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
NewListSections(
sections: {
"1": [
if (_privacySettingsViewModel.isAutoGenerateSubaddressesVisible)
ListItemToggle(
keyValue: "auto_generate_subaddresses",
label: _privacySettingsViewModel.isMoneroWallet
? S.of(context).auto_generate_subaddresses
: S.of(context).auto_generate_addresses,
value: _privacySettingsViewModel.isAutoGenerateSubaddressesEnabled,
onChanged: (val) {
_privacySettingsViewModel.setAutoGenerateSubaddresses(val);
}),
ListItemToggle(
keyValue: "save_recipient_address",
label: S.of(context).settings_save_recipient_address,
value: _privacySettingsViewModel.shouldSaveRecipientAddress,
onChanged: (val) {
_privacySettingsViewModel.setShouldSaveRecipientAddress(val);
}),
if (_privacySettingsViewModel.canUsePayjoin)
ListItemToggle(
keyValue: "use_payjoin",
label: S.of(context).use_payjoin,
value: _privacySettingsViewModel.usePayjoin,
onChanged: (val) {
_privacySettingsViewModel.setUsePayjoin(val);
}),
if (_privacySettingsViewModel.canUseLightning)
ListItemToggle(
keyValue: "enable_lightning",
label: S.of(context).enable_lightning,
value: _privacySettingsViewModel.useLightning,
onChanged: (val) {
_privacySettingsViewModel.setUseLightning(val);
}),
],
"": [
if (_privacySettingsViewModel.isBitcoin)
ListItemRegularRow(
iconPath: "assets/new-ui/settings_row_icons/silent-payments.svg",
keyValue: "silent_payments",
label: S.of(context).silent_payments,
onTap: () =>
Navigator.of(context).pushNamed(Routes.silentPaymentsSettings)),
if (_privacySettingsViewModel.isLitecoin)
ListItemRegularRow(
iconPath: "assets/new-ui/settings_row_icons/mweb.svg",
keyValue: "mweb",
label: "MWEB",
onTap: () =>
Navigator.of(context).pushNamed(Routes.mwebSettings)),
if (_privacySettingsViewModel.hasCoinControl)
ListItemRegularRow(
iconPath: "assets/new-ui/settings_row_icons/coin-control.svg",
keyValue: "coin_control",
label: "Coin Control",
onTap: () =>
Navigator.of(context).pushNamed(Routes.unspentCoinsList)),
],
}
),
),
SettingsChoicesCell(
ChoicesListItem<ExchangeApiMode>(
title: S.current.swap,
items: ExchangeApiMode.all,
selectedItem: _privacySettingsViewModel.exchangeStatus,
onItemSelected: (ExchangeApiMode mode) =>
_privacySettingsViewModel.setExchangeApiMode(mode),
),
),
if (_privacySettingsViewModel.canUseLightning)
SettingsSwitcherCell(
title: S.of(context).enable_lightning,
value: _privacySettingsViewModel.useLightning,
onValueChange: (_, value) => _privacySettingsViewModel.setUseLightning(value),
),
if (_privacySettingsViewModel.canUsePayjoin)
SettingsSwitcherCell(
title: S.of(context).use_payjoin,
value: _privacySettingsViewModel.usePayjoin,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUsePayjoin(value);
},
),
SettingsSwitcherCell(
title: S.current.settings_save_recipient_address,
value: _privacySettingsViewModel.shouldSaveRecipientAddress,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setShouldSaveRecipientAddress(value);
}),
if (_privacySettingsViewModel.isAutoGenerateSubaddressesVisible)
SettingsSwitcherCell(
title: _privacySettingsViewModel.isMoneroWallet
? S.current.auto_generate_subaddresses
: S.current.auto_generate_addresses,
value: _privacySettingsViewModel.isAutoGenerateSubaddressesEnabled,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setAutoGenerateSubaddresses(value);
},
),
if (DeviceInfo.instance.isMobile)
SettingsSwitcherCell(
title: S.current.prevent_screenshots,
value: _privacySettingsViewModel.isAppSecure,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setIsAppSecure(value);
},
),
SettingsSwitcherCell(
title: S.current.disable_buy,
value: _privacySettingsViewModel.disableTradeOption,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setDisableTradeOption(value);
},
),
SettingsSwitcherCell(
title: S.current.disable_automatic_exchange_status_updates,
value: _privacySettingsViewModel.disableAutomaticExchangeStatusUpdates,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setDisableAutomaticExchangeStatusUpdates(value);
},
),
// SettingsSwitcherCell(
// title: S.current.disable_bulletin,
// value: _privacySettingsViewModel.disableBulletin,
// onValueChange: (BuildContext _, bool value) {
// _privacySettingsViewModel.setDisableBulletin(value);
// },
// ),
if (_privacySettingsViewModel.canUseBlinkProtection)
SettingsSwitcherCell(
title: S.current.use_blink_protection,
value: _privacySettingsViewModel.useBlinkProtection,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUseBlinkProtection(value);
},
),
if (_privacySettingsViewModel.canUseEtherscan)
SettingsSwitcherCell(
title: S.current.etherscan_history,
value: _privacySettingsViewModel.useEtherscan,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUseEtherscan(value);
},
),
if (_privacySettingsViewModel.canUsePolygonScan)
SettingsSwitcherCell(
title: S.current.polygonscan_history,
value: _privacySettingsViewModel.usePolygonScan,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUsePolygonScan(value);
},
),
if (_privacySettingsViewModel.canUseBaseScan)
SettingsSwitcherCell(
title: S.current.basescan_history,
value: _privacySettingsViewModel.useBaseScan,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUseBaseScan(value);
},
),
if (_privacySettingsViewModel.canUseArbiScan)
SettingsSwitcherCell(
title: S.current.arbiscan_history,
value: _privacySettingsViewModel.useArbiScan,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUseArbiScan(value);
},
),
if (_privacySettingsViewModel.canUseBscScan)
SettingsSwitcherCell(
title: S.current.bscscan_history,
value: _privacySettingsViewModel.useBscScan,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUseBscScan(value);
},
),
if (_privacySettingsViewModel.canUseTronGrid)
SettingsSwitcherCell(
title: S.current.trongrid_history,
value: _privacySettingsViewModel.useTronGrid,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUseTronGrid(value);
},
),
if (_privacySettingsViewModel.canUseMempoolFeeAPI)
SettingsSwitcherCell(
title: S.current.enable_mempool_api,
value: _privacySettingsViewModel.useMempoolFeeAPI,
onValueChange: (BuildContext _, bool isEnabled) async {
if (!isEnabled) {
final bool confirmation = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).warning,
alertContent: S.of(context).disable_fee_api_warning,
rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
false;
if (confirmation) {
_privacySettingsViewModel.setUseMempoolFeeAPI(isEnabled);
}
return;
}
_privacySettingsViewModel.setUseMempoolFeeAPI(isEnabled);
},
),
SettingsCellWithArrow(
title: S.current.domain_looks_up,
handler: (context) => Navigator.of(context).pushNamed(Routes.domainLookupsPage),
),
if(_privacySettingsViewModel.hasCoinControl)
SettingsCellWithArrow(
title: "Coin Control",
handler: (context) => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
),
// SettingsCellWithArrow(
// title: 'Trocador providers',
// handler: (context) => Navigator.of(context).pushNamed(Routes.trocadorProvidersPage),
// ),
],
);
}),
],
);
}),
),
),
);
}
+151 -156
View File
@@ -2,9 +2,12 @@ import 'dart:io';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_selector.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_toggle.dart';
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
import 'package:cake_wallet/new-ui/widgets/modal_header.dart';
import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -14,7 +17,6 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
@@ -28,7 +30,10 @@ class SecurityBackupPage extends BasePage {
final AuthService _authService;
@override
String get title => S.current.security;
bool get hideAppBar => true;
@override
String get title => S.current.security_and_backup;
final SecuritySettingsViewModel _securitySettingsViewModel;
@@ -36,170 +41,160 @@ class SecurityBackupPage extends BasePage {
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Column(
spacing: 16,
mainAxisSize: MainAxisSize.min,
children: [
ModalHeader(
iconPath: "assets/new-ui/settings_row_icons/security.svg",
message: S.of(context).privacy_and_security_desc,
title: S.of(context).security),
Observer(
builder: (_) => NewListSections(sections: {
"": [
if (DeviceInfo.instance.isMobile || Platform.isMacOS || Platform.isLinux)
ListItemToggle(
keyValue: "security_backup_page_allow_biometrics_button_key",
label: S.current.settings_allow_biometrical_authentication,
value: _securitySettingsViewModel.allowBiometricalAuthentication,
onChanged: (bool value) {
if (value) {
_authService.authenticateAction(
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (isAuthenticatedSuccessfully) {
if (await _securitySettingsViewModel.biometricAuthenticated()) {
_securitySettingsViewModel.setAllowBiometricalAuthentication(
isAuthenticatedSuccessfully);
}
} else {
return ModalPageWrapper(
topBar: ModalTopBar(
title: "",
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: () => Navigator.of(context).pop()),
header: ModalHeader(
iconPath: "assets/new-ui/settings_row_icons/security.svg",
message: S.of(context).privacy_and_security_desc,
title: S.of(context).security),
content: Column(
spacing: 16,
mainAxisSize: MainAxisSize.min,
children: [
Observer(
builder: (_) => NewListSections(sections: {
"": [
if (DeviceInfo.instance.isMobile || Platform.isMacOS || Platform.isLinux)
ListItemToggle(
keyValue: "security_backup_page_allow_biometrics_button_key",
label: S.current.settings_allow_biometrical_authentication,
value: _securitySettingsViewModel.allowBiometricalAuthentication,
onChanged: (bool value) {
if (value) {
_authService.authenticateAction(
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (isAuthenticatedSuccessfully) {
if (await _securitySettingsViewModel.biometricAuthenticated()) {
_securitySettingsViewModel.setAllowBiometricalAuthentication(
isAuthenticatedSuccessfully);
}
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
);
} else {
_securitySettingsViewModel.setAllowBiometricalAuthentication(value);
}
}),
if (FeatureFlag.duressPinEnabled)
ListItemToggle(
keyValue: "security_backup_page_duress_pin_button_key",
label: "Duress PIN",
value: _securitySettingsViewModel.enableDuressPin,
onChanged: (bool value) {
_authService
.authenticateAction(context, route: Routes.securityBackupDuressPin,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (isAuthenticatedSuccessfully) {
if (!value) {
_securitySettingsViewModel.setEnableDuressPin(value);
_securitySettingsViewModel.clearDuressPin();
return;
} else {
_securitySettingsViewModel.setAllowBiometricalAuthentication(
isAuthenticatedSuccessfully);
}
final res = await _showDuressPinDescription(context);
if (res) {
final confirmation = await _showDuressPinConfirmation(context);
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
);
} else {
_securitySettingsViewModel.setAllowBiometricalAuthentication(value);
}
}),
if (DeviceInfo.instance.isMobile)
ListItemToggle(
keyValue: "display_settings_prevent_screen_capture",
label: S.of(context).prevent_screenshots,
value: _securitySettingsViewModel.isAppSecure,
onChanged: (val) {
_securitySettingsViewModel.setIsAppSecure(val);
}),
if (FeatureFlag.duressPinEnabled)
ListItemToggle(
keyValue: "security_backup_page_duress_pin_button_key",
label: "Duress PIN",
value: _securitySettingsViewModel.enableDuressPin,
onChanged: (bool value) {
_authService
.authenticateAction(context, route: Routes.securityBackupDuressPin,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (isAuthenticatedSuccessfully) {
if (!value) {
_securitySettingsViewModel.setEnableDuressPin(value);
_securitySettingsViewModel.clearDuressPin();
return;
}
final res = await _showDuressPinDescription(context);
if (res) {
final confirmation = await _showDuressPinConfirmation(context);
if (confirmation) {
Navigator.of(context).pushNamed(
Routes.setupDuressPin,
arguments:
(PinCodeState<PinCodeWidget> pinCtx, String _) async {
pinCtx.close();
_securitySettingsViewModel.setEnableDuressPin(true);
},
);
}
if (confirmation) {
Navigator.of(context).pushNamed(
Routes.setupDuressPin,
arguments:
(PinCodeState<PinCodeWidget> pinCtx, String _) async {
pinCtx.close();
_securitySettingsViewModel.setEnableDuressPin(true);
},
);
}
}
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings);
}),
ListItemRegularRow(
keyValue: "security_backup_page_require_pin_after_button_key",
label: S.current.require_pin_after,
trailingText: _securitySettingsViewModel.pinCodeRequiredDuration.toString(),
onTap: () async {
final items = PinCodeRequiredDuration.values;
}
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings);
}),
ListItemSelector(
keyValue: "security_backup_page_require_pin_after_button_key",
label: S.current.require_pin_after,
options: [_securitySettingsViewModel.pinCodeRequiredDuration.toString()],
onTap: () async {
final items = PinCodeRequiredDuration.values;
final selectedAtIndex =
items.indexOf(_securitySettingsViewModel.pinCodeRequiredDuration);
final selectedAtIndex =
items.indexOf(_securitySettingsViewModel.pinCodeRequiredDuration);
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedAtIndex,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: (PinCodeRequiredDuration item) {
_securitySettingsViewModel.setPinCodeRequiredDuration(item);
},
isSeparated: false,
),
);
}),
// if (!_isHardwareWallet)
// ListItemRegularRow(
// keyValue: "security_backup_page_show_keys_button_key",
// label: S.current.show_keys,
// onTap: () {
// _authService.authenticateAction(
// context,
// route: Routes.showKeys,
// conditionToDetermineIfToUse2FA: _securitySettingsViewModel
// .shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
// );
// }),
// if (!SettingsStoreBase.walletPasswordDirectInput)
// ListItemRegularRow(
// keyValue: "security_backup_page_change_password_button_key",
// label: S.current.create_backup,
// onTap: () {
// _authService.authenticateAction(
// context,
// route: Routes.backup,
// conditionToDetermineIfToUse2FA: _securitySettingsViewModel
// .shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
// );
// }),
ListItemRegularRow(
keyValue: "security_backup_page_change_pin_button_key",
label: S.current.settings_change_pin,
onTap: () {
_authService.authenticateAction(
context,
route: Routes.setupPin,
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
setupPinContext.close();
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedAtIndex,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: (PinCodeRequiredDuration item) {
_securitySettingsViewModel.setPinCodeRequiredDuration(item);
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
);
}),
ListItemRegularRow(
keyValue: "security_backup_page_sign_and_verify",
label: S.current.sign_verify_title,
onTap: () {
Navigator.of(context).pushNamed(Routes.signPage);
}),
ListItemRegularRow(
keyValue: "security_backup_page_totp_2fa_button_key",
label: _securitySettingsViewModel.useTotp2FA
? S.current.modify_2fa
: S.current.setup_2fa,
onTap: () {
_authService.authenticateAction(
context,
route: _securitySettingsViewModel.useTotp2FA
? Routes.modify2FAPage
: Routes.setup2faInfoPage,
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
);
})
]
}),
),
],
),
isSeparated: false,
),
);
}),
// if (!_isHardwareWallet)
// ListItemRegularRow(
// keyValue: "security_backup_page_show_keys_button_key",
// label: S.current.show_keys,
// onTap: () {
// _authService.authenticateAction(
// context,
// route: Routes.showKeys,
// conditionToDetermineIfToUse2FA: _securitySettingsViewModel
// .shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
// );
// }),
ListItemRegularRow(
keyValue: "security_backup_page_change_pin_button_key",
label: S.current.settings_change_pin,
onTap: () {
_authService.authenticateAction(
context,
route: Routes.setupPin,
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
setupPinContext.close();
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
);
}),
ListItemRegularRow(
keyValue: "security_backup_page_totp_2fa_button_key",
label: _securitySettingsViewModel.useTotp2FA
? S.current.modify_2fa
: S.current.setup_2fa,
onTap: () {
_authService.authenticateAction(
context,
route: _securitySettingsViewModel.useTotp2FA
? Routes.modify2FAPage
: Routes.setup2faInfoPage,
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
);
})
]
}),
),
],
),
);
}
@@ -12,9 +12,13 @@ class SettingsChoicesCell extends StatelessWidget {
@override
Widget build(BuildContext context) {
final items = choicesListItem.items;
final selectedIndex = items.indexOf(choicesListItem.selectedItem);
final itemCount = items.length;
return Container(
color: useGenericColor ? Theme.of(context).colorScheme.surface : null,
padding: padding ?? EdgeInsets.only(left: 24, right: 24, top: 16, bottom: 16),
padding: padding ?? const EdgeInsets.only(left: 24, right: 24, top: 16, bottom: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -33,46 +37,68 @@ class SettingsChoicesCell extends StatelessWidget {
Center(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(12),
border: useGenericColor
? null
: Border.all(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
width: 1.5,
),
color:
useGenericColor ? Theme.of(context).colorScheme.surfaceContainerHighest : null,
color: useGenericColor
? Theme.of(context).colorScheme.surfaceContainerHighest
: null,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: choicesListItem.items.map((dynamic e) {
final isSelected = choicesListItem.selectedItem == e;
return Expanded(
child: GestureDetector(
onTap: () {
choicesListItem.onItemSelected.call(e);
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: isSelected ? Theme.of(context).colorScheme.primary : null,
),
child: Center(
child: Text(
choicesListItem.displayItem.call(e),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: isSelected
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
),
child: LayoutBuilder(
builder: (context, constraints) {
final itemWidth = constraints.maxWidth / itemCount;
return Stack(
children: [
if (selectedIndex >= 0)
AnimatedPositioned(
duration: const Duration(milliseconds: 450),
curve: Curves.easeInOut,
left: selectedIndex * itemWidth,
top: 0,
bottom: 0,
width: itemWidth,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Theme.of(context).colorScheme.primary,
),
),
),
Row(
children: items.map((dynamic e) {
final isSelected = choicesListItem.selectedItem == e;
return Expanded(
child: GestureDetector(
onTap: () => choicesListItem.onItemSelected.call(e),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
color: Colors.transparent,
child: Center(
child: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 220),
curve: Curves.easeInOut,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: isSelected
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight:
isSelected ? FontWeight.w600 : FontWeight.normal,
),
child: Text(choicesListItem.displayItem.call(e)),
),
),
),
),
);
}).toList(),
),
),
],
);
}).toList(),
},
),
),
),
@@ -80,4 +106,4 @@ class SettingsChoicesCell extends StatelessWidget {
),
);
}
}
}
@@ -13,6 +13,7 @@ class SettingsSwitcherCell extends StandardListRow {
Key? key,
this.padding,
this.switchBackgroundColor,
this.height = 56,
}) : super(title: title, isSelected: false, decoration: decoration, onTap: onTap, key: key);
final bool value;
@@ -20,6 +21,7 @@ class SettingsSwitcherCell extends StandardListRow {
final void Function(BuildContext context, bool value)? onValueChange;
final Widget? leading;
final EdgeInsets? padding;
final double? height;
@override
Widget buildTrailing(BuildContext context) => StandardSwitch(
@@ -33,7 +35,7 @@ class SettingsSwitcherCell extends StandardListRow {
final leading = buildLeading(context);
final trailing = buildTrailing(context);
return Container(
height: 56,
height: height,
padding: padding ?? EdgeInsets.only(left: 12, right: 12),
child: TextButton(
onPressed: () {
@@ -12,9 +12,9 @@ class SettingsThemeChoicesCell extends StatelessWidget {
final DisplaySettingsViewModel _displaySettingsViewModel;
final double cellHeight = 25;
final double cellHeight = 12;
final double cellWidth = 12;
final double cellRadius = 8;
final double cellRadius = 18;
@override
Widget build(BuildContext context) {
@@ -28,14 +28,17 @@ class SettingsThemeChoicesCell extends StatelessWidget {
final previewHeight = _getResponsivePreviewHeight(screenHeight);
final previewWidth = previewHeight * 0.6;
return Container(
return AnimatedContainer(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
height: getHeight(context, currentTheme, currentTheme.hasAccentColors),
padding: EdgeInsets.all(12),
padding: EdgeInsets.all(14),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
SizedBox(
height: previewHeight,
child: ListView.builder(
itemCount: availableThemes.length,
scrollDirection: Axis.horizontal,
@@ -50,16 +53,16 @@ class SettingsThemeChoicesCell extends StatelessWidget {
onTap: () {
_displaySettingsViewModel.onThemeSelected(theme);
},
child: Container(
child: AnimatedContainer(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
margin: EdgeInsets.only(right: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(cellRadius),
border: isSelected
? Border.all(
color: Theme.of(context).colorScheme.primary,
decoration: ShapeDecoration(
shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(cellRadius),
side: BorderSide(
color: isSelected ? Theme.of(context).colorScheme.primary : Colors.transparent,
strokeAlign: BorderSide.strokeAlignOutside)
: null,
),
)),
child: ClipRRect(
borderRadius: BorderRadius.circular(cellRadius),
child: CakeImageWidget(
@@ -75,56 +78,94 @@ class SettingsThemeChoicesCell extends StatelessWidget {
},
),
),
if (_displaySettingsViewModel.currentTheme.hasAccentColors) ...[
SizedBox(height: cellHeight),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).accent_color,
style: Theme.of(context).textTheme.bodyMedium,
),
Container(
child: Row(
children: availableAccentColors.map((accentColor) {
final isSelected = _displaySettingsViewModel
.isAccentColorSelected(accentColor.name.toLowerCase());
return GestureDetector(
onTap: () {
_displaySettingsViewModel
.onAccentColorSelected(accentColor.name.toLowerCase());
},
child: Container(
width: 26,
height: 26,
margin: EdgeInsets.only(right: 11),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: isSelected
? Border.all(
color: Theme.of(context).colorScheme.onSurfaceVariant,
width: 3)
: null,
color: accentColor.color,
AnimatedSwitcher(
duration: const Duration(milliseconds: 350),
switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeIn,
transitionBuilder: (child, animation) => FadeTransition(
opacity: animation,
child: SizeTransition(
sizeFactor: animation,
axisAlignment: -1,
child: child,
),
),
child: _displaySettingsViewModel.currentTheme.hasAccentColors
? Column(
key: const ValueKey('accent'),
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(top: 14),
child: Container(height: 1, color: Theme.of(context).colorScheme.outlineVariant)
),
SizedBox(height: cellHeight),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).accent_color,
style: Theme.of(context).textTheme.bodyMedium,
),
),
);
}).toList(),
),
),
],
),
],
Row(
spacing: 4,
children: availableAccentColors.map((accentColor) {
final isSelected = _displaySettingsViewModel
.isAccentColorSelected(accentColor.name.toLowerCase());
return GestureDetector(
onTap: () {
_displaySettingsViewModel
.onAccentColorSelected(accentColor.name.toLowerCase());
},
child: Stack(
children: [
AnimatedOpacity(
duration: Duration(milliseconds: 350),
opacity: isSelected ? 1 : 0,
child: Container(
width:28,height:28,decoration: BoxDecoration(borderRadius: BorderRadius.circular(99999999),border: Border.all(color:Theme.of(context)
.colorScheme
.onSurface))
),
),
AnimatedScale(
duration: Duration(milliseconds: 350),
scale: isSelected ? 0.8 : 1,
child: Container(
width: 28,
height: 28,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(99999999),
color: accentColor.color),
),
),
],
),
);
}).toList(),
),
],
),
],
)
: const SizedBox.shrink(key: ValueKey('no-accent')),
),
if (_displaySettingsViewModel.currentTheme is BlackTheme)
SettingsSwitcherCell(
title: S.current.oled_mode,
value: _displaySettingsViewModel.isBlackThemeOledEnabled,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setBlackThemeOled(value);
},
padding: EdgeInsets.zero,
switchBackgroundColor: currentTheme.colorScheme.secondaryContainer,
Padding(
padding: EdgeInsets.only(top: 12, bottom: 4),
child: Container(height: 1, color: Theme.of(context).colorScheme.outlineVariant)
),
if (_displaySettingsViewModel.currentTheme is BlackTheme)
SettingsSwitcherCell(
height: 40,
title: S.current.oled_mode,
value: _displaySettingsViewModel.isBlackThemeOledEnabled,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setBlackThemeOled(value);
},
padding: EdgeInsets.zero,
switchBackgroundColor: currentTheme.colorScheme.secondaryContainer,
),
],
),
);
@@ -135,14 +176,14 @@ class SettingsThemeChoicesCell extends StatelessWidget {
double getHeight(BuildContext context, MaterialThemeBase theme, bool hasAccentColors) {
final screenHeight = MediaQuery.of(context).size.height;
double baseHeight = (screenHeight * 0.25).clamp(150.0, screenHeight * 0.5);
double baseHeight = (screenHeight * 0.253).clamp(150.0, screenHeight * 0.5);
if (hasAccentColors) {
baseHeight += (screenHeight * 0.05).clamp(35.0, 60.0);
baseHeight += (screenHeight * 0.60).clamp(35.0, 60.0);
}
if (theme is BlackTheme) {
baseHeight += (screenHeight * 0.08).clamp(48.0, 96.0);
baseHeight += (screenHeight * 0.059).clamp(48.0, 96.0);
}
return baseHeight;
@@ -6,20 +6,21 @@ class ListItemStyleWrapper extends StatelessWidget {
required this.isFirstInSection,
required this.isLastInSection,
required this.builder,
this.hasImage,
this.onTap,
this.height = 50,
this.iconPath,
});
final String? iconPath;
final bool isFirstInSection;
final bool isLastInSection;
final bool ?hasImage;
final double height;
final VoidCallback? onTap;
final Widget Function(BuildContext context, TextStyle textStyle, TextStyle labelStyle) builder;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textStyle = TextStyle(
@@ -60,17 +61,17 @@ class ListItemStyleWrapper extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: builder(context, textStyle, labelStyle))))),
if(hasImage == true && isLastInSection == false) Container(
if(iconPath != null && isLastInSection == false) Container(
color: theme.colorScheme.surfaceContainer,
child: Padding(
padding: const EdgeInsets.only(left: 48, right: 13),
padding: const EdgeInsets.only(left: 50, right: 13),
child: Container(height: 1, color: theme.colorScheme.outlineVariant),
),
)
else if(!isLastInSection) Container(
color: theme.colorScheme.surfaceContainer,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 13),
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Container(height: 1, color: theme.colorScheme.outlineVariant),
),
)
@@ -37,6 +37,7 @@ class _ListItemCheckboxWidgetState extends State<ListItemCheckboxWidget> {
@override
Widget build(BuildContext context) {
return ListItemStyleWrapper(
iconPath: widget.iconPath,
onTap: widget.onTap ?? () {
widget.onChanged(!widget.value);
},
@@ -53,11 +54,11 @@ class _ListItemCheckboxWidgetState extends State<ListItemCheckboxWidget> {
children: [
if (widget.iconPath != null)
widget.iconPath!.toLowerCase().endsWith("svg")
? CakeImageWidget(imageUrl:widget.iconPath!, height: 24, width: 24)
? CakeImageWidget(imageUrl:widget.iconPath!, height: 26, width: 26)
: Image.asset(
widget.iconPath!,
width: 24,
height: 24,
width: 26,
height: 26,
),
Expanded(
child: Column(
@@ -12,7 +12,6 @@ class ListItemRegularRowWidget extends StatelessWidget {
this.trailingText,
this.iconPath,
this.onTap,
this.hasImage,
this.isFirstInSection = false,
this.isLastInSection = false,
this.showArrow = true,
@@ -28,7 +27,6 @@ class ListItemRegularRowWidget extends StatelessWidget {
final String? trailingText;
final String? iconPath;
final VoidCallback? onTap;
final bool? hasImage;
final bool isFirstInSection;
final bool isLastInSection;
final bool showArrow;
@@ -46,7 +44,7 @@ class ListItemRegularRowWidget extends StatelessWidget {
return ListItemStyleWrapper(
onTap: onTap,
hasImage: iconPath != null ? true : false,
iconPath: iconPath,
isFirstInSection: isFirstInSection,
isLastInSection: isLastInSection,
height: subtitle != null ? 64 : 50,
@@ -33,25 +33,25 @@ class ListItemSelectorWidget extends StatelessWidget {
isLastInSection: isLastInSection,
builder: (context, textStyle, labelStyle) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(label, style: textStyle)),
Row(
children: [
Text(
options[selectedIndex],
style: labelStyle,
),
CakeImageWidget(imageUrl:
"assets/new-ui/chooser.svg",
colorFilter:
ColorFilter.mode(theme.colorScheme.onSurfaceVariant, BlendMode.srcIn),
),
SizedBox(width: 8)
],
),
],
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(label, style: textStyle)),
Row(
spacing: 8,
children: [
Text(
options[selectedIndex],
style: labelStyle,
),
CakeImageWidget(imageUrl:
"assets/new-ui/chooser.svg",
colorFilter:
ColorFilter.mode(theme.colorScheme.onSurfaceVariant, BlendMode.srcIn),
),
],
),
],
);
});
}
}
}
+44 -5
View File
@@ -6,12 +6,14 @@ class StandardListRow extends StatelessWidget {
StandardListRow({
required this.title,
required this.isSelected,
this.subtitle,
this.onTap,
this.decoration,
super.key,
});
final String title;
final String? subtitle;
final bool isSelected;
final void Function(BuildContext context)? onTap;
final Decoration? decoration;
@@ -48,18 +50,52 @@ class StandardListRow extends StatelessWidget {
Widget? buildLeading(BuildContext context) => null;
Widget buildCenter(BuildContext context, {required bool hasLeftOffset}) {
return Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
if (hasLeftOffset) SizedBox(width: 10),
Expanded(
child: Text(
title,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: titleColor(context),
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w400,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (subtitle != null && subtitle!.isNotEmpty)
Align(
alignment: Alignment.centerLeft,
child: Text(
textAlign: TextAlign.left,
subtitle!,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: titleColor(context),
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w400,
),
),
),
if (subtitle != null && subtitle!.isNotEmpty)
Align(
alignment: Alignment.centerLeft,
child: Text(
textAlign: TextAlign.left,
title,
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w400,
),
),
)
else
Align(
alignment: Alignment.centerLeft,
child: Text(
title,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: titleColor(context),
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w400,
),
),
),
],
),
)
],
@@ -138,6 +174,7 @@ class SectionStandardList extends StatelessWidget {
this.hasTopSeparator = false,
this.scrollController,
this.physics,
this.shrinkWrap = false,
}) : totalRows = [];
final int sectionCount;
@@ -150,6 +187,7 @@ class SectionStandardList extends StatelessWidget {
final EdgeInsets dividerPadding;
final ScrollController? scrollController;
final ScrollPhysics? physics;
final bool shrinkWrap;
List<Widget> transform(
bool hasTopSeparator,
@@ -208,6 +246,7 @@ class SectionStandardList extends StatelessWidget {
return ListView.separated(
controller: scrollController,
physics: physics,
shrinkWrap: shrinkWrap,
separatorBuilder: (_, index) {
final row = totalRows[index];