44f22dcf52
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
160 lines
5.5 KiB
Dart
160 lines
5.5 KiB
Dart
// 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()),
|
|
]),
|
|
),
|
|
);
|
|
}
|
|
}
|