gcx/frontend/mobile/lib/features/coupons/presentation/pages/home_page.dart

287 lines
10 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
/// A2. 首页 - 搜索栏 + Banner + 热门分类 + 精选券 + AI Agent
///
/// 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,
),
],
),
// Banner Carousel
SliverToBoxAdapter(child: _buildBanner(context)),
// Category Grid
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),
),
],
),
),
);
}
Widget _buildBanner(BuildContext context) {
return Container(
height: 160,
margin: const EdgeInsets.fromLTRB(20, 8, 20, 0),
child: PageView.builder(
itemCount: 3,
itemBuilder: (context, index) {
final colors = [
AppColors.primaryGradient,
AppColors.successGradient,
AppColors.cardGradient,
];
final titleKeys = ['home.bannerNewUser', 'home.bannerDiscount', 'home.bannerHot'];
final subtitleKeys = ['home.bannerNewUserDesc', 'home.bannerDiscountDesc', 'home.bannerHotDesc'];
return Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
gradient: colors[index],
borderRadius: AppSpacing.borderRadiusLg,
),
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
context.t(titleKeys[index]),
style: AppTypography.h1.copyWith(color: Colors.white),
),
const SizedBox(height: 4),
Text(
context.t(subtitleKeys[index]),
style: AppTypography.bodyMedium.copyWith(
color: Colors.white.withValues(alpha: 0.8),
),
),
],
),
);
},
),
);
}
Widget _buildCategoryGrid(BuildContext context) {
final categoryKeys = [
('home.dining', Icons.restaurant_rounded, AppColors.couponDining),
('home.shopping', Icons.shopping_bag_rounded, AppColors.couponShopping),
('home.entertainment', Icons.sports_esports_rounded, AppColors.couponEntertainment),
('home.travel', Icons.directions_car_rounded, AppColors.couponTravel),
('home.lifestyle', Icons.home_rounded, AppColors.couponOther),
('home.brand', Icons.storefront_rounded, AppColors.primary),
('home.discount', Icons.local_offer_rounded, AppColors.error),
('home.allCategories', 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: 8,
crossAxisSpacing: 8,
childAspectRatio: 0.85,
),
itemCount: categoryKeys.length,
itemBuilder: (context, index) {
final (key, icon, color) = categoryKeys[index];
return GestureDetector(
onTap: () {},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: AppSpacing.borderRadiusMd,
),
child: Icon(icon, color: color, size: 24),
),
const SizedBox(height: 6),
Text(context.t(key), 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'];