diff --git a/frontend/genex-mobile/lib/features/trading/presentation/pages/transfer_page.dart b/frontend/genex-mobile/lib/features/trading/presentation/pages/transfer_page.dart index be60098..f77550f 100644 --- a/frontend/genex-mobile/lib/features/trading/presentation/pages/transfer_page.dart +++ b/frontend/genex-mobile/lib/features/trading/presentation/pages/transfer_page.dart @@ -1,12 +1,14 @@ 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'; -/// A9. P2P转赠页面 +/// 转赠页面 - 混合方案 /// -/// 选择好友 → 确认转赠 → 转赠成功 -/// 零区块链术语:使用"转赠"而非"转移NFT" +/// 顶部:扫码转赠 / 输入ID 两个入口卡片 +/// 中部:最近转赠人列表(联系方式有有效期,过期需重新确认) +/// 流程:选方式 → 填/扫地址 → 选券 → 确认 class TransferPage extends StatefulWidget { const TransferPage({super.key}); @@ -15,147 +17,631 @@ class TransferPage extends StatefulWidget { } class _TransferPageState extends State { - final _searchController = TextEditingController(); - String? _selectedFriend; - @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('转赠给好友')), - body: Column( + appBar: AppBar( + title: const Text('转赠'), + actions: [ + TextButton( + onPressed: () {}, + child: Text('转赠记录', + style: AppTypography.labelSmall + .copyWith(color: AppColors.primary)), + ), + ], + ), + body: ListView( + padding: const EdgeInsets.fromLTRB(20, 16, 20, 40), children: [ - // Coupon Info - Container( - margin: const EdgeInsets.all(20), - 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: const Icon(Icons.card_giftcard_rounded, color: AppColors.primary), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('星巴克 \$25 礼品卡', style: AppTypography.labelMedium), - Text('面值 \$25.00', style: AppTypography.bodySmall), - ], - ), - ), - ], - ), - ), + // 两个入口卡片 + _buildEntryCards(context), - // Search Friend - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: TextField( - controller: _searchController, - decoration: const InputDecoration( - hintText: '搜索好友(手机号/用户名)', - prefixIcon: Icon(Icons.search_rounded), - ), - ), - ), - const SizedBox(height: 16), + const SizedBox(height: 28), - // Friends List - Expanded( - child: ListView( - padding: const EdgeInsets.symmetric(horizontal: 20), - children: [ - _buildFriendTile('Alice', 'alice@example.com', 'A'), - _buildFriendTile('Bob', 'bob@example.com', 'B'), - _buildFriendTile('Charlie', 'charlie@example.com', 'C'), - _buildFriendTile('Diana', 'diana@example.com', 'D'), - ], - ), - ), + // 最近转赠 + _buildRecentSection(), - // Transfer Button - Container( - padding: const EdgeInsets.all(20), - child: SizedBox( - width: double.infinity, - height: AppSpacing.buttonHeight, - child: ElevatedButton( - onPressed: _selectedFriend != null ? () => _showConfirm(context) : null, - child: Text(_selectedFriend != null ? '转赠给 $_selectedFriend' : '请选择好友'), - ), - ), - ), + const SizedBox(height: 28), + + // 转赠记录摘要 + _buildTransferHistory(), ], ), ); } - Widget _buildFriendTile(String name, String email, String avatar) { - final isSelected = _selectedFriend == name; + // ============================================================ + // 入口卡片:扫码 / 输入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('扫码转赠', + style: AppTypography.labelMedium + .copyWith(color: Colors.white)), + const SizedBox(height: 4), + Text('扫描对方接收码', + 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('输入ID', style: AppTypography.labelMedium), + const SizedBox(height: 4), + Text('邮箱 / 手机 / 接收ID', + style: AppTypography.caption.copyWith( + color: AppColors.textTertiary, + )), + ], + ), + ), + ), + ), + ], + ); + } + + // ============================================================ + // 最近转赠人列表(联系方式有有效期) + // ============================================================ + Widget _buildRecentSection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('最近转赠', style: AppTypography.h3), + GestureDetector( + onTap: () {}, + child: Text('管理', + 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('暂无转赠记录', + style: AppTypography.bodySmall + .copyWith(color: AppColors.textTertiary)), + const SizedBox(height: 4), + Text('通过扫码或输入ID开始第一次转赠', + style: AppTypography.caption + .copyWith(color: AppColors.textDisabled)), + ], + ), + ); + } + + Widget _buildRecentItem(_RecentRecipient recipient) { + final isExpired = recipient.isExpired; + return GestureDetector( - onTap: () => setState(() => _selectedFriend = name), + onTap: isExpired + ? () => _showExpiredDialog(context, recipient) + : () => _navigateToSelectCoupon(context, recipient), child: Container( - margin: const EdgeInsets.only(bottom: 8), + margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppColors.surface, borderRadius: AppSpacing.borderRadiusMd, border: Border.all( - color: isSelected ? AppColors.primary : AppColors.borderLight, - width: isSelected ? 1.5 : 1, + color: isExpired ? AppColors.borderLight : AppColors.borderLight, ), ), child: Row( children: [ + // 头像/首字母 CircleAvatar( radius: 20, - backgroundColor: AppColors.primaryContainer, - child: Text(avatar, style: const TextStyle(color: AppColors.primary, fontWeight: FontWeight.w600)), + 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: [ - Text(name, style: AppTypography.labelMedium), - Text(email, style: AppTypography.caption), + 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, + style: AppTypography.caption.copyWith( + fontSize: 10, + color: isExpired + ? AppColors.textDisabled + : AppColors.primary, + ), + ), + ), + ], + ), + const SizedBox(height: 2), + Row( + children: [ + Text( + recipient.maskedContact, + style: AppTypography.caption.copyWith( + color: AppColors.textTertiary, + ), + ), + const SizedBox(width: 8), + Text( + '上次: ${recipient.lastTransferText}', + style: AppTypography.caption.copyWith( + color: AppColors.textDisabled, + fontSize: 10, + ), + ), + ], + ), ], ), ), - if (isSelected) const Icon(Icons.check_circle_rounded, color: AppColors.primary, size: 22), + + // 状态 + if (isExpired) + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppColors.gray100, + borderRadius: AppSpacing.borderRadiusFull, + ), + child: Text('已过期', + style: AppTypography.caption.copyWith( + color: AppColors.textDisabled, + fontSize: 10, + )), + ) + else + const Icon(Icons.chevron_right_rounded, + color: AppColors.textTertiary, size: 20), ], ), ), ); } - void _showConfirm(BuildContext context) { + // ============================================================ + // 转赠记录摘要 + // ============================================================ + Widget _buildTransferHistory() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('最近转赠记录', 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 ? "转赠给" : "收到来自"} ${h.personName}', + style: AppTypography.caption + .copyWith(color: AppColors.textTertiary), + ), + ], + ), + ), + Text(h.dateText, + 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('输入收款人', style: AppTypography.h2), + const SizedBox(height: 4), + Text('支持接收ID、邮箱或手机号', + 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: '粘贴', + ), + ), + ), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + height: AppSpacing.buttonHeight, + child: ElevatedButton( + onPressed: () { + Navigator.pop(ctx); + // 进入选券页面,带上收款人信息 + _navigateToSelectCouponWithInput( + context, controller.text); + }, + child: const Text('下一步:选择券'), + ), + ), + ], + ), + ), + ); + } + + // ============================================================ + // 联系方式过期提示 + // ============================================================ + void _showExpiredDialog(BuildContext context, _RecentRecipient recipient) { + showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text('联系方式已过期'), + content: Text( + '${recipient.displayName} 的${recipient.contactTypeLabel}' + '已超过90天未验证,请重新输入确认。', + style: AppTypography.bodyMedium + .copyWith(color: AppColors.textSecondary), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: const Text('取消'), + ), + TextButton( + onPressed: () { + Navigator.pop(ctx); + _showInputRecipient(context); + }, + child: const Text('重新输入'), + ), + ], + ), + ); + } + + // ============================================================ + // 导航到选券(带收款人信息) + // ============================================================ + 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('选择要转赠的券', style: AppTypography.h2), + const SizedBox(height: 2), + Text('转赠给 $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 Icon(Icons.card_giftcard_rounded, + color: AppColors.primary, size: 48), const SizedBox(height: 16), Text('确认转赠', 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('面值 \$${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('转赠给 ', + style: AppTypography.bodyMedium + .copyWith(color: AppColors.textSecondary)), + Text(recipientName, + style: AppTypography.labelMedium + .copyWith(color: AppColors.primary)), + ], + ), const SizedBox(height: 8), - Text('将 星巴克 \$25 礼品卡 转赠给 $_selectedFriend?', style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)), - const SizedBox(height: 8), - Text('转赠后您将不再持有此券', style: AppTypography.caption.copyWith(color: AppColors.warning)), + + Text('转赠后您将不再持有此券', + style: AppTypography.caption + .copyWith(color: AppColors.warning)), const SizedBox(height: 24), + Row( children: [ Expanded( @@ -169,7 +655,7 @@ class _TransferPageState extends State { child: ElevatedButton( onPressed: () { Navigator.pop(ctx); - _showSuccess(context); + _showSuccess(context, recipientName, coupon); }, child: const Text('确认转赠'), ), @@ -182,18 +668,23 @@ class _TransferPageState extends State { ); } - void _showSuccess(BuildContext context) { + 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 Icon(Icons.check_circle_rounded, + color: AppColors.success, size: 56), const SizedBox(height: 16), Text('转赠成功', style: AppTypography.h2), const SizedBox(height: 8), - Text('$_selectedFriend 已收到您的券', style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)), + Text('${coupon.name} 已转赠给 $recipientName', + style: AppTypography.bodyMedium + .copyWith(color: AppColors.textSecondary), + textAlign: TextAlign.center), ], ), actions: [ @@ -202,10 +693,175 @@ class _TransferPageState extends State { Navigator.pop(ctx); Navigator.pop(context); }, - child: const Text('确定'), + child: const Text('完成'), ), ], ), ); } } + +// ============================================================ +// 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 get contactTypeLabel { + switch (contactType) { + case _ContactType.email: + return '邮箱'; + case _ContactType.phone: + return '手机'; + 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 get lastTransferText { + final days = DateTime.now().difference(lastTransfer).inDays; + if (days == 0) return '今天'; + if (days == 1) return '昨天'; + if (days < 7) return '$days天前'; + if (days < 30) return '${days ~/ 7}周前'; + return '${days ~/ 30}月前'; + } +} + +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 get dateText { + final days = DateTime.now().difference(date).inDays; + if (days == 0) return '今天'; + if (days == 1) return '昨天'; + if (days < 7) return '$days天前'; + 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)), + ), +];