Files

129 lines
3.3 KiB
Dart

// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter/material.dart';
import '../theme/app_colors.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 TextStyle _toastTextStyle = TextStyle(
color: AppColors.onSurface,
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: AppColors.raised,
borderRadius: BorderRadius.circular(_toastRadius),
border: Border.all(color: AppColors.border),
),
child: Text(
widget.message,
textAlign: TextAlign.center,
style: _toastTextStyle,
),
),
),
),
),
),
);
}
}