diff --git a/lib/screens/6_mint/invoice_screen.dart b/lib/screens/6_mint/invoice_screen.dart deleted file mode 100644 index 9e6f903..0000000 --- a/lib/screens/6_mint/invoice_screen.dart +++ /dev/null @@ -1,519 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import 'package:qr_flutter/qr_flutter.dart'; -import 'package:lucide_icons/lucide_icons.dart'; -import '../../src/rust/api/wallet.dart'; -import 'package:elcaju/l10n/app_localizations.dart'; -import '../../core/constants/colors.dart'; -import '../../core/constants/dimensions.dart'; -import '../../core/utils/formatters.dart'; -import '../../widgets/common/gradient_background.dart'; -import '../../widgets/common/glass_card.dart'; -import '../../providers/wallet_provider.dart'; - -/// Estados del proceso de mint -enum MintStatus { loading, unpaid, paid, issued, error } - -/// Pantalla para mostrar el invoice generado y esperar el pago -class InvoiceScreen extends StatefulWidget { - final BigInt amount; - final String unit; - final String? description; - - const InvoiceScreen({ - super.key, - required this.amount, - required this.unit, - this.description, - }); - - @override - State createState() => _InvoiceScreenState(); -} - -class _InvoiceScreenState extends State { - MintStatus _status = MintStatus.loading; - String? _invoice; - String? _errorMessage; - StreamSubscription? _mintSubscription; - - @override - void initState() { - super.initState(); - _startMintProcess(); - } - - @override - void dispose() { - _mintSubscription?.cancel(); - super.dispose(); - } - - Future _startMintProcess() async { - final walletProvider = context.read(); - - try { - final mintStream = await walletProvider.mintTokens( - widget.amount, - widget.description, - ); - - _mintSubscription = mintStream.listen( - (quote) { - if (!mounted) return; - - setState(() { - switch (quote.state) { - case MintQuoteState.unpaid: - _status = MintStatus.unpaid; - _invoice = quote.request; - break; - - case MintQuoteState.paid: - _status = MintStatus.paid; - break; - - case MintQuoteState.issued: - _status = MintStatus.issued; - _onMintCompleted(); - break; - - case MintQuoteState.error: - _status = MintStatus.error; - _errorMessage = quote.error ?? L10n.of(context)!.unknownError; - } - }); - }, - onError: (error) { - if (!mounted) return; - setState(() { - _status = MintStatus.error; - _errorMessage = error.toString(); - }); - }, - ); - } catch (e) { - setState(() { - _status = MintStatus.error; - _errorMessage = e.toString(); - }); - } - } - - void _onMintCompleted() { - // Confetti se dispara globalmente desde WalletProvider._saveMintMetadata - - // Esperar a que termine el confetti antes de navegar - Future.delayed(const Duration(seconds: 3), () { - if (mounted) { - final l10n = L10n.of(context)!; - final formattedAmount = '+${UnitFormatter.formatBalance(widget.amount, widget.unit)}'; - final unitLabel = UnitFormatter.getUnitLabel(widget.unit); - // Volver al home con mensaje de éxito - Navigator.popUntil(context, (route) => route.isFirst); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - l10n.deposited(formattedAmount, unitLabel), - ), - backgroundColor: AppColors.success, - duration: const Duration(seconds: 3), - ), - ); - } - }); - } - - @override - Widget build(BuildContext context) { - return GradientBackground( - child: Scaffold( - backgroundColor: Colors.transparent, - appBar: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - leading: IconButton( - icon: const Icon( - LucideIcons.arrowLeft, - color: Colors.white, - ), - onPressed: () => Navigator.pop(context), - ), - title: Text( - L10n.of(context)!.payInvoiceTitle, - style: const TextStyle( - fontFamily: 'Inter', - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - actions: [ - if (_status == MintStatus.unpaid) - TextButton( - onPressed: () => Navigator.pop(context), - child: Text( - L10n.of(context)!.cancel, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 14, - color: AppColors.textSecondary, - ), - ), - ), - ], - ), - body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - child: _buildContent(), - ), - ), - ), - ); - } - - Widget _buildContent() { - switch (_status) { - case MintStatus.loading: - return _buildLoading(); - case MintStatus.unpaid: - case MintStatus.paid: - case MintStatus.issued: - return _buildInvoiceView(); - case MintStatus.error: - return _buildError(); - } - } - - Widget _buildLoading() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const CircularProgressIndicator( - color: AppColors.primaryAction, - ), - const SizedBox(height: AppDimensions.paddingMedium), - Text( - L10n.of(context)!.generatingInvoice, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 16, - color: AppColors.textSecondary, - ), - ), - ], - ), - ); - } - - Widget _buildInvoiceView() { - return Column( - children: [ - // Contenido scrolleable - Expanded( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Título con monto - _buildHeader(), - - const SizedBox(height: AppDimensions.paddingLarge), - - // QR del invoice - if (_invoice != null) _buildQRSection(), - - const SizedBox(height: AppDimensions.paddingLarge), - - // Invoice truncado - if (_invoice != null) _buildInvoiceText(), - - const SizedBox(height: AppDimensions.paddingMedium), - - // Botón copiar - if (_invoice != null && _status == MintStatus.unpaid) _buildCopyButton(), - ], - ), - ), - ), - - // Estado y descripción (fijos abajo) - Column( - children: [ - // Estado del invoice - _buildStatus(), - - const SizedBox(height: AppDimensions.paddingMedium), - - // Descripción (si existe) - if (widget.description != null && widget.description!.isNotEmpty) - _buildDescription(), - ], - ), - ], - ); - } - - Widget _buildError() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 80, - height: 80, - decoration: BoxDecoration( - color: AppColors.error.withValues(alpha: 0.2), - shape: BoxShape.circle, - ), - child: const Icon( - LucideIcons.alertCircle, - color: AppColors.error, - size: 40, - ), - ), - const SizedBox(height: AppDimensions.paddingMedium), - Text( - L10n.of(context)!.error, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: AppDimensions.paddingSmall), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Text( - _errorMessage ?? L10n.of(context)!.unknownError, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 14, - color: AppColors.textSecondary.withValues(alpha: 0.7), - ), - textAlign: TextAlign.center, - ), - ), - const SizedBox(height: AppDimensions.paddingLarge), - GestureDetector( - onTap: () => Navigator.pop(context), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - L10n.of(context)!.back, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ), - ], - ), - ); - } - - Widget _buildHeader() { - final l10n = L10n.of(context)!; - final formattedAmount = UnitFormatter.formatBalance(widget.amount, widget.unit); - final unitLabel = UnitFormatter.getUnitLabel(widget.unit); - - return Column( - children: [ - Text( - l10n.depositAmountTitle(formattedAmount, unitLabel), - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ], - ); - } - - Widget _buildQRSection() { - return GlassCard( - padding: const EdgeInsets.all(AppDimensions.paddingLarge), - child: Center( - child: QrImageView( - data: _invoice!, - version: QrVersions.auto, - size: 200, - backgroundColor: Colors.white, - errorCorrectionLevel: QrErrorCorrectLevel.M, - ), - ), - ); - } - - Widget _buildInvoiceText() { - return GlassCard( - padding: const EdgeInsets.all(AppDimensions.paddingSmall), - child: SelectableText( - _invoice!, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 14, - color: Colors.white, - ), - textAlign: TextAlign.center, - maxLines: 2, - ), - ); - } - - Widget _buildCopyButton() { - return GestureDetector( - onTap: _copyInvoice, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: AppDimensions.paddingMedium, - vertical: AppDimensions.paddingSmall + 4, - ), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: Colors.white.withValues(alpha: 0.2), - width: 1, - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(LucideIcons.copy, color: AppColors.textSecondary, size: 20), - const SizedBox(width: 8), - Text( - L10n.of(context)!.copyInvoice, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 16, - fontWeight: FontWeight.w500, - color: AppColors.textSecondary, - ), - ), - ], - ), - ), - ); - } - - Widget _buildStatus() { - final l10n = L10n.of(context)!; - IconData icon; - String text; - Color color; - - switch (_status) { - case MintStatus.loading: - icon = LucideIcons.clock; - text = l10n.generating; - color = AppColors.textSecondary; - break; - case MintStatus.unpaid: - icon = LucideIcons.clock; - text = l10n.waitingForPayment; - color = AppColors.textSecondary; - break; - case MintStatus.paid: - icon = LucideIcons.checkCircle; - text = l10n.paymentReceived; - color = AppColors.success; - break; - case MintStatus.issued: - icon = LucideIcons.checkCircle2; - text = l10n.tokensIssued; - color = AppColors.success; - break; - case MintStatus.error: - icon = LucideIcons.xCircle; - text = l10n.error; - color = AppColors.error; - break; - } - - return GlassCard( - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (_status == MintStatus.unpaid || _status == MintStatus.loading) - SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(color), - ), - ) - else - Icon(icon, color: color, size: 24), - const SizedBox(width: 12), - Text( - text, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 16, - fontWeight: FontWeight.w600, - color: color, - ), - ), - ], - ), - ); - } - - Widget _buildDescription() { - return GlassCard( - padding: const EdgeInsets.all(AppDimensions.paddingSmall), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - L10n.of(context)!.description, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 12, - color: AppColors.textSecondary.withValues(alpha: 0.7), - ), - ), - const SizedBox(height: 4), - Text( - widget.description!, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 14, - color: Colors.white, - ), - ), - ], - ), - ); - } - - Future _copyInvoice() async { - if (_invoice == null) return; - await Clipboard.setData(ClipboardData(text: _invoice!)); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context)!.invoiceCopiedToClipboard), - backgroundColor: AppColors.success, - duration: const Duration(seconds: 2), - ), - ); - } - } -} diff --git a/lib/screens/6_mint/mint_screen.dart b/lib/screens/6_mint/mint_screen.dart deleted file mode 100644 index 436153e..0000000 --- a/lib/screens/6_mint/mint_screen.dart +++ /dev/null @@ -1,424 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -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 '../../core/utils/formatters.dart'; -import '../../widgets/common/gradient_background.dart'; -import '../../widgets/common/glass_card.dart'; -import '../../widgets/common/primary_button.dart'; -import '../../widgets/common/numpad_widget.dart'; -import '../../providers/wallet_provider.dart'; -import 'invoice_screen.dart'; - -/// Pantalla para depositar sats via Lightning (Mint) -class MintScreen extends StatefulWidget { - const MintScreen({super.key}); - - @override - State createState() => _MintScreenState(); -} - -class _MintScreenState extends State { - final TextEditingController _descriptionController = TextEditingController(); - - String _amountValue = ''; - late String _activeUnit; - - bool _isProcessing = false; - String? _errorMessage; - - @override - void initState() { - super.initState(); - _activeUnit = context.read().activeUnit; - } - - @override - void dispose() { - _descriptionController.dispose(); - super.dispose(); - } - - /// Obtiene la etiqueta de la unidad para display - String get _unitLabel => UnitFormatter.getUnitLabel(_activeUnit); - - /// Parsea los dígitos crudos a BigInt (ya son centavos para USD/EUR) - BigInt get _amount => UnitFormatter.parseRawDigits(_amountValue, _activeUnit); - - bool get _isValidAmount => _amount > BigInt.zero; - - @override - Widget build(BuildContext context) { - return GradientBackground( - child: Scaffold( - backgroundColor: Colors.transparent, - appBar: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - leading: IconButton( - icon: const Icon(LucideIcons.arrowLeft, color: Colors.white), - onPressed: () => Navigator.pop(context), - ), - title: Text( - L10n.of(context)!.deposit, - style: const TextStyle( - fontFamily: 'Inter', - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - body: SafeArea( - child: Column( - children: [ - // Contenido scrolleable - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Monto a depositar - _buildAmountSection(), - - const SizedBox(height: AppDimensions.paddingLarge), - - // Descripción opcional - _buildDescriptionSection(), - - const SizedBox(height: AppDimensions.paddingMedium), - - // Mensaje de error (si hay) - if (_errorMessage != null) _buildErrorMessage(), - ], - ), - ), - ), - - // Botón generar invoice (fijo abajo) - Padding( - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - child: _buildGenerateButton(), - ), - ], - ), - ), - ), - ); - } - - Widget _buildAmountSection() { - return Column( - children: [ - // Display del monto - _buildAmountDisplay(), - const SizedBox(height: AppDimensions.paddingMedium), - - // Teclado numérico - NumpadWidget( - value: _amountValue, - onChanged: (newValue) { - setState(() { - _errorMessage = null; - _amountValue = newValue; - }); - }, - ), - ], - ); - } - - Widget _buildAmountDisplay() { - final displayAmount = UnitFormatter.formatRawDigitsForDisplay(_amountValue, _activeUnit); - - return Column( - children: [ - Text( - displayAmount, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 48, - fontWeight: FontWeight.bold, - color: _isValidAmount || _amountValue.isEmpty - ? Colors.white - : AppColors.error, - ), - ), - const SizedBox(height: 4), - Text( - _unitLabel, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 18, - color: AppColors.textSecondary, - ), - ), - ], - ); - } - - Widget _buildDescriptionSection() { - final l10n = L10n.of(context)!; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.descriptionOptional, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 16, - fontWeight: FontWeight.w500, - color: AppColors.textSecondary, - ), - ), - const SizedBox(height: AppDimensions.paddingSmall), - - GlassCard( - padding: const EdgeInsets.all(AppDimensions.paddingSmall), - child: TextField( - controller: _descriptionController, - maxLines: 2, - maxLength: 100, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 16, - color: Colors.white, - ), - decoration: InputDecoration( - hintText: l10n.depositPlaceholder, - hintStyle: TextStyle( - fontFamily: 'Inter', - fontSize: 16, - color: Colors.white.withValues(alpha: 0.3), - ), - border: InputBorder.none, - counterStyle: TextStyle( - fontFamily: 'Inter', - fontSize: 12, - color: AppColors.textSecondary.withValues(alpha: 0.5), - ), - ), - ), - ), - ], - ); - } - - Widget _buildErrorMessage() { - return Container( - margin: const EdgeInsets.only(bottom: AppDimensions.paddingMedium), - padding: const EdgeInsets.all(AppDimensions.paddingMedium), - decoration: BoxDecoration( - color: AppColors.error.withValues(alpha: 0.15), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: AppColors.error.withValues(alpha: 0.3), - width: 1, - ), - ), - child: Row( - children: [ - Icon(LucideIcons.alertCircle, color: AppColors.error, size: 20), - const SizedBox(width: 8), - Expanded( - child: Text( - _errorMessage!, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 14, - color: AppColors.error, - ), - ), - ), - ], - ), - ); - } - - Widget _buildGenerateButton() { - final l10n = L10n.of(context)!; - return PrimaryButton( - text: _isProcessing ? l10n.generating : l10n.generateInvoice, - onPressed: _isValidAmount && !_isProcessing ? _showConfirmation : null, - ); - } - - void _showConfirmation() { - showModalBottomSheet( - context: context, - backgroundColor: Colors.transparent, - isScrollControlled: true, - builder: (context) => _ConfirmationModal( - amount: _amount, - unit: _activeUnit, - description: _descriptionController.text.isNotEmpty - ? _descriptionController.text - : null, - onConfirm: () { - Navigator.pop(context); - _navigateToInvoice(); - }, - onCancel: () => Navigator.pop(context), - ), - ); - } - - void _navigateToInvoice() { - // Navegar a InvoiceScreen que manejará el stream - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => InvoiceScreen( - amount: _amount, - unit: _activeUnit, - description: _descriptionController.text.isNotEmpty - ? _descriptionController.text - : null, - ), - ), - ); - } -} - -/// Modal de confirmación -class _ConfirmationModal extends StatelessWidget { - final BigInt amount; - final String unit; - final String? description; - final VoidCallback onConfirm; - final VoidCallback onCancel; - - const _ConfirmationModal({ - required this.amount, - required this.unit, - this.description, - required this.onConfirm, - required this.onCancel, - }); - - @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, - ), - ), - 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), - ), - ), - - // Icono - Container( - width: 64, - height: 64, - decoration: BoxDecoration( - color: AppColors.primaryAction.withValues(alpha: 0.2), - shape: BoxShape.circle, - ), - child: const Icon( - LucideIcons.zap, - color: AppColors.primaryAction, - size: 32, - ), - ), - const SizedBox(height: AppDimensions.paddingMedium), - - // Título - Text( - L10n.of(context)!.depositLightning, - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: AppDimensions.paddingSmall), - - // Monto - Text( - '${UnitFormatter.formatBalance(amount, unit)} ${UnitFormatter.getUnitLabel(unit)}', - style: const TextStyle( - fontFamily: 'Inter', - fontSize: 32, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - - // Descripción - if (description != null && description!.isNotEmpty) ...[ - const SizedBox(height: AppDimensions.paddingSmall), - Text( - '"$description"', - style: TextStyle( - fontFamily: 'Inter', - fontSize: 14, - fontStyle: FontStyle.italic, - color: AppColors.textSecondary.withValues(alpha: 0.7), - ), - ), - ], - - const SizedBox(height: AppDimensions.paddingLarge), - - // Botones - 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, - ), - ), - ), - ), - ), - ), - const SizedBox(width: AppDimensions.paddingMedium), - Expanded( - child: PrimaryButton( - text: L10n.of(context)!.generateInvoice, - onPressed: onConfirm, - height: 52, - ), - ), - ], - ), - - const SizedBox(height: AppDimensions.paddingSmall), - ], - ), - ); - } -}