feat(client): toast 限定主显示区/自适应宽/上限80%/超长多行居中(1.0.26)
按真相源新规:toast 改为自定义 Overlay 实现(widgets/pangolin_toast.dart),完全控制布局—— 不覆盖左侧栏(left=侧栏宽 desktop204/tablet232/mobile0)、宽度自适应内容上限主区80%、超长文本 多行、主区内水平居中贴底、淡入轻起;仍走语义 token 明暗适配。自动连接改用 showPangolinToast。 同步 design/CONTRACT.md §2 toast 规格(canonical=showPangolinToast)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+3
-10
@@ -22,9 +22,7 @@ import 'system_tray.dart';
|
||||
import 'widgets/auth_screen.dart';
|
||||
import 'widgets/onboarding_screen.dart';
|
||||
import 'widgets/pangolin_logo.dart';
|
||||
|
||||
/// 全局 ScaffoldMessenger key:供无 BuildContext 处(如自动连接)弹 SnackBar/toast。
|
||||
final rootMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
import 'widgets/pangolin_toast.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -101,7 +99,6 @@ class _PangolinAppState extends ConsumerState<PangolinApp> {
|
||||
final mode = ref.watch(themeModeProvider);
|
||||
return MaterialApp(
|
||||
title: '穿山甲',
|
||||
scaffoldMessengerKey: rootMessengerKey,
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: PangolinTheme.light,
|
||||
darkTheme: PangolinTheme.dark,
|
||||
@@ -155,13 +152,9 @@ class _RootFlowState extends ConsumerState<RootFlow> {
|
||||
if (settings.autoConnect && phase == VpnPhase.off) {
|
||||
logLine('AutoConnect', 'triggering connect → ${node.code}');
|
||||
ref.read(connectionProvider.notifier).toggle();
|
||||
// 自动连接 toast:告知用户「正在自动连接到 X」。
|
||||
// 自动连接 toast:「正在自动连接到 X」(主显示区内居中,见 pangolin_toast)。
|
||||
final t = ref.read(appTextProvider);
|
||||
rootMessengerKey.currentState?.showSnackBar(SnackBar(
|
||||
content: Text(t.autoConnectingTo(node.localizedName(t.lang))),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 3),
|
||||
));
|
||||
if (mounted) showPangolinToast(context, t.autoConnectingTo(node.localizedName(t.lang)));
|
||||
} else {
|
||||
logLine('AutoConnect', 'NOT connecting (autoConnect=${settings.autoConnect} phase=$phase)');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// pangolin_toast.dart — 统一轻提示(toast)。真相源规格见 design/CONTRACT.md §2「Toast/SnackBar」。
|
||||
//
|
||||
// 用 Overlay 实现(非 Material SnackBar),以完全控制布局:
|
||||
// · 只在主显示区内(避开左侧栏:desktop 204 / tablet 232 / mobile 0)。
|
||||
// · 宽度自适应内容,上限为主显示区的 80%;超长则文本多行、居中。
|
||||
// · 跟随明暗主题:surface 底 + fg1 字 + border 描边 + md 圆角 + 柔和暖阴影,淡入轻起。
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../core/responsive/form_factor.dart';
|
||||
import '../pangolin_theme.dart';
|
||||
|
||||
/// 在主显示区底部居中弹一条 toast(默认 3 秒)。需要一个能拿到 Overlay/主题的 context。
|
||||
void showPangolinToast(BuildContext context, String message,
|
||||
{Duration duration = const Duration(seconds: 3)}) {
|
||||
final overlay = Overlay.maybeOf(context, rootOverlay: true);
|
||||
if (overlay == null) return;
|
||||
late final OverlayEntry entry;
|
||||
entry = OverlayEntry(builder: (_) => _PangolinToast(message: message));
|
||||
overlay.insert(entry);
|
||||
Timer(duration, () {
|
||||
if (entry.mounted) entry.remove();
|
||||
});
|
||||
}
|
||||
|
||||
class _PangolinToast extends StatelessWidget {
|
||||
const _PangolinToast({required this.message});
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = context.pangolin;
|
||||
// 侧栏宽度(避开它):取自各 shell 的 NavSidebar 宽度。
|
||||
final sidebar = switch (context.formFactor) {
|
||||
FormFactor.desktop => 204.0,
|
||||
FormFactor.tablet => 232.0,
|
||||
FormFactor.mobile => 0.0,
|
||||
};
|
||||
final w = MediaQuery.sizeOf(context).width;
|
||||
final contentW = (w - sidebar).clamp(0.0, w);
|
||||
return Positioned(
|
||||
left: sidebar,
|
||||
right: 0,
|
||||
bottom: 28,
|
||||
child: IgnorePointer(
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0, end: 1),
|
||||
duration: const Duration(milliseconds: 240),
|
||||
curve: Curves.easeOut,
|
||||
builder: (_, t, child) =>
|
||||
Opacity(opacity: t, child: Transform.translate(offset: Offset(0, (1 - t) * 8), child: child)),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: contentW * 0.8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: c.surface,
|
||||
borderRadius: BorderRadius.circular(PangolinRadius.md),
|
||||
border: Border.all(color: c.border),
|
||||
boxShadow: PangolinShadow.sm,
|
||||
),
|
||||
child: Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: PangolinText.sm.copyWith(color: c.fg1, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: pangolin_vpn
|
||||
description: 穿山甲 · Pangolin — 极简、稳定、跨平台网络加速客户端。
|
||||
publish_to: "none"
|
||||
version: 1.0.25+26
|
||||
version: 1.0.26+27
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.0
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
; 前置: 先在 client/ 跑 `flutter build windows`(Release),产物在
|
||||
; ..\..\build\windows\x64\runner\Release
|
||||
#define MyAppName "穿山甲 Pangolin"
|
||||
#define MyAppVersion "1.0.25"
|
||||
#define MyAppVersion "1.0.26"
|
||||
#define MyAppPublisher "Pangolin"
|
||||
#define MyAppExeName "pangolin_vpn.exe"
|
||||
#define BuildDir "..\..\build\windows\x64\runner\Release"
|
||||
|
||||
+9
-6
@@ -32,13 +32,16 @@
|
||||
| 节点行 | `widgets/server_tile.dart` / `nodes_page` `_NodeGridTile` | 双列网格 |
|
||||
| 指标卡 | `stats_page` `_MetricCard` | 复用 |
|
||||
| 段控/开关/行 | `account_page` 内组件 | 复用 |
|
||||
| Toast / 通知条 | `pangolin_theme.dart` `snackBarTheme`(全局) | 新增组件(无原型) |
|
||||
| Toast / 通知条 | `widgets/pangolin_toast.dart` `showPangolinToast` | 新增组件(无原型) |
|
||||
|
||||
### Toast / SnackBar(通知条)规格
|
||||
跨平台轻提示(如自动连接「正在自动连接到 X」)。**无 React 原型 → 此处即真相源**;全部走语义 token,明暗自动适配:
|
||||
- 底色 `--surface`(浅=白 / 深=`#221E19`);文字 `--fg1`,Manrope 14/600;描边 `--border` 1px。
|
||||
- 圆角 `md`(10);`SnackBarBehavior.floating`;阴影柔和暖调(elevation≈6,`rgba(45,30,20,…)` 体系)。
|
||||
- 绝不用 Material 默认黑底。Flutter canonical:`PangolinTheme._build` 的 `snackBarTheme`(改样式只改此一处,全 app 一致)。
|
||||
### Toast / 通知条规格
|
||||
跨平台轻提示(如自动连接「正在自动连接到 X」)。**无 React 原型 → 此处即真相源**;全部走语义 token,明暗自动适配。
|
||||
**外观**:底 `--surface`(浅=白 / 深=`#221E19`)+ 文字 `--fg1`(Manrope 14/600,居中)+ 描边 `--border` 1px + 圆角 `md`(10)+ 柔和暖阴影(`shadow-sm`);淡入并轻微上移(240ms ease-out)。绝不用 Material 默认黑底。
|
||||
**布局**(铁律):
|
||||
- **只在主显示区内**,不覆盖左侧栏(left = 侧栏宽:desktop 204 / tablet 232 / mobile 0)。
|
||||
- **宽度自适应内容**,上限 = 主显示区宽度的 **80%**;超过则**文本多行**。
|
||||
- 在主显示区内**水平居中**,贴底(bottom 28)。
|
||||
- Canonical 实现:`widgets/pangolin_toast.dart` `showPangolinToast(context, msg)`(Overlay 实现,非 SnackBar,以完全控制上述布局)。`pangolin_theme` 的 `snackBarTheme` 只作普通 SnackBar 的兜底底色样式。
|
||||
|
||||
## 3. 页面像素规格(取自 tabapp.jsx)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user