import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_typography.dart'; import '../../../../app/theme/app_spacing.dart'; import '../../../../app/i18n/app_localizations.dart'; /// 转赠页面 - 混合方案 /// /// 顶部:扫码转赠 / 输入ID 两个入口卡片 /// 中部:最近转赠人列表(联系方式有有效期,过期需重新确认) /// 流程:选方式 → 填/扫地址 → 选券 → 确认 class TransferPage extends StatefulWidget { const TransferPage({super.key}); @override State createState() => _TransferPageState(); } class _TransferPageState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(context.t('transfer.title')), actions: [ TextButton( onPressed: () {}, child: Text(context.t('transfer.transferHistory'), style: AppTypography.labelSmall .copyWith(color: AppColors.primary)), ), ], ), body: ListView( padding: const EdgeInsets.fromLTRB(20, 16, 20, 40), children: [ // 两个入口卡片 _buildEntryCards(context), const SizedBox(height: 28), // 最近转赠 _buildRecentSection(), const SizedBox(height: 28), // 转赠记录摘要 _buildTransferHistory(), ], ), ); } // ============================================================ // 入口卡片:扫码 / 输入ID // ============================================================ Widget _buildEntryCards(BuildContext context) { return Row( children: [ // 扫码转赠 Expanded( child: GestureDetector( onTap: () { // TODO: 打开扫码器,扫描对方接收二维码 }, child: Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: AppColors.primaryGradient, borderRadius: AppSpacing.borderRadiusMd, boxShadow: AppSpacing.shadowPrimary, ), child: Column( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: AppSpacing.borderRadiusMd, ), child: const Icon(Icons.qr_code_scanner_rounded, size: 26, color: Colors.white), ), const SizedBox(height: 10), Text(context.t('transfer.scanTransfer'), style: AppTypography.labelMedium .copyWith(color: Colors.white)), const SizedBox(height: 4), Text(context.t('transfer.scanDesc').split('\n')[0], style: AppTypography.caption.copyWith( color: Colors.white.withValues(alpha: 0.7), )), ], ), ), ), ), const SizedBox(width: 12), // 输入ID/邮箱/手机 Expanded( child: GestureDetector( onTap: () => _showInputRecipient(context), child: Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppColors.surface, borderRadius: AppSpacing.borderRadiusMd, border: Border.all(color: AppColors.borderLight), ), child: Column( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: AppColors.primarySurface, borderRadius: AppSpacing.borderRadiusMd, ), child: const Icon(Icons.edit_rounded, size: 26, color: AppColors.primary), ), const SizedBox(height: 10), Text(context.t('transfer.inputTransfer').split('/')[0], style: AppTypography.labelMedium), const SizedBox(height: 4), Text(context.t('transfer.recipientIdHint'), style: AppTypography.caption.copyWith( color: AppColors.textTertiary, )), ], ), ), ), ), ], ); } // ============================================================ // 最近转赠人列表(联系方式有有效期) // ============================================================ Widget _buildRecentSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(context.t('transfer.recentRecipients'), style: AppTypography.h3), GestureDetector( onTap: () {}, child: Text(context.t('transfer.manage'), style: AppTypography.caption .copyWith(color: AppColors.primary)), ), ], ), const SizedBox(height: 12), if (_mockRecents.isEmpty) _buildEmptyRecent() else ..._mockRecents.map((r) => _buildRecentItem(r)), ], ); } Widget _buildEmptyRecent() { return Container( padding: const EdgeInsets.symmetric(vertical: 32), width: double.infinity, decoration: BoxDecoration( color: AppColors.gray50, borderRadius: AppSpacing.borderRadiusMd, ), child: Column( children: [ Icon(Icons.people_outline_rounded, size: 36, color: AppColors.textDisabled), const SizedBox(height: 8), Text(context.t('transfer.noRecent').split('\n')[0], style: AppTypography.bodySmall .copyWith(color: AppColors.textTertiary)), const SizedBox(height: 4), Text(context.t('transfer.noRecent').split('\n')[1], style: AppTypography.caption .copyWith(color: AppColors.textDisabled)), ], ), ); } Widget _buildRecentItem(_RecentRecipient recipient) { final isExpired = recipient.isExpired; return GestureDetector( onTap: isExpired ? () => _showExpiredDialog(context, recipient) : () => _navigateToSelectCoupon(context, recipient), child: Container( margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppColors.surface, borderRadius: AppSpacing.borderRadiusMd, border: Border.all( color: isExpired ? AppColors.borderLight : AppColors.borderLight, ), ), child: Row( children: [ // 头像/首字母 CircleAvatar( radius: 20, backgroundColor: isExpired ? AppColors.gray100 : AppColors.primaryContainer, child: Text( recipient.avatarLetter, style: TextStyle( color: isExpired ? AppColors.textDisabled : AppColors.primary, fontWeight: FontWeight.w600, ), ), ), const SizedBox(width: 12), // 信息 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( recipient.displayName, style: AppTypography.labelMedium.copyWith( color: isExpired ? AppColors.textDisabled : AppColors.textPrimary, ), ), const SizedBox(width: 6), // 联系方式类型标签 Container( padding: const EdgeInsets.symmetric( horizontal: 5, vertical: 1), decoration: BoxDecoration( color: isExpired ? AppColors.gray100 : AppColors.primarySurface, borderRadius: AppSpacing.borderRadiusFull, ), child: Text( recipient.contactTypeLabel(context), style: AppTypography.caption.copyWith( fontSize: 10, color: isExpired ? AppColors.textDisabled : AppColors.primary, ), ), ), ], ), const SizedBox(height: 2), Row( children: [ Flexible( child: Text( recipient.maskedContact, overflow: TextOverflow.ellipsis, style: AppTypography.caption.copyWith( color: AppColors.textTertiary, ), ), ), const SizedBox(width: 8), Text( '${context.t('transfer.lastTransfer')}: ${recipient.lastTransferText(context)}', style: AppTypography.caption.copyWith( color: AppColors.textDisabled, fontSize: 10, ), ), ], ), ], ), ), // 状态 if (isExpired) Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: AppColors.gray100, borderRadius: AppSpacing.borderRadiusFull, ), child: Text(context.t('transfer.expired'), style: AppTypography.caption.copyWith( color: AppColors.textDisabled, fontSize: 10, )), ) else const Icon(Icons.chevron_right_rounded, color: AppColors.textTertiary, size: 20), ], ), ), ); } // ============================================================ // 转赠记录摘要 // ============================================================ Widget _buildTransferHistory() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(context.t('transfer.transferHistory'), style: AppTypography.h3), const SizedBox(height: 12), ..._mockHistory.map((h) => Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.gray50, borderRadius: AppSpacing.borderRadiusSm, ), child: Row( children: [ Icon( h.isOutgoing ? Icons.arrow_upward_rounded : Icons.arrow_downward_rounded, size: 16, color: h.isOutgoing ? AppColors.error : AppColors.success, ), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(h.couponName, style: AppTypography.labelSmall), Text( '${h.isOutgoing ? context.t('transfer.transferTo') : context.t('transfer.incoming')} ${h.personName}', style: AppTypography.caption .copyWith(color: AppColors.textTertiary), ), ], ), ), Text(h.dateText(context), style: AppTypography.caption .copyWith(color: AppColors.textDisabled)), ], ), )), ], ); } // ============================================================ // 输入收款人弹窗 // ============================================================ void _showInputRecipient(BuildContext context) { final controller = TextEditingController(); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: AppColors.surface, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (ctx) => Padding( padding: EdgeInsets.fromLTRB( 20, 24, 20, MediaQuery.of(ctx).viewInsets.bottom + 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(context.t('transfer.inputRecipient'), style: AppTypography.h2), const SizedBox(height: 4), Text(context.t('transfer.recipientIdHint'), style: AppTypography.bodySmall .copyWith(color: AppColors.textTertiary)), const SizedBox(height: 20), TextField( controller: controller, autofocus: true, decoration: InputDecoration( hintText: 'GNX-USR-XXXX / email / phone', prefixIcon: const Icon(Icons.person_search_rounded, size: 22), suffixIcon: IconButton( icon: const Icon(Icons.content_paste_rounded, size: 20), onPressed: () async { final data = await Clipboard.getData('text/plain'); if (data?.text != null) { controller.text = data!.text!; } }, tooltip: context.t('transfer.paste'), ), ), ), const SizedBox(height: 20), SizedBox( width: double.infinity, height: AppSpacing.buttonHeight, child: ElevatedButton( onPressed: () { Navigator.pop(ctx); // 进入选券页面,带上收款人信息 _navigateToSelectCouponWithInput( context, controller.text); }, child: Text(context.t('transfer.selectCoupon')), ), ), ], ), ), ); } // ============================================================ // 联系方式过期提示 // ============================================================ void _showExpiredDialog(BuildContext context, _RecentRecipient recipient) { showDialog( context: context, builder: (ctx) => AlertDialog( title: Text(context.t('transfer.expired')), content: Text( '${recipient.displayName} ${recipient.contactTypeLabel(context)}', style: AppTypography.bodyMedium .copyWith(color: AppColors.textSecondary), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: Text(context.t('transfer.refresh')), ), TextButton( onPressed: () { Navigator.pop(ctx); _showInputRecipient(context); }, child: Text(context.t('transfer.inputRecipient')), ), ], ), ); } // ============================================================ // 导航到选券(带收款人信息) // ============================================================ void _navigateToSelectCoupon( BuildContext context, _RecentRecipient recipient) { _showSelectCouponSheet(context, recipient.displayName); } void _navigateToSelectCouponWithInput( BuildContext context, String input) { if (input.trim().isEmpty) return; _showSelectCouponSheet(context, input); } void _showSelectCouponSheet(BuildContext context, String recipientName) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: AppColors.surface, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (ctx) => DraggableScrollableSheet( initialChildSize: 0.7, maxChildSize: 0.9, minChildSize: 0.5, expand: false, builder: (_, scrollController) => Column( children: [ // Header Padding( padding: const EdgeInsets.fromLTRB(20, 16, 20, 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(context.t('transfer.selectCoupon'), style: AppTypography.h2), const SizedBox(height: 2), Text('${context.t('transfer.transferTo')} $recipientName', style: AppTypography.caption .copyWith(color: AppColors.primary)), ], ), IconButton( icon: const Icon(Icons.close_rounded), onPressed: () => Navigator.pop(ctx), ), ], ), ), const Divider(), // Coupon list Expanded( child: ListView.separated( controller: scrollController, padding: const EdgeInsets.fromLTRB(20, 8, 20, 24), itemCount: _mockCouponsForTransfer.length, separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, index) { final c = _mockCouponsForTransfer[index]; return GestureDetector( onTap: () { Navigator.pop(ctx); _showConfirm(context, recipientName, c); }, child: Container( padding: AppSpacing.cardPadding, decoration: BoxDecoration( color: AppColors.surface, borderRadius: AppSpacing.borderRadiusMd, border: Border.all(color: AppColors.borderLight), ), child: Row( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: AppColors.primarySurface, borderRadius: AppSpacing.borderRadiusSm, ), child: Icon( Icons.confirmation_number_outlined, color: AppColors.primary .withValues(alpha: 0.4), size: 22), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(c.brandName, style: AppTypography.caption), Text(c.name, style: AppTypography.labelMedium), ], ), ), Text( '\$${c.faceValue.toStringAsFixed(0)}', style: AppTypography.labelMedium.copyWith( color: AppColors.primary), ), ], ), ), ); }, ), ), ], ), ), ); } // ============================================================ // 确认转赠 // ============================================================ void _showConfirm( BuildContext context, String recipientName, _TransferCoupon coupon) { showModalBottomSheet( context: context, backgroundColor: AppColors.surface, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (ctx) => Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.card_giftcard_rounded, color: AppColors.primary, size: 48), const SizedBox(height: 16), Text(context.t('transfer.confirmTransfer'), style: AppTypography.h2), const SizedBox(height: 12), // 券信息 Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.gray50, borderRadius: AppSpacing.borderRadiusSm, ), child: Column( children: [ Text(coupon.name, style: AppTypography.labelMedium), const SizedBox(height: 4), Text('${context.t('market.faceValue')} \$${coupon.faceValue.toStringAsFixed(2)}', style: AppTypography.bodySmall), ], ), ), const SizedBox(height: 12), // 收款人 Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.arrow_downward_rounded, size: 16, color: AppColors.primary), const SizedBox(width: 6), Text('${context.t('transfer.transferTo')} ', style: AppTypography.bodyMedium .copyWith(color: AppColors.textSecondary)), Text(recipientName, style: AppTypography.labelMedium .copyWith(color: AppColors.primary)), ], ), const SizedBox(height: 8), Text(context.t('transfer.confirmTransfer'), style: AppTypography.caption .copyWith(color: AppColors.warning)), const SizedBox(height: 24), Row( children: [ Expanded( child: OutlinedButton( onPressed: () => Navigator.pop(ctx), child: Text(context.t('trading.cancelOrder')), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton( onPressed: () { Navigator.pop(ctx); _showSuccess(context, recipientName, coupon); }, child: Text(context.t('transfer.confirmBtn')), ), ), ], ), ], ), ), ); } void _showSuccess( BuildContext context, String recipientName, _TransferCoupon coupon) { showDialog( context: context, builder: (ctx) => AlertDialog( content: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.check_circle_rounded, color: AppColors.success, size: 56), const SizedBox(height: 16), Text(context.t('transfer.title'), style: AppTypography.h2), const SizedBox(height: 8), Text('${coupon.name} ${context.t('transfer.transferTo')} $recipientName', style: AppTypography.bodyMedium .copyWith(color: AppColors.textSecondary), textAlign: TextAlign.center), ], ), actions: [ TextButton( onPressed: () { Navigator.pop(ctx); Navigator.pop(context); }, child: Text(context.t('sellOrder.ok')), ), ], ), ); } } // ============================================================ // Data Models // ============================================================ enum _ContactType { email, phone, receiveId } class _RecentRecipient { final String displayName; final String contact; final _ContactType contactType; final DateTime lastTransfer; final DateTime contactExpiry; const _RecentRecipient({ required this.displayName, required this.contact, required this.contactType, required this.lastTransfer, required this.contactExpiry, }); bool get isExpired => DateTime.now().isAfter(contactExpiry); String get avatarLetter => displayName.isNotEmpty ? displayName[0] : '?'; String contactTypeLabel(BuildContext context) { switch (contactType) { case _ContactType.email: return context.t('transfer.contactEmail'); case _ContactType.phone: return context.t('transfer.contactPhone'); case _ContactType.receiveId: return 'ID'; } } String get maskedContact { switch (contactType) { case _ContactType.email: final parts = contact.split('@'); if (parts.length == 2) { return '${parts[0].substring(0, 2)}***@${parts[1]}'; } return contact; case _ContactType.phone: if (contact.length >= 7) { return '${contact.substring(0, 3)}****${contact.substring(contact.length - 4)}'; } return contact; case _ContactType.receiveId: return contact; } } String lastTransferText(BuildContext context) { final days = DateTime.now().difference(lastTransfer).inDays; if (days == 0) return context.t('common.today'); if (days == 1) return context.t('transfer.yesterday'); if (days < 7) return '$days${context.t('transfer.daysAgo')}'; if (days < 30) return '${days ~/ 7}${context.t('transfer.weeksAgo')}'; return '${days ~/ 30}${context.t('transfer.monthsAgo')}'; } } class _TransferCoupon { final String brandName; final String name; final double faceValue; const _TransferCoupon({ required this.brandName, required this.name, required this.faceValue, }); } class _TransferRecord { final String couponName; final String personName; final bool isOutgoing; final DateTime date; const _TransferRecord({ required this.couponName, required this.personName, required this.isOutgoing, required this.date, }); String dateText(BuildContext context) { final days = DateTime.now().difference(date).inDays; if (days == 0) return context.t('common.today'); if (days == 1) return context.t('transfer.yesterday'); if (days < 7) return '$days${context.t('transfer.daysAgo')}'; return '${date.month}/${date.day}'; } } // ============================================================ // Mock Data // ============================================================ final _mockRecents = [ _RecentRecipient( displayName: 'Alice', contact: 'alice@gmail.com', contactType: _ContactType.email, lastTransfer: DateTime.now().subtract(const Duration(days: 3)), contactExpiry: DateTime.now().add(const Duration(days: 87)), ), _RecentRecipient( displayName: 'Bob', contact: '1385551234', contactType: _ContactType.phone, lastTransfer: DateTime.now().subtract(const Duration(days: 12)), contactExpiry: DateTime.now().add(const Duration(days: 78)), ), _RecentRecipient( displayName: 'Charlie', contact: 'GNX-USR-7B2E-M4F1', contactType: _ContactType.receiveId, lastTransfer: DateTime.now().subtract(const Duration(days: 45)), contactExpiry: DateTime.now().subtract(const Duration(days: 5)), // 已过期 ), ]; const _mockCouponsForTransfer = [ _TransferCoupon( brandName: 'Starbucks', name: '星巴克 \$25 礼品卡', faceValue: 25.0, ), _TransferCoupon( brandName: 'Amazon', name: 'Amazon \$100 购物券', faceValue: 100.0, ), _TransferCoupon( brandName: 'Target', name: 'Target \$30 折扣券', faceValue: 30.0, ), ]; final _mockHistory = [ _TransferRecord( couponName: '星巴克 \$25 礼品卡', personName: 'Alice', isOutgoing: true, date: DateTime.now().subtract(const Duration(days: 3)), ), _TransferRecord( couponName: 'Nike \$80 运动券', personName: 'Bob', isOutgoing: false, date: DateTime.now().subtract(const Duration(days: 7)), ), _TransferRecord( couponName: 'Walmart \$50 生活券', personName: 'Diana', isOutgoing: true, date: DateTime.now().subtract(const Duration(days: 15)), ), ];