import 'package:flutter/material.dart'; import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_typography.dart'; import '../../../../app/i18n/app_localizations.dart'; import '../../../../shared/widgets/empty_state.dart'; import '../../../../core/services/notification_service.dart'; import '../../../../core/providers/notification_badge_manager.dart'; /// A8. 消息模块 /// /// 通知 + 公告,分类Tab + 消息详情 /// 接入后端真实 API class MessagePage extends StatefulWidget { const MessagePage({super.key}); @override State createState() => _MessagePageState(); } class _MessagePageState extends State with SingleTickerProviderStateMixin { late TabController _tabController; final NotificationService _notificationService = NotificationService(); List _notifications = []; bool _isLoading = false; String? _error; NotificationType? _currentFilter; @override void initState() { super.initState(); _tabController = TabController(length: 4, vsync: this); _tabController.addListener(() { if (!_tabController.indexIsChanging) { _onTabChanged(_tabController.index); } }); _loadNotifications(); } @override void dispose() { _tabController.dispose(); super.dispose(); } void _onTabChanged(int index) { switch (index) { case 0: _currentFilter = null; break; case 1: _currentFilter = NotificationType.system; break; case 2: _currentFilter = NotificationType.announcement; break; case 3: _currentFilter = NotificationType.activity; break; } _loadNotifications(); } Future _loadNotifications() async { setState(() { _isLoading = true; _error = null; }); try { final response = await _notificationService.getNotifications( type: _currentFilter, ); if (mounted) { setState(() { _notifications = response.notifications; _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _error = e.toString(); _isLoading = false; }); } } } Future _markAllAsRead() async { final success = await _notificationService.markAllAnnouncementsAsRead(); if (success) { NotificationBadgeManager().clearCount(); _loadNotifications(); } } Future _markAsRead(NotificationItem item) async { if (item.isRead) return; final success = await _notificationService.markAsRead(item.id); if (success) { NotificationBadgeManager().decrementCount(); _loadNotifications(); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(context.t('message.title')), actions: [ TextButton( onPressed: _markAllAsRead, child: Text(context.t('notification.markAllRead'), style: AppTypography.labelSmall.copyWith(color: AppColors.primary)), ), ], bottom: TabBar( controller: _tabController, tabs: [ Tab(text: context.t('common.all')), Tab(text: context.t('notification.system')), Tab(text: context.t('notification.announcement')), Tab(text: context.t('notification.activity')), ], ), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : _error != null ? _buildErrorView() : _notifications.isEmpty ? EmptyState.noMessages() : RefreshIndicator( onRefresh: _loadNotifications, child: ListView.separated( padding: const EdgeInsets.symmetric(vertical: 8), itemCount: _notifications.length, separatorBuilder: (_, __) => const Divider(indent: 76), itemBuilder: (context, index) { return _buildMessageItem(_notifications[index]); }, ), ), ); } Widget _buildErrorView() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(context.t('notification.loadFailed'), style: AppTypography.bodyMedium), const SizedBox(height: 16), ElevatedButton( onPressed: _loadNotifications, child: Text(context.t('notification.retry')), ), ], ), ); } Widget _buildMessageItem(NotificationItem item) { return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), leading: Container( width: 44, height: 44, decoration: BoxDecoration( color: _iconColor(item.type).withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Icon(_iconData(item.type), size: 22, color: _iconColor(item.type)), ), title: Row( children: [ Expanded( child: Text(item.title, style: AppTypography.labelMedium.copyWith( fontWeight: item.isRead ? FontWeight.w400 : FontWeight.w600, )), ), if (item.publishedAt != null) Text(_formatTime(item.publishedAt!), style: AppTypography.caption), ], ), subtitle: Padding( padding: const EdgeInsets.only(top: 4), child: Text( item.content, style: AppTypography.bodySmall, maxLines: 2, overflow: TextOverflow.ellipsis, ), ), trailing: !item.isRead ? Container( width: 8, height: 8, decoration: const BoxDecoration( color: AppColors.primary, shape: BoxShape.circle, ), ) : null, onTap: () { _markAsRead(item); Navigator.pushNamed(context, '/message/detail'); }, ); } String _formatTime(DateTime time) { final now = DateTime.now(); final diff = now.difference(time); if (diff.inMinutes < 1) return '刚刚'; if (diff.inMinutes < 60) return '${diff.inMinutes}分钟前'; if (diff.inHours < 24) return '${diff.inHours}小时前'; if (diff.inDays < 7) return '${diff.inDays}天前'; return '${time.month}/${time.day}'; } IconData _iconData(NotificationType type) { switch (type) { case NotificationType.system: return Icons.settings_rounded; case NotificationType.activity: return Icons.swap_horiz_rounded; case NotificationType.reward: return Icons.card_giftcard_rounded; case NotificationType.upgrade: return Icons.system_update_rounded; case NotificationType.announcement: return Icons.campaign_rounded; } } Color _iconColor(NotificationType type) { switch (type) { case NotificationType.system: return AppColors.primary; case NotificationType.activity: return AppColors.success; case NotificationType.reward: return AppColors.warning; case NotificationType.upgrade: return AppColors.info; case NotificationType.announcement: return AppColors.info; } } }