import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:shimmer/shimmer.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../core/theme/app_colors.dart'; import '../../../data/models/announcement/announcement.dart'; import '../../../providers/announcement_unread_provider.dart'; import '../../../providers/notifications_provider.dart'; import '../../widgets/common/app_refresh_indicator.dart'; class NotificationsScreen extends ConsumerStatefulWidget { const NotificationsScreen({super.key}); @override ConsumerState createState() => _NotificationsScreenState(); } class _NotificationsScreenState extends ConsumerState { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { ref.invalidate(announcementUnreadProvider); }); } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final state = ref.watch(notificationsProvider); final notifier = ref.read(notificationsProvider.notifier); final unreadState = ref.watch(announcementUnreadProvider).valueOrNull; return Scaffold( appBar: AppBar( elevation: 0, leading: IconButton( icon: const Icon(Icons.chevron_left, size: 28), onPressed: () => context.pop(), ), title: Text( AppLocalizations.of(context)!.systemAnnouncement, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600), ), centerTitle: true, ), body: _buildBody(context, cs, state, notifier, unreadState), ); } Widget _buildBody( BuildContext context, ColorScheme cs, AppNotificationsState state, AppNotificationsNotifier notifier, AnnouncementUnreadState? unreadState, ) { // 首次加载 → shimmer 骨架屏 if (state.isLoading && state.notifications.isEmpty) { return _ShimmerList(cs: cs); } // 空状态 if (state.notifications.isEmpty) { return Center( child: Text( AppLocalizations.of(context)!.noAnnouncement, style: TextStyle(color: cs.onSurface.withAlpha(153)), ), ); } return AppRefreshIndicator( onRefresh: notifier.refresh, child: NotificationListener( onNotification: (notification) { final metrics = notification.metrics; // 滚动到底部附近 → 加载更多 if (notification is ScrollEndNotification && metrics.pixels >= metrics.maxScrollExtent - 100) { notifier.loadMore(); } // 内容不足以滚动(全部可见)→ 自动触发加载更多 if (notification is ScrollMetricsNotification && metrics.maxScrollExtent == 0 && state.hasMore && !state.isLoading) { WidgetsBinding.instance .addPostFrameCallback((_) => notifier.loadMore()); } return false; }, child: ListView.separated( padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), itemCount: state.notifications.length + 1, separatorBuilder: (_, __) => const SizedBox(height: 1), itemBuilder: (_, index) { if (index >= state.notifications.length) { if (state.isLoading) { return const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center( child: CircularProgressIndicator(strokeWidth: 2)), ); } if (!state.hasMore) { return Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: Center( child: Text( AppLocalizations.of(context)!.noMore, style: TextStyle( color: cs.onSurface.withAlpha(102), fontSize: 13), ), ), ); } // hasMore && !isLoading → 下拉提示 return Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.keyboard_arrow_down, size: 18, color: cs.onSurface.withAlpha(102)), const SizedBox(width: 4), Text( AppLocalizations.of(context)!.pullDownToLoadMore, style: TextStyle( color: cs.onSurface.withAlpha(102), fontSize: 13), ), ], ), ), ); } final item = state.notifications[index]; final isFirst = index == 0; final isLast = index == state.notifications.length - 1 && !state.hasMore; return _NotificationRow( item: item, isFirst: isFirst, isLast: isLast, isUnread: !(unreadState?.isRead(int.tryParse(item.id) ?? -1) ?? true), onTap: () => context.push('/user/messages/${item.id}'), ); }, ), ), ); } } // ── Shimmer 骨架屏 ────────────────────────────────────────── class _ShimmerList extends StatelessWidget { const _ShimmerList({required this.cs}); final ColorScheme cs; @override Widget build(BuildContext context) { return Shimmer.fromColors( baseColor: cs.onSurface.withAlpha(15), highlightColor: cs.onSurface.withAlpha(30), child: Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: Column( mainAxisSize: MainAxisSize.min, children: List.generate(8, (i) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 14, width: double.infinity, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 8), Container( height: 12, width: 120, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), ], ), ), const SizedBox(width: 8), Container( height: 18, width: 18, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), ], ), ); }), ), ), ), ); } } // ── 数据行 ────────────────────────────────────────────────── class _NotificationRow extends StatelessWidget { const _NotificationRow({ required this.item, required this.isFirst, required this.isLast, required this.isUnread, required this.onTap, }); final AnnouncementContent item; final bool isFirst; final bool isLast; final bool isUnread; final VoidCallback onTap; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Container( decoration: BoxDecoration( color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary, borderRadius: BorderRadius.vertical( top: isFirst ? const Radius.circular(12) : Radius.zero, bottom: isLast ? const Radius.circular(12) : Radius.zero, ), ), child: Column( children: [ InkWell( onTap: onTap, borderRadius: BorderRadius.vertical( top: isFirst ? const Radius.circular(12) : Radius.zero, bottom: isLast ? const Radius.circular(12) : Radius.zero, ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ SizedBox( width: 10, child: isUnread ? Container( width: 8, height: 8, decoration: const BoxDecoration( color: AppColors.fall, shape: BoxShape.circle, ), ) : null, ), const SizedBox(width: 6), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.title, style: TextStyle( color: cs.onSurface, fontSize: 14, fontWeight: FontWeight.w500, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 3), Text( item.createTime, style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 12, ), ), ], ), ), Icon(Icons.chevron_right, size: 18, color: cs.onSurface.withAlpha(153)), ], ), ), ), if (!isLast) Divider( height: 1, indent: 16, color: cs.outline, ), ], ), ); } }