fix(auth): 登录异常改为居中吐司
This commit is contained in:
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user