277d1c6e97
按真相源新规: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>
77 lines
2.9 KiB
Dart
77 lines
2.9 KiB
Dart
// 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),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|