awesome_toast 2.0.2 copy "awesome_toast: ^2.0.2" to clipboard
awesome_toast: ^2.0.2 copied to clipboard

A beautiful, customizable toast notification system for Flutter with automatic stacking, smooth animations, and full navigation persistence.

example/lib/main.dart

import 'package:awesome_toast/awesome_toast.dart';
import 'package:flutter/material.dart';

// ============================================================================ 
// EXAMPLE APP
// ============================================================================ 

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ToastPosition _position = ToastPosition.topCenter;
  int _stackThreshold = 3;
  bool _isDark = false;

  void _updateConfig(ToastPosition position, int threshold) {
    setState(() {
      _position = position;
      _stackThreshold = threshold;
    });
  }

  void _toggleTheme() {
    setState(() {
      _isDark = !_isDark;
    });
  }

  @override
  Widget build(BuildContext context) {
    return ToastProvider(
      config: ToastStackConfig(
        position: _position,
        stackThreshold: _stackThreshold,
      ),
      child: MaterialApp(
        title: 'Awesome Toast Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          brightness: Brightness.light,
          useMaterial3: true,
        ),
        darkTheme: ThemeData(
          primarySwatch: Colors.blue,
          brightness: Brightness.dark,
          useMaterial3: true,
        ),
        themeMode: _isDark ? ThemeMode.dark : ThemeMode.light,
        home: DemoScreen(
          isDark: _isDark,
          toggleTheme: _toggleTheme,
          position: _position,
          stackThreshold: _stackThreshold,
          onConfigChanged: _updateConfig,
        ),
        debugShowCheckedModeBanner: false,
      ),
    );
  }
}

class DemoScreen extends StatefulWidget {
  final bool isDark;
  final VoidCallback toggleTheme;
  final ToastPosition position;
  final int stackThreshold;
  final void Function(ToastPosition, int) onConfigChanged;

  const DemoScreen({
    super.key,
    required this.isDark,
    required this.toggleTheme,
    required this.position,
    required this.stackThreshold,
    required this.onConfigChanged,
  });

  @override
  State<DemoScreen> createState() => _DemoScreenState();
}

class _DemoScreenState extends State<DemoScreen> {
  int _counter = 0;

  void _showRandomToast() {
    final types = [
      () => ToastService.instance.success(
            'Success',
            'Operation completed successfully!',
          ),
      () => ToastService.instance.error(
            'Error',
            'Something went wrong. Please try again.',
          ),
      () => ToastService.instance.warning(
            'Warning',
            'Please review your information carefully.',
          ),
      () => ToastService.instance.info(
            'Info',
            'Here is some useful information for you.',
          ),
    ];

    types[_counter % types.length]();
    _counter++;
  }

  void _showMultipleToasts(int count) {
    for (int i = 0; i < count; i++) {
      Future.delayed(Duration(milliseconds: i * 200), _showRandomToast);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Optimized Toast Stack Demo'),
        actions: [
          IconButton(
            icon: Icon(widget.isDark ? Icons.light_mode : Icons.dark_mode),
            onPressed: widget.toggleTheme,
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            _buildSection('Settings', [
              const Text('Position:'),
              const SizedBox(height: 8),
              Wrap(
                spacing: 8.0,
                runSpacing: 8.0,
                children: ToastPosition.values.map((ToastPosition value) {
                  return ChoiceChip(
                    label: Text(value.name),
                    selected: widget.position == value,
                    onSelected: (bool selected) {
                      if (selected) {
                        widget.onConfigChanged(value, widget.stackThreshold);
                      }
                    },
                  );
                }).toList(),
              ),
              const SizedBox(height: 16),
              Row(
                children: [
                  const Text('Stack Threshold:'),
                  Expanded(
                    child: Slider(
                      value: widget.stackThreshold.toDouble(),
                      min: 1,
                      max: 10,
                      divisions: 9,
                      label: widget.stackThreshold.toString(),
                      onChanged: (double value) {
                        widget.onConfigChanged(
                            widget.position, value.round());
                      },
                    ),
                  ),
                  Text('${widget.stackThreshold}'),
                ],
              ),
            ]),
            const SizedBox(height: 24),
            _buildSection('Quick Actions', [
              ElevatedButton.icon(
                onPressed: _showRandomToast,
                icon: const Icon(Icons.add),
                label: const Text('Add Single Toast'),
              ),
              const SizedBox(height: 8),
              ElevatedButton.icon(
                onPressed: () => _showMultipleToasts(3),
                icon: const Icon(Icons.add_circle),
                label: const Text('Add 3 Toasts'),
              ),
              const SizedBox(height: 8),
              ElevatedButton.icon(
                onPressed: () => _showMultipleToasts(5),
                icon: const Icon(Icons.add_circle_outline),
                label: const Text('Add 5 Toasts (Test Stack)'),
              ),
              const SizedBox(height: 8),
              OutlinedButton.icon(
                onPressed: ToastService.instance.clear,
                icon: const Icon(Icons.clear_all),
                label: const Text('Clear All'),
              ),
            ]),
            const SizedBox(height: 24),
            _buildSection('Toast Types', [
              _buildToastButton(
                'Success Toast',
                Icons.check_circle,
                Colors.green,
                () => ToastService.instance.success(
                  'Success',
                  'Your changes have been saved successfully!',
                  showProgress: true,
                ),
              ),
              _buildToastButton(
                'Error Toast',
                Icons.error,
                Colors.red,
                () => ToastService.instance.error(
                  'Error',
                  'Failed to save changes. Please try again.',
                  showProgress: true,
                ),
              ),
              _buildToastButton(
                'Warning Toast',
                Icons.warning,
                Colors.orange,
                () => ToastService.instance.warning(
                  'Warning',
                  'You have unsaved changes that will be lost.',
                ),
              ),
              _buildToastButton(
                'Info Toast',
                Icons.info,
                Colors.blue,
                () => ToastService.instance.info(
                  'Information',
                  'You can dismiss toasts by swiping or clicking close.',
                ),
              ),
            ]),
            const SizedBox(height: 24),
            _buildSection('Advanced Features', [
              OutlinedButton.icon(
                onPressed: () {
                  ToastService.instance.show(
                      contentBuilder:
                          (context, progress, dismissToast, actions) {
                        return ValueListenableBuilder(
                            valueListenable: progress ?? ValueNotifier(0.0),
                            builder: (context, value, child) {
                              return Stack(
                                children: [
                                  Container(
                                    height: 150,
                                    width: 400,
                                    decoration: BoxDecoration(
                                        color:
                                            Colors.blueAccent.withAlpha(200),
                                        borderRadius:
                                            BorderRadius.circular(10),
                                        border: Border.all(
                                            width: 10 - 10 * value,
                                            color: Colors.lightBlueAccent),
                                    ),
                                    child: Row(
                                      children: [
                                        SizedBox(width: 200 * value),
                                        Transform.rotate(
                                          angle: value * 5,
                                          child: Transform.scale(
                                            scale: value * 5,
                                            child: Text(
                                              '${(5 - value * 5).round()}',
                                              style: const TextStyle(
                                                  fontSize: 40,
                                                  fontWeight: FontWeight.bold,
                                                  color: Colors.white),
                                            ),
                                          ),
                                        ),
                                      ],
                                    ),
                                  ),
                                  Positioned(
                                    right: -100 + 100 * value,
                                    bottom: 0,
                                    child: (actions?.isNotEmpty ?? false)
                                        ? Padding(
                                            padding:
                                                const EdgeInsets.all(16.0),
                                            child: Wrap(
                                              runAlignment: WrapAlignment.end,
                                              alignment: WrapAlignment.end,
                                              crossAxisAlignment:
                                                  WrapCrossAlignment.end,
                                              spacing: 8,
                                              runSpacing: 8,
                                              children: actions! 
                                                  .map(
                                                    (action) => Container(
                                                      decoration:
                                                          BoxDecoration(
                                                        borderRadius:
                                                            BorderRadius
                                                                .circular(20),
                                                        border: Border.all(
                                                          color: Colors.white,
                                                          width: 1,
                                                        ),
                                                      ),
                                                      child: TextButton(
                                                        onPressed: () {
                                                          action.onPressed();
                                                          dismissToast
                                                              ?.call();
                                                        },
                                                        child: Text(
                                                          action.label,
                                                          style:
                                                              const TextStyle(
                                                            color:
                                                                Colors.white,
                                                            fontWeight:
                                                                FontWeight
                                                                    .bold,
                                                            fontSize: 14,
                                                          ),
                                                        ),
                                                      ),
                                                    ),
                                                  )
                                                  .toList(),
                                            ),
                                          )
                                        : const SizedBox.shrink(),
                                  ),
                                  Positioned(
                                    top: (-20 + 100 * value).clamp(-20, 10),
                                    right: 5,
                                    child: IconButton(
                                      onPressed: dismissToast,
                                      icon: const Icon(
                                        Icons.waving_hand,
                                        color: Colors.white,
                                      ),
                                    ),
                                  )
                                ],
                              );
                            });
                      },
                      actions: [
                        ToastAction(
                          label: 'Isn\'t it Beautiful?',
                          onPressed: () {
                            ToastService.instance.success(
                              'Absolutely!',
                              'Toast has been dismissed!!!',
                            );
                          },
                        ),
                      ],
                      duration: const Duration(seconds: 5));
                },
                icon: const Icon(Icons.palette),
                label: const Text('Custom Styled Toast'),
              ),
              const SizedBox(height: 8),
              OutlinedButton.icon(
                onPressed: () {
                  ToastService.instance.info(
                    'Action Required',
                    'This toast will not be dismissed automatically.',
                    autoDismiss: false,
                    actions: [
                      ToastAction(
                        label: 'Do Something',
                        onPressed: () {
                          ToastService.instance.success(
                            'Done!',
                            'Action done.',
                          );
                        },
                      ),
                      ToastAction(
                        label: 'Cancel',
                        onPressed: () {
                          ToastService.instance.error(
                            'Cancelled',
                            'Action has been cancelled.',
                          );
                        },
                      ),
                    ],
                  );
                },
                icon: const Icon(Icons.undo),
                label: const Text('Toast with Action (No Auto-Dismiss)'),
              ),
              const SizedBox(height: 8),
              OutlinedButton.icon(
                onPressed: () {
                  ToastService.instance.warning(
                    'Confirm Action',
                    'Are you sure you want to proceed?',
                    duration: const Duration(seconds: 10),
                    showProgress: true,
                    actions: [
                      ToastAction(
                        label: 'Confirm',
                        onPressed: () {
                          ToastService.instance.success(
                            'Confirmed',
                            'Action has been completed.',
                          );
                        },
                      ),
                    ],
                  );
                },
                icon: const Icon(Icons.done_all),
                label: const Text('Toast with Action & Progress'),
              ),
              const SizedBox(height: 8),
              OutlinedButton.icon(
                onPressed: () {
                  ToastService.instance.error('Critical Alert',
                      'This toast cannot be dismissed by swiping.',
                      dismissable: false,
                      duration: const Duration(seconds: 5),
                      actions: [
                        ToastAction(
                          label: 'Do Something',
                          onPressed: () {
                            ToastService.instance.success(
                              'Done!',
                              'Action done.',
                            );
                          },
                        ),
                      ]);
                },
                icon: const Icon(Icons.lock),
                label: const Text('Non-Dismissable Toast'),
              ),
            ]),
            const SizedBox(height: 24),
            _buildSection('Progress value changes Test', [
              OutlinedButton.icon(
                onPressed: () {
                  final progressNotifier = ValueNotifier<double>(0.0);
                  VoidCallback? action = () {};
                  ToastService.instance.show(
                    dismissable: false,
                    contentBuilder:
                        (context, progress, dismissToast, actions) {
                      action = dismissToast;
                      return DefaultToast(
                        title: 'Progress Test',
                        message: 'This toast has a progress indicator.',
                        type: ToastType.none,
                        showProgress: true,
                        dismissable: false,
                        progress: progressNotifier,
                        icon: Icons.download,
                        progressBackgroundColor: Colors.white10,
                        progressColor: Colors.green.withValues(alpha: 0.8),
                        actions: [
                          ToastAction(
                            label: 'Cancel',
                            onPressed: () {
                              ToastService.instance.error(
                                'Cancelled',
                                'Download has been cancelled.',
                              );
                              dismissToast?.call();
                              progressNotifier.dispose();
                            },
                          ),
                        ],
                      );
                    },
                  );

                  // Simulate a download
                  Future.doWhile(() async {
                    if (progressNotifier.value >= 1.0) {
                      return false;
                    }
                    await Future.delayed(const Duration(milliseconds: 50));
                    if (progressNotifier.value < 1.0) {
                      progressNotifier.value += 0.01;
                    }
                    if (progressNotifier.value >= 1.0) {
                      ToastService.instance.success(
                          'Downloaded', 'Your download is complete.');
                      action?.call();
                      progressNotifier.dispose();
                      return false;
                    }
                    return true;
                  });
                },
                icon: const Icon(Icons.download),
                label: const Text('Simulate Download'),
              ),
            ]),
            const SizedBox(height: 24),
            _buildSection('Navigation Test', [
              ElevatedButton.icon(
                onPressed: () {
                  ToastService.instance.info(
                    'Navigation Test',
                    'This toast will persist across navigation.',
                    duration: const Duration(seconds: 10),
                  );
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => SecondScreen(
                        isDark: widget.isDark,
                      ),
                    ),
                  );
                },
                icon: const Icon(Icons.navigation),
                label: const Text('Navigate with Toast'),
              ),
            ]),
            const SizedBox(height: 24),
            _buildSection('Customization', [
              OutlinedButton.icon(
                onPressed: () {
                  ToastService.instance.show(
                    contentBuilder:
                        (context, progress, dismissToast, actions) =>
                            DefaultToast(
                      title: 'Custom Style',
                      message: 'This toast has custom text styles.',
                      type: ToastType.info,
                      onDismiss: dismissToast,
                      showProgress: true,
                      progress: progress,
                      titleTextStyle: const TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                        fontSize: 20,
                      ),
                      messageTextStyle: const TextStyle(
                        color: Colors.white,
                        fontSize: 16,
                      ),
                    ),
                    duration: const Duration(seconds: 5),
                  );
                },
                icon: const Icon(Icons.text_fields),
                label: const Text('Custom Text Styles'),
              ),
              const SizedBox(height: 8),
              OutlinedButton.icon(
                onPressed: () {
                  ToastService.instance.show(
                    contentBuilder:
                        (context, progress, dismissToast, actions) =>
                            DefaultToast(
                      title: 'Custom Action',
                      message: 'This toast has a custom action button style.',
                      type: ToastType.info,
                      actions: [
                        ToastAction(
                          label: 'Custom',
                          onPressed: () {},
                        ),
                      ],
                      onDismiss: dismissToast,
                      buttonsActionStyle: const TextStyle(
                          color: Colors.red, fontWeight: FontWeight.bold),
                      showProgress: true,
                      progress: progress,
                    ),
                    duration: const Duration(seconds: 10),
                  );
                },
                icon: const Icon(Icons.touch_app),
                label: const Text('Custom Action Button Style'),
              ),
              const SizedBox(height: 8),
              OutlinedButton.icon(
                onPressed: () {
                  ToastService.instance.show(
                    contentBuilder:
                        (context, progress, dismissToast, actions) =>
                            DefaultToast(
                      title: 'Custom Progress',
                      message: 'This toast has a custom progress indicator.',
                      type: ToastType.info,
                      onDismiss: dismissToast,
                      progressColor: Colors.green,
                      progressBackgroundColor:
                          Colors.grey.withAlpha((0.5 * 255).round()),
                      progressStrokeWidth: 10,
                      expandProgress: false,
                      showProgress: true,
                      progress: progress,
                    ),
                    duration: const Duration(seconds: 5),
                  );
                },
                icon: const Icon(Icons.linear_scale),
                label: const Text('Custom Progress Indicator'),
              ),
            ]),
            const SizedBox(height: 16),
            _buildSection('Instructions', [
              _buildInstruction(
                Icons.mouse,
                'Hover over toasts to expand stack and pause timers',
              ),
              _buildInstruction(
                Icons.swipe,
                'Swipe left or right to dismiss (40% threshold)',
              ),
              _buildInstruction(Icons.close, 'Click close button to dismiss'),
              _buildInstruction(
                Icons.layers,
                'Stack appears when toast count exceeds threshold',
              ),
            ]),
            const SizedBox(height: 16),
            _buildSection('Current State', [
              ListenableBuilder(
                listenable: ToastService.instance,
                builder: (context, child) {
                  return Text(
                      'Active Toasts: ${ToastService.instance.count}');
                },
              ),
              Text('Theme: ${widget.isDark ? "Dark" : "Light"}'),
              Text(
                  'Position: ${widget.position.name}'),
              Text(
                  'Stack Threshold: ${widget.stackThreshold}'),
            ]),
          ],
        ),
      ),
    );
  }

  Widget _buildSection(String title, List<Widget> children) {
    return Card(
      elevation: 5,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              title,
              style: Theme.of(context).textTheme.titleMedium?.copyWith(
                  fontWeight: FontWeight.bold,
                  color: Theme.of(context).colorScheme.primary),
            ),
            const SizedBox(height: 12),
            ...children,
          ],
        ),
      ),
    );
  }

  Widget _buildToastButton(
    String label,
    IconData icon,
    Color color,
    VoidCallback onPressed,
  ) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: OutlinedButton.icon(
        onPressed: onPressed,
        icon: Icon(icon, color: color),
        label: Text(label),
      ),
    );
  }

  Widget _buildInstruction(IconData icon, String text) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Row(
        children: [
          Icon(icon, size: 20),
          const SizedBox(width: 8),
          Expanded(
            child: Text(text),
          ),
        ],
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  const SecondScreen({super.key, required this.isDark});
  final bool isDark;

  @override
  Widget build(BuildContext context) {
    // Note: Theme is now handled by MaterialApp via themeMode.
    // However, if we want to ensure this screen uses the right theme independently
    // (though it should inherit from MaterialApp), we can keep the logic or rely on context.
    // Since we passed isDark, we might not strictly need it for Theme if MaterialApp handles it.
    // But let's leave it as is to avoid breaking changes if it was intended to override.
    // Actually, MaterialApp handles it globally.
    
    return Scaffold(
      appBar: AppBar(title: const Text('Second Screen')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Notice the toast persists across navigation!'),
            const SizedBox(height: 24),
            ElevatedButton.icon(
              onPressed: () {
                ToastService.instance.success(
                  'From Second Screen',
                  'This toast was shown from the second screen.',
                );
              },
              icon: const Icon(Icons.add),
              label: const Text('Show Toast from Here'),
            ),
            const SizedBox(height: 16),
            OutlinedButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('Go Back'),
            ),
          ],
        ),
      ),
    );
  }
}
3
likes
160
points
132
downloads

Publisher

unverified uploader

Weekly Downloads

A beautiful, customizable toast notification system for Flutter with automatic stacking, smooth animations, and full navigation persistence.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on awesome_toast