import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/theme/app_theme_extension.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../providers/asset_provider.dart'; import '../../../data/models/account_models.dart'; /// 劃轉頁面 - 現代化設計版本 class TransferPage extends StatefulWidget { const TransferPage({super.key}); @override State createState() => _TransferPageState(); } class _TransferPageState extends State with TickerProviderStateMixin { final _amountController = TextEditingController(); final _focusNode = FocusNode(); int _direction = 1; // 1: 資金→交易, 2: 交易→資金 bool _isLoading = false; // Animation controllers late AnimationController _swapAnimationController; late AnimationController _amountAnimationController; late AnimationController _balanceAnimationController; late Animation _swapAnimation; late Animation _amountScaleAnimation; // Balance animation values String _displayedFromBalance = '0.00'; String _displayedToBalance = '0.00'; String _targetFromBalance = '0.00'; String _targetToBalance = '0.00'; @override void initState() { super.initState(); // Initialize swap animation _swapAnimationController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); _swapAnimation = CurvedAnimation( parent: _swapAnimationController, curve: Curves.easeInOutCubic, ); // Initialize amount scale animation _amountAnimationController = AnimationController( duration: const Duration(milliseconds: 200), vsync: this, ); _amountScaleAnimation = Tween(begin: 1.0, end: 1.05).animate( CurvedAnimation( parent: _amountAnimationController, curve: Curves.easeInOut, ), ); // Initialize balance animation _balanceAnimationController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, )..addListener(_updateBalanceAnimation); _amountController.addListener(_onAmountChanged); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().refreshAll(force: true); _updateBalanceTargets(); }); } @override void dispose() { _amountController.removeListener(_onAmountChanged); _amountController.dispose(); _focusNode.dispose(); _swapAnimationController.dispose(); _amountAnimationController.dispose(); _balanceAnimationController.removeListener(_updateBalanceAnimation); _balanceAnimationController.dispose(); super.dispose(); } void _onAmountChanged() { // Trigger amount input animation _amountAnimationController.forward().then((_) { _amountAnimationController.reverse(); }); } void _updateBalanceTargets() { setState(() { _targetFromBalance = _fromBalance; _targetToBalance = _toBalance; _displayedFromBalance = _targetFromBalance; _displayedToBalance = _targetToBalance; }); } void _updateBalanceAnimation() { // This will be used for smooth balance transitions } // ============================================ // 數據訪問 // ============================================ String get _fundBalance { try { final provider = context.read(); final balance = provider.fundAccount?.balance ?? provider.overview?.fundBalance ?? '0.00'; return _formatBalance(balance); } catch (e) { return '0.00'; } } String get _tradeUsdtBalance { try { final provider = context.read(); if (provider.tradeAccounts.isEmpty) { return '0.00'; } final usdtHolding = provider.tradeAccounts.firstWhere( (t) => t.coinCode.toUpperCase() == 'USDT', orElse: () => AccountTrade( id: 0, userId: 0, coinCode: 'USDT', quantity: '0', avgPrice: '1', totalCost: '0', currentValue: '0', profit: '0', profitRate: 0, ), ); return _formatBalance(usdtHolding.quantity); } catch (e) { return '0.00'; } } String get _availableBalance => _direction == 1 ? _fundBalance : _tradeUsdtBalance; String get _fromLabel => _direction == 1 ? '資金賬戶' : '交易賬戶'; String get _toLabel => _direction == 1 ? '交易賬戶' : '資金賬戶'; String get _fromBalance => _direction == 1 ? _fundBalance : _tradeUsdtBalance; String get _toBalance => _direction == 1 ? _tradeUsdtBalance : _fundBalance; IconData get _fromIcon => _direction == 1 ? LucideIcons.wallet : LucideIcons.trendingUp; IconData get _toIcon => _direction == 1 ? LucideIcons.trendingUp : LucideIcons.wallet; // ============================================ // 業務邏輯 // ============================================ Future _doTransfer() async { final amount = _amountController.text; final available = double.tryParse(_availableBalance) ?? 0; final transferAmount = double.tryParse(amount) ?? 0; if (transferAmount <= 0) { _showSnackBar('請輸入有效的劃轉金額'); return; } if (transferAmount > available) { _showSnackBar('餘額不足'); return; } setState(() => _isLoading = true); try { final response = await context.read().transfer( direction: _direction, amount: amount, ); if (mounted) { if (response.success) { _amountController.clear(); _showSnackBar('劃轉成功'); await Future.delayed(const Duration(milliseconds: 500)); if (mounted) { Navigator.of(context).pop(true); } } else { _showSnackBar(response.message ?? '劃轉失敗'); } } } finally { if (mounted) { setState(() => _isLoading = false); } } } void _showSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); } void _setQuickAmount(double percent) { final available = double.tryParse(_availableBalance) ?? 0; final amount = available * percent; _amountController.text = amount.toStringAsFixed(8).replaceAll(RegExp(r'\.?0+$'), ''); // Trigger haptic feedback HapticFeedback.selectionClick(); } Future _toggleDirection() async { // Trigger haptic feedback HapticFeedback.mediumImpact(); await _swapAnimationController.forward(); setState(() { _direction = _direction == 1 ? 2 : 1; _updateBalanceTargets(); }); _swapAnimationController.reset(); } // ============================================ // 構建 UI // ============================================ @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Scaffold( backgroundColor: colorScheme.surface, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, scrolledUnderElevation: 0, leading: IconButton( icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(12), ), child: Icon(LucideIcons.arrowLeft, color: colorScheme.onSurface, size: 20), ), onPressed: () => Navigator.of(context).pop(), ), title: Text( '賬戶劃轉', style: AppTextStyles.headlineLarge(context).copyWith( fontWeight: FontWeight.w700, ), ), centerTitle: true, ), body: Consumer( builder: (context, provider, _) { return Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ colorScheme.surface, colorScheme.surfaceContainerLowest.withValues(alpha: 0.3), ], ), ), child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(AppSpacing.lg, AppSpacing.sm, AppSpacing.lg, AppSpacing.xl), child: Column( children: [ _buildTransferDirectionCard(), const SizedBox(height: AppSpacing.xl), _buildAmountSection(), const SizedBox(height: AppSpacing.xl), _buildTipsCard(), const SizedBox(height: AppSpacing.xl + AppSpacing.sm), _buildConfirmButton(), const SizedBox(height: AppSpacing.lg), ], ), ), ); }, ), ); } // ============================================ // Transfer direction card - Modern Design // ============================================ Widget _buildTransferDirectionCard() { final colorScheme = Theme.of(context).colorScheme; final isDark = colorScheme.brightness == Brightness.dark; // Gold gradient colors final goldStart = isDark ? const Color(0xFFD4AF37) : const Color(0xFFF59E0B); final goldEnd = isDark ? const Color(0xFFFFD700) : const Color(0xFFFFA500); return Container( width: double.infinity, padding: const EdgeInsets.all(AppSpacing.lg), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isDark ? [ colorScheme.surfaceContainerHigh, colorScheme.surfaceContainer, ] : [ Colors.white, colorScheme.surfaceContainerLow, ], ), borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: colorScheme.shadow.withValues(alpha: isDark ? 0.3 : 0.08), blurRadius: 24, offset: const Offset(0, 8), ), BoxShadow( color: goldStart.withValues(alpha: 0.1), blurRadius: 16, offset: const Offset(0, 4), spreadRadius: 2, ), ], border: Border.all( color: colorScheme.outlineVariant.withValues(alpha: 0.3), width: 1, ), ), child: Column( children: [ // From Account Card _AnimatedAccountCard( key: ValueKey('from-$_direction'), label: '從', accountName: _fromLabel, balance: _displayedFromBalance, icon: _fromIcon, isSource: true, animation: _swapAnimation, ), // Swap Button - Enhanced Design Padding( padding: const EdgeInsets.symmetric(vertical: AppSpacing.md), child: GestureDetector( onTap: _toggleDirection, child: Container( width: 56, height: 56, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [goldStart, goldEnd], ), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: goldStart.withValues(alpha: 0.4), blurRadius: 16, offset: const Offset(0, 4), ), BoxShadow( color: goldStart.withValues(alpha: 0.2), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Container( margin: const EdgeInsets.all(2), decoration: BoxDecoration( color: colorScheme.surface, shape: BoxShape.circle, ), child: Container( margin: const EdgeInsets.all(2), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [goldStart, goldEnd], ), shape: BoxShape.circle, ), child: Icon( LucideIcons.arrowUpDown, size: 24, color: colorScheme.surface, ), ), ), ), ), ), // To Account Card _AnimatedAccountCard( key: ValueKey('to-$_direction'), label: '到', accountName: _toLabel, balance: _displayedToBalance, icon: _toIcon, isSource: false, animation: _swapAnimation, ), ], ), ); } // ============================================ // Amount input section - Enhanced Design // ============================================ Widget _buildAmountSection() { final colorScheme = Theme.of(context).colorScheme; final isDark = colorScheme.brightness == Brightness.dark; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header with label and max button Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon( LucideIcons.coins, size: 20, color: colorScheme.onSurfaceVariant, ), const SizedBox(width: 8), Text( '劃轉金額', style: AppTextStyles.headlineSmall(context).copyWith( color: colorScheme.onSurface, fontWeight: FontWeight.w600, ), ), ], ), Material( color: Colors.transparent, child: InkWell( onTap: () => _setQuickAmount(1.0), borderRadius: BorderRadius.circular(16), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( gradient: LinearGradient( colors: [ colorScheme.secondary.withValues(alpha: 0.15), colorScheme.secondary.withValues(alpha: 0.05), ], ), borderRadius: BorderRadius.circular(16), border: Border.all( color: colorScheme.secondary.withValues(alpha: 0.3), width: 1, ), ), child: Text( '全部', style: AppTextStyles.labelLarge(context).copyWith( color: colorScheme.secondary, fontWeight: FontWeight.w600, ), ), ), ), ), ], ), const SizedBox(height: 16), // Amount input card with glow effect AnimatedBuilder( animation: _amountScaleAnimation, builder: (context, child) { return Transform.scale( scale: _amountScaleAnimation.value, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), boxShadow: _focusNode.hasFocus ? [ BoxShadow( color: colorScheme.secondary.withValues(alpha: 0.2), blurRadius: 16, spreadRadius: 2, ), ] : [], ), child: Container( width: double.infinity, height: 80, padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg, vertical: AppSpacing.md), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isDark ? [ colorScheme.surfaceContainerHighest, colorScheme.surfaceContainerHigh, ] : [ Colors.white, colorScheme.surfaceContainerLow, ], ), borderRadius: BorderRadius.circular(20), border: Border.all( color: _focusNode.hasFocus ? colorScheme.secondary : colorScheme.outlineVariant.withValues(alpha: 0.5), width: _focusNode.hasFocus ? 2 : 1, ), boxShadow: [ BoxShadow( color: colorScheme.shadow.withValues(alpha: isDark ? 0.2 : 0.05), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Row( children: [ Expanded( child: TextField( controller: _amountController, focusNode: _focusNode, keyboardType: const TextInputType.numberWithOptions(decimal: true), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,8}')), ], style: TextStyle( fontSize: 36, fontWeight: FontWeight.w700, color: colorScheme.onSurface, letterSpacing: -0.5, ), decoration: InputDecoration( hintText: '0.00', hintStyle: TextStyle( fontSize: 36, fontWeight: FontWeight.w700, color: colorScheme.onSurfaceVariant.withValues(alpha: 0.3), letterSpacing: -0.5, ), border: InputBorder.none, contentPadding: EdgeInsets.zero, isDense: true, ), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(12), ), child: Text( 'USDT', style: AppTextStyles.headlineSmall(context).copyWith( fontWeight: FontWeight.w600, color: colorScheme.onSurfaceVariant, ), ), ), ], ), ), ), ); }, ), const SizedBox(height: 16), // Available balance display Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '可用餘額', style: AppTextStyles.bodyMedium(context).copyWith( color: colorScheme.onSurfaceVariant, ), ), Text( '${_formatBalance(_availableBalance)} USDT', style: AppTextStyles.bodyLarge(context).copyWith( fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), ], ), ), const SizedBox(height: 20), // Percent buttons - Capsule design with gradient Row( children: [ _buildPercentButton('25%', 0.25, 0), const SizedBox(width: 8), _buildPercentButton('50%', 0.50, 1), const SizedBox(width: 8), _buildPercentButton('75%', 0.75, 2), const SizedBox(width: 8), _buildPercentButton('100%', 1.0, 3), ], ), ], ); } Widget _buildPercentButton(String label, double percent, int index) { final colorScheme = Theme.of(context).colorScheme; final isDark = colorScheme.brightness == Brightness.dark; final goldStart = isDark ? const Color(0xFFD4AF37) : const Color(0xFFF59E0B); final goldEnd = isDark ? const Color(0xFFFFD700) : const Color(0xFFFFA500); return Expanded( child: Material( color: Colors.transparent, child: InkWell( onTap: () => _setQuickAmount(percent), borderRadius: BorderRadius.circular(20), child: Container( height: 44, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ goldStart.withValues(alpha: 0.12), goldEnd.withValues(alpha: 0.08), ], ), borderRadius: BorderRadius.circular(20), border: Border.all( color: goldStart.withValues(alpha: 0.25), width: 1.5, ), ), child: Center( child: Text( label, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: goldStart, ), ), ), ), ), ), ); } // ============================================ // Tips card - Refined Design // ============================================ Widget _buildTipsCard() { final colorScheme = Theme.of(context).colorScheme; final isDark = colorScheme.brightness == Brightness.dark; final successColor = isDark ? const Color(0xFF4ADE80) : const Color(0xFF10B981); return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( gradient: LinearGradient( colors: [ successColor.withValues(alpha: 0.08), successColor.withValues(alpha: 0.03), ], ), borderRadius: BorderRadius.circular(16), border: Border.all( color: successColor.withValues(alpha: 0.2), width: 1, ), ), child: Row( children: [ Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: successColor.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(8), ), child: Icon( LucideIcons.sparkles, size: 18, color: successColor, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '即時到賬', style: AppTextStyles.bodyMedium(context).copyWith( color: successColor, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 2), Text( '劃轉即時到賬,無需手續費', style: AppTextStyles.bodySmall(context).copyWith( color: successColor.withValues(alpha: 0.8), ), ), ], ), ), ], ), ); } // ============================================ // Confirm button - Premium Design // ============================================ Widget _buildConfirmButton() { final colorScheme = Theme.of(context).colorScheme; final isDark = colorScheme.brightness == Brightness.dark; final goldStart = isDark ? const Color(0xFFD4AF37) : const Color(0xFFF59E0B); final goldEnd = isDark ? const Color(0xFFFFD700) : const Color(0xFFFFA500); final hasAmount = _amountController.text.isNotEmpty && double.tryParse(_amountController.text) != null && double.parse(_amountController.text) > 0; return Container( height: 60, decoration: BoxDecoration( gradient: hasAmount && !_isLoading ? LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [goldStart, goldEnd], ) : null, color: hasAmount && !_isLoading ? null : colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), boxShadow: hasAmount && !_isLoading ? [ BoxShadow( color: goldStart.withValues(alpha: 0.4), blurRadius: 20, offset: const Offset(0, 8), ), BoxShadow( color: goldStart.withValues(alpha: 0.2), blurRadius: 8, offset: const Offset(0, 4), ), ] : [ BoxShadow( color: colorScheme.shadow.withValues(alpha: 0.08), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Material( color: Colors.transparent, child: InkWell( onTap: _isLoading ? null : _doTransfer, borderRadius: BorderRadius.circular(20), child: Center( child: _isLoading ? SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 2.5, valueColor: AlwaysStoppedAnimation( isDark ? Colors.white : colorScheme.onSurface, ), ), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( LucideIcons.arrowRight, size: 22, color: hasAmount ? Colors.white : colorScheme.onSurfaceVariant, ), const SizedBox(width: 8), Text( '確認劃轉', style: TextStyle( color: hasAmount ? Colors.white : colorScheme.onSurfaceVariant, fontSize: 18, fontWeight: FontWeight.w700, letterSpacing: 0.5, ), ), ], ), ), ), ), ); } // ============================================ // Helpers // ============================================ String _formatBalance(String balance) { final val = double.tryParse(balance); if (val == null) return '0.00'; return val.toStringAsFixed(2); } } // ============================================ // Animated Account Card Widget // ============================================ class _AnimatedAccountCard extends StatelessWidget { final String label; final String accountName; final String balance; final IconData icon; final bool isSource; final Animation animation; const _AnimatedAccountCard({ super.key, required this.label, required this.accountName, required this.balance, required this.icon, required this.isSource, required this.animation, }); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isDark = colorScheme.brightness == Brightness.dark; final goldColor = isDark ? const Color(0xFFD4AF37) : const Color(0xFFF59E0B); return AnimatedBuilder( animation: animation, builder: (context, child) { return Transform.translate( offset: Offset(0, animation.value * (isSource ? -10 : 10)), child: Opacity( opacity: 1.0 - (animation.value * 0.3), child: child, ), ); }, child: Container( width: double.infinity, padding: const EdgeInsets.all(AppSpacing.md), decoration: BoxDecoration( color: isDark ? colorScheme.surfaceContainer.withValues(alpha: 0.5) : colorScheme.surface.withValues(alpha: 0.8), borderRadius: BorderRadius.circular(16), border: Border.all( color: colorScheme.outlineVariant.withValues(alpha: 0.2), width: 1, ), ), child: Row( children: [ // Icon container with gradient Container( width: 48, height: 48, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ goldColor.withValues(alpha: 0.15), goldColor.withValues(alpha: 0.05), ], ), borderRadius: BorderRadius.circular(12), border: Border.all( color: goldColor.withValues(alpha: 0.2), width: 1, ), ), child: Icon( icon, size: 22, color: goldColor, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: AppTextStyles.bodySmall(context).copyWith( color: colorScheme.onSurfaceVariant, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 4), Text( accountName, style: AppTextStyles.headlineSmall(context).copyWith( fontWeight: FontWeight.w600, ), ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '¥ $balance', style: AppTextStyles.headlineSmall(context).copyWith( fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), const SizedBox(height: 4), Text( 'USDT', style: AppTextStyles.bodySmall(context).copyWith( color: colorScheme.onSurfaceVariant, ), ), ], ), ], ), ), ); } }