refactor: 轻量化首页钱包卡片,新增完整钱包页面

- 首页钱包区域从重量级(stats+filter+coupon cards+actions)
  精简为轻量卡片(汇总信息+4个快捷入口),点击进入完整钱包页
- 新增 wallet_coupons_page.dart:融合"我的券"全部功能
  (汇总面板+4-Tab筛选+券列表+转赠/出售快捷操作+接收券)
- 分类网格从6项(3列)扩展为8项(4列x2行):
  限时抢购/新券首发/折扣排行/即将到期/比价/转让市场/热门交易/全部
- HomePage 从 StatefulWidget 简化为 StatelessWidget
- main.dart 新增 /wallet/coupons 路由

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-11 18:39:22 -08:00
parent b63414542b
commit b1a0f29f06
3 changed files with 486 additions and 385 deletions

View File

@ -6,19 +6,12 @@ import '../../../../shared/widgets/coupon_card.dart';
import '../../../ai_agent/presentation/widgets/ai_fab.dart';
import '../widgets/receive_coupon_sheet.dart';
/// - + + AI推荐 +
/// - + + AI推荐 +
///
/// Tab导航///
class HomePage extends StatefulWidget {
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _walletFilter = 0; // 0=, 1=使, 2=, 3=
@override
Widget build(BuildContext context) {
return Scaffold(
@ -43,10 +36,10 @@ class _HomePageState extends State<HomePage> {
],
),
// Coupon Wallet (replaces Banner)
SliverToBoxAdapter(child: _buildCouponWallet(context)),
// Lightweight Wallet Card (replaces heavy wallet)
SliverToBoxAdapter(child: _buildWalletCard(context)),
// Category Grid (6 new categories)
// Category Grid (8 items, 4x2)
SliverToBoxAdapter(child: _buildCategoryGrid()),
// AI Smart Suggestions
@ -143,277 +136,79 @@ class _HomePageState extends State<HomePage> {
}
// ============================================================
// Coupon Wallet Section (replaces Banner)
// Lightweight Wallet Card
//
// ============================================================
Widget _buildCouponWallet(BuildContext context) {
return Container(
margin: const EdgeInsets.fromLTRB(20, 8, 20, 0),
decoration: BoxDecoration(
gradient: AppColors.cardGradient,
borderRadius: AppSpacing.borderRadiusLg,
boxShadow: AppSpacing.shadowPrimary,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header: + button
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Icon(Icons.account_balance_wallet_rounded,
size: 20, color: Colors.white),
const SizedBox(width: 8),
Text('我的钱包',
style: AppTypography.h3.copyWith(color: Colors.white)),
],
),
GestureDetector(
onTap: () => _showReceiveSheet(context),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.2),
borderRadius: AppSpacing.borderRadiusFull,
border: Border.all(
color: Colors.white.withValues(alpha: 0.3),
width: 0.5,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.qr_code_rounded,
size: 14, color: Colors.white),
const SizedBox(width: 4),
Text('接收',
style: AppTypography.labelSmall.copyWith(
color: Colors.white,
fontWeight: FontWeight.w500,
)),
],
),
),
),
],
),
),
// Stats row
Padding(
padding: const EdgeInsets.fromLTRB(16, 14, 16, 0),
child: Row(
children: [
_buildWalletStat('可使用', '3', true),
const SizedBox(width: 20),
_buildWalletStat('待核销', '1', false),
const SizedBox(width: 20),
_buildWalletStat('已过期', '0', false),
],
),
),
// Filter tabs
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
child: Row(
children: [
_buildWalletTab('全部', 0),
const SizedBox(width: 6),
_buildWalletTab('可使用', 1),
const SizedBox(width: 6),
_buildWalletTab('待核销', 2),
const SizedBox(width: 6),
_buildWalletTab('已过期', 3),
],
),
),
// Coupon mini-cards (horizontal scroll)
SizedBox(
height: 88,
child: _filteredWalletCoupons.isEmpty
? Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'暂无券,去交易市场看看吧',
style: AppTypography.bodySmall.copyWith(
color: Colors.white.withValues(alpha: 0.6),
),
),
),
)
: ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.fromLTRB(16, 10, 16, 14),
itemCount: _filteredWalletCoupons.length,
separatorBuilder: (_, __) => const SizedBox(width: 10),
itemBuilder: (context, index) {
final coupon = _filteredWalletCoupons[index];
return _buildWalletCouponCard(context, coupon);
},
),
),
// Quick actions bar
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.1),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildWalletAction(Icons.qr_code_rounded, '接收', () {
_showReceiveSheet(context);
}),
_buildWalletAction(Icons.card_giftcard_rounded, '转赠', () {
Navigator.pushNamed(context, '/transfer');
}),
_buildWalletAction(Icons.sell_rounded, '出售', () {
Navigator.pushNamed(context, '/sell');
}),
_buildWalletAction(Icons.check_circle_outline_rounded, '核销', () {
Navigator.pushNamed(context, '/redeem');
}),
],
),
),
],
),
);
}
Widget _buildWalletStat(String label, String count, bool highlight) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
count,
style: AppTypography.h2.copyWith(
color: highlight ? Colors.white : Colors.white.withValues(alpha: 0.7),
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 2),
Text(
label,
style: AppTypography.caption.copyWith(
color: Colors.white.withValues(alpha: 0.6),
),
),
],
);
}
Widget _buildWalletTab(String label, int index) {
final isSelected = _walletFilter == index;
Widget _buildWalletCard(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => _walletFilter = index),
onTap: () => Navigator.pushNamed(context, '/wallet/coupons'),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
margin: const EdgeInsets.fromLTRB(20, 8, 20, 0),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isSelected
? Colors.white.withValues(alpha: 0.25)
: Colors.transparent,
borderRadius: AppSpacing.borderRadiusFull,
border: Border.all(
color: isSelected
? Colors.white.withValues(alpha: 0.4)
: Colors.transparent,
width: 0.5,
),
),
child: Text(
label,
style: AppTypography.caption.copyWith(
color: isSelected
? Colors.white
: Colors.white.withValues(alpha: 0.5),
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
),
),
),
);
}
Widget _buildWalletCouponCard(BuildContext context, _WalletCoupon coupon) {
return GestureDetector(
onTap: () => Navigator.pushNamed(context, '/coupon/mine/detail'),
child: Container(
width: 140,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.15),
borderRadius: AppSpacing.borderRadiusMd,
border: Border.all(
color: Colors.white.withValues(alpha: 0.2),
width: 0.5,
),
gradient: AppColors.cardGradient,
borderRadius: AppSpacing.borderRadiusLg,
boxShadow: AppSpacing.shadowPrimary,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Top row: wallet info + receive button
Row(
children: [
Icon(Icons.confirmation_number_outlined,
size: 14, color: Colors.white.withValues(alpha: 0.7)),
const SizedBox(width: 4),
Expanded(
child: Text(
coupon.brandName,
style: AppTypography.caption.copyWith(
const Icon(Icons.account_balance_wallet_rounded,
size: 20, color: Colors.white),
const SizedBox(width: 8),
Text('我的钱包',
style: AppTypography.labelMedium.copyWith(color: Colors.white)),
const Spacer(),
// Summary
Text('持有 ',
style: AppTypography.bodySmall.copyWith(
color: Colors.white.withValues(alpha: 0.7),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
)),
Text('4',
style: AppTypography.h3.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
)),
Text(' 张券 总值 ',
style: AppTypography.bodySmall.copyWith(
color: Colors.white.withValues(alpha: 0.7),
)),
Text('\$235',
style: AppTypography.h3.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
)),
const SizedBox(width: 4),
Icon(Icons.chevron_right_rounded,
size: 18, color: Colors.white.withValues(alpha: 0.7)),
],
),
Text(
coupon.name,
style: AppTypography.labelSmall.copyWith(
color: Colors.white,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 14),
// Quick action entries
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'\$${coupon.faceValue.toStringAsFixed(0)}',
style: AppTypography.priceSmall.copyWith(
color: Colors.white,
fontSize: 13,
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
decoration: BoxDecoration(
color: coupon.statusColor.withValues(alpha: 0.3),
borderRadius: AppSpacing.borderRadiusFull,
),
child: Text(
coupon.statusLabel,
style: AppTypography.caption.copyWith(
color: Colors.white,
fontSize: 9,
fontWeight: FontWeight.w500,
),
),
),
_buildQuickAction(context, Icons.qr_code_rounded, '接收', () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => const ReceiveCouponSheet(),
);
}),
_buildQuickAction(context, Icons.card_giftcard_rounded, '转赠', () {
Navigator.pushNamed(context, '/transfer');
}),
_buildQuickAction(context, Icons.sell_rounded, '出售', () {
Navigator.pushNamed(context, '/sell');
}),
_buildQuickAction(context, Icons.check_circle_outline_rounded, '核销', () {
Navigator.pushNamed(context, '/redeem');
}),
],
),
],
@ -422,18 +217,28 @@ class _HomePageState extends State<HomePage> {
);
}
Widget _buildWalletAction(IconData icon, String label, VoidCallback onTap) {
Widget _buildQuickAction(
BuildContext context, IconData icon, String label, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 20, color: Colors.white.withValues(alpha: 0.9)),
const SizedBox(height: 4),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.15),
borderRadius: AppSpacing.borderRadiusMd,
),
child: Icon(icon, size: 20, color: Colors.white),
),
const SizedBox(height: 6),
Text(
label,
style: AppTypography.caption.copyWith(
color: Colors.white.withValues(alpha: 0.8),
color: Colors.white.withValues(alpha: 0.9),
fontWeight: FontWeight.w500,
),
),
@ -442,43 +247,19 @@ class _HomePageState extends State<HomePage> {
);
}
void _showReceiveSheet(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => const ReceiveCouponSheet(),
);
}
List<_WalletCoupon> get _filteredWalletCoupons {
if (_walletFilter == 0) return _mockWalletCoupons;
if (_walletFilter == 1) {
return _mockWalletCoupons
.where((c) => c.status == CouponStatus.active)
.toList();
}
if (_walletFilter == 2) {
return _mockWalletCoupons
.where((c) => c.status == CouponStatus.pending)
.toList();
}
return _mockWalletCoupons
.where((c) => c.status == CouponStatus.expired)
.toList();
}
// ============================================================
// Category Grid (6 new savings-focused categories)
// Category Grid (8 items, 4x2, A+C savings-focused)
// ============================================================
Widget _buildCategoryGrid() {
final categories = [
('限时抢购', Icons.flash_on_rounded, AppColors.error),
('新券首发', Icons.fiber_new_rounded, AppColors.primary),
('折扣排行', Icons.trending_up_rounded, AppColors.couponEntertainment),
('即将到期', Icons.timer_rounded, AppColors.info),
('即将到期', Icons.timer_rounded, AppColors.warning),
('比价', Icons.compare_arrows_rounded, AppColors.couponShopping),
('全部分类', Icons.grid_view_rounded, AppColors.textSecondary),
('转让市场', Icons.swap_horiz_rounded, AppColors.info),
('热门交易', Icons.local_fire_department_rounded, AppColors.couponFood),
('全部', Icons.grid_view_rounded, AppColors.textSecondary),
];
return Padding(
@ -487,10 +268,10 @@ class _HomePageState extends State<HomePage> {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 1.1,
crossAxisCount: 4,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
childAspectRatio: 0.9,
),
itemCount: categories.length,
itemBuilder: (context, index) {
@ -501,13 +282,13 @@ class _HomePageState extends State<HomePage> {
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 48,
height: 48,
width: 44,
height: 44,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: AppSpacing.borderRadiusMd,
),
child: Icon(icon, color: color, size: 24),
child: Icon(icon, color: color, size: 22),
),
const SizedBox(height: 6),
Text(name, style: AppTypography.caption.copyWith(
@ -567,83 +348,7 @@ class _HomePageState extends State<HomePage> {
}
}
// ============================================================
// Wallet Coupon Model
// ============================================================
class _WalletCoupon {
final String brandName;
final String name;
final double faceValue;
final CouponStatus status;
final DateTime expiryDate;
const _WalletCoupon({
required this.brandName,
required this.name,
required this.faceValue,
required this.status,
required this.expiryDate,
});
String get statusLabel {
switch (status) {
case CouponStatus.active:
return '可使用';
case CouponStatus.pending:
return '待核销';
case CouponStatus.expired:
return '已过期';
case CouponStatus.used:
return '已使用';
}
}
Color get statusColor {
switch (status) {
case CouponStatus.active:
return AppColors.success;
case CouponStatus.pending:
return AppColors.warning;
case CouponStatus.expired:
return AppColors.textTertiary;
case CouponStatus.used:
return AppColors.textDisabled;
}
}
}
// Mock data
final _mockWalletCoupons = [
_WalletCoupon(
brandName: 'Starbucks',
name: '星巴克 \$25 礼品卡',
faceValue: 25.0,
status: CouponStatus.active,
expiryDate: DateTime.now().add(const Duration(days: 30)),
),
_WalletCoupon(
brandName: 'Amazon',
name: 'Amazon \$100 购物券',
faceValue: 100.0,
status: CouponStatus.active,
expiryDate: DateTime.now().add(const Duration(days: 45)),
),
_WalletCoupon(
brandName: 'Nike',
name: 'Nike \$80 运动券',
faceValue: 80.0,
status: CouponStatus.pending,
expiryDate: DateTime.now().add(const Duration(days: 15)),
),
_WalletCoupon(
brandName: 'Target',
name: 'Target \$30 折扣券',
faceValue: 30.0,
status: CouponStatus.active,
expiryDate: DateTime.now().add(const Duration(days: 60)),
),
];
const _mockBrands = ['Starbucks', 'Amazon', 'Walmart', 'Target', 'Nike'];
const _mockNames = ['星巴克 \$25 礼品卡', 'Amazon \$100 购物券', 'Walmart \$50 生活券', 'Target \$30 折扣券', 'Nike \$80 运动券'];
const _mockFaceValues = [25.0, 100.0, 50.0, 30.0, 80.0];

View File

@ -0,0 +1,393 @@
import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart';
import '../../../../app/theme/app_typography.dart';
import '../../../../app/theme/app_spacing.dart';
import '../../../../shared/widgets/status_tag.dart';
import '../../../../shared/widgets/empty_state.dart';
import '../widgets/receive_coupon_sheet.dart';
/// - "我的券"
///
/// + 4-Tab筛选(/使//)
/// (++++)
/// AppBar含接收按钮
class WalletCouponsPage extends StatefulWidget {
const WalletCouponsPage({super.key});
@override
State<WalletCouponsPage> createState() => _WalletCouponsPageState();
}
class _WalletCouponsPageState extends State<WalletCouponsPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('我的钱包'),
actions: [
//
IconButton(
icon: const Icon(Icons.qr_code_rounded, size: 22),
onPressed: () => _showReceiveSheet(context),
tooltip: '接收券',
),
//
IconButton(
icon: const Icon(Icons.sort_rounded, size: 22),
onPressed: () {},
),
],
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: '全部'),
Tab(text: '可使用'),
Tab(text: '待核销'),
Tab(text: '已过期'),
],
),
),
body: Column(
children: [
//
_buildSummaryCard(),
//
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_buildCouponList(null),
_buildCouponList(CouponStatus.active),
_buildCouponList(CouponStatus.pending),
_buildCouponList(CouponStatus.expired),
],
),
),
],
),
);
}
/// + +
Widget _buildSummaryCard() {
return Container(
margin: const EdgeInsets.fromLTRB(16, 12, 16, 4),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: AppColors.cardGradient,
borderRadius: AppSpacing.borderRadiusMd,
boxShadow: AppSpacing.shadowPrimary,
),
child: Row(
children: [
//
Expanded(
child: Column(
children: [
Text('4',
style: AppTypography.display.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
)),
const SizedBox(height: 2),
Text('持有券数',
style: AppTypography.caption.copyWith(
color: Colors.white.withValues(alpha: 0.7),
)),
],
),
),
Container(
width: 1,
height: 36,
color: Colors.white.withValues(alpha: 0.2),
),
//
Expanded(
child: Column(
children: [
Text('\$235',
style: AppTypography.display.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
)),
const SizedBox(height: 2),
Text('总面值',
style: AppTypography.caption.copyWith(
color: Colors.white.withValues(alpha: 0.7),
)),
],
),
),
Container(
width: 1,
height: 36,
color: Colors.white.withValues(alpha: 0.2),
),
//
Expanded(
child: Column(
children: [
Text('\$38',
style: AppTypography.display.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
)),
const SizedBox(height: 2),
Text('已节省',
style: AppTypography.caption.copyWith(
color: Colors.white.withValues(alpha: 0.7),
)),
],
),
),
],
),
);
}
Widget _buildCouponList(CouponStatus? filter) {
final coupons = _filterCoupons(filter);
if (coupons.isEmpty) {
return EmptyState.noCoupons(
onBrowse: () => Navigator.pop(context),
);
}
return ListView.separated(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 100),
itemCount: coupons.length,
separatorBuilder: (_, __) => const SizedBox(height: 12),
itemBuilder: (context, index) {
final coupon = coupons[index];
return _buildCouponCard(context, coupon);
},
);
}
Widget _buildCouponCard(BuildContext context, _WalletCouponItem coupon) {
return GestureDetector(
onTap: () => Navigator.pushNamed(context, '/coupon/mine/detail'),
child: Container(
padding: AppSpacing.cardPadding,
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: AppSpacing.borderRadiusMd,
boxShadow: AppSpacing.shadowSm,
border: Border.all(color: AppColors.borderLight),
),
child: Column(
children: [
Row(
children: [
//
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: AppColors.primarySurface,
borderRadius: AppSpacing.borderRadiusSm,
),
child: Icon(
Icons.confirmation_number_outlined,
color: AppColors.primary.withValues(alpha: 0.4),
size: 24,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(coupon.brandName, style: AppTypography.caption),
Text(coupon.name, style: AppTypography.labelMedium),
const SizedBox(height: 4),
Row(
children: [
Text('面值 \$${coupon.faceValue.toStringAsFixed(0)}',
style: AppTypography.bodySmall),
const SizedBox(width: 8),
_statusWidget(coupon.status),
],
),
],
),
),
const Icon(Icons.chevron_right_rounded,
color: AppColors.textTertiary, size: 20),
],
),
// +
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: AppColors.gray50,
borderRadius: AppSpacing.borderRadiusSm,
),
child: Row(
children: [
Icon(Icons.access_time_rounded,
size: 14, color: _expiryColor(coupon.expiryDate)),
const SizedBox(width: 4),
Text(
_expiryText(coupon.expiryDate),
style: AppTypography.caption.copyWith(
color: _expiryColor(coupon.expiryDate)),
),
const Spacer(),
if (coupon.status == CouponStatus.active) ...[
_quickAction('转赠', Icons.card_giftcard_rounded, () {
Navigator.pushNamed(context, '/transfer');
}),
const SizedBox(width: 12),
_quickAction('出售', Icons.sell_rounded, () {
Navigator.pushNamed(context, '/sell');
}),
],
],
),
),
],
),
),
);
}
Widget _statusWidget(CouponStatus status) {
switch (status) {
case CouponStatus.active:
return StatusTags.active();
case CouponStatus.pending:
return StatusTags.pending();
case CouponStatus.expired:
return StatusTags.expired();
case CouponStatus.used:
return StatusTags.used();
}
}
Widget _quickAction(String label, IconData icon, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 14, color: AppColors.primary),
const SizedBox(width: 3),
Text(label,
style: AppTypography.caption.copyWith(
color: AppColors.primary,
fontWeight: FontWeight.w500,
)),
],
),
);
}
String _expiryText(DateTime expiryDate) {
final days = expiryDate.difference(DateTime.now()).inDays;
if (days < 0) return '已过期';
if (days == 0) return '今天到期';
if (days <= 7) return '$days天后到期';
return '${expiryDate.year}/${expiryDate.month}/${expiryDate.day}到期';
}
Color _expiryColor(DateTime expiryDate) {
final days = expiryDate.difference(DateTime.now()).inDays;
if (days <= 3) return AppColors.error;
if (days <= 7) return AppColors.warning;
return AppColors.textTertiary;
}
void _showReceiveSheet(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => const ReceiveCouponSheet(),
);
}
List<_WalletCouponItem> _filterCoupons(CouponStatus? filter) {
if (filter == null) return _mockCoupons;
return _mockCoupons.where((c) => c.status == filter).toList();
}
}
// ============================================================
// Data Model
// ============================================================
class _WalletCouponItem {
final String brandName;
final String name;
final double faceValue;
final CouponStatus status;
final DateTime expiryDate;
const _WalletCouponItem({
required this.brandName,
required this.name,
required this.faceValue,
required this.status,
required this.expiryDate,
});
}
// Mock data
final _mockCoupons = [
_WalletCouponItem(
brandName: 'Starbucks',
name: '星巴克 \$25 礼品卡',
faceValue: 25.0,
status: CouponStatus.active,
expiryDate: DateTime.now().add(const Duration(days: 30)),
),
_WalletCouponItem(
brandName: 'Amazon',
name: 'Amazon \$100 购物券',
faceValue: 100.0,
status: CouponStatus.active,
expiryDate: DateTime.now().add(const Duration(days: 45)),
),
_WalletCouponItem(
brandName: 'Nike',
name: 'Nike \$80 运动券',
faceValue: 80.0,
status: CouponStatus.pending,
expiryDate: DateTime.now().add(const Duration(days: 15)),
),
_WalletCouponItem(
brandName: 'Target',
name: 'Target \$30 折扣券',
faceValue: 30.0,
status: CouponStatus.active,
expiryDate: DateTime.now().add(const Duration(days: 60)),
),
_WalletCouponItem(
brandName: 'Walmart',
name: 'Walmart \$50 生活券',
faceValue: 50.0,
status: CouponStatus.expired,
expiryDate: DateTime.now().subtract(const Duration(days: 5)),
),
];

View File

@ -28,6 +28,7 @@ import 'features/message/presentation/pages/message_detail_page.dart';
import 'features/issuer/presentation/pages/issuer_main_page.dart';
import 'features/merchant/presentation/pages/merchant_home_page.dart';
import 'features/trading/presentation/pages/trading_detail_page.dart';
import 'features/coupons/presentation/pages/wallet_coupons_page.dart';
void main() {
runApp(const GenexConsumerApp());
@ -109,6 +110,8 @@ class GenexConsumerApp extends StatelessWidget {
return MaterialPageRoute(builder: (_) => const MerchantHomePage());
case '/trading/detail':
return MaterialPageRoute(builder: (_) => const TradingDetailPage());
case '/wallet/coupons':
return MaterialPageRoute(builder: (_) => const WalletCouponsPage());
default:
return MaterialPageRoute(
builder: (_) => Scaffold(