diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 618b61f4..56ae895d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -745,6 +745,12 @@ "leaveFederationConfirm": "Are you sure you want to leave this federation? You will need to re-join this federation to access any remaining funds.", "@leaveFederationConfirm": {}, + "currentBalance": "Current balance", + "@currentBalance": {}, + + "leaveFederationError": "Could not leave federation. Please try again.", + "@leaveFederationError": {}, + "joinFederation": "Join Federation", "@joinFederation": {}, diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index f6cc429d..27b87116 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -213,6 +213,8 @@ "couldNotRegisterLnAddress": "No se pudo registrar la Dirección Lightning", "leaveFederation": "Abandonar Federación", "leaveFederationConfirm": "¿Estás seguro de que quieres abandonar esta federación? Necesitarás volver a unirte para acceder a los fondos restantes.", + "currentBalance": "Saldo actual", + "leaveFederationError": "No se pudo abandonar la federación. Inténtalo de nuevo.", "joinFederation": "Unirse a Federación", "joinAndRedeemEcash": "Unirse y canjear Ecash", "recover": "Recuperar", diff --git a/lib/screens/federation_info_screen.dart b/lib/screens/federation_info_screen.dart index 40a6eb3d..1a17e190 100644 --- a/lib/screens/federation_info_screen.dart +++ b/lib/screens/federation_info_screen.dart @@ -5,11 +5,13 @@ import 'package:ecashapp/screens/guardian_dashboard.dart'; import 'package:ecashapp/widgets/federation_utxo_list.dart'; import 'package:ecashapp/lib.dart'; import 'package:ecashapp/multimint.dart'; +import 'package:ecashapp/providers/preferences_provider.dart'; import 'package:ecashapp/toast.dart'; import 'package:ecashapp/utils.dart'; import 'package:ecashapp/widgets/gateways.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; enum _InfoSection { guardians, utxos, gateways } @@ -164,6 +166,20 @@ class _FederationInfoScreenState extends State { Future _onLeavePressed() async { final screenNavigator = Navigator.of(context); + final bitcoinDisplay = context.read().bitcoinDisplay; + + // Fetch the balance up front so we can warn the user before they leave a + // federation that still holds funds. Failures here are non-fatal — we just + // fall back to the plain confirmation dialog. + BigInt? balanceMsats; + try { + balanceMsats = await balance(federationId: _fed!.federationId); + } catch (e) { + AppLogger.instance.warn( + "Could not fetch balance for leave confirmation: $e", + ); + } + if (!mounted) return; await showDialog( context: context, @@ -172,9 +188,73 @@ class _FederationInfoScreenState extends State { return StatefulBuilder( builder: (sbContext, setState) { + final theme = Theme.of(sbContext); + final hasBalance = + balanceMsats != null && balanceMsats > BigInt.zero; + return AlertDialog( - title: Text(sbContext.l10n.leaveFederation), - content: Text(sbContext.l10n.leaveFederationConfirm), + // Keep the dialog from stretching across wide screens (tablet, + // desktop, foldable) — a modal that spans the viewport reads as + // a full-screen takeover rather than a focused confirmation. + insetPadding: const EdgeInsets.symmetric( + horizontal: 40, + vertical: 24, + ), + title: Row( + children: [ + Icon( + Icons.warning_amber_rounded, + color: theme.colorScheme.error, + ), + const SizedBox(width: 12), + Expanded(child: Text(sbContext.l10n.leaveFederation)), + ], + ), + content: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 420), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(sbContext.l10n.leaveFederationConfirm), + if (hasBalance) ...[ + const SizedBox(height: 20), + // Compact "balance at risk" row — the amount is what + // matters, so lead with it. Kept intentionally quiet + // (no filled background) so it feels like a fact about + // the federation, not a second alarm. + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.account_balance_wallet_outlined, + color: theme.colorScheme.onSurfaceVariant, + size: 18, + ), + const SizedBox(width: 10), + Text( + sbContext.l10n.currentBalance, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + const Spacer(), + Text( + formatBalance(balanceMsats, false, bitcoinDisplay), + style: theme.textTheme.bodyLarge?.copyWith( + color: theme.colorScheme.error, + fontWeight: FontWeight.w600, + fontFeatures: const [ + FontFeature.tabularFigures(), + ], + ), + ), + ], + ), + ], + ], + ), + ), actions: [ TextButton( onPressed: @@ -184,6 +264,9 @@ class _FederationInfoScreenState extends State { child: Text(sbContext.l10n.cancel), ), TextButton( + style: TextButton.styleFrom( + foregroundColor: theme.colorScheme.error, + ), onPressed: isLeaving ? null @@ -215,12 +298,15 @@ class _FederationInfoScreenState extends State { AppLogger.instance.error( "Error leaving federation: $e", ); + ToastService().show( + message: sbContext.l10n.leaveFederationError, + duration: const Duration(seconds: 5), + onTap: () {}, + icon: const Icon(Icons.error), + ); if (dialogContext.mounted) { Navigator.of(dialogContext).pop(); } - screenNavigator.popUntil( - (route) => route.isFirst, - ); } finally { if (mounted) { setState(() { @@ -236,7 +322,7 @@ class _FederationInfoScreenState extends State { width: 20, child: CircularProgressIndicator(strokeWidth: 2), ) - : Text(sbContext.l10n.confirm), + : Text(sbContext.l10n.leaveFederation), ), ], ); diff --git a/macos/Podfile.lock b/macos/Podfile.lock index eb45eaa9..cae42484 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -2,6 +2,9 @@ PODS: - app_links (6.4.1): - FlutterMacOS - FlutterMacOS (1.0.0) + - geolocator_apple (1.2.0): + - Flutter + - FlutterMacOS - package_info_plus (0.0.1): - FlutterMacOS - path_provider_foundation (0.0.1): @@ -16,6 +19,7 @@ PODS: DEPENDENCIES: - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) - FlutterMacOS (from `Flutter/ephemeral`) + - geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -26,6 +30,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/app_links/macos FlutterMacOS: :path: Flutter/ephemeral + geolocator_apple: + :path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin package_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: @@ -36,12 +42,13 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: - app_links: c3185399a5cabc2e610ee5ad52fb7269b84ff869 + app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 + geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009