Files
xianyan/lib/shared/widgets/feedback/app_toast.dart
Developer 5a49d20c8a chore: 完成项目品牌域名批量替换与功能迭代
本次提交包含多项核心更新:
1. 全量替换项目内所有xianyan.app域名变更为s2ss.com,包含配置文件、路由、隐私政策等
2. 重构图表库从fl_chart迁移至syncfusion_flutter_charts,优化图表渲染效果
3. 新增宽屏分屏布局支持,包含右侧面板注册表与可拖拽分割线
4. 完善触觉反馈服务与认证感知Mixin,修复多处内存泄漏问题
5. 合并勋章墙与金币记录入口至成就中心,简化个人中心导航
6. 新增收藏与时间线数据合并导入功能
7. 修复多处UI样式问题,统一主题颜色使用规范
8. 新增日历同步与跨平台触觉反馈依赖库
9. 修复BotToast初始化流程,避免路由切换时的弹窗崩溃
2026-05-29 10:06:55 +08:00

199 lines
5.4 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// ============================================================
/// 闲言APP — Toast提示通用封装
/// 创建时间: 2026-04-28
/// 更新时间: 2026-05-29
/// 作用: 基于 bot_toast 封装统一的Toast提示组件
/// 上次更新: 新增_isInitialized安全守卫防止BotToast未初始化时崩溃
///
/// BotToast 初始化顺序必须在App启动时严格遵守:
/// 1. MaterialApp.builder 中调用 BotToastInit() 创建 builder
/// 2. 立即调用 AppToast.markInitialized() 标记初始化完成
/// 3. MaterialApp.navigatorObservers 中添加 BotToastNavigatorObserver()
/// 4. GoRouter.observers 中添加 BotToastNavigatorObserver()
///
/// ⚠️ 任何在初始化完成前调用 AppToast 方法的行为将被降级为 debugPrint
/// ⚠️ BotToastNavigatorObserver 缺失会导致路由切换时 Toast 状态异常
/// ============================================================
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/cupertino.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/theme/app_spacing.dart';
import '../../../core/theme/app_typography.dart';
class AppToast {
AppToast._();
static bool _isInitialized = false;
static bool get isInitialized => _isInitialized;
static void markInitialized() {
_isInitialized = true;
debugPrint('[AppToast] BotToast 已标记为初始化完成');
}
static void markDisposed() {
_isInitialized = false;
}
static void _safeCall(VoidCallback action, {String? fallbackMessage}) {
if (_isInitialized) {
try {
action();
} catch (e) {
debugPrint('[AppToast] BotToast调用异常: $e');
}
} else {
debugPrint(
'[AppToast] BotToast未初始化跳过Toast: ${fallbackMessage ?? '未知消息'}',
);
}
}
static void show(
String message, {
Duration duration = const Duration(seconds: 2),
bool showClose = false,
}) {
_safeCall(
() => BotToast.showCustomText(
duration: duration,
onlyOne: true,
clickClose: true,
crossPage: true,
backgroundColor: const Color(0x00000000),
toastBuilder: (_) =>
_ToastWidget(message: message, showClose: showClose),
),
fallbackMessage: message,
);
}
static void showSuccess(String message) {
show('$message');
}
static void showError(String message) {
show('$message', duration: const Duration(seconds: 3));
}
static void showWarning(String message) {
show('⚠️ $message');
}
static void showInfo(String message) {
show(' $message');
}
static void showLoading({String? message}) {
_safeCall(
() => BotToast.showCustomLoading(
toastBuilder: (_) => _LoadingWidget(message: message),
clickClose: false,
allowClick: false,
),
fallbackMessage: message ?? 'Loading',
);
}
static void closeLoading() {
_safeCall(BotToast.closeAllLoading, fallbackMessage: 'closeLoading');
}
}
class _ToastWidget extends StatelessWidget {
const _ToastWidget({required this.message, this.showClose = false});
final String message;
final bool showClose;
@override
Widget build(BuildContext context) {
final ext = AppTheme.ext(context);
return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.lg),
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm + 4,
),
decoration: BoxDecoration(
color: ext.bgSecondary.withValues(alpha: 0.95),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: ext.textHint.withValues(alpha: 0.1),
width: 0.5,
),
boxShadow: const [
BoxShadow(
color: Color(0x1A000000),
blurRadius: 20,
offset: Offset(0, 4),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
message,
style: AppTypography.subhead.copyWith(color: ext.textPrimary),
textAlign: TextAlign.center,
),
),
if (showClose) ...[
const SizedBox(width: AppSpacing.sm),
GestureDetector(
onTap: () => BotToast.cleanAll(),
child: Icon(
CupertinoIcons.xmark_circle_fill,
size: 18,
color: ext.textHint,
),
),
],
],
),
);
}
}
class _LoadingWidget extends StatelessWidget {
const _LoadingWidget({this.message});
final String? message;
@override
Widget build(BuildContext context) {
final ext = AppTheme.ext(context);
return Container(
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
color: ext.bgSecondary.withValues(alpha: 0.95),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: ext.textHint.withValues(alpha: 0.1),
width: 0.5,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CupertinoActivityIndicator(radius: 14),
if (message != null) ...[
const SizedBox(height: AppSpacing.md),
Text(
message!,
style: AppTypography.subhead.copyWith(color: ext.textPrimary),
),
],
],
),
);
}
}