import 'package:easy_refresh/easy_refresh.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import '../../core/theme/app_colors.dart'; import '../../shared/widgets/empty_state.dart'; import '../shell/nav_bar_config.dart'; import '../../core/i18n/app_localizations.dart'; import '../../shared/widgets/loading_widget.dart'; import '../../shared/widgets/message_item.dart'; import 'message_controller.dart'; import 'message_model.dart'; /// 消息通知聚合页 /// /// 展示 5 种消息类型: /// - 审批待办(📋)→ 详情页 + 审批操作栏 /// - 审批结果(✅/❌)→ 详情查看结果 /// - 撤回通知(↩)→ 消息列表 /// - 系统公告(📢)→ 公告详情 /// - 过期提醒(⏰)→ 对应详情页 /// /// 左滑操作:标记已读(蓝)+ 删除(红)。已读消息不可左滑。 class MessageListPage extends ConsumerWidget { const MessageListPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final messagesAsync = ref.watch(messageListProvider); final unreadCount = ref.watch(unreadCountProvider); final location = GoRouterState.of(context).uri.toString(); final l10n = AppLocalizations.of(context); if (location.startsWith('/messages')) { ref .read(navBarConfigProvider.notifier) .update( NavBarConfig( title: l10n.get('messageNotifications'), showBack: true, leadingIcon: Icons.close, // 仅在有未读时显示"全部已读"按钮 showRight: unreadCount > 0, rightWidget: unreadCount > 0 ? GestureDetector( onTap: () { TDMessage.showMessage( context: context, content: l10n.get('markAllRead'), theme: MessageTheme.success, icon: true, duration: 2000, ); }, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), child: Text( l10n.get('markAllRead'), style: const TextStyle( fontSize: AppFontSizes.body, color: AppColors.primary, ), ), ), ) : null, ), ); } return EasyRefresh( header: TDRefreshHeader(), onRefresh: () async { ref.invalidate(messageListProvider); await ref.read(messageListProvider.future); }, child: messagesAsync.when( loading: () => const SingleChildScrollView( physics: AlwaysScrollableScrollPhysics(), child: LoadingWidget(), ), error: (_, _) => SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: EmptyState(message: l10n.get('loadFailed')), ), data: (messages) => messages.isEmpty ? SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: EmptyState(message: l10n.get('noMessages')), ) : ListView.separated( padding: const EdgeInsets.fromLTRB( AppSpacing.md, AppSpacing.md, AppSpacing.md, AppSpacing.lg, ), itemCount: messages.length, separatorBuilder: (_, _) => const SizedBox(height: 12), itemBuilder: (_, i) => _buildItem(context, ref, messages[i], l10n), ), ), ); } Widget _buildItem( BuildContext context, WidgetRef ref, MessageModel msg, AppLocalizations l10n, ) { final (icon, iconColor, iconBg) = _messageIconProps(msg.msgType); return MessageItem( icon: icon, iconColor: iconColor, iconBackground: iconBg, title: msg.title, time: _formatTime(msg.createTime), sender: _messageSender(msg.msgType, l10n), summary: msg.content, unread: !msg.isRead, onTap: () => _navigateToBiz(context, msg), // 仅未读消息展示左滑操作 onToggleRead: msg.isRead ? null : () { TDMessage.showMessage( context: context, content: '${l10n.get("markRead")}:${msg.title}', theme: MessageTheme.success, icon: true, duration: 2000, ); }, onDelete: () { showDialog( context: context, builder: (ctx) => AlertDialog( title: Text(l10n.get('confirm')), content: Text( l10n.getString('confirmAction', args: {'action': l10n.get('delete')}), ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), child: Text(l10n.get('cancel')), ), TextButton( onPressed: () { Navigator.of(ctx).pop(); TDMessage.showMessage( context: context, content: '${l10n.get("deletedToast")}${msg.title}', theme: MessageTheme.warning, icon: true, duration: 2000, ); }, child: Text(l10n.get('confirm')), ), ], ), ); }, ); } /// 按消息类型返回(图标, 图标色, 图标背景色) (IconData, Color, Color) _messageIconProps(String msgType) { switch (msgType) { case 'announcement': return (Icons.campaign_outlined, AppColors.warning, AppColors.warningBg); case 'approval_result': return (Icons.check_circle_outlined, AppColors.success, AppColors.successBg); case 'withdraw_notice': return (Icons.undo_outlined, AppColors.statusGray, const Color(0xFFF0F0F0)); case 'expiry_reminder': return (Icons.timer_off_outlined, AppColors.danger, AppColors.dangerBg); case 'approval_notice': default: return (Icons.assignment_outlined, AppColors.primary, AppColors.primaryLight); } } /// 按消息类型返回发送者标签 String _messageSender(String msgType, AppLocalizations l10n) { switch (msgType) { case 'announcement': return l10n.get('systemNotice'); case 'approval_notice': return l10n.get('approvalNotice'); case 'approval_result': return l10n.get('systemMessage'); case 'withdraw_notice': return l10n.get('systemMessage'); case 'expiry_reminder': return l10n.get('systemMessage'); default: return l10n.get('systemMessage'); } } /// 格式化时间为 MM-DD HH:mm String _formatTime(DateTime time) { final month = time.month.toString().padLeft(2, '0'); final day = time.day.toString().padLeft(2, '0'); final hour = time.hour.toString().padLeft(2, '0'); final minute = time.minute.toString().padLeft(2, '0'); return '$month-$day $hour:$minute'; } /// 按 BizType 路由跳转 void _navigateToBiz(BuildContext context, MessageModel msg) { if (msg.bizType == null || msg.bizId == null) return; switch (msg.bizType) { case 'expense': context.push('/expense/detail/${msg.bizId}'); break; case 'overtime': context.push('/overtime/detail/${msg.bizId}'); break; case 'vehicle': context.push('/vehicle/detail/${msg.bizId}'); break; case 'announcement': context.push('/announcement/detail/${msg.bizId}'); break; case 'expense_application': context.push('/expense-apply/detail/${msg.bizId}'); break; } } }