// notifications_page.dart — 通知(占位屏) // // TODO(spec-3): 列表来自 GET /v1/notices,已读态本地持久化。当前为 3 条静态示例, // 仅供 IA 走查——接后端前不接任何真实业务逻辑。 import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../l10n/app_text.dart'; import '../pangolin_theme.dart'; import '../widgets/pangolin_icons.dart'; import '../widgets/status_pill.dart'; typedef _Notice = ({ IconData icon, PangolinStatus status, String typeLabel, String title, String sub, bool read, }); class NotificationsScreen extends ConsumerWidget { const NotificationsScreen({super.key, required this.t, this.onBack, this.embedded = false}); final AppText t; final VoidCallback? onBack; final bool embedded; @override Widget build(BuildContext context, WidgetRef ref) { final c = context.pangolin; // 静态示例数据(占位):News / Feature / Important 各一条,类型 pill 文案走 t.notifType*。 final notices = <_Notice>[ ( icon: PangolinIcons.bell, status: PangolinStatus.neutral, typeLabel: t.notifTypeNews, title: 'Product update', // TODO(spec-3): placeholder sub: '2 days ago', read: false, ), ( icon: PangolinIcons.zap, status: PangolinStatus.connected, typeLabel: t.notifTypeFeature, title: 'New feature released', // TODO(spec-3): placeholder sub: '5 days ago', read: false, ), ( icon: PangolinIcons.alertTriangle, status: PangolinStatus.error, typeLabel: t.notifTypeImportant, title: 'Important notice', // TODO(spec-3): placeholder sub: '1 week ago', read: true, ), ]; Widget empty() => Padding( padding: const EdgeInsets.only(top: 60), child: Column(children: [ Container( width: 56, height: 56, decoration: BoxDecoration(color: c.bgSubtle, shape: BoxShape.circle), child: Icon(PangolinIcons.bell, size: 26, color: c.fg3), ), const SizedBox(height: 14), Text(t.notifEmpty, style: PangolinText.sm.copyWith(color: c.fg3)), ]), ); Widget row(_Notice n, bool divider) { return Container( decoration: BoxDecoration(border: divider ? Border(bottom: BorderSide(color: c.border)) : null), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13), child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack(clipBehavior: Clip.none, children: [ Container( width: 38, height: 38, decoration: BoxDecoration(color: c.accentSubtle, borderRadius: BorderRadius.circular(PangolinRadius.md)), child: Icon(n.icon, size: 18, color: c.accent), ), if (!n.read) Positioned( top: -2, right: -2, child: Container( width: 8, height: 8, decoration: BoxDecoration(color: c.danger, shape: BoxShape.circle), ), ), ]), const SizedBox(width: 12), Expanded( child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row(children: [ Expanded( child: Text(n.title, overflow: TextOverflow.ellipsis, style: PangolinText.sm.copyWith( color: c.fg1, fontWeight: n.read ? FontWeight.w500 : FontWeight.w700, fontSize: 14.5)), ), const SizedBox(width: 8), StatusPill(label: n.typeLabel, status: n.status), ]), const SizedBox(height: 4), Text(n.sub, style: PangolinText.caption.copyWith(color: c.fg3, fontWeight: FontWeight.w400)), ]), ), ]), ); } Widget content() => ListView( padding: const EdgeInsets.fromLTRB(20, 8, 20, 24), children: [ if (notices.isEmpty) empty() else Container( decoration: BoxDecoration( color: c.surface, borderRadius: BorderRadius.circular(PangolinRadius.lg), border: Border.all(color: c.border), boxShadow: PangolinShadow.sm, ), clipBehavior: Clip.antiAlias, child: Column(children: [ for (var i = 0; i < notices.length; i++) row(notices[i], i < notices.length - 1), ]), ), ], ); if (embedded) return content(); return Scaffold( backgroundColor: c.bg, body: SafeArea( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.fromLTRB(8, 6, 16, 10), child: Row(children: [ IconButton( onPressed: onBack ?? () => Navigator.of(context).maybePop(), icon: Icon(PangolinIcons.arrowLeft, size: 22, color: c.fg1), ), Text(t.notifTitle, style: PangolinText.h3.copyWith(color: c.fg1, fontWeight: FontWeight.w700)), ]), ), Expanded(child: content()), ]), ), ); } }