import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/network/api_exception.dart'; import '../../core/theme/app_theme.dart'; import '../../models/membership_models.dart'; import '../../providers/auth_provider.dart'; import '../../providers/member_provider.dart'; import '../feed/widgets/feed_overlays.dart'; /// 会员中心:当前状态卡 + 套餐列表 + 权益说明 + 开通历史 /// 游客可浏览套餐,点开通时引导登录。 class MembershipScreen extends ConsumerWidget { const MembershipScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(memberProvider); final isLoggedIn = ref.watch(authProvider).isLoggedIn; return Scaffold( appBar: AppBar(title: const Text('会员中心')), body: state.isLoading && state.plans.isEmpty ? const Center(child: CircularProgressIndicator()) : state.loadFailed && state.plans.isEmpty ? _LoadFailed(onRetry: () => ref.read(memberProvider.notifier).load()) : RefreshIndicator( onRefresh: () => ref.read(memberProvider.notifier).load(), child: ListView( padding: const EdgeInsets.all(16), children: [ if (isLoggedIn && state.me != null) _StatusCard(me: state.me!), if (isLoggedIn && state.me != null) const SizedBox(height: 16), // 套餐列表 ...state.plans.map((plan) => Padding( padding: const EdgeInsets.only(bottom: 12), child: _PlanCard( plan: plan, isLifetimeOwned: state.me?.isLifetime == true, redeeming: state.redeeming, onRedeem: () => _onRedeem(context, ref, plan), ), )), // 权益说明 if (state.benefits.isNotEmpty) ...[ const SizedBox(height: 8), const Text('会员权益', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w700)), const SizedBox(height: 8), ...state.benefits.map((b) => Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( children: [ const Icon(Icons.check_circle, size: 14, color: AppColors.gold), const SizedBox(width: 6), Expanded( child: Text(b, style: const TextStyle( color: AppColors.textSecondary, fontSize: 13)), ), ], ), )), ], // 开通历史 if (isLoggedIn && state.me != null && state.me!.history.isNotEmpty) ...[ const SizedBox(height: 20), const Text('开通记录', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w700)), const SizedBox(height: 4), ...state.me!.history.map(_HistoryTile.new), ], ], ), ), ); } Future _onRedeem( BuildContext context, WidgetRef ref, MembershipPlan plan) async { // 游客 → 引导登录 if (!ref.read(authProvider).isLoggedIn) { showTopToast(context, '请先登录后再开通会员'); context.push('/login'); return; } final points = ref.read(authProvider).user?.points ?? 0; final ok = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: AppColors.surface, title: Text('开通${plan.name}', style: const TextStyle(fontSize: 16)), content: Text( '需消耗 ${plan.points} 积分,当前积分 $points。\n' '${plan.days == 0 ? '有效期:终身' : '有效期:${plan.days} 天'}', ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.pop(ctx, true), child: const Text('确认开通', style: TextStyle(color: AppColors.gold)), ), ], ), ); if (ok != true || !context.mounted) return; try { final result = await ref.read(memberProvider.notifier).redeem(plan.planCode); if (context.mounted) { showTopToast(context, '开通成功:${result.planName},剩余积分 ${result.remainingPoints}'); } } on ApiException catch (e) { if (!context.mounted) return; if (e.code == 2004) { showTopToast(context, '终身卡已开通,无需重复购买'); } else if (e.isPointsInsufficient) { showTopToast(context, '积分不足,已为你打开充值页'); context.push('/recharge'); } else { showTopToast(context, e.message); } } catch (_) { if (context.mounted) showTopToast(context, '开通失败,请稍后重试'); } } } /// 当前会员状态卡 class _StatusCard extends StatelessWidget { const _StatusCard({required this.me}); final MyMemberState me; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: AppColors.goldGradient, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ const Icon(Icons.workspace_premium, size: 36, color: Colors.black87), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( me.isMember ? (me.isLifetime ? '终身会员' : (me.planName ?? '会员')) : '暂未开通会员', style: const TextStyle( color: Colors.black87, fontSize: 16, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 4), Text( me.isMember ? (me.isLifetime ? '永久有效' : '剩余 ${me.remainingDays} 天 · ${me.expireAt ?? ''} 到期') : '开通会员尊享多项特权', style: const TextStyle(color: Colors.black54, fontSize: 12), ), ], ), ), ], ), ); } } /// 套餐卡片 class _PlanCard extends StatelessWidget { const _PlanCard({ required this.plan, required this.isLifetimeOwned, required this.redeeming, required this.onRedeem, }); final MembershipPlan plan; /// 已持有终身卡:所有套餐不可再开通(终身卡重复开通 2004) final bool isLifetimeOwned; final bool redeeming; final VoidCallback onRedeem; @override Widget build(BuildContext context) { final disabled = !plan.enabled || isLifetimeOwned || redeeming; return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(12), border: plan.days == 0 ? Border.all(color: AppColors.goldDeep, width: 1.2) : null, ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text(plan.name, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700)), if (plan.days == 0) ...[ const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric( horizontal: 5, vertical: 1), decoration: BoxDecoration( gradient: AppColors.goldGradient, borderRadius: BorderRadius.circular(4), ), child: const Text('推荐', style: TextStyle( color: Colors.black, fontSize: 10, fontWeight: FontWeight.w700)), ), ], ], ), const SizedBox(height: 4), Text( plan.days == 0 ? '终身有效' : '${plan.days} 天', style: const TextStyle( color: AppColors.textSecondary, fontSize: 12), ), if (plan.desc.isNotEmpty) ...[ const SizedBox(height: 2), Text(plan.desc, style: const TextStyle( color: AppColors.textWeak, fontSize: 12)), ], ], ), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text('${plan.points} 积分', style: const TextStyle( color: AppColors.gold, fontSize: 15, fontWeight: FontWeight.w700)), const SizedBox(height: 8), SizedBox( height: 34, child: ElevatedButton( onPressed: disabled ? null : onRedeem, style: ElevatedButton.styleFrom( minimumSize: const Size(84, 34), padding: const EdgeInsets.symmetric(horizontal: 14), textStyle: const TextStyle(fontSize: 13), ), child: Text(isLifetimeOwned ? '已开通' : '开通'), ), ), ], ), ], ), ); } } /// 开通历史条目 class _HistoryTile extends StatelessWidget { const _HistoryTile(this.item); final MemberHistoryItem item; @override Widget build(BuildContext context) { return ListTile( contentPadding: EdgeInsets.zero, dense: true, title: Text(item.planName, style: const TextStyle(fontSize: 14)), subtitle: Text( '${item.startAt} ~ ${item.endAt.isEmpty ? '终身' : item.endAt} · ${item.sourceLabel}', style: const TextStyle(color: AppColors.textWeak, fontSize: 12), ), trailing: Text('${item.pointsCost} 积分', style: const TextStyle(color: AppColors.gold, fontSize: 13)), ); } } class _LoadFailed extends StatelessWidget { const _LoadFailed({required this.onRetry}); final VoidCallback onRetry; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('加载失败', style: TextStyle(color: AppColors.textSecondary)), const SizedBox(height: 12), TextButton(onPressed: onRetry, child: const Text('点击重试')), ], ), ); } }