queued_toast 1.1.0
queued_toast: ^1.1.0 copied to clipboard
Overlay toasts for Flutter that queue per screen position, de-duplicate identical messages, stack up to five at a time and fade in and out.
import 'package:flutter/material.dart';
import 'package:queued_toast/queued_toast.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() {
// Toasts render into this navigator's overlay, so wire it up once at startup.
Toast.init(navigatorKey: navigatorKey);
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'queued_toast',
theme: ThemeData(colorSchemeSeed: Colors.indigo),
darkTheme: ThemeData.dark(),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
ToastPosition _position = ToastPosition.bottom;
double _offset = 80;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('queued_toast')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('Position'),
const SizedBox(height: 8),
SegmentedButton<ToastPosition>(
segments: const [
ButtonSegment(value: ToastPosition.top, label: Text('Top')),
ButtonSegment(
value: ToastPosition.center,
label: Text('Center'),
),
ButtonSegment(
value: ToastPosition.bottom,
label: Text('Bottom'),
),
],
selected: {_position},
onSelectionChanged: (selection) =>
setState(() => _position = selection.first),
),
const SizedBox(height: 16),
// Distance from the top or bottom edge. Ignored at center.
Text('Offset from edge: ${_offset.round()} px'),
Slider(
value: _offset,
max: 320,
divisions: 32,
onChanged: _position == ToastPosition.center
? null
: (value) => setState(() => _offset = value),
),
const SizedBox(height: 8),
const Text('Styles'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final style in ToastStyle.values)
FilledButton.tonal(
onPressed: () => Toast.show(
title: style.name,
description: 'A $style toast at ${_position.name}.',
style: style,
position: _position,
offset: _offset,
),
child: Text(style.name),
),
],
),
const SizedBox(height: 24),
const Divider(),
const SizedBox(height: 8),
FilledButton(
// Eight distinct toasts, but only five fit on screen — the last
// three wait in the queue for a slot to free up.
onPressed: () {
for (var i = 1; i <= 8; i++) {
Toast.show(
title: 'Queued #$i',
description: 'Only five show at once.',
style: ToastStyle.info,
position: _position,
offset: _offset,
duration: const Duration(seconds: 3),
);
}
},
child: const Text('Queue eight toasts'),
),
const SizedBox(height: 8),
OutlinedButton(
// Both calls are identical, so the second one is dropped.
onPressed: () {
Toast.show(
description: 'Sent twice, shown once.',
style: ToastStyle.warning,
position: _position,
offset: _offset,
);
Toast.show(
description: 'Sent twice, shown once.',
style: ToastStyle.warning,
position: _position,
offset: _offset,
);
},
child: const Text('Show a duplicate'),
),
const SizedBox(height: 8),
OutlinedButton(
onPressed: () => Toast.show(
title: 'Custom',
description: 'Own colours, icon and radius.',
position: _position,
offset: _offset,
icon: const Icon(Icons.rocket_launch, color: Colors.white),
bgColor: Colors.deepPurple,
textColor: Colors.white,
borderRadius: 24,
duration: const Duration(seconds: 4),
),
child: const Text('Fully customised'),
),
],
),
),
);
}
}