import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:sales_chat/providers/invite_provider.dart'; import 'package:sales_chat/providers/chat_provider.dart'; import 'package:sales_chat/models/invite.dart'; import 'package:sales_chat/theme/app_theme.dart'; import 'package:sales_chat/services/api_service.dart'; import 'package:intl/intl.dart'; /// 邀请页面 —— Twitter 风格 class InvitePage extends StatefulWidget { const InvitePage({super.key}); @override State createState() => _InvitePageState(); } class _InvitePageState extends State { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _loadData()); } Future _loadData() async { final inviteProvider = context.read(); final chatProvider = context.read(); // 先确保群组数据已加载 await chatProvider.loadGroups(); await Future.wait([ inviteProvider.loadInvites(), inviteProvider.loadAvailableGroups(chatProvider.groups), ]); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('邀请'), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _loadData, ), ], ), body: Consumer( builder: (context, inviteProvider, _) { if (inviteProvider.isLoading && inviteProvider.invites.isEmpty) { return const Center(child: CircularProgressIndicator()); } return SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 创建邀请按钮 —— 顶部醒目主色按钮 _buildCreateInviteButton(), const SizedBox(height: 8), // 我的邀请列表 _buildInviteListSection(inviteProvider), ], ), ); }, ), ); } /// 创建邀请按钮 —— 醒目的主色按钮,非卡片样式 Widget _buildCreateInviteButton() { return Container( width: double.infinity, color: AppTheme.cardBackground, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: ElevatedButton.icon( onPressed: () => _showCreateInviteDialog(), icon: const Icon(Icons.add, size: 20), label: const Text('创建新邀请'), style: ElevatedButton.styleFrom( backgroundColor: AppTheme.primaryColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), elevation: 0, ), ), ); } /// 邀请列表区块 Widget _buildInviteListSection(InviteProvider inviteProvider) { return Container( color: AppTheme.cardBackground, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 区块标题 const Padding( padding: EdgeInsets.fromLTRB(16, 16, 16, 0), child: Text( '我的邀请', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), ), if (inviteProvider.invites.isEmpty) _buildEmptyState() else ..._buildInviteListItems(inviteProvider.invites), ], ), ); } /// 构建邀请列表项(带分隔线) List _buildInviteListItems(List invites) { final items = []; for (int i = 0; i < invites.length; i++) { final invite = invites[i]; items.add(_InviteListTile( invite: invite, onTap: () => _showInviteDetail(invite), onDeactivate: () => _deactivateInvite(invite.code), )); if (i < invites.length - 1) { items.add(const Padding( padding: EdgeInsets.only(left: 16), child: Divider(height: 0.5, thickness: 0.5), )); } } return items; } /// 空状态 Widget _buildEmptyState() { return Padding( padding: const EdgeInsets.all(32), child: Center( child: Column( children: [ Icon( Icons.card_giftcard_outlined, size: 48, color: AppTheme.textHint, ), const SizedBox(height: 16), Text( '暂无邀请', style: TextStyle(color: AppTheme.textSecondary, fontSize: 14), ), const SizedBox(height: 8), Text( '点击上方按钮创建您的第一个邀请', style: TextStyle(color: AppTheme.textHint, fontSize: 12), ), ], ), ), ); } /// 创建邀请对话框 Future _showCreateInviteDialog() async { final chatProvider = context.read(); var groups = chatProvider.groups; // 如果没有群组,先创建一个 if (groups.isEmpty) { final created = await _showCreateGroupDialog(); if (!created) return; groups = chatProvider.groups; if (groups.isEmpty) return; } String? selectedGroupId = groups.first.id; int expiryDays = 7; await showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setState) { return AlertDialog( title: const Text('创建邀请'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 群组选择 DropdownButtonFormField( value: selectedGroupId, decoration: const InputDecoration( labelText: '选择群组', border: OutlineInputBorder(), ), items: groups.map((g) { return DropdownMenuItem( value: g.id, child: Text(g.name), ); }).toList(), onChanged: (value) { setState(() { selectedGroupId = value; }); }, ), const SizedBox(height: 16), // 有效期 Text('有效期(天)', style: Theme.of(context).textTheme.bodySmall), Slider( value: expiryDays.toDouble(), min: 1, max: 30, divisions: 29, label: '$expiryDays 天', onChanged: (value) { setState(() { expiryDays = value.round(); }); }, ), Text( '$expiryDays 天', style: TextStyle(color: AppTheme.textSecondary), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), ElevatedButton( onPressed: () async { final inviteProvider = context.read(); Navigator.pop(context); final invite = await inviteProvider.createInvite( groupId: selectedGroupId!, expiresInDays: expiryDays, ); if (invite != null && mounted) { _showInviteDetail(invite); } }, child: const Text('创建'), ), ], ); }, ), ); } /// 显示邀请详情底部弹窗 void _showInviteDetail(Invite invite) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: AppTheme.cardBackground, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(12)), ), builder: (context) => _InviteDetailSheet(invite: invite), ); } /// 停用邀请确认 Future _deactivateInvite(String code) async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('停用邀请'), content: const Text('确定吗?此邀请将不再可用。'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.pop(context, true), style: TextButton.styleFrom(foregroundColor: AppTheme.errorColor), child: const Text('停用'), ), ], ), ); if (confirmed == true && mounted) { final inviteProvider = context.read(); await inviteProvider.deactivateInvite(code); } } /// 创建群组对话框(没有群组时自动弹出) Future _showCreateGroupDialog() async { final name = await showDialog( context: context, builder: (context) { final controller = TextEditingController(); return AlertDialog( title: const Text('创建群组'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('您还没有群组,需要先创建一个群组才能生成邀请。'), const SizedBox(height: 16), TextField( controller: controller, autofocus: true, decoration: const InputDecoration( labelText: '群组名称', hintText: '例如:客户交流群', ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), ElevatedButton( onPressed: () => Navigator.pop(context, controller.text.trim()), child: const Text('创建'), ), ], ); }, ); if (name == null || name.isEmpty) return false; try { final apiService = context.read(); await apiService.createGroup(name); await context.read().loadGroups(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('群组已创建')), ); } return true; } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('创建失败:$e')), ); } return false; } } } /// 邀请列表行 —— 卡片背景行,邀请码 + 状态徽章 + 统计 class _InviteListTile extends StatelessWidget { final Invite invite; final VoidCallback onTap; final VoidCallback onDeactivate; const _InviteListTile({ required this.invite, required this.onTap, required this.onDeactivate, }); @override Widget build(BuildContext context) { final dateFormat = DateFormat('yyyy-MM-dd HH:mm'); return InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ // 左侧:邀请码 + 统计 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 第一行:邀请码 + 状态 Row( children: [ // 邀请码(等宽字体) Text( invite.code, style: const TextStyle( fontFamily: 'monospace', fontSize: 15, fontWeight: FontWeight.w500, color: AppTheme.textPrimary, ), ), const SizedBox(width: 8), // 状态徽章 Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: invite.isValid ? AppTheme.successColor.withValues(alpha: 0.1) : AppTheme.errorColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), ), child: Text( invite.isValid ? '有效' : '已失效', style: TextStyle( fontSize: 11, color: invite.isValid ? AppTheme.successColor : AppTheme.errorColor, fontWeight: FontWeight.w500, ), ), ), ], ), const SizedBox(height: 6), // 第二行:点击数 + 加入数 Row( children: [ Icon(Icons.touch_app_outlined, size: 14, color: AppTheme.textHint), const SizedBox(width: 3), Text( '${invite.clickCount} 点击', style: const TextStyle(color: AppTheme.textHint, fontSize: 12), ), const SizedBox(width: 12), Icon(Icons.person_add_outlined, size: 14, color: AppTheme.textHint), const SizedBox(width: 3), Text( '${invite.joinCount} 加入', style: const TextStyle(color: AppTheme.textHint, fontSize: 12), ), if (invite.expiresAt != null) ...[ const SizedBox(width: 12), Icon(Icons.schedule, size: 14, color: AppTheme.textHint), const SizedBox(width: 3), Text( dateFormat.format(invite.expiresAt!), style: const TextStyle(color: AppTheme.textHint, fontSize: 12), ), ], ], ), ], ), ), // 右侧:删除按钮或箭头 if (!invite.isValid) GestureDetector( onTap: onDeactivate, child: const Padding( padding: EdgeInsets.all(4), child: Icon(Icons.delete_outline, size: 20, color: AppTheme.textHint), ), ) else const Icon(Icons.chevron_right, color: AppTheme.textHint, size: 20), ], ), ), ); } } /// 邀请详情底部弹窗 —— Twitter 风格 class _InviteDetailSheet extends StatelessWidget { final Invite invite; const _InviteDetailSheet({required this.invite}); @override Widget build(BuildContext context) { final dateFormat = DateFormat('yyyy-MM-dd HH:mm'); final inviteUrl = invite.link ?? 'http://localhost:3000/join/${invite.code}'; return Container( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 顶部把手 Container( width: 36, height: 4, decoration: BoxDecoration( color: AppTheme.dragHandleColor, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 16), const Text( '邀请详情', style: TextStyle( fontSize: 17, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), const SizedBox(height: 24), // 二维码 Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppTheme.cardBackground, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppTheme.dividerColor), ), child: QrImageView( data: inviteUrl, size: 200, backgroundColor: AppTheme.cardBackground, ), ), const SizedBox(height: 20), // 统计数据 Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _SheetStatItem(label: '点击数', value: '${invite.clickCount}'), Container( width: 0.5, height: 30, color: AppTheme.dividerColor, ), _SheetStatItem(label: '加入数', value: '${invite.joinCount}'), Container( width: 0.5, height: 30, color: AppTheme.dividerColor, ), _SheetStatItem( label: '创建时间', value: invite.createdAt != null ? dateFormat.format(invite.createdAt!) : '无', ), ], ), const SizedBox(height: 20), // 邀请链接 Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( color: AppTheme.chatInputBg, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Expanded( child: Text( inviteUrl, style: const TextStyle(fontSize: 13, color: AppTheme.textSecondary), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), GestureDetector( onTap: () { // 复制到剪贴板 }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: AppTheme.primaryColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), ), child: const Text( '复制', style: TextStyle( fontSize: 12, color: AppTheme.primaryColor, fontWeight: FontWeight.w500, ), ), ), ), ], ), ), const SizedBox(height: 20), // 操作按钮 Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: () { // 分享 }, icon: const Icon(Icons.share, size: 18), label: const Text('分享'), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton.icon( onPressed: () { // 保存二维码 }, icon: const Icon(Icons.download, size: 18), label: const Text('保存二维码'), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), ], ), ], ), ); } } /// 底部弹窗统计项 class _SheetStatItem extends StatelessWidget { final String label; final String value; const _SheetStatItem({required this.label, required this.value}); @override Widget build(BuildContext context) { return Column( children: [ Text( value, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), const SizedBox(height: 4), Text( label, style: const TextStyle( fontSize: 12, color: AppTheme.textHint, ), ), ], ); } }