Compare commits
No commits in common. 'master' and 'fatima_dev' have entirely different histories.
master
...
fatima_dev
@ -1,149 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mc_common_app/config/routes.dart';
|
||||
import 'package:mc_common_app/theme/colors.dart';
|
||||
|
||||
/// Self-dismissing toast with a visible countdown, shown on top of whatever view is
|
||||
/// currently on screen. Mounts via the root navigator's Overlay, so callers don't need
|
||||
/// to pass a BuildContext (avoids the stale-context issues a dialog-based approach has,
|
||||
/// e.g. when shown from a VM/repo layer that doesn't hold one).
|
||||
class AppToast {
|
||||
static OverlayEntry? _entry;
|
||||
|
||||
// Single place to retune how long an action-performing toast waits before firing its
|
||||
// onComplete (either naturally, or immediately if the user taps it early -- see _dismiss).
|
||||
static const int actionSeconds = 4;
|
||||
|
||||
// Duration for the plain toast with no onComplete callback -- change this single value
|
||||
// to retune it everywhere that uses it.
|
||||
static const int plainSeconds = 3;
|
||||
|
||||
static const Color errorBgColor = Color(0xFFF1CECE);
|
||||
static const Color errorTextColor = MyColors.redColor;
|
||||
static const Color successBgColor = Color(0xFFECFDF5);
|
||||
static const Color successTextColor = MyColors.greenColor;
|
||||
|
||||
static void show(String message, {int seconds = 5, VoidCallback? onComplete, bool showIcon = true, bool showCountdownText = true, Color? bgColor, Color? textColor}) {
|
||||
OverlayState? overlay = navigatorKey.currentState?.overlay;
|
||||
if (overlay == null) return;
|
||||
|
||||
// Only one toast on screen at a time -- a new call replaces whatever is currently
|
||||
// showing. Its onComplete (if any) never fires in that case -- _dismiss only acts on
|
||||
// the entry that's still current.
|
||||
_entry?.remove();
|
||||
_entry = null;
|
||||
|
||||
late OverlayEntry entry;
|
||||
entry = OverlayEntry(
|
||||
builder: (BuildContext context) => _CountdownToast(
|
||||
message: message,
|
||||
seconds: seconds,
|
||||
showIcon: showIcon,
|
||||
showCountdownText: showCountdownText,
|
||||
bgColor: bgColor,
|
||||
textColor: textColor,
|
||||
onDismiss: () => _dismiss(entry, onComplete),
|
||||
),
|
||||
);
|
||||
_entry = entry;
|
||||
overlay.insert(entry);
|
||||
}
|
||||
|
||||
// Fires on both the timer completing and an early tap -- a tap just means "don't make
|
||||
// me wait", not "cancel this". Guarded so a toast that's already been replaced never
|
||||
// fires its (now stale) action.
|
||||
static void _dismiss(OverlayEntry entry, VoidCallback? onComplete) {
|
||||
if (_entry == entry) {
|
||||
entry.remove();
|
||||
_entry = null;
|
||||
onComplete?.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _CountdownToast extends StatefulWidget {
|
||||
final String message;
|
||||
final int seconds;
|
||||
final VoidCallback onDismiss;
|
||||
final bool showIcon;
|
||||
final bool showCountdownText;
|
||||
final Color? bgColor;
|
||||
final Color? textColor;
|
||||
|
||||
const _CountdownToast({required this.message, required this.seconds, required this.onDismiss, this.showIcon = true, this.showCountdownText = true, this.bgColor, this.textColor});
|
||||
|
||||
@override
|
||||
State<_CountdownToast> createState() => _CountdownToastState();
|
||||
}
|
||||
|
||||
class _CountdownToastState extends State<_CountdownToast> {
|
||||
late int _remaining = widget.seconds;
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (Timer t) {
|
||||
setState(() => _remaining--);
|
||||
if (_remaining <= 0) {
|
||||
t.cancel();
|
||||
widget.onDismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color bgColor = widget.bgColor ?? MyColors.darkTextColor;
|
||||
Color textColor = widget.textColor ?? Colors.white;
|
||||
|
||||
return Positioned(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 24,
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: GestureDetector(
|
||||
onTap: widget.onDismiss,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
boxShadow: const <BoxShadow>[BoxShadow(color: Color(0x33000000), blurRadius: 12, offset: Offset(0, 4))],
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
if (widget.showIcon) ...<Widget>[Icon(Icons.error_outline, color: textColor, size: 20), const SizedBox(width: 10)],
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(widget.message, style: TextStyle(color: textColor, fontSize: 14, height: 1.3, fontWeight: FontWeight.w600)),
|
||||
if (widget.showCountdownText) ...<Widget>[
|
||||
const SizedBox(height: 4),
|
||||
Text("Dismissing in ${_remaining}s", style: TextStyle(color: textColor.withOpacity(0.7), fontSize: 12, fontWeight: FontWeight.w500)),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue