358 lines
13 KiB
Dart
358 lines
13 KiB
Dart
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/coupon_card.dart';
|
||
import '../../../ai_agent/presentation/widgets/ai_fab.dart';
|
||
import '../widgets/receive_coupon_sheet.dart';
|
||
import '../../../../app/i18n/app_localizations.dart';
|
||
|
||
/// 首页 - 轻量持仓卡 + 分类网格 + AI推荐 + 精选券
|
||
///
|
||
/// Tab导航:首页/交易/消息/我的
|
||
class HomePage extends StatelessWidget {
|
||
const HomePage({super.key});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
body: Stack(
|
||
children: [
|
||
CustomScrollView(
|
||
slivers: [
|
||
// Floating App Bar
|
||
SliverAppBar(
|
||
floating: true,
|
||
pinned: false,
|
||
backgroundColor: AppColors.background,
|
||
elevation: 0,
|
||
toolbarHeight: 60,
|
||
title: _buildSearchBar(context),
|
||
actions: [
|
||
IconButton(
|
||
icon: const Icon(Icons.qr_code_scanner_rounded, size: 24),
|
||
onPressed: () {},
|
||
color: AppColors.textPrimary,
|
||
),
|
||
],
|
||
),
|
||
|
||
// Lightweight Position Card (持仓)
|
||
SliverToBoxAdapter(child: _buildWalletCard(context)),
|
||
|
||
// Category Grid (8 items, 4x2)
|
||
SliverToBoxAdapter(child: _buildCategoryGrid(context)),
|
||
|
||
// AI Smart Suggestions
|
||
SliverToBoxAdapter(child: _buildAiSuggestions(context)),
|
||
|
||
// Section: Featured Coupons
|
||
SliverToBoxAdapter(
|
||
child: Padding(
|
||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 12),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(context.t('home.featuredCoupons'), style: AppTypography.h2),
|
||
GestureDetector(
|
||
onTap: () {},
|
||
child: Text(context.t('home.viewAllCoupons'), style: AppTypography.labelSmall.copyWith(
|
||
color: AppColors.primary,
|
||
)),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
|
||
// Coupon List
|
||
SliverPadding(
|
||
padding: AppSpacing.pagePadding,
|
||
sliver: SliverList(
|
||
delegate: SliverChildBuilderDelegate(
|
||
(context, index) => Padding(
|
||
padding: const EdgeInsets.only(bottom: 12),
|
||
child: CouponCard(
|
||
brandName: _mockBrands[index % _mockBrands.length],
|
||
couponName: _mockNames[index % _mockNames.length],
|
||
faceValue: _mockFaceValues[index % _mockFaceValues.length],
|
||
currentPrice: _mockPrices[index % _mockPrices.length],
|
||
creditRating: _mockRatings[index % _mockRatings.length],
|
||
expiryDate: DateTime.now().add(Duration(days: (index + 1) * 5)),
|
||
onTap: () {
|
||
Navigator.pushNamed(context, '/coupon/detail');
|
||
},
|
||
),
|
||
),
|
||
childCount: 10,
|
||
),
|
||
),
|
||
),
|
||
|
||
const SliverPadding(padding: EdgeInsets.only(bottom: 100)),
|
||
],
|
||
),
|
||
|
||
// AI FAB
|
||
Positioned(
|
||
right: 20,
|
||
bottom: 100,
|
||
child: AiFab(
|
||
unreadCount: 3,
|
||
onTap: () {
|
||
Navigator.pushNamed(context, '/ai-chat');
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildSearchBar(BuildContext context) {
|
||
return GestureDetector(
|
||
onTap: () {
|
||
Navigator.pushNamed(context, '/search');
|
||
},
|
||
child: Container(
|
||
height: 40,
|
||
decoration: BoxDecoration(
|
||
color: AppColors.gray50,
|
||
borderRadius: AppSpacing.borderRadiusFull,
|
||
border: Border.all(color: AppColors.borderLight),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
const SizedBox(width: 14),
|
||
const Icon(Icons.search_rounded, size: 20, color: AppColors.textTertiary),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
context.t('home.searchHint'),
|
||
style: AppTypography.bodyMedium.copyWith(color: AppColors.textTertiary),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// Lightweight Position Card (持仓卡片)
|
||
// 点击非快捷入口区域打开完整持仓页面
|
||
// ============================================================
|
||
Widget _buildWalletCard(BuildContext context) {
|
||
return GestureDetector(
|
||
onTap: () => Navigator.pushNamed(context, '/wallet/coupons'),
|
||
child: Container(
|
||
margin: const EdgeInsets.fromLTRB(20, 8, 20, 0),
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
gradient: AppColors.cardGradient,
|
||
borderRadius: AppSpacing.borderRadiusLg,
|
||
boxShadow: AppSpacing.shadowPrimary,
|
||
),
|
||
child: Column(
|
||
children: [
|
||
// Top row: wallet info + receive button
|
||
Row(
|
||
children: [
|
||
const Icon(Icons.inventory_2_rounded,
|
||
size: 20, color: Colors.white),
|
||
const SizedBox(width: 8),
|
||
Text(context.t('home.position'),
|
||
style: AppTypography.labelMedium.copyWith(color: Colors.white)),
|
||
const Spacer(),
|
||
// Summary
|
||
Text('${context.t('home.hold')} ',
|
||
style: AppTypography.bodySmall.copyWith(
|
||
color: Colors.white.withValues(alpha: 0.7),
|
||
)),
|
||
Text('4',
|
||
style: AppTypography.h3.copyWith(
|
||
color: Colors.white,
|
||
fontWeight: FontWeight.w700,
|
||
)),
|
||
Text(' ${context.t('home.couponUnit')} ${context.t('home.totalValue')} ',
|
||
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)),
|
||
],
|
||
),
|
||
|
||
const SizedBox(height: 14),
|
||
|
||
// Quick action entries
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||
children: [
|
||
_buildQuickAction(context, Icons.qr_code_rounded, context.t('home.receive'), () {
|
||
showModalBottomSheet(
|
||
context: context,
|
||
isScrollControlled: true,
|
||
backgroundColor: Colors.transparent,
|
||
builder: (_) => const ReceiveCouponSheet(),
|
||
);
|
||
}),
|
||
_buildQuickAction(context, Icons.card_giftcard_rounded, context.t('home.transfer'), () {
|
||
Navigator.pushNamed(context, '/transfer');
|
||
}),
|
||
_buildQuickAction(context, Icons.sell_rounded, context.t('home.sell'), () {
|
||
Navigator.pushNamed(context, '/sell');
|
||
}),
|
||
_buildQuickAction(context, Icons.check_circle_outline_rounded, context.t('home.redeem'), () {
|
||
Navigator.pushNamed(context, '/redeem');
|
||
}),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildQuickAction(
|
||
BuildContext context, IconData icon, String label, VoidCallback onTap) {
|
||
return GestureDetector(
|
||
onTap: onTap,
|
||
behavior: HitTestBehavior.opaque,
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
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.9),
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// Category Grid (8 items, 4x2, A+C savings-focused)
|
||
// ============================================================
|
||
Widget _buildCategoryGrid(BuildContext context) {
|
||
final categories = [
|
||
(context.t('home.flashSale'), Icons.flash_on_rounded, AppColors.error),
|
||
(context.t('home.newRelease'), Icons.fiber_new_rounded, AppColors.primary),
|
||
(context.t('home.discountRank'), Icons.trending_up_rounded, AppColors.couponEntertainment),
|
||
(context.t('home.expiringSoon'), Icons.timer_rounded, AppColors.warning),
|
||
(context.t('home.priceCompare'), Icons.compare_arrows_rounded, AppColors.couponShopping),
|
||
(context.t('home.resaleMarket'), Icons.swap_horiz_rounded, AppColors.info),
|
||
(context.t('home.hotTrades'), Icons.local_fire_department_rounded, AppColors.couponDining),
|
||
(context.t('home.viewAll'), Icons.grid_view_rounded, AppColors.textSecondary),
|
||
];
|
||
|
||
return Padding(
|
||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 0),
|
||
child: GridView.builder(
|
||
shrinkWrap: true,
|
||
physics: const NeverScrollableScrollPhysics(),
|
||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||
crossAxisCount: 4,
|
||
mainAxisSpacing: 4,
|
||
crossAxisSpacing: 4,
|
||
childAspectRatio: 0.9,
|
||
),
|
||
itemCount: categories.length,
|
||
itemBuilder: (context, index) {
|
||
final (name, icon, color) = categories[index];
|
||
return GestureDetector(
|
||
onTap: () {},
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Container(
|
||
width: 44,
|
||
height: 44,
|
||
decoration: BoxDecoration(
|
||
color: color.withValues(alpha: 0.1),
|
||
borderRadius: AppSpacing.borderRadiusMd,
|
||
),
|
||
child: Icon(icon, color: color, size: 22),
|
||
),
|
||
const SizedBox(height: 6),
|
||
Text(name, style: AppTypography.caption.copyWith(
|
||
color: AppColors.textPrimary,
|
||
fontWeight: FontWeight.w500,
|
||
)),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildAiSuggestions(BuildContext context) {
|
||
return Container(
|
||
margin: const EdgeInsets.fromLTRB(20, 16, 20, 0),
|
||
padding: const EdgeInsets.all(14),
|
||
decoration: BoxDecoration(
|
||
color: AppColors.primarySurface,
|
||
borderRadius: AppSpacing.borderRadiusMd,
|
||
border: Border.all(color: AppColors.primary.withValues(alpha: 0.15)),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Container(
|
||
width: 32,
|
||
height: 32,
|
||
decoration: BoxDecoration(
|
||
gradient: AppColors.primaryGradient,
|
||
borderRadius: AppSpacing.borderRadiusSm,
|
||
),
|
||
child: const Icon(Icons.auto_awesome_rounded, color: Colors.white, size: 16),
|
||
),
|
||
const SizedBox(width: 10),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(context.t('home.aiRecommend'), style: AppTypography.labelSmall.copyWith(
|
||
color: AppColors.primary,
|
||
)),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
context.t('home.aiRecommendDesc'),
|
||
style: AppTypography.bodySmall,
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const Icon(Icons.chevron_right_rounded, color: AppColors.primary, size: 20),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
// Mock data
|
||
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];
|
||
const _mockPrices = [21.25, 85.0, 42.5, 24.0, 68.0];
|
||
const _mockRatings = ['AAA', 'AA', 'AAA', 'A', 'AA'];
|