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.
This commit is contained in:
jwinterm
2026-10-24 23:49:29 -04:00
1406 changed files with 81062 additions and 48972 deletions
-1
View File
@@ -1 +0,0 @@
enum Attributes { arrow, header, link, switcher, widget, rawWidget }
@@ -34,6 +34,7 @@ class ConnectionSyncPage extends BasePage {
topBar: ModalTopBar(
title: "",
leadingIcon: Icon(Icons.arrow_back_ios_new),
leadingSemanticLabel: S.of(context).seed_alert_back,
onLeadingPressed: () => Navigator.of(context).pop()),
header: ModalHeader(
iconPath: "assets/new-ui/settings_row_icons/connections.svg",
@@ -88,10 +89,18 @@ class ConnectionSyncPage extends BasePage {
ListItemToggle(
keyValue: "can_use_basescan",
label: S.of(context).basescan_history,
value: _connectionSyncViewModel.canUseBaseScan,
value: _connectionSyncViewModel.useBaseScan,
onChanged: (val) {
_connectionSyncViewModel.setUseBaseScan(val);
}),
if (_connectionSyncViewModel.canUseRobinhoodScan)
ListItemToggle(
keyValue: "can_use_robinhoodscan",
label: S.of(context).robinhoodscan_history,
value: _connectionSyncViewModel.useRobinhoodScan,
onChanged: (val) {
_connectionSyncViewModel.setUseRobinhoodScan(val);
}),
if (_connectionSyncViewModel.canUseArbiScan)
ListItemToggle(
keyValue: "can_use_arbiscan",
@@ -198,6 +207,11 @@ class ConnectionSyncPage extends BasePage {
keyValue: "domain_lookups",
label: S.of(context).domain_looks_up,
onTap: () => Navigator.of(context).pushNamed(Routes.domainLookupsPage)),
if (_connectionSyncViewModel.hasRescan)
ListItemRegularRow(
keyValue: "rescan",
label: S.of(context).rescan,
onTap: () => Navigator.of(context).pushNamed(Routes.rescan))
],
})),
],
@@ -1,119 +0,0 @@
import 'package:hash_wallet/generated/i18n.dart';
import 'package:hash_wallet/routes.dart';
import 'package:hash_wallet/src/widgets/setting_action_button.dart';
import 'package:hash_wallet/src/widgets/setting_actions.dart';
import 'package:hash_wallet/typography.dart';
import 'package:hash_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/material.dart';
import 'package:hash_wallet/router.dart' as Router;
final _settingsNavigatorKey = GlobalKey<NavigatorState>();
class DesktopSettingsPage extends StatefulWidget {
const DesktopSettingsPage(this.dashboardViewModel, {super.key});
final DashboardViewModel dashboardViewModel;
@override
State<DesktopSettingsPage> createState() => _DesktopSettingsPageState();
}
class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
final int itemCount = SettingActions.all.length;
int? currentPage;
void _onItemChange(int index) {
setState(() {
currentPage = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Theme.of(context).colorScheme.surface,
height: MediaQuery.of(context).size.height,
child: Row(
children: [
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 4),
child: Text(
S.current.settings,
style: textXLarge(),
),
),
Expanded(
child: ListView.separated(
padding: EdgeInsets.only(top: 0),
itemBuilder: (_, index) {
final item = SettingActions.all[index];
if (!widget.dashboardViewModel.hasSilentPayments &&
item.name(context) == S.of(context).silent_payments_settings) {
return Container();
}
if ((!widget.dashboardViewModel.isMoneroViewOnly &&
item.name(context) == S.of(context).export_outputs) ||
(!widget.dashboardViewModel.hasMweb &&
item.name(context) == S.of(context).litecoin_mweb_settings)) {
return Container();
}
final isLastTile = index == itemCount - 1;
return SettingActionButton(
isLastTile: isLastTile,
selectionActive: currentPage != null,
isSelected: currentPage == index,
isArrowVisible: true,
onTap: () {
if (currentPage != index) {
final settingContext =
_settingsNavigatorKey.currentState?.context ?? context;
item.onTap.call(settingContext);
_onItemChange(index);
}
},
image: item.image,
title: item.name.call(context),
);
},
separatorBuilder: (_, index) => Container(
height: 0.0,
color: Theme.of(context).colorScheme.outlineVariant,
),
itemCount: itemCount,
),
),
],
),
),
Flexible(
flex: 2,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Navigator(
key: _settingsNavigatorKey,
initialRoute: Routes.empty_no_route,
onGenerateRoute: (settings) => Router.createRoute(settings),
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
return [
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
];
},
),
),
)
],
),
),
);
}
}
@@ -1,11 +1,11 @@
import 'package:hash_wallet/entities/bitcoin_amount_display_mode.dart';
import 'package:hash_wallet/entities/fiat_currency.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';
@@ -35,6 +35,7 @@ class DisplaySettingsPage extends StatelessWidget {
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(
@@ -115,21 +116,12 @@ class DisplaySettingsPage extends StatelessWidget {
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);
@@ -175,35 +167,11 @@ class DisplaySettingsPage extends StatelessWidget {
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,
),
);
}),
onTap: () => FiatCurrencyPickerSheet.show(
context: context,
selected: _displaySettingsViewModel.fiatCurrency,
onSelected: _displaySettingsViewModel.setFiatCurrency,
)),
ListItemSelector(
keyValue: "display_settings_language",
label: S.of(context).settings_change_language,
@@ -1,6 +1,8 @@
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
import 'package:hash_wallet/generated/i18n.dart';
import 'package:hash_wallet/src/screens/base_page.dart';
import 'package:hash_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:hash_wallet/src/widgets/hash_image_widget.dart';
import 'package:hash_wallet/view_model/settings/connection_sync_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -20,22 +22,29 @@ class DomainLookupsPage extends BasePage {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
SettingsSwitcherCell(
title: 'OpenAlias',
value: _connectionsSyncViewModel.looksUpOpenAlias,
onValueChange: (_, bool value) =>
_connectionsSyncViewModel.setLookupsOpenAlias(value)),
SettingsSwitcherCell(
title: 'Ethereum Name Service',
value: _connectionsSyncViewModel.looksUpENS,
onValueChange: (_, bool value) => _connectionsSyncViewModel.setLookupsENS(value)),
SettingsSwitcherCell(
title: '.well-known',
value: _connectionsSyncViewModel.looksUpWellKnown,
onValueChange: (_, bool value) =>
_connectionsSyncViewModel.setLookupsWellKnown(value)),
],
// Hash Bags: only domain-anchored resolvers get a toggle (see PRIVACY.md).
children: _connectionsSyncViewModel.domainLookupSources
.where((source) => const [
AddressSource.openAlias,
AddressSource.ens,
AddressSource.wellKnown,
].contains(source))
.map(
(source) => SettingsSwitcherCell(
title: source.label,
leading: source.iconPath.isNotEmpty
? HashImageWidget(
imageUrl: source.iconPath,
width: 24,
height: 24,
)
: SizedBox(width: 24, height: 24),
value: _connectionsSyncViewModel.lookupValue(source),
onValueChange: (_, bool value) =>
_connectionsSyncViewModel.setLookupValue(source, value),
),
)
.toList(),
),
);
}),
@@ -1,18 +0,0 @@
class ItemHeaders {
static const nodes = 'Nodes';
static const currentNode = 'Current node';
static const wallets = 'Wallets';
static const displayBalanceAs = 'Display balance as';
static const currency = 'Currency';
static const feePriority = 'Fee priority';
static const saveRecipientAddress = 'Save recipient address';
static const personal = 'Personal';
static const changePIN = 'Change PIN';
static const changeLanguage = 'Change language';
static const allowBiometricalAuthentication = 'Allow biometrical authentication';
static const darkMode = 'Dark mode';
static const support = 'Support';
static const termsAndConditions = 'Terms and conditions';
static const faq = 'FAQ';
static const version = 'Version';
}
@@ -1,21 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hash_wallet/src/screens/settings/attributes.dart';
class SettingsItem {
SettingsItem(
{required this.onTaped,
required this.title,
required this.link,
required this.image,
required this.widget,
required this.attribute,
required this.widgetBuilder});
final VoidCallback onTaped;
final String title;
final String link;
final Image image;
final Widget widget;
final Attributes attribute;
final WidgetBuilder widgetBuilder;
}
+110 -113
View File
@@ -1,166 +1,163 @@
import 'package:hash_wallet/generated/i18n.dart';
import 'package:hash_wallet/new-ui/widgets/modal_header.dart';
import 'package:hash_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:hash_wallet/new-ui/widgets/modern_button.dart';
import 'package:hash_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:hash_wallet/routes.dart';
import 'package:hash_wallet/src/screens/base_page.dart';
import 'package:hash_wallet/src/screens/nodes/widgets/node_list_row.dart';
import 'package:hash_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:hash_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:hash_wallet/src/widgets/standard_list.dart';
import 'package:hash_wallet/utils/feature_flag.dart';
import 'package:hash_wallet/utils/show_pop_up.dart';
import 'package:hash_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:hash_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:hash_wallet/view_model/node_list/pow_node_list_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
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, this.dashboardViewModel})
: assert((isPow && powNodeListViewModel != null) || (!isPow && nodeListViewModel != null)),
assert(powNodeListViewModel == null || nodeListViewModel == null);
class ManageNodesPage extends StatefulWidget {
ManageNodesPage(this.isPow, {required this.nodeListViewModel, this.dashboardViewModel});
final DashboardViewModel? dashboardViewModel;
final NodeListViewModel? nodeListViewModel;
final PowNodeListViewModel? powNodeListViewModel;
final NodeListViewModel nodeListViewModel;
final bool isPow;
@override
bool get hideAppBar => true;
State<ManageNodesPage> createState() => _ManageNodesPageState();
}
class _ManageNodesPageState extends State<ManageNodesPage> {
@override
void initState() {
super.initState();
Future.delayed(Duration(milliseconds: 300))
.then((_) => widget.nodeListViewModel.speedTestNodes());
}
@override
String get title => S.current.manage_nodes;
@override
Widget body(BuildContext context) {
Widget build(BuildContext context) {
return ModalPageWrapper(
horizontalPadding: 0,
topBar: ModalTopBar(
title: S.of(context).manage_nodes,
leadingIcon: Icon(Icons.arrow_back_ios_new),
leadingSemanticLabel: S.of(context).seed_alert_back,
onLeadingPressed: () => Navigator.of(context).pop(),
trailingWidget: Row(
spacing: 8,
children: [
if (dashboardViewModel?.hasRescan ?? true)
ModernButton(
Observer(
builder: (_) => ModernButton(
size: 36,
icon: Icon(Icons.history),
onPressed: () => Navigator.of(context).pushNamed(Routes.rescan)),
icon: widget.nodeListViewModel.isTestingNodeSpeed
? CupertinoActivityIndicator()
: Icon(Icons.refresh),
semanticLabel: S.of(context).test_node_speeds,
onPressed: () => widget.nodeListViewModel.speedTestNodes()),
),
ModernButton(
size: 36,
icon: Icon(Icons.add),
onPressed: () =>
Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode))
semanticLabel: S.of(context).add_new_node,
onPressed: () async {
final res = await Navigator.of(context)
.pushNamed(widget.isPow ? Routes.newPowNode : Routes.newNode);
if (res != null && res is Node) {
widget.nodeListViewModel.nodes.add(res);
}
})
],
),
),
content: Column(
children: [
if (FeatureFlag.isAutomaticNodeSwitchingEnabled)
Observer(
builder: (_) => SettingsSwitcherCell(
key: ValueKey('manage_nodes_page_enable_auto_node_switching_button_key'),
title: S.current.enable_auto_node_switching,
value: isPow
? powNodeListViewModel!.enableAutomaticNodeSwitching
: nodeListViewModel!.enableAutomaticNodeSwitching,
onValueChange: (BuildContext context, bool value) {
if (isPow) {
powNodeListViewModel!.setEnableAutomaticNodeSwitching(value);
} else {
nodeListViewModel!.setEnableAutomaticNodeSwitching(value);
header: ModalHeader(
iconPath: "assets/new-ui/settings_row_icons/nodes.svg",
message: S.of(context).nodes_desc,
title: S.of(context).nodes,
bottomWidget: Observer(
builder: (_) => NodeListRow(
// vertical: 0,
// horizontal: 0,
node: widget.nodeListViewModel.currentNode,
speed: widget.nodeListViewModel.nodeSpeedFor(widget.nodeListViewModel.currentNode),
onEditComplete: (res) async {
if (res != null && res is Node) {
widget.nodeListViewModel.nodes.removeWhere((item) => item.id == res.id);
widget.nodeListViewModel.nodes.add(res);
}
},
),
),
if (FeatureFlag.isAutomaticNodeSwitchingEnabled) SizedBox(height: 8),
Observer(
builder: (BuildContext context) {
int itemsCount =
nodeListViewModel?.nodes.length ?? powNodeListViewModel!.nodes.length;
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,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: nodeListViewModel?.getAlertContent(node.uriRaw) ??
powNodeListViewModel!.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () async {
if (isPow) {
await powNodeListViewModel!.setAsCurrent(node);
} else {
await nodeListViewModel!.setAsCurrent(node);
}
Navigator.of(context).pop();
onTap: () {},
isSelected: true,
isPow: widget.isPow)),
),
content: Column(
children: [
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(18)),
child: ClipRRect(
borderRadius: BorderRadius.circular(18),
child: Observer(
builder: (BuildContext context) {
int itemsCount = widget.nodeListViewModel.nonCurrentNodes.length;
return ListView.separated(
controller: ModalScrollController.of(context),
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemCount: itemsCount,
separatorBuilder: (context, index) => Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Container(
height: 1,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
),
),
itemBuilder: (_, index) {
return Observer(
builder: (context) {
final node = widget.nodeListViewModel.nonCurrentNodes[index];
final nodeListRow = NodeListRow(
node: node,
isSelected: false,
isPow: widget.isPow,
speed: widget.nodeListViewModel.nodeSpeedFor(node),
onEditComplete: (res) async {
if (res != null && res is Node) {
widget.nodeListViewModel.nodes
.removeWhere((item) => item.id == res.id);
widget.nodeListViewModel.nodes.add(res);
}
},
onTap: () async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent:
widget.nodeListViewModel.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () async {
await widget.nodeListViewModel.setAsCurrent(node);
Navigator.of(context).pop();
},
);
},
);
},
);
return nodeListRow;
},
);
return nodeListRow;
},
);
},
);
},
),
),
),
],
),
);
}
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());
},
);
}
}
@@ -34,6 +34,7 @@ class _MwebNodePageState extends State<MwebNodePage> {
topBar: ModalTopBar(
title: S.current.litecoin_mweb_settings,
onLeadingPressed: Navigator.of(context).pop,
leadingSemanticLabel: S.current.seed_alert_back,
leadingIcon: Icon(Icons.arrow_back_ios_new)),
content: Container(
child: NewListSections(controllers: {
@@ -1,6 +1,10 @@
import 'dart:io';
import 'package:hash_wallet/bitcoin/bitcoin.dart';
import "package:hash_wallet/ci_build_overlay.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/priority_for_wallet_type.dart';
import 'package:hash_wallet/generated/i18n.dart';
import 'package:hash_wallet/new-ui/widgets/modal_page_wrapper.dart';
@@ -8,17 +12,21 @@ import 'package:hash_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:hash_wallet/routes.dart';
import 'package:hash_wallet/src/screens/base_page.dart';
import 'package:hash_wallet/src/screens/dev/moneroc_cache_debug.dart';
import "package:hash_wallet/src/widgets/alert_with_one_action.dart";
import 'package:hash_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:hash_wallet/src/widgets/picker.dart';
import 'package:hash_wallet/utils/feature_flag.dart';
import 'package:hash_wallet/utils/share_util.dart';
import 'package:hash_wallet/utils/show_pop_up.dart';
import 'package:hash_wallet/view_model/settings/other_settings_view_model.dart';
import 'package:cw_core/root_dir.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";
import 'package:path/path.dart' as p;
class OtherSettingsPage extends BasePage {
OtherSettingsPage(this._otherSettingsViewModel) {
@@ -38,13 +46,14 @@ class OtherSettingsPage extends BasePage {
topBar: ModalTopBar(
title: S.of(context).other,
leadingIcon: Icon(Icons.arrow_back_ios_new),
leadingSemanticLabel: S.of(context).seed_alert_back,
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: {
content: Observer(builder: (_) => NewListSections(sections: {
"": [
if (_otherSettingsViewModel.displayTransactionPriority)
_otherSettingsViewModel.walletType == WalletType.bitcoin
@@ -138,6 +147,7 @@ class OtherSettingsPage extends BasePage {
},
}),
),
if(_otherSettingsViewModel.hasSignVerify)
ListItemRegularRow(
keyValue: "security_backup_page_sign_and_verify",
label: S.of(context).sign_verify_title,
@@ -147,10 +157,12 @@ class OtherSettingsPage extends BasePage {
],
if (_otherSettingsViewModel.walletType == WalletType.bitcoin)
"btc_logging": [
if(_otherSettingsViewModel.hasLightning)
ListItemRegularRow(
keyValue: "export_lightning_logs",
label: S.of(context).export_lightning_logs,
onTap: () => onExportLNLog(context)),
if(_otherSettingsViewModel.hasPayjoin)
ListItemRegularRow(
keyValue: "export_payjoin_logs",
label: S.of(context).export_payjoin_logs,
@@ -159,6 +171,24 @@ class OtherSettingsPage extends BasePage {
"dev": FeatureFlag.hasDevOptions == false
? []
: [
if (CiBuildOverlay.isCiBuild)
ListItemToggle(
keyValue: "[dev] show commit/branch overlay",
label: "[dev] show commit/branch overlay",
value: _otherSettingsViewModel.showCiBuildOverlay,
onChanged: (val){
if(!val) {
showPopUp(
context: context,
builder: (context) => AlertWithOneAction(
alertTitle: "overlay disabled",
alertContent:
"don't send issue reports without the overlay, pretty please",
buttonText: "okay",
buttonAction: Navigator.of(context).pop,
));
}
_otherSettingsViewModel.setShowCiBuildOverlay(val);}),
if (_otherSettingsViewModel.walletType == WalletType.monero)
ListItemRegularRow(
keyValue: "[dev] monero background sync",
@@ -215,8 +245,15 @@ class OtherSettingsPage extends BasePage {
),
);
}),
ListItemRegularRow(
keyValue: '[dev] fake corrupt sqlite db',
label: '[dev] fake corrupt sqlite db',
onTap: () async {
final dbDebugMarker = await sqliteDebugMarkerFile();
dbDebugMarker.create();
}),
]
}),
})),
);
}
+1 -23
View File
@@ -1,10 +1,8 @@
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_toggle.dart';
import 'package:hash_wallet/generated/i18n.dart';
import 'package:hash_wallet/new-ui/widgets/modal_header.dart';
import 'package:hash_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:hash_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:hash_wallet/routes.dart';
import 'package:hash_wallet/src/screens/base_page.dart';
import 'package:hash_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:hash_wallet/view_model/settings/privacy_settings_view_model.dart';
@@ -25,6 +23,7 @@ class PrivacyPage extends BasePage {
topBar: ModalTopBar(
title: "",
leadingIcon: Icon(Icons.arrow_back_ios_new),
leadingSemanticLabel: S.of(context).seed_alert_back,
onLeadingPressed: () => Navigator.of(context).pop(),
),
header: ModalHeader(
@@ -73,27 +72,6 @@ class PrivacyPage extends BasePage {
_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.hasMWEB)
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)),
],
}),
],
);
@@ -45,6 +45,7 @@ class SecurityBackupPage extends BasePage {
topBar: ModalTopBar(
title: "",
leadingIcon: Icon(Icons.arrow_back_ios_new),
leadingSemanticLabel: S.of(context).seed_alert_back,
onLeadingPressed: () => Navigator.of(context).pop()),
header: ModalHeader(
iconPath: "assets/new-ui/settings_row_icons/security.svg",
@@ -22,6 +22,7 @@ class SilentPaymentsSettingsPage extends StatelessWidget {
ModalTopBar(
title: S.current.silent_payments_settings,
leadingIcon: Icon(Icons.arrow_back_ios_new),
leadingSemanticLabel: S.current.seed_alert_back,
onLeadingPressed: Navigator.of(context).pop,
),
Expanded(
@@ -31,14 +32,6 @@ class SilentPaymentsSettingsPage extends StatelessWidget {
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
if (!FeatureFlag.hasNewUi)
SettingsSwitcherCell(
title: S.current.silent_payments_display_card,
value: _silentPaymentsSettingsViewModel.silentPaymentsCardDisplay,
onValueChange: (_, bool value) {
_silentPaymentsSettingsViewModel.setSilentPaymentsCardDisplay(value);
},
),
SettingsSwitcherCell(
title: S.current.silent_payments_always_scan,
value: _silentPaymentsSettingsViewModel.silentPaymentsAlwaysScan,
@@ -1,109 +0,0 @@
import 'package:hash_wallet/generated/i18n.dart';
import 'package:hash_wallet/src/screens/base_page.dart';
import 'package:hash_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:hash_wallet/view_model/settings/trocador_providers_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class TrocadorProvidersPage extends BasePage {
TrocadorProvidersPage(this.trocadorProvidersViewModel);
@override
String get title => 'Trocador Providers';
final TrocadorProvidersViewModel trocadorProvidersViewModel;
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Observer(
builder: (_) {
if (trocadorProvidersViewModel.isLoading) {
return Center(child: CircularProgressIndicator());
}
var providerStates = trocadorProvidersViewModel.providerStates;
final providerRatings = trocadorProvidersViewModel.providerRatings;
if (providerStates.isEmpty) {
return Center(child: Text(S.of(context).no_providers_available));
}
return ListView.builder(
itemCount: providerStates.length,
itemBuilder: (_, index) {
final providerName = providerStates.keys.elementAt(index);
final providerEnabled = providerStates[providerName] ?? true;
return SettingsSwitcherCell(
title: providerName,
value: providerEnabled,
leading: Badge(
title: 'KYC \nRATING',
subTitle: providerRatings[providerName] ?? 'N/A',
textColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
),
onValueChange: (BuildContext _, value) =>
trocadorProvidersViewModel.toggleProviderState(providerName),
);
},
);
},
),
);
}
}
class Badge extends StatelessWidget {
Badge(
{required this.textColor,
required this.backgroundColor,
required this.title,
required this.subTitle});
final String title;
final String subTitle;
final Color textColor;
final Color backgroundColor;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(right: 8),
child: FittedBox(
fit: BoxFit.fitHeight,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(24)), color: backgroundColor),
alignment: Alignment.center,
child: IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
title,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: textColor,
fontSize: 7,
fontWeight: FontWeight.w600,
),
),
VerticalDivider(
color: textColor,
thickness: 1,
),
Text(
subTitle,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: textColor,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
),
);
}
}
@@ -0,0 +1,202 @@
import "package:hash_wallet/core/execution_state.dart";
import "package:hash_wallet/generated/i18n.dart";
import "package:hash_wallet/new-ui/widgets/new_primary_button.dart";
import "package:hash_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
import "package:hash_wallet/src/widgets/alert_with_one_action.dart";
import "package:hash_wallet/src/widgets/hash_image_widget.dart";
import "package:hash_wallet/view_model/wallet_account_list/account_edit_or_create_view_model.dart";
import "package:cw_core/generate_name.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
class AccountCreationModal extends StatefulWidget {
const AccountCreationModal({required this.viewModel, super.key});
final WalletAccountEditOrCreateViewModel viewModel;
@override
State<AccountCreationModal> createState() => _AccountCreationModalState();
}
class _AccountCreationModalState extends State<AccountCreationModal> {
static const int maxAccountNameLength = 25;
final TextEditingController _controller = TextEditingController();
bool _loading = false;
bool get _canContinue =>
_controller.text.trim().isNotEmpty && _controller.text.length <= maxAccountNameLength;
Future<void> _generateAccountName() async {
final generatedName = await generateName();
if (!mounted) {
return;
}
setState(() => _controller.text = generatedName.length > maxAccountNameLength
? generatedName.substring(0, maxAccountNameLength)
: generatedName);
}
Future<void> _save() async {
if (_loading || !_canContinue) {
return;
}
setState(() => _loading = true);
widget.viewModel.label = _controller.text;
late final String errorMessage;
try {
await widget.viewModel.save();
if (!mounted) {
return;
}
final state = widget.viewModel.state;
if (state is ExecutedSuccessfullyState) {
Navigator.of(context).pop(true);
return;
}
errorMessage = state is FailureState ? state.error : S.of(context).error_while_processing;
} catch (error) {
errorMessage = error.toString();
}
if (!mounted) {
return;
}
setState(() => _loading = false);
await showDialog<void>(
context: context,
builder: (dialogContext) => AlertWithOneAction(
alertTitle: S.of(dialogContext).error,
alertContent: errorMessage,
buttonText: S.of(dialogContext).ok,
buttonAction: Navigator.of(dialogContext).pop,
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => PopScope(
canPop: !_loading,
child: _buildContent(context),
);
Widget _buildContent(BuildContext context) => Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
),
child: SafeArea(
top: false,
child: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom),
child: Column(
children: [
ModalTopBar(
title: S.of(context).create_account,
trailingIcon: const Icon(Icons.close),
trailingSemanticLabel: S.of(context).close,
onTrailingPressed: Navigator.of(context).maybePop,
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(18, 32, 18, 24),
child: Column(
spacing: 24,
children: [
const HashImageWidget(
imageUrl: "assets/new-ui/account_education/create_account.svg",
width: 125,
height: 125,
),
Text(
S.of(context).account_creation_description,
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 14,
),
),
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
maxLength: maxAccountNameLength,
inputFormatters: [
LengthLimitingTextInputFormatter(maxAccountNameLength)
],
textInputAction: TextInputAction.done,
onChanged: (_) => setState(() {}),
decoration: InputDecoration(
hintText: S.of(context).account_name,
counterText: "",
),
),
),
Padding(
padding: const EdgeInsets.all(12),
child: Semantics(
button: true,
label: S.of(context).generate_name,
onTap: _generateAccountName,
child: ExcludeSemantics(
child: GestureDetector(
onTap: _generateAccountName,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(5),
),
child: HashImageWidget(
imageUrl: "assets/new-ui/randomize.svg",
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.primary,
BlendMode.srcIn,
),
),
),
),
),
),
),
],
),
),
],
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(18, 0, 18, 12),
child: SizedBox(
width: double.infinity,
child: NewPrimaryButton(
onPressed: _save,
text: S.of(context).continue_text,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: _loading,
disabled: !_canContinue,
),
),
),
],
),
),
),
);
}
@@ -1,79 +0,0 @@
import 'package:hash_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:hash_wallet/src/widgets/picker.dart';
import 'package:hash_wallet/src/widgets/standard_list.dart';
class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
SettingsPriorityPickerCell({
required String title,
required this.selectedItem,
required this.items,
this.displayItem,
this.images,
this.searchHintText,
this.isGridView = false,
this.matchingCriteria,
this.customValue,
this.maxValue,
this.customItemIndex,
this.onItemSelected,
}) : super(
title: title,
isSelected: false,
onTap: (BuildContext context) async {
var selectedAtIndex = items.indexOf(selectedItem);
double sliderValue = customValue ?? 0.0;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Picker(
items: items,
displayItem: (ItemType item) => displayItem!(item, sliderValue.round()),
selectedAtIndex: selectedAtIndex,
customItemIndex: customItemIndex,
maxValue: maxValue,
headerEnabled: false,
closeOnItemSelected: false,
mainAxisAlignment: MainAxisAlignment.center,
sliderValue: sliderValue,
onSliderChanged: (double newValue) =>
setState(() => sliderValue = newValue),
onItemSelected: (ItemType priority) {
setState(() => selectedAtIndex = items.indexOf(priority));
onItemSelected?.call(priority, sliderValue);
},
);
},
);
},
);
onItemSelected?.call(items[selectedAtIndex], sliderValue);
});
final ItemType selectedItem;
final List<ItemType> items;
final void Function(ItemType item, double customValue)? onItemSelected;
final String Function(ItemType item, int value)? displayItem;
final List<Image>? images;
final String? searchHintText;
final bool isGridView;
final bool Function(ItemType, String)? matchingCriteria;
double? customValue;
double? maxValue;
int? customItemIndex;
@override
Widget buildTrailing(BuildContext context) {
return Text(
displayItem?.call(selectedItem, customValue?.round() ?? 0) ?? selectedItem.toString(),
textAlign: TextAlign.right,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
}
}
@@ -1,9 +0,0 @@
import 'package:flutter/cupertino.dart';
class SettingsPickerRaw extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
@@ -1,27 +0,0 @@
import 'package:flutter/material.dart';
class SettingsVersionCell extends StatelessWidget {
SettingsVersionCell({required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: 24),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
title,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.normal,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
}
}
@@ -1,42 +0,0 @@
import 'package:hash_wallet/generated/i18n.dart';
import 'package:flutter/material.dart';
class WalletConnectTile extends StatelessWidget {
const WalletConnectTile({required this.onTap});
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Padding(
padding: EdgeInsets.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/images/walletconnect_logo.png',
height: 24,
width: 24,
),
SizedBox(width: 16),
Expanded(
child: Text(
S.current.walletConnect,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
),
),
Image.asset(
'assets/images/select_arrow.png',
color: Theme.of(context).colorScheme.onSurfaceVariant,
)
],
),
),
);
}
}