gcx/frontend/genex-mobile/lib/features/profile/presentation/pages/profile_page.dart

329 lines
12 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 '../../../../app/i18n/app_localizations.dart';
import '../../../../core/services/coupon_service.dart';
import '../../data/models/holdings_summary_model.dart';
/// A7. 个人中心
///
/// 头像、昵称、KYC等级标识、信用积分
/// KYC认证、支付管理、设置、Pro模式
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
HoldingsSummaryModel? _holdingsSummary;
@override
void initState() {
super.initState();
_loadSummary();
}
Future<void> _loadSummary() async {
try {
final summary = await CouponApiService().getHoldingsSummary();
if (mounted) setState(() => _holdingsSummary = summary);
} catch (e) {
debugPrint('[ProfilePage] loadSummary error: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
// Profile Header
SliverToBoxAdapter(child: _buildProfileHeader(context)),
// Quick Stats
SliverToBoxAdapter(child: _buildQuickStats(context)),
// Invite Banner
SliverToBoxAdapter(child: _buildInviteBanner(context)),
// Menu Sections
SliverToBoxAdapter(child: _buildMenuSection(context.t('profile.account'), [
_MenuItem(Icons.verified_user_outlined, context.t('profile.kyc'), '${context.t('kyc.completed')} L1', true,
onTap: () => Navigator.pushNamed(context, '/kyc')),
_MenuItem(Icons.credit_card_rounded, context.t('profile.paymentManage'), '', true,
onTap: () => Navigator.pushNamed(context, '/payment/manage')),
_MenuItem(Icons.account_balance_wallet_outlined, context.t('wallet.myBalance'), '\$1,234.56', true,
onTap: () => Navigator.pushNamed(context, '/wallet')),
])),
SliverToBoxAdapter(child: _buildMenuSection(context.t('profile.trade'), [
_MenuItem(Icons.confirmation_number_outlined, context.t('profile.holdCoupons'),
'${_holdingsSummary?.count ?? '--'}', true,
onTap: () => Navigator.pushNamed(context, '/wallet/coupons')),
_MenuItem(Icons.receipt_long_rounded, context.t('wallet.records'), '', true,
onTap: () => Navigator.pushNamed(context, '/trading')),
_MenuItem(Icons.storefront_rounded, context.t('tradingPage.pendingOrders'), context.t('status.onSale'), true,
onTap: () => Navigator.pushNamed(context, '/trading')),
_MenuItem(Icons.favorite_border_rounded, context.t('profile.myFavorites'), '', true),
])),
SliverToBoxAdapter(child: _buildMenuSection(context.t('profile.settings'), [
_MenuItem(Icons.notifications_outlined, context.t('settings.notifications'), '', true),
_MenuItem(Icons.language_rounded, context.t('settings.language'), context.t('profile.simplifiedChinese'), true),
_MenuItem(Icons.shield_outlined, context.t('profile.securitySettings'), '', true),
_MenuItem(Icons.tune_rounded, context.t('profile.advancedSettings'), context.t('profile.proMode'), true,
onTap: () => Navigator.pushNamed(context, '/pro-mode')),
_MenuItem(Icons.info_outline_rounded, context.t('profile.aboutGenex'), 'v1.0.0', true),
])),
// Logout
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 40),
child: TextButton(
onPressed: () {
Navigator.of(context).pushNamedAndRemoveUntil('/', (_) => false);
},
child: Text(context.t('settings.logout'), style: AppTypography.labelMedium.copyWith(
color: AppColors.error,
)),
),
),
),
const SliverPadding(padding: EdgeInsets.only(bottom: 80)),
],
),
);
}
Widget _buildProfileHeader(BuildContext context) {
return Container(
padding: const EdgeInsets.fromLTRB(20, 60, 20, 24),
decoration: const BoxDecoration(
gradient: AppColors.primaryGradient,
),
child: Row(
children: [
// Avatar
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: Colors.white24,
shape: BoxShape.circle,
border: Border.all(color: Colors.white38, width: 2),
),
child: const Icon(Icons.person_rounded, color: Colors.white, size: 32),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(context.t('merchant.userNickname'), style: AppTypography.h2.copyWith(color: Colors.white)),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.white24,
borderRadius: AppSpacing.borderRadiusFull,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.shield_rounded, size: 10, color: Colors.white),
const SizedBox(width: 2),
Text('L1', style: AppTypography.caption.copyWith(
color: Colors.white,
fontWeight: FontWeight.w600,
)),
],
),
),
],
),
const SizedBox(height: 4),
Text('${context.t('profile.creditScore')}: 750', style: AppTypography.bodySmall.copyWith(
color: Colors.white70,
)),
],
),
),
// Settings icon
IconButton(
icon: const Icon(Icons.settings_outlined, color: Colors.white),
onPressed: () {
Navigator.pushNamed(context, '/settings');
},
),
],
),
);
}
Widget _buildQuickStats(BuildContext context) {
final holdCount = _holdingsSummary?.count ?? 0;
final saved = _holdingsSummary?.totalSaved ?? 0;
final stats = [
(context.t('profile.holdCoupons'), '$holdCount', () => Navigator.pushNamed(context, '/wallet/coupons')),
(context.t('profile.trade'), '28', null),
(context.t('profile.saved'), '\$${saved.toStringAsFixed(0)}', null),
(context.t('profile.credit'), '750', null),
];
return Container(
margin: const EdgeInsets.fromLTRB(20, 16, 20, 8),
padding: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: AppSpacing.borderRadiusMd,
boxShadow: AppSpacing.shadowSm,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: stats.map((stat) {
return GestureDetector(
onTap: stat.$3,
child: Column(
children: [
Text(stat.$2, style: AppTypography.h2.copyWith(color: AppColors.primary)),
const SizedBox(height: 4),
Text(stat.$1, style: AppTypography.caption),
],
),
);
}).toList(),
),
);
}
Widget _buildInviteBanner(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
child: GestureDetector(
onTap: () => Navigator.pushNamed(context, '/share'),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Color(0xFF6C5CE7), Color(0xFF9B8FFF)],
),
borderRadius: AppSpacing.borderRadiusMd,
boxShadow: [
BoxShadow(
color: AppColors.primary.withOpacity(0.25),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: const BoxDecoration(
color: Colors.white24,
shape: BoxShape.circle,
),
child: const Icon(Icons.card_giftcard_rounded, color: Colors.white, size: 22),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.t('share.inviteBanner'),
style: AppTypography.labelMedium.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 2),
Text(
context.t('share.inviteBannerSub'),
style: AppTypography.caption.copyWith(color: Colors.white70),
),
],
),
),
const Icon(Icons.chevron_right_rounded, color: Colors.white70, size: 24),
],
),
),
),
);
}
Widget _buildMenuSection(String title, List<_MenuItem> items) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: AppTypography.labelSmall.copyWith(color: AppColors.textTertiary)),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: AppSpacing.borderRadiusMd,
border: Border.all(color: AppColors.borderLight),
),
child: Column(
children: items.asMap().entries.map((entry) {
final item = entry.value;
final isLast = entry.key == items.length - 1;
return Column(
children: [
ListTile(
leading: Icon(item.icon, color: AppColors.textPrimary, size: 22),
title: Text(item.title, style: AppTypography.bodyMedium),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (item.subtitle.isNotEmpty)
Text(item.subtitle, style: AppTypography.caption),
if (item.hasArrow) ...[
const SizedBox(width: 4),
const Icon(Icons.chevron_right_rounded,
color: AppColors.textTertiary, size: 20),
],
],
),
onTap: item.onTap ?? () {},
),
if (!isLast)
const Divider(indent: 56, height: 1),
],
);
}).toList(),
),
),
],
),
);
}
}
class _MenuItem {
final IconData icon;
final String title;
final String subtitle;
final bool hasArrow;
final VoidCallback? onTap;
const _MenuItem(this.icon, this.title, this.subtitle, this.hasArrow, {this.onTap});
}