fix(auth): 登录异常改为居中吐司

This commit is contained in:
Zhou Weidong committed 2026-10-22 09:57:57 +08:00
1 parent 11e37a92b5
commit d076c57283
16 files changed
+341 -175

No files matched your search

@@ -1,67 +0,0 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../l10n/app_localizations.dart';
import '../theme/app_colors.dart';
const Key _dialogKey = Key('centered-error-dialog');
const Key _confirmKey = Key('centered-error-confirm');
const double _dialogRadius = 16;
const double _titleHorizontalInset = 24;
const double _titleTopInset = 24;
const double _contentHorizontalInset = 24;
const double _contentTopInset = 12;
const double _contentBottomInset = 8;
const double _actionsHorizontalInset = 16;
const double _actionsBottomInset = 12;
const EdgeInsets _titlePadding = EdgeInsets.fromLTRB(
_titleHorizontalInset,
_titleTopInset,
_titleHorizontalInset,
0,
);
const EdgeInsets _contentPadding = EdgeInsets.fromLTRB(
_contentHorizontalInset,
_contentTopInset,
_contentHorizontalInset,
_contentBottomInset,
);
const EdgeInsets _actionsPadding = EdgeInsets.fromLTRB(
_actionsHorizontalInset,
0,
_actionsHorizontalInset,
_actionsBottomInset,
);
/// Shows a reusable error dialog centered on the screen.
Future<void> showCenteredErrorDialog(
BuildContext context, {
required String title,
required String message,
}) async {
final l10n = AppLocalizations.of(context)!;
await showDialog<void>(
context: context,
barrierDismissible: true,
builder: (dialogContext) => AlertDialog(
key: _dialogKey,
backgroundColor: AppColors.card,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(_dialogRadius),
),
titlePadding: _titlePadding,
contentPadding: _contentPadding,
title: Text(title),
content: Text(message),
actionsPadding: _actionsPadding,
actions: [
TextButton(
key: _confirmKey,
onPressed: () => Navigator.of(dialogContext).pop(),
child: Text(l10n.authErrorConfirm),
),
],
),
);
}
+128
View File
@@ -0,0 +1,128 @@
// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter/material.dart';
const Key _toastKey = Key('centered-toast');
const Duration _fadeDuration = Duration(milliseconds: 200);
const Duration _visibleDuration = Duration(seconds: 2);
const double _toastRadius = 12;
const double _toastMaxWidth = 320;
const double _toastHorizontalInset = 20;
const double _toastVerticalInset = 12;
const double _toastBackgroundOpacity = 0.78;
const TextStyle _toastTextStyle = TextStyle(
color: Colors.white,
fontSize: 14,
height: 1.4,
fontWeight: FontWeight.w500,
);
const EdgeInsets _toastPadding = EdgeInsets.symmetric(
horizontal: _toastHorizontalInset,
vertical: _toastVerticalInset,
);
OverlayEntry? _activeToast;
/// Shows a lightweight centered message and replaces any active toast.
void showCenteredToast(BuildContext context, String message) {
_removeActiveToast();
final overlay = Overlay.of(context, rootOverlay: true);
late final OverlayEntry entry;
entry = OverlayEntry(
builder: (_) => _CenteredToast(
message: message,
onDismissed: () {
if (!identical(_activeToast, entry)) return;
_activeToast = null;
if (entry.mounted) entry.remove();
},
),
);
_activeToast = entry;
overlay.insert(entry);
}
void _removeActiveToast() {
final entry = _activeToast;
_activeToast = null;
if (entry != null && entry.mounted) entry.remove();
}
class _CenteredToast extends StatefulWidget {
const _CenteredToast({required this.message, required this.onDismissed});
final String message;
final VoidCallback onDismissed;
@override
State<_CenteredToast> createState() => _CenteredToastState();
}
class _CenteredToastState extends State<_CenteredToast>
with SingleTickerProviderStateMixin {
late final AnimationController _opacity = AnimationController(
vsync: this,
duration: _fadeDuration,
);
Timer? _dismissTimer;
@override
void initState() {
super.initState();
_opacity.forward();
_dismissTimer = Timer(_visibleDuration, _dismiss);
}
Future<void> _dismiss() async {
try {
await _opacity.reverse();
} on TickerCanceled {
return;
}
if (mounted) widget.onDismissed();
}
@override
void dispose() {
_dismissTimer?.cancel();
_opacity.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Positioned.fill(
child: IgnorePointer(
child: Center(
child: FadeTransition(
opacity: CurvedAnimation(
parent: _opacity,
curve: Curves.easeOutCubic,
),
child: Material(
type: MaterialType.transparency,
child: Container(
key: _toastKey,
constraints: const BoxConstraints(maxWidth: _toastMaxWidth),
padding: _toastPadding,
decoration: BoxDecoration(
color: Colors.black.withValues(
alpha: _toastBackgroundOpacity,
),
borderRadius: BorderRadius.circular(_toastRadius),
),
child: Text(
widget.message,
textAlign: TextAlign.center,
style: _toastTextStyle,
),
),
),
),
),
),
);
}
}