Files
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

349 lines
16 KiB
Dart

import "dart:async";
import "package:hash_wallet/bitcoin/bitcoin.dart";
import "package:hash_wallet/core/auth_service.dart";
import "package:hash_wallet/di.dart";
import "package:hash_wallet/generated/i18n.dart";
import "package:hash_wallet/main.dart";
import "package:hash_wallet/new-ui/modal_navigator.dart";
import "package:hash_wallet/new-ui/pages/card_customizer.dart";
import "package:hash_wallet/new-ui/pages/account_education_page.dart";
import "package:hash_wallet/new-ui/widgets/coins_page/accounts_promo.dart";
import "package:hash_wallet/src/widgets/alert_with_one_action.dart";
import "package:cw_core/sync_status.dart";
import "package:hash_wallet/new-ui/pages/seed/seed_backup_reminder_page.dart";
import "package:hash_wallet/new-ui/pages/send_page.dart";
import "package:hash_wallet/new-ui/pages/settings_page.dart";
import "package:hash_wallet/new-ui/pages/wallet_accounts_page.dart";
import "package:hash_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart";
import "package:hash_wallet/new-ui/widgets/coins_page/action_row/coin_action_row.dart";
import "package:hash_wallet/new-ui/widgets/coins_page/assets_history/assets_history_section.dart";
import "package:hash_wallet/new-ui/widgets/coins_page/cards/balance_card.dart";
import "package:hash_wallet/new-ui/widgets/coins_page/cards/cards_view.dart";
import "package:hash_wallet/new-ui/widgets/coins_page/mweb_ad.dart";
import "package:hash_wallet/new-ui/widgets/coins_page/seed_backup_reminder_card.dart";
import "package:hash_wallet/new-ui/widgets/coins_page/top_bar_widget/top_bar.dart";
import "package:hash_wallet/new-ui/widgets/coins_page/unconfirmed_balance_widget.dart";
import "package:hash_wallet/new-ui/widgets/coins_page/zcash_migration_modal.dart";
import "package:hash_wallet/routes.dart";
import "package:hash_wallet/utils/payment_request.dart";
import "package:hash_wallet/view_model/dashboard/dashboard_view_model.dart";
import "package:hash_wallet/view_model/dashboard/nft_view_model.dart";
import "package:cw_core/unspent_coin_type.dart";
import "package:cw_core/wallet_type.dart";
import "package:flutter/cupertino.dart";
import "package:flutter/material.dart";
import "package:flutter_mobx/flutter_mobx.dart";
import "package:mobx/mobx.dart";
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
class NewHomePage extends StatefulWidget {
const NewHomePage({required this.dashboardViewModel, required this.nftViewModel, super.key});
final DashboardViewModel dashboardViewModel;
final NFTViewModel nftViewModel;
@override
State<NewHomePage> createState() => _NewHomePageState();
}
class _NewHomePageState extends State<NewHomePage> with RouteAware {
late final ReactionDisposer _walletReaction;
late final ReactionDisposer _migrationReaction;
@override
void initState() {
super.initState();
_walletReaction = reaction((_) => widget.dashboardViewModel.wallet, (_) {
setState(() {});
});
_migrationReaction = reaction((_) => widget.dashboardViewModel.isMigratingToIronwood, (val) {
if (val && !widget.dashboardViewModel.settingsStore.zcashMigrationModalViewed) {
if (!context.mounted) {
return;
}
widget.dashboardViewModel.settingsStore.zcashMigrationModalViewed = true;
showMaterialModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ZcashMigrationModal(
hasContinue: true,
balance: widget.dashboardViewModel.wallet.balance.values.first.unavailable
.toStringWithSymbol(),
),
);
}
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute);
}
@override
void dispose() {
_walletReaction();
_migrationReaction();
routeObserver.unsubscribe(this);
super.dispose();
}
@override
void didPopNext() => widget.dashboardViewModel.loadSeedBackupReminder();
@override
Widget build(BuildContext context) => Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context).colorScheme.surface,
Theme.of(context).colorScheme.surfaceDim,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: Stack(
children: [
CustomScrollView(
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
slivers: [
SliverPadding(
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
sliver: CupertinoSliverRefreshControl(
refreshTriggerPullDistance: 160,
refreshIndicatorExtent: 90,
onRefresh: () {
unawaited(widget.nftViewModel.getNFTAssetByWallet());
return widget.dashboardViewModel.refreshDashboard();
},
),
),
SliverToBoxAdapter(
child: Observer(
builder: (_) {
final _lightningMode = widget.dashboardViewModel.lightningMode;
return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
spacing: 24.0,
children: [
TopBar(
key: ValueKey(widget.dashboardViewModel.wallet.id),
dashboardViewModel: widget.dashboardViewModel,
onLightningSwitchPress: () {
widget.dashboardViewModel.toggleLightningMode();
},
onSettingsButtonPress: () {
CupertinoScaffold.showCupertinoModalBottomSheet(
context: context,
barrierColor: Colors.black.withAlpha(85),
builder: (context) => FractionallySizedBox(
child: Material(
child: NewSettingsPage(
dashboardViewModel: widget.dashboardViewModel,
authService: getIt.get<AuthService>(),
),
),
),
).then((_) async {
widget.dashboardViewModel.accountListViewModel?.reload();
await Future<void>.delayed(Duration.zero);
await widget.dashboardViewModel.loadCardDesigns();
if (mounted) setState(() {});
});
},
),
Column(
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeOut,
layoutBuilder: (currentChild, previousChildren) => Stack(
// the front (selected) card sits at the bottom of the stack box
alignment: Alignment.bottomCenter,
children: <Widget>[
...previousChildren,
if (currentChild != null) currentChild,
],
),
child: KeyedSubtree(
key: ValueKey(_lightningMode),
child: CardsView(
key: ValueKey(
"${widget.dashboardViewModel.wallet.name}_${_lightningMode}_${widget.dashboardViewModel.accountListViewModel?.accounts.length ?? 0}_${widget.dashboardViewModel.cardDesigns.length}"),
onCustomizeTapped: openCardCustomizer,
dashboardViewModel: widget.dashboardViewModel,
accountListViewModel:
widget.dashboardViewModel.accountListViewModel,
onCompactModeBackgroundCardsTapped: openAccountCustomizer,
lightningMode: _lightningMode),
),
),
Observer(
builder: (_) => AnimatedSize(
duration: const Duration(milliseconds: 150),
curve: Curves.easeInOutCubic,
child: (widget.dashboardViewModel
.shouldShowBalanceHiddenMessage)
? Column(
children: [
const SizedBox(
height: 12,
width: double.infinity,
),
Text(
S.of(context).long_press_show_balance,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant),
)
],
)
: const SizedBox(width: double.infinity),
)),
UnconfirmedBalanceWidget(
dashboardViewModel: widget.dashboardViewModel),
SeedBackupReminderCard(
dashboardViewModel: widget.dashboardViewModel,
onTap: openSeedBackupReminder,
),
],
),
if (AccountsPromo.supportsWallet(widget.dashboardViewModel.wallet.type) &&
widget.dashboardViewModel.accountListViewModel != null &&
!_lightningMode)
AccountsPromo(
settingsStore: widget.dashboardViewModel.settingsStore,
walletName: walletTypeToString(widget.dashboardViewModel.wallet.type),
onTap: _openAccountsFromPromo,
),
Column(
children: [
CoinActionRow(
lightningMode: _lightningMode,
showSwap: widget.dashboardViewModel.isEnabledSwapAction,
walletType: widget.dashboardViewModel.wallet.type,
),
MwebAd(
dashboardViewModel: widget.dashboardViewModel,
),
],
)
],
);
},
),
),
Observer(
builder: (_) => AssetsHistorySection(
nftViewModel: widget.nftViewModel,
dashboardViewModel: widget.dashboardViewModel,
),
),
const SliverToBoxAdapter(
child: SizedBox(height: 80.0),
)
]),
Container(
height: (MediaQuery.of(context).padding.top),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: <Color>[
Theme.of(context).colorScheme.surface.withAlpha(5),
Theme.of(context).colorScheme.surface.withAlpha(25),
Theme.of(context).colorScheme.surface.withAlpha(50),
Theme.of(context).colorScheme.surface.withAlpha(100),
Theme.of(context).colorScheme.surface.withAlpha(150),
Theme.of(context).colorScheme.surface.withAlpha(175),
Theme.of(context).colorScheme.surface.withAlpha(200),
],
),
),
),
],
),
);
Future<void> openCardCustomizer() async {
if (!_checkReadyToManage()) return;
await CardCustomizer.show(
context: context,
dashboardViewModel: widget.dashboardViewModel,
account: !widget.dashboardViewModel.lightningMode &&
widget.dashboardViewModel.isMultiAccountsEnabled
? widget.dashboardViewModel.accountListViewModel?.selectedAccount
: null,
lightningMode: widget.dashboardViewModel.lightningMode,
);
await widget.dashboardViewModel.accountListViewModel?.reload();
await widget.dashboardViewModel.loadCardDesigns();
}
void openSeedBackupReminder() {
Navigator.of(context).push(
CupertinoPageRoute<void>(
builder: (_) => SeedBackupReminderPage(
dashboardViewModel: widget.dashboardViewModel,
authService: getIt.get<AuthService>(),
),
),
);
}
Future<void> openAccountCustomizer() async {
if (widget.dashboardViewModel.accountListViewModel == null || !_checkReadyToManage()) return;
final page = getIt.get<WalletAccountsPage>(param1: widget.dashboardViewModel);
await CupertinoScaffold.showCupertinoModalBottomSheet(
barrierColor: Colors.black.withAlpha(60),
context: context,
builder: (context) => ModalNavigator(
parentContext: context,
heightMode: ModalHeightModes.fullScreen,
rootPage: Material(
child: page,
),
),
);
await widget.dashboardViewModel.loadCardDesigns();
}
Future<void> _openAccountsFromPromo() async {
if (!AccountsPromo.supportsWallet(widget.dashboardViewModel.wallet.type) ||
widget.dashboardViewModel.accountListViewModel == null || !_checkReadyToManage()) return;
await AccountEducationPage(
settingsStore: widget.dashboardViewModel.settingsStore,
).show(context);
if (mounted) await openAccountCustomizer();
}
bool _checkReadyToManage() {
if (widget.dashboardViewModel.status is! SyncedSyncStatus) {
showDialog(
context: context,
builder: (context) => AlertWithOneAction(
alertTitle: S.of(context).wallet_is_syncing,
alertContent: S.of(context).cannot_manage_accounts_during_sync,
buttonText: S.of(context).ok,
buttonAction: Navigator.of(context).pop,
),
);
return false;
}
return true;
}
}