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,
),
),
),
),
),
),
);
}
}
-3
View File
@@ -435,9 +435,6 @@
"authPasswordLength": "Password must be 8-64 characters",
"authPasswordWeak": "Password needs both letters and numbers",
"authNetworkError": "Network error, please try again",
"authLoginFailed": "Login failed",
"authRegisterFailed": "Registration failed",
"authErrorConfirm": "OK",
"authSwitchToSignUp": "New here? Create an account",
"authSwitchToSignIn": "Already have an account? Sign in",
"authGateTitle": "Sign in to continue",
-3
View File
@@ -409,9 +409,6 @@
"authPasswordLength": "Kata sandi harus 8-64 karakter",
"authPasswordWeak": "Kata sandi perlu huruf dan angka",
"authNetworkError": "Jaringan bermasalah, coba lagi",
"authLoginFailed": "Gagal masuk",
"authRegisterFailed": "Pendaftaran gagal",
"authErrorConfirm": "Mengerti",
"authSwitchToSignUp": "Baru di sini? Buat akun",
"authSwitchToSignIn": "Sudah punya akun? Masuk",
"authGateTitle": "Masuk untuk melanjutkan",
-18
View File
@@ -1888,24 +1888,6 @@ abstract class AppLocalizations {
/// **'Network error, please try again'**
String get authNetworkError;
/// No description provided for @authLoginFailed.
///
/// In en, this message translates to:
/// **'Login failed'**
String get authLoginFailed;
/// No description provided for @authRegisterFailed.
///
/// In en, this message translates to:
/// **'Registration failed'**
String get authRegisterFailed;
/// No description provided for @authErrorConfirm.
///
/// In en, this message translates to:
/// **'OK'**
String get authErrorConfirm;
/// No description provided for @authSwitchToSignUp.
///
/// In en, this message translates to:
-9
View File
@@ -958,15 +958,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get authNetworkError => 'Network error, please try again';
@override
String get authLoginFailed => 'Login failed';
@override
String get authRegisterFailed => 'Registration failed';
@override
String get authErrorConfirm => 'OK';
@override
String get authSwitchToSignUp => 'New here? Create an account';
-9
View File
@@ -962,15 +962,6 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get authNetworkError => 'Jaringan bermasalah, coba lagi';
@override
String get authLoginFailed => 'Gagal masuk';
@override
String get authRegisterFailed => 'Pendaftaran gagal';
@override
String get authErrorConfirm => 'Mengerti';
@override
String get authSwitchToSignUp => 'Baru di sini? Buat akun';
-9
View File
@@ -946,15 +946,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get authNetworkError => '网络异常,请稍后重试';
@override
String get authLoginFailed => '登录失败';
@override
String get authRegisterFailed => '注册失败';
@override
String get authErrorConfirm => '知道了';
@override
String get authSwitchToSignUp => '还没有账号?去注册';
-3
View File
@@ -371,9 +371,6 @@
"authPasswordLength": "密码长度需在 8-64 位之间",
"authPasswordWeak": "密码需同时包含字母和数字",
"authNetworkError": "网络异常,请稍后重试",
"authLoginFailed": "登录失败",
"authRegisterFailed": "注册失败",
"authErrorConfirm": "知道了",
"authSwitchToSignUp": "还没有账号?去注册",
"authSwitchToSignIn": "已有账号?去登录",
"authGateTitle": "登录后可继续",
+2 -6
View File
@@ -12,7 +12,7 @@ import '../../core/platform/keyboard_inset.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/centered_error_dialog.dart';
import '../../core/widgets/centered_toast.dart';
import '../../core/widgets/subpage_header.dart';
import '../../l10n/app_localizations.dart';
import 'auth_logic.dart';
@@ -251,11 +251,7 @@ class _AuthPageState extends State<AuthPage> {
if (mounted) setState(() => _logic.busy = false);
}
if (!mounted || failureMessage == null) return;
await showCenteredErrorDialog(
context,
title: _logic.signUpMode ? l10n.authRegisterFailed : l10n.authLoginFailed,
message: failureMessage,
);
showCenteredToast(context, failureMessage);
}
@override
+3 -12
View File
@@ -11,7 +11,7 @@ import '../../core/data/services/auth_controller.dart';
import '../../core/platform/haptics.dart';
import '../../core/platform/keyboard_inset.dart';
import '../../core/theme/app_colors.dart';
import '../../core/widgets/centered_error_dialog.dart';
import '../../core/widgets/centered_toast.dart';
import '../../l10n/app_localizations.dart';
const double _fieldHeight = 52;
@@ -187,7 +187,7 @@ class _PhoneAuthSheetState extends State<PhoneAuthSheet> {
if (mounted) setState(() => _sending = false);
}
if (mounted && failureMessage != null) {
await _showFailure(failureMessage);
showCenteredToast(context, failureMessage);
}
}
@@ -235,19 +235,10 @@ class _PhoneAuthSheetState extends State<PhoneAuthSheet> {
if (mounted) setState(() => _submitting = false);
}
if (mounted && failureMessage != null) {
await _showFailure(failureMessage);
showCenteredToast(context, failureMessage);
}
}
Future<void> _showFailure(String message) {
final l10n = AppLocalizations.of(context)!;
return showCenteredErrorDialog(
context,
title: _signUp ? l10n.authRegisterFailed : l10n.authLoginFailed,
message: message,
);
}
void _switchMode() {
setState(() {
_signUp = !_signUp;