From df19629944a9e969bdb51e107deb1209b4c7bedc Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sun, 12 Jul 2026 17:51:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(shell):=20=E6=A1=8C=E9=9D=A2=E4=BE=A7?= =?UTF-8?q?=E6=A0=8F=E5=8A=A0=20Account=20=E4=B8=80=E7=BA=A7=E9=A1=B9=20+?= =?UTF-8?q?=20=E9=A1=B6=E6=A0=8F=E9=80=9A=E7=9F=A5=E9=93=83=E9=93=9B=20+?= =?UTF-8?q?=20invite/notifications=20=E8=A7=86=E5=9B=BE=E6=8E=A5=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- client/lib/shell/desktop_shell.dart | 4 +++ client/lib/shell/tablet_shell.dart | 2 ++ client/lib/widgets/content_top_bar.dart | 15 +++++++- client/lib/widgets/notification_bell.dart | 42 +++++++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 client/lib/widgets/notification_bell.dart diff --git a/client/lib/shell/desktop_shell.dart b/client/lib/shell/desktop_shell.dart index 11798d7..b846edd 100644 --- a/client/lib/shell/desktop_shell.dart +++ b/client/lib/shell/desktop_shell.dart @@ -36,6 +36,7 @@ class DesktopShell extends ConsumerWidget { (icon: PangolinIcons.power, label: t.tabConnect, view: NavView.connect), (icon: PangolinIcons.globe, label: t.tabServers, view: NavView.servers), (icon: PangolinIcons.chartNoAxesColumn, label: t.tabStats, view: NavView.stats), + (icon: PangolinIcons.user, label: t.tabMe, view: NavView.account), (icon: PangolinIcons.settings, label: t.settingsTitle, view: NavView.settings), (icon: PangolinIcons.messageCircle, label: t.contactTitle, view: NavView.contact), ]; @@ -54,6 +55,7 @@ class DesktopShell extends ConsumerWidget { NavView.payment: t.paymentTitle, NavView.orders: t.ordersTitle, NavView.invite: t.inviteTitle, + NavView.notifications: t.notifTitle, }; void go(NavView v) => ref.read(navViewProvider.notifier).state = v; @@ -98,6 +100,7 @@ class DesktopShell extends ConsumerWidget { // 其余下钻子页(含 purchase 本身)→ account。 VoidCallback? backTarget() { if (view == NavView.payment) return () => go(NavView.purchase); + if (view == NavView.notifications) return () => go(NavView.account); if (kAccountSubViews.contains(view)) return () => go(NavView.account); return null; } @@ -111,6 +114,7 @@ class DesktopShell extends ConsumerWidget { ContentTopBar( title: titles[view] ?? t.brand, onBack: backTarget(), + onBell: () => go(NavView.notifications), ), Expanded(child: content()), ]), diff --git a/client/lib/shell/tablet_shell.dart b/client/lib/shell/tablet_shell.dart index 06bffa1..001c095 100644 --- a/client/lib/shell/tablet_shell.dart +++ b/client/lib/shell/tablet_shell.dart @@ -43,6 +43,8 @@ class TabletShell extends ConsumerWidget { NavView.account: t.tabMe, NavView.settings: t.settingsTitle, NavView.contact: t.contactTitle, + NavView.invite: t.inviteTitle, + NavView.notifications: t.notifTitle, }; void go(NavView v) => ref.read(navViewProvider.notifier).state = v; diff --git a/client/lib/widgets/content_top_bar.dart b/client/lib/widgets/content_top_bar.dart index 39a63c7..8f79334 100644 --- a/client/lib/widgets/content_top_bar.dart +++ b/client/lib/widgets/content_top_bar.dart @@ -9,15 +9,24 @@ import '../pangolin_theme.dart'; import '../state/app_providers.dart'; import '../state/connection_provider.dart'; import '../state/nodes_provider.dart'; +import 'notification_bell.dart'; import 'pangolin_icons.dart'; class ContentTopBar extends ConsumerWidget { - const ContentTopBar({super.key, required this.title, this.onBack, this.showThemeToggle = true, this.titleSize = 17}); + const ContentTopBar({ + super.key, + required this.title, + this.onBack, + this.showThemeToggle = true, + this.titleSize = 17, + this.onBell, + }); final String title; final VoidCallback? onBack; final bool showThemeToggle; final double titleSize; + final VoidCallback? onBell; @override Widget build(BuildContext context, WidgetRef ref) { @@ -52,6 +61,10 @@ class ContentTopBar extends ConsumerWidget { color: conn.phase == VpnPhase.on ? c.success : c.fg3, ), ), + if (onBell != null) ...[ + const SizedBox(width: 12), + NotificationBell(onTap: onBell, hasUnread: true), + ], if (showThemeToggle) ...[ const SizedBox(width: 12), InkWell( diff --git a/client/lib/widgets/notification_bell.dart b/client/lib/widgets/notification_bell.dart new file mode 100644 index 0000000..ecfab2b --- /dev/null +++ b/client/lib/widgets/notification_bell.dart @@ -0,0 +1,42 @@ +// notification_bell.dart — 顶栏通知铃铛(带未读红点) +// +// 桌面顶栏(content_top_bar.dart)在主题切换旁挂一枚铃铛,点击进 NavView.notifications。 +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../pangolin_theme.dart'; +import 'pangolin_icons.dart'; + +class NotificationBell extends ConsumerWidget { + const NotificationBell({super.key, required this.onTap, this.hasUnread = false}); + + final VoidCallback? onTap; + final bool hasUnread; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final c = context.pangolin; + + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(PangolinRadius.sm), + child: Padding( + padding: const EdgeInsets.all(6), + child: Stack(clipBehavior: Clip.none, children: [ + Icon(PangolinIcons.bell, size: 18, color: c.fg2), + // TODO(spec-3): 未读态来自 notices provider,现暂由调用方写死演示。 + if (hasUnread) + Positioned( + top: -1, + right: -1, + child: Container( + width: 7, + height: 7, + decoration: BoxDecoration(color: c.danger, shape: BoxShape.circle), + ), + ), + ]), + ), + ); + } +}