Files
hash-wallet/lib/src/screens/settings/display_settings_page.dart
T
jwinterm dd449f2594 Merge upstream Cake Wallet v6.5.0
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535
upstream commits, including the Monero silent-open fix, Monero on Trezor,
Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin
sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive
adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9.

Merged with both sides normalized first (import rename, 100-column
format), which took the conflict count from 614 paths to 322. The
procedure is now scripts/upstream_sync.sh.

Kept from Hash Bags:
- WalletType enum order and Hive IDs. SQLite stores WalletType.index and
  our order differs from upstream's; taking theirs would reread saved
  wallets as other chains. Our frozen adapter keeps the shipped IDs.
- The 1.0.2 legacy PIN and wallet-password decode and self-heal.
- Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new
  Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out.
- Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me),
  converted to upstream's isDefault node-list key; Wownero is back in the
  node reset list.
- Privacy: address resolvers limited to domain-anchored sources again;
  prices.neroswap.com for fiat; upstream's charts tab, which reads
  prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false.
- Lightning stays behind FeatureFlag.isLightningEnabled, including
  upstream's new post-seed Lightning username step.
- Our icon pipeline, branding and bundle identifiers.

Twelve new upstream widget tests are skipped: test and code are unchanged
from v6.5.0 apart from renames (not rerun on a pristine upstream checkout;
upstream's top_bar_test does not compile against its own TopBar).
flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped;
flutter build bundle compiles. Built with Flutter 3.41.9.
2026-10-24 23:49:29 -04:00

259 lines
12 KiB
Dart

import 'package:hash_wallet/entities/bitcoin_amount_display_mode.dart';
import 'package:hash_wallet/entities/language_service.dart';
import 'package:hash_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:hash_wallet/entities/new_ui_entities/list_item/list_item_selector.dart';
import 'package:hash_wallet/entities/new_ui_entities/list_item/list_item_toggle.dart';
import 'package:hash_wallet/entities/sync_status_display_mode.dart';
import 'package:hash_wallet/generated/i18n.dart';
import 'package:hash_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart';
import 'package:hash_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:hash_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
import 'package:hash_wallet/src/screens/settings/widgets/settings_theme_choice.dart';
import 'package:hash_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:hash_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:hash_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:hash_wallet/src/widgets/picker.dart';
import 'package:hash_wallet/src/widgets/standard_list.dart';
import 'package:hash_wallet/utils/device_info.dart';
import 'package:hash_wallet/utils/feature_flag.dart';
import 'package:hash_wallet/utils/responsive_layout_util.dart';
import 'package:hash_wallet/utils/show_pop_up.dart';
import 'package:hash_wallet/view_model/settings/choices_list_item.dart';
import 'package:hash_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';
class DisplaySettingsPage extends StatelessWidget {
DisplaySettingsPage(this._displaySettingsViewModel);
final DisplaySettingsViewModel _displaySettingsViewModel;
@override
Widget build(BuildContext context) {
return ModalPageWrapper(
topBar: ModalTopBar(
title: S.of(context).display,
leadingIcon: Icon(Icons.arrow_back_ios_new),
leadingSemanticLabel: S.of(context).seed_alert_back,
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);
}),
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: () => FiatCurrencyPickerSheet.show(
context: context,
selected: _displaySettingsViewModel.fiatCurrency,
onSelected: _displaySettingsViewModel.setFiatCurrency,
)),
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
Future<void> _pickImage(BuildContext context) async {
if (_displaySettingsViewModel.backgroundImage.isNotEmpty) {
final bool? shouldReplace = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).replace,
alertContent: S.of(context).customBackgroundDescription,
rightButtonText: S.of(context).replace,
leftButtonText: S.of(context).remove,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
});
if (shouldReplace == false) {
// remove the current background by setting it as an empty string
_displaySettingsViewModel.setBackgroundImage("");
return;
} else if (shouldReplace == null) {
// user didn't choose anything, then just return
return;
}
}
final ImagePicker picker = ImagePicker();
// Pick an image from the gallery
final XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
_displaySettingsViewModel.setBackgroundImage(pickedFile.path);
}
}
}