Merge branch 'dev' of https://github.com/cake-tech/cake_wallet into CW-1529-Improved-Seed-UI

This commit is contained in:
Blazebrain
2026-09-09 14:55:06 +01:00
47 changed files with 567 additions and 86 deletions
@@ -641,6 +641,13 @@ Future<void> defaultSettingsMigration(
case 70:
await _addTbbTokenToExistingSolanaWallets();
break;
case 71:
_changeExchangeProviderAvailability(
sharedPreferences,
providerName: "Swaps.XYZ",
enabled: false,
);
break;
default:
break;
}
+1 -1
View File
@@ -308,7 +308,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
payjoinSessionSource: payjoinSessionSource,
anonpayInvoiceInfo: anonpayInvoiceInfo,
havenSeedStore: havenSeedStore,
initialMigrationVersion: 70,
initialMigrationVersion: 71,
);
}
@@ -1,15 +1,20 @@
import "package:cake_wallet/new-ui/widgets/money/money_settings_cubit.dart";
import "package:cake_wallet/new-ui/widgets/money/use_base_unit_provider.dart";
import "package:cake_wallet/store/settings_store.dart";
import "package:flutter/material.dart";
import "package:flutter_bloc/flutter_bloc.dart";
class MoneySettingsProvider extends StatelessWidget {
const MoneySettingsProvider({required this.settingsStore, this.child, super.key});
const MoneySettingsProvider({required this.settingsStore, required this.child, super.key});
final Widget? child;
final Widget child;
final SettingsStore settingsStore;
@override
Widget build(BuildContext context) =>
BlocProvider(create: (_) => MoneySettingsCubit(settingsStore), child: child);
Widget build(BuildContext context) => BlocProvider(
create: (_) => MoneySettingsCubit(settingsStore),
child: BaseUnitScope(
child: child,
),
);
}
@@ -0,0 +1,63 @@
import "dart:async";
import "package:cake_wallet/new-ui/widgets/money/money_settings_cubit.dart";
import "package:cw_core/currency.dart";
import "package:flutter/widgets.dart";
import "package:flutter_bloc/flutter_bloc.dart";
/// Read-only view of the money display settings for the current subtree.
///
/// ```dart
/// final useBaseUnit = BaseUnit.useBaseUnitOf(context, CryptoCurrency.btc);
/// ```
class BaseUnit extends InheritedModel<Currency> {
const BaseUnit({required this.state, required super.child});
final MoneySettingsState state;
static bool useBaseUnitOf(BuildContext context, Currency currency) =>
InheritedModel.inheritFrom<BaseUnit>(context, aspect: currency)!.state.useBaseUnit(currency);
static String getSymbolOf(BuildContext context, Currency currency) =>
InheritedModel.inheritFrom<BaseUnit>(context, aspect: currency)!.state.getSymbol(currency);
@override
bool updateShouldNotify(BaseUnit old) =>
state.bitcoinAmountDisplayMode != old.state.bitcoinAmountDisplayMode;
@override
bool updateShouldNotifyDependent(BaseUnit old, Set<Currency> aspects) => aspects.any((c) =>
state.useBaseUnit(c) != old.state.useBaseUnit(c) ||
state.getSymbol(c) != old.state.getSymbol(c));
}
class BaseUnitScope extends StatefulWidget {
const BaseUnitScope({required this.child, super.key});
final Widget child;
@override
State<BaseUnitScope> createState() => _BaseUnitScopeState();
}
class _BaseUnitScopeState extends State<BaseUnitScope> {
late MoneySettingsState _state;
late final StreamSubscription<MoneySettingsState> _sub;
@override
void initState() {
super.initState();
final cubit = context.read<MoneySettingsCubit>();
_state = cubit.state;
_sub = cubit.stream.listen((s) => setState(() => _state = s));
}
@override
void dispose() {
_sub.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) => BaseUnit(state: _state, child: widget.child);
}
@@ -151,7 +151,7 @@ class _NFTSendPage extends BasePage {
}
Future<void> _onSendPressed(BuildContext context) async {
if (nftSendViewModel.state is IsExecutingState) {
if (nftSendViewModel.isStartingSend || nftSendViewModel.state is IsExecutingState) {
return;
}
@@ -163,18 +163,23 @@ class _NFTSendPage extends BasePage {
final destinationAddress = addressController.text.trim();
await authService.authenticateAction(
context,
conditionToDetermineIfToUse2FA: nftSendViewModel.shouldRequireTOTP2FAFor(destinationAddress),
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully || !context.mounted) {
nftSendViewModel.reset();
return;
}
try {
await authService.authenticateAction(
context,
conditionToDetermineIfToUse2FA:
nftSendViewModel.shouldRequireTOTP2FAFor(destinationAddress),
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully || !context.mounted) {
nftSendViewModel.reset();
return;
}
await _createAndConfirm(context, destinationAddress);
},
);
await _createAndConfirm(context, destinationAddress);
},
);
} finally {
nftSendViewModel.markSendSettled();
}
}
Future<void> _createAndConfirm(BuildContext context, String destinationAddress) async {
@@ -90,11 +90,25 @@ abstract class NFTSendViewModelBase with Store {
}
}
@action
void markSendStarted() => state = IsExecutingState();
/// Re-entrancy latch for the send button, held only while the tap handler is
/// inside [AuthService.authenticateAction].
///
/// Deliberately not [state]: the auth sheet is pushed closable, and closing it
/// pops the route without ever invoking onAuthenticationFinished, so a flag
/// that waits on the auth result would never be cleared. The caller clears
/// this in a finally instead. Once auth succeeds, createTransaction takes over
/// the guard by setting IsExecutingState synchronously.
bool _isStartingSend = false;
bool get isStartingSend => _isStartingSend;
void markSendStarted() => _isStartingSend = true;
void markSendSettled() => _isStartingSend = false;
@action
void reset() {
_isStartingSend = false;
pendingTransaction = null;
state = InitialExecutionState();
}
+5 -3
View File
@@ -907,9 +907,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
final isSendAll = outputs.any((output) => output.sendAll);
if (!isSendAll) {
final estimateTxAmountDouble = outputs.fold<double>(
0, (acc, output) => acc + (double.tryParse(output.cryptoAmount) ?? 0));
if (estimateTxAmountDouble <= 0) throw Exception('Amount must be greater than 0');
final estimateTxAmount = outputs.fold<BigInt>(
BigInt.zero, (acc, output) => acc + output.cryptoAmountMoney.amount);
if (estimateTxAmount <= BigInt.zero) {
throw Exception('Amount must be greater than 0');
}
}
pendingTransaction = await wallet.createTransaction(_credentials(provider));