// widgets/ds/ds_toast.dart — 原型 .toast(atoms.css)1:1: // fixed bottom:26 水平居中 / --toast-bg 底 + 白字 fs-body / pad 11 18 / r-md / // **内容自适应宽**(不定宽、不贯穿)/ .25s 淡入+上滑 / 2.2s 自动消失。 // 单例复用:新消息顶替旧消息(对齐原型同一 #toast 节点 + 计时器重置), // 天然避免多条提示叠罗汉。语义底色(成功/失败)由调用点传 [bg] 覆盖。 import 'dart:async'; import 'package:flutter/material.dart'; import '../../core/theme/app_dims.g.dart'; import '../../core/theme/context_tokens.dart'; OverlayEntry? _entry; Timer? _timer; void showDsToast( BuildContext context, String message, { Color? bg, Duration duration = const Duration(milliseconds: 2200), }) { final overlay = Overlay.maybeOf(context, rootOverlay: true); if (overlay == null) return; final color = bg ?? context.tokens.toastBg; _timer?.cancel(); if (_entry?.mounted ?? false) _entry!.remove(); _entry = null; final entry = OverlayEntry( builder: (_) => _DsToast(message: message, bg: color), ); _entry = entry; overlay.insert(entry); _timer = Timer(duration, () { if (_entry == entry) { if (entry.mounted) entry.remove(); _entry = null; } }); } class _DsToast extends StatelessWidget { final String message; final Color bg; const _DsToast({required this.message, required this.bg}); @override Widget build(BuildContext context) { return Positioned( left: 0, right: 0, bottom: 26, child: IgnorePointer( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ TweenAnimationBuilder( tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 250), curve: Curves.easeOut, builder: (_, v, child) => Opacity( opacity: v, child: Transform.translate( offset: Offset(0, 20 * (1 - v)), child: child), ), child: Material( color: Colors.transparent, child: Container( constraints: const BoxConstraints(maxWidth: 560), padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 11), decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(AppDims.rMd), ), child: Text( message, style: const TextStyle( fontSize: AppDims.fsBody, color: Colors .white, // ds-ignore: toast 白字固定(承 toast-bg/语义色底) ), ), ), ), ), ], ), ), ); } }