From dacec9ea22bb94be4a23e556ff2b98be4cfdf81c Mon Sep 17 00:00:00 2001 From: Forte11Cuba Date: Fri, 17 Apr 2026 03:39:41 -0600 Subject: [PATCH] fix: bottom sheet modals overlap system navigation bar --- lib/screens/13_swap/swap_screen.dart | 50 +-- lib/screens/3_home/home_screen.dart | 149 ++++--- lib/screens/5_send/send_screen.dart | 55 +-- lib/screens/7_melt/amount_screen.dart | 54 +-- lib/screens/7_melt/melt_screen.dart | 55 +-- .../8_settings/delete_wallet_modal.dart | 381 +++++++++--------- .../8_settings/recover_tokens_modal.dart | 37 +- .../common/bottom_sheet_container.dart | 80 ++++ 8 files changed, 384 insertions(+), 477 deletions(-) create mode 100644 lib/widgets/common/bottom_sheet_container.dart diff --git a/lib/screens/13_swap/swap_screen.dart b/lib/screens/13_swap/swap_screen.dart index 0bb333d..936b0ab 100644 --- a/lib/screens/13_swap/swap_screen.dart +++ b/lib/screens/13_swap/swap_screen.dart @@ -14,6 +14,8 @@ import '../../src/rust/api/wallet.dart'; import '../../widgets/common/gradient_background.dart'; import '../../widgets/common/glass_card.dart'; import '../../widgets/common/primary_button.dart'; +import '../../widgets/common/secondary_button.dart'; +import '../../widgets/common/bottom_sheet_container.dart'; import '../../providers/wallet_provider.dart'; import '../../providers/price_provider.dart'; @@ -380,29 +382,11 @@ class _SwapScreenState extends State backgroundColor: Colors.transparent, isDismissible: false, enableDrag: false, - builder: (ctx) => Container( - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - decoration: BoxDecoration( - color: AppColors.deepVoidPurple, - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - border: Border.all( - color: Colors.white.withValues(alpha: 0.1), - width: 1, - ), - ), + builder: (ctx) => BottomSheetContainer( child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Handle - Container( - margin: const EdgeInsets.only(bottom: 16), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(2), - ), - ), + const BottomSheetHandle(), // Icono swap Container( @@ -483,29 +467,13 @@ class _SwapScreenState extends State Row( children: [ Expanded( - child: GestureDetector( - onTap: () { + child: SecondaryButton( + text: l10n.cancel, + onPressed: () { Navigator.pop(ctx); _mintSubscription?.cancel(); }, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(16), - ), - child: Center( - child: Text( - l10n.cancel, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ), + height: 52, ), ), const SizedBox(width: AppDimensions.paddingMedium), @@ -526,8 +494,6 @@ class _SwapScreenState extends State ), ], ), - - const SizedBox(height: AppDimensions.paddingSmall), ], ), ), diff --git a/lib/screens/3_home/home_screen.dart b/lib/screens/3_home/home_screen.dart index 520e46a..af5cff2 100644 --- a/lib/screens/3_home/home_screen.dart +++ b/lib/screens/3_home/home_screen.dart @@ -9,6 +9,7 @@ import '../../core/utils/incoming_data_parser.dart'; import '../../widgets/common/gradient_background.dart'; import '../../widgets/common/glass_card.dart'; import '../../widgets/common/animated_action_button.dart'; +import '../../widgets/common/bottom_sheet_container.dart'; import '../../providers/wallet_provider.dart'; import '../../providers/settings_provider.dart'; import '../../providers/p2pk_provider.dart'; @@ -573,29 +574,11 @@ class _MethodSelectorModal extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - decoration: BoxDecoration( - color: AppColors.deepVoidPurple, - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - border: Border.all( - color: Colors.white.withValues(alpha: 0.1), - width: 1, - ), - ), + return BottomSheetContainer( child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Handle - Container( - margin: const EdgeInsets.only(bottom: 16), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(2), - ), - ), + const BottomSheetHandle(), // Título Text( @@ -618,9 +601,6 @@ class _MethodSelectorModal extends StatelessWidget { child: _MethodOptionTile(option: option), ), ), - - // Espacio para la barra de navegación del sistema - SizedBox(height: MediaQuery.of(context).padding.bottom + AppDimensions.paddingSmall), ], ), ); @@ -635,67 +615,76 @@ class _MethodOptionTile extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: option.onTap, - child: Container( - width: double.infinity, - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.05), + return Semantics( + button: true, + label: option.label, + hint: option.description, + child: Material( + color: Colors.white.withValues(alpha: 0.05), + borderRadius: BorderRadius.circular(16), + child: InkWell( + onTap: option.onTap, borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Colors.white.withValues(alpha: 0.1), - width: 1, - ), - ), - child: Row( - children: [ - // Icono - Container( - width: 48, - height: 48, - decoration: BoxDecoration( - gradient: const LinearGradient( - colors: AppColors.buttonGradient, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(AppDimensions.paddingMedium), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.white.withValues(alpha: 0.1), + width: 1, + ), + ), + child: Row( + children: [ + // Icono + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: AppColors.buttonGradient, + ), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(option.icon, color: Colors.white, size: 24), ), - borderRadius: BorderRadius.circular(12), - ), - child: Icon(option.icon, color: Colors.white, size: 24), - ), - const SizedBox(width: AppDimensions.paddingMedium), - // Texto - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - option.label, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.white, - ), + const SizedBox(width: AppDimensions.paddingMedium), + // Texto + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + option.label, + style: const TextStyle( + fontFamily: 'Inter', + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + const SizedBox(height: 2), + Text( + option.description, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 14, + color: AppColors.textSecondary.withValues(alpha: 0.7), + ), + ), + ], ), - const SizedBox(height: 2), - Text( - option.description, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 14, - color: AppColors.textSecondary.withValues(alpha: 0.7), - ), - ), - ], - ), + ), + // Flecha + Icon( + LucideIcons.chevronRight, + color: Colors.white.withValues(alpha: 0.5), + size: 24, + ), + ], ), - // Flecha - Icon( - LucideIcons.chevronRight, - color: Colors.white.withValues(alpha: 0.5), - size: 24, - ), - ], + ), ), ), ); diff --git a/lib/screens/5_send/send_screen.dart b/lib/screens/5_send/send_screen.dart index c2e5f2e..5292fff 100644 --- a/lib/screens/5_send/send_screen.dart +++ b/lib/screens/5_send/send_screen.dart @@ -12,6 +12,8 @@ import '../../core/utils/keyset_debug.dart'; import '../../widgets/common/gradient_background.dart'; import '../../widgets/common/glass_card.dart'; import '../../widgets/common/primary_button.dart'; +import '../../widgets/common/secondary_button.dart'; +import '../../widgets/common/bottom_sheet_container.dart'; import '../../widgets/common/numpad_widget.dart'; import '../../providers/wallet_provider.dart'; import '../../core/utils/nostr_utils.dart'; @@ -797,29 +799,12 @@ class _ConfirmationModal extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - decoration: BoxDecoration( - color: AppColors.deepVoidPurple, - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - border: Border.all( - color: Colors.white.withValues(alpha: 0.1), - width: 1, - ), - ), + final l10n = L10n.of(context)!; + return BottomSheetContainer( child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Handle - Container( - margin: const EdgeInsets.only(bottom: 16), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(2), - ), - ), + const BottomSheetHandle(), // Icono Container( @@ -839,7 +824,7 @@ class _ConfirmationModal extends StatelessWidget { // Titulo Text( - L10n.of(context)!.confirmSend, + l10n.confirmSend, style: const TextStyle( fontFamily: 'Inter', fontSize: 20, @@ -880,40 +865,22 @@ class _ConfirmationModal extends StatelessWidget { Row( children: [ Expanded( - child: GestureDetector( - onTap: onCancel, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(16), - ), - child: Center( - child: Text( - L10n.of(context)!.cancel, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ), + child: SecondaryButton( + text: l10n.cancel, + onPressed: onCancel, + height: 52, ), ), const SizedBox(width: AppDimensions.paddingMedium), Expanded( child: PrimaryButton( - text: L10n.of(context)!.confirm, + text: l10n.confirm, onPressed: onConfirm, height: 52, ), ), ], ), - - const SizedBox(height: AppDimensions.paddingSmall), ], ), ); diff --git a/lib/screens/7_melt/amount_screen.dart b/lib/screens/7_melt/amount_screen.dart index 149112e..f41f0ef 100644 --- a/lib/screens/7_melt/amount_screen.dart +++ b/lib/screens/7_melt/amount_screen.dart @@ -9,6 +9,8 @@ import '../../core/services/lnurl_service.dart'; import '../../widgets/common/gradient_background.dart'; import '../../widgets/common/glass_card.dart'; import '../../widgets/common/primary_button.dart'; +import '../../widgets/common/secondary_button.dart'; +import '../../widgets/common/bottom_sheet_container.dart'; import '../../widgets/common/numpad_widget.dart'; import '../../providers/wallet_provider.dart'; import '../../providers/price_provider.dart'; @@ -516,32 +518,16 @@ class _AmountScreenState extends State { Future _showConfirmation(BigInt amount, BigInt fee, BigInt total) async { final unitLabel = UnitFormatter.getUnitLabel(_activeUnit); + final l10n = L10n.of(context)!; final result = await showModalBottomSheet( context: context, backgroundColor: Colors.transparent, - builder: (context) => Container( - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - decoration: BoxDecoration( - color: AppColors.deepVoidPurple, - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - border: Border.all( - color: Colors.white.withValues(alpha: 0.1), - ), - ), + builder: (context) => BottomSheetContainer( child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Handle - Container( - margin: const EdgeInsets.only(bottom: 16), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(2), - ), - ), + const BottomSheetHandle(), // Icono Container( @@ -561,7 +547,7 @@ class _AmountScreenState extends State { // Título Text( - L10n.of(context)!.confirmPayment, + l10n.confirmPayment, style: const TextStyle( fontFamily: 'Inter', fontSize: 20, @@ -606,40 +592,22 @@ class _AmountScreenState extends State { Row( children: [ Expanded( - child: GestureDetector( - onTap: () => Navigator.pop(context, false), - child: Container( - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(16), - ), - child: Center( - child: Text( - L10n.of(context)!.cancel, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ), + child: SecondaryButton( + text: l10n.cancel, + onPressed: () => Navigator.pop(context, false), + height: 52, ), ), const SizedBox(width: AppDimensions.paddingMedium), Expanded( child: PrimaryButton( - text: L10n.of(context)!.pay, + text: l10n.pay, onPressed: () => Navigator.pop(context, true), height: 52, ), ), ], ), - - const SizedBox(height: AppDimensions.paddingSmall), ], ), ), diff --git a/lib/screens/7_melt/melt_screen.dart b/lib/screens/7_melt/melt_screen.dart index d0946cd..43725e4 100644 --- a/lib/screens/7_melt/melt_screen.dart +++ b/lib/screens/7_melt/melt_screen.dart @@ -12,6 +12,8 @@ import '../../core/services/lnurl_service.dart'; import '../../widgets/common/gradient_background.dart'; import '../../widgets/common/glass_card.dart'; import '../../widgets/common/primary_button.dart'; +import '../../widgets/common/secondary_button.dart'; +import '../../widgets/common/bottom_sheet_container.dart'; import '../../providers/wallet_provider.dart'; import '../10_scanner/scan_screen.dart'; import 'amount_screen.dart'; @@ -858,29 +860,12 @@ class _ConfirmationModal extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - decoration: BoxDecoration( - color: AppColors.deepVoidPurple, - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - border: Border.all( - color: Colors.white.withValues(alpha: 0.1), - width: 1, - ), - ), + final l10n = L10n.of(context)!; + return BottomSheetContainer( child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Handle - Container( - margin: const EdgeInsets.only(bottom: 16), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(2), - ), - ), + const BottomSheetHandle(), // Icono Container( @@ -900,7 +885,7 @@ class _ConfirmationModal extends StatelessWidget { // Título Text( - L10n.of(context)!.confirmPayment, + l10n.confirmPayment, style: const TextStyle( fontFamily: 'Inter', fontSize: 20, @@ -945,40 +930,22 @@ class _ConfirmationModal extends StatelessWidget { Row( children: [ Expanded( - child: GestureDetector( - onTap: onCancel, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(16), - ), - child: Center( - child: Text( - L10n.of(context)!.cancel, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ), + child: SecondaryButton( + text: l10n.cancel, + onPressed: onCancel, + height: 52, ), ), const SizedBox(width: AppDimensions.paddingMedium), Expanded( child: PrimaryButton( - text: L10n.of(context)!.pay, + text: l10n.pay, onPressed: onConfirm, height: 52, ), ), ], ), - - const SizedBox(height: AppDimensions.paddingSmall), ], ), ); diff --git a/lib/screens/8_settings/delete_wallet_modal.dart b/lib/screens/8_settings/delete_wallet_modal.dart index bec0988..07b3c72 100644 --- a/lib/screens/8_settings/delete_wallet_modal.dart +++ b/lib/screens/8_settings/delete_wallet_modal.dart @@ -3,6 +3,8 @@ import 'package:lucide_icons/lucide_icons.dart'; import 'package:elcaju/l10n/app_localizations.dart'; import '../../core/constants/colors.dart'; import '../../core/constants/dimensions.dart'; +import '../../widgets/common/bottom_sheet_container.dart'; +import '../../widgets/common/secondary_button.dart'; /// Modal para confirmar borrado de wallet class DeleteWalletModal extends StatefulWidget { @@ -33,215 +35,204 @@ class _DeleteWalletModalState extends State { Widget build(BuildContext context) { final l10n = L10n.of(context)!; final confirmWord = l10n.deleteConfirmWord; - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: Container( - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - decoration: BoxDecoration( - color: AppColors.deepVoidPurple, - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - border: Border.all( - color: Colors.white.withValues(alpha: 0.1), - width: 1, + return BottomSheetContainer( + respectKeyboard: true, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const BottomSheetHandle(), + + // Icono advertencia + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: AppColors.error.withValues(alpha: 0.2), + shape: BoxShape.circle, + ), + child: Icon( + LucideIcons.alertTriangle, + color: AppColors.error, + size: 32, + ), ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Handle - Container( - margin: const EdgeInsets.only(bottom: 16), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(2), - ), + const SizedBox(height: AppDimensions.paddingMedium), + + // Título + Text( + l10n.deleteWalletQuestion, + style: const TextStyle( + fontFamily: 'Inter', + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, ), + ), + const SizedBox(height: AppDimensions.paddingSmall), - // Icono advertencia - Container( - width: 64, - height: 64, - decoration: BoxDecoration( - color: AppColors.error.withValues(alpha: 0.2), - shape: BoxShape.circle, - ), - child: Icon( - LucideIcons.alertTriangle, - color: AppColors.error, - size: 32, - ), + // Advertencia + Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColors.error.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), ), - const SizedBox(height: AppDimensions.paddingMedium), - - // Título - Text( - l10n.deleteWalletQuestion, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: AppDimensions.paddingSmall), - - // Advertencia - Container( - margin: const EdgeInsets.symmetric(horizontal: 16), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: AppColors.error.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Column( - children: [ - Row( - children: [ - Icon( - LucideIcons.alertCircle, - color: AppColors.error, - size: 16, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - l10n.actionIrreversible, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 14, - fontWeight: FontWeight.w600, - color: AppColors.error, - ), - ), - ), - ], - ), - const SizedBox(height: 8), - Text( - l10n.deleteWalletWarning, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 12, - color: AppColors.error.withValues(alpha: 0.8), - ), - ), - ], - ), - ), - - const SizedBox(height: AppDimensions.paddingMedium), - - // Input de confirmación - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.typeDeleteToConfirm, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 14, - color: AppColors.textSecondary, - ), - ), - const SizedBox(height: 8), - TextField( - controller: _controller, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 16, - color: Colors.white, - ), - decoration: InputDecoration( - hintText: confirmWord, - hintStyle: TextStyle( - fontFamily: 'Inter', - fontSize: 16, - color: Colors.white.withValues(alpha: 0.3), - ), - filled: true, - fillColor: Colors.white.withValues(alpha: 0.05), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - ), - onChanged: (value) { - setState(() { - _canDelete = value == confirmWord; - }); - }, - ), - ], - ), - ), - - const SizedBox(height: AppDimensions.paddingLarge), - - // Botones - Row( + child: Column( children: [ - Expanded( - child: GestureDetector( - onTap: widget.onCancel, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(16), - ), - child: Center( - child: Text( - l10n.cancel, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), + Row( + children: [ + Icon( + LucideIcons.alertCircle, + color: AppColors.error, + size: 16, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + l10n.actionIrreversible, + style: const TextStyle( + fontFamily: 'Inter', + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.error, ), ), ), - ), + ], ), - const SizedBox(width: AppDimensions.paddingMedium), - Expanded( - child: GestureDetector( - onTap: _canDelete ? widget.onConfirm : null, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: _canDelete - ? AppColors.error - : AppColors.error.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(16), - ), - child: Center( - child: Text( - l10n.deleteWallet, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 16, - fontWeight: FontWeight.w600, - color: _canDelete - ? Colors.white - : Colors.white.withValues(alpha: 0.5), - ), - ), - ), - ), + const SizedBox(height: 8), + Text( + l10n.deleteWalletWarning, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 12, + color: AppColors.error.withValues(alpha: 0.8), ), ), ], ), + ), - const SizedBox(height: AppDimensions.paddingSmall), - ], + const SizedBox(height: AppDimensions.paddingMedium), + + // Input de confirmación + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.typeDeleteToConfirm, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 14, + color: AppColors.textSecondary, + ), + ), + const SizedBox(height: 8), + TextField( + controller: _controller, + style: const TextStyle( + fontFamily: 'Inter', + fontSize: 16, + color: Colors.white, + ), + decoration: InputDecoration( + hintText: confirmWord, + hintStyle: TextStyle( + fontFamily: 'Inter', + fontSize: 16, + color: Colors.white.withValues(alpha: 0.3), + ), + filled: true, + fillColor: Colors.white.withValues(alpha: 0.05), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + ), + onChanged: (value) { + setState(() { + _canDelete = value == confirmWord; + }); + }, + ), + ], + ), + ), + + const SizedBox(height: AppDimensions.paddingLarge), + + // Botones + Row( + children: [ + Expanded( + child: SecondaryButton( + text: l10n.cancel, + onPressed: widget.onCancel, + height: 52, + ), + ), + const SizedBox(width: AppDimensions.paddingMedium), + Expanded( + child: _DestructiveButton( + text: l10n.deleteWallet, + enabled: _canDelete, + onPressed: widget.onConfirm, + ), + ), + ], + ), + ], + ), + ); + } +} + +/// Botón destructivo con semantics accesibles. +/// Separado de los botones estándar porque la acción es irreversible. +class _DestructiveButton extends StatelessWidget { + final String text; + final bool enabled; + final VoidCallback onPressed; + + const _DestructiveButton({ + required this.text, + required this.enabled, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return Semantics( + button: true, + enabled: enabled, + label: text, + child: SizedBox( + height: 52, + child: Material( + color: enabled + ? AppColors.error + : AppColors.error.withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(16), + child: InkWell( + onTap: enabled ? onPressed : null, + borderRadius: BorderRadius.circular(16), + child: Center( + child: Text( + text, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 16, + fontWeight: FontWeight.w600, + color: enabled + ? Colors.white + : Colors.white.withValues(alpha: 0.5), + ), + ), + ), + ), ), ), ); diff --git a/lib/screens/8_settings/recover_tokens_modal.dart b/lib/screens/8_settings/recover_tokens_modal.dart index 061b44f..83dd48b 100644 --- a/lib/screens/8_settings/recover_tokens_modal.dart +++ b/lib/screens/8_settings/recover_tokens_modal.dart @@ -6,6 +6,7 @@ import '../../core/constants/colors.dart'; import '../../core/constants/dimensions.dart'; import '../../core/utils/formatters.dart'; import '../../providers/wallet_provider.dart'; +import '../../widgets/common/bottom_sheet_container.dart'; /// Modal para recuperar tokens usando NUT-13 class RecoverTokensModal extends StatefulWidget { @@ -51,34 +52,13 @@ class _RecoverTokensModalState extends State { @override Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: Container( - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - decoration: BoxDecoration( - color: AppColors.deepVoidPurple, - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - border: Border.all( - color: Colors.white.withValues(alpha: 0.1), - width: 1, - ), - ), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Handle - Container( - margin: const EdgeInsets.only(bottom: 16), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(2), - ), - ), + return BottomSheetContainer( + respectKeyboard: true, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const BottomSheetHandle(), // Icono Container( @@ -144,7 +124,6 @@ class _RecoverTokensModalState extends State { ], ), ), - ), ); } diff --git a/lib/widgets/common/bottom_sheet_container.dart b/lib/widgets/common/bottom_sheet_container.dart new file mode 100644 index 0000000..b68077b --- /dev/null +++ b/lib/widgets/common/bottom_sheet_container.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import '../../core/constants/colors.dart'; +import '../../core/constants/dimensions.dart'; + +/// Frame común para modales tipo bottom sheet. +/// +/// Aplica SafeArea(top: false) para que los botones internos no queden +/// tapados por la barra de navegación del sistema (gestos o 3 botones). +/// +/// Usá [respectKeyboard]: true cuando el modal contenga TextField, para +/// combinar el inset de la barra con el del teclado (viewInsets.bottom). +class BottomSheetContainer extends StatelessWidget { + final Widget child; + final EdgeInsetsGeometry padding; + final bool respectKeyboard; + + const BottomSheetContainer({ + super.key, + required this.child, + this.padding = const EdgeInsets.all(AppDimensions.paddingMedium), + this.respectKeyboard = false, + }); + + @override + Widget build(BuildContext context) { + final mq = MediaQuery.of(context); + // viewPadding.bottom es el inset crudo de la nav bar del sistema + // (no se consume aunque un SafeArea padre ya lo haya absorbido). + final navBarInset = mq.viewPadding.bottom; + + // Combinar el padding base con el extra del navBar abajo. + final resolvedPadding = padding.resolve(Directionality.of(context)); + final effectivePadding = EdgeInsets.only( + left: resolvedPadding.left, + top: resolvedPadding.top, + right: resolvedPadding.right, + bottom: resolvedPadding.bottom + navBarInset, + ); + + final frame = Container( + decoration: BoxDecoration( + color: AppColors.deepVoidPurple, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(AppDimensions.radiusXLarge), + ), + border: Border.all( + color: Colors.white.withValues(alpha: 0.1), + width: 1, + ), + ), + child: Padding(padding: effectivePadding, child: child), + ); + + if (respectKeyboard) { + return Padding( + padding: EdgeInsets.only(bottom: mq.viewInsets.bottom), + child: frame, + ); + } + return frame; + } +} + +/// Handle visual en la parte superior del modal (la barrita). +class BottomSheetHandle extends StatelessWidget { + const BottomSheetHandle({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 16), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(2), + ), + ); + } +}