snacktoast 0.0.1 copy "snacktoast: ^0.0.1" to clipboard
snacktoast: ^0.0.1 copied to clipboard

A production-ready Flutter package for customizable Toast messages and Snackbars with queue management, theming, and zero-dependency design.

example/lib/main.dart

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

// ─────────────────────────────────────────────────────────────────────────────
// Entry point
// ─────────────────────────────────────────────────────────────────────────────

void main() {
  SnackToastKit.configure(
    const SnackToastConfig(
      defaultPosition: SnackToastPosition.bottom,
      visualStyle: SnackToastVisualStyle.gradient,
      animationStyle: SnackToastAnimation.bounceUp,
      showProgressBar: true,
      enableSwipeToDismiss: true,
      animateIcon: true,
      glassBlurIntensity: 20.0,
      // Default sizing: wrap content
      wrapContent: true,
    ),
  );
  runApp(const ExampleApp());
}

// ─────────────────────────────────────────────────────────────────────────────
// App shell
// ─────────────────────────────────────────────────────────────────────────────

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnackToast Demo',
      debugShowCheckedModeBanner: false,
      navigatorKey: SnackToastKit.navigatorKey,
      scaffoldMessengerKey: SnackToastKit.scaffoldMessengerKey,
      theme: _buildTheme(),
      home: const ExamplePage(),
    );
  }

  ThemeData _buildTheme() => ThemeData(
    useMaterial3: true,
    brightness: Brightness.dark,
    scaffoldBackgroundColor: _C.bg,
    colorScheme: ColorScheme.dark(primary: _C.accent, surface: _C.surface),
    appBarTheme: const AppBarTheme(
      backgroundColor: _C.bgElevated,
      foregroundColor: _C.textPrimary,
      elevation: 0,
      centerTitle: false,
      titleTextStyle: TextStyle(
        fontFamily: 'monospace',
        color: _C.textPrimary,
        fontSize: 16,
        fontWeight: FontWeight.w600,
        letterSpacing: 1.2,
      ),
    ),
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Design tokens
// ─────────────────────────────────────────────────────────────────────────────

abstract final class _C {
  static const bg = Color(0xFF080B14);
  static const bgElevated = Color(0xFF0E1220);
  static const surface = Color(0xFF141827);
  static const surfaceHi = Color(0xFF1C2235);
  static const border = Color(0xFF252D44);
  static const accent = Color(0xFF6C63FF);
  static const accentSoft = Color(0xFF3D3778);
  static const textPrimary = Color(0xFFF0F2FF);
  static const textSecondary = Color(0xFF8891B0);
  static const textMuted = Color(0xFF4A5270);
  static const success = Color(0xFF1B8A4A);
  static const error = Color(0xFFD32F2F);
  static const warning = Color(0xFFE65100);
  static const info = Color(0xFF0277BD);
}

// ─────────────────────────────────────────────────────────────────────────────
// ExamplePage
// ─────────────────────────────────────────────────────────────────────────────

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

  @override
  State<ExamplePage> createState() => _ExamplePageState();
}

class _ExamplePageState extends State<ExamplePage> {
  // ── Live config state ─────────────────────────────────────────────────────
  SnackToastVisualStyle _visualStyle = SnackToastVisualStyle.gradient;
  SnackToastType _activeType = SnackToastType.success;
  bool _showProgressBar = true;
  bool _enableSwipe = true;
  bool _animateIcon = true;

  // ── Sizing demo state ─────────────────────────────────────────────────────
  bool _wrapContent = true;
  double _fixedHeight = 72.0;
  double _fixedWidth = 320.0;

  void _syncConfig() {
    SnackToastKit.configure(
      SnackToastConfig(
        visualStyle: _visualStyle,
        showProgressBar: _showProgressBar,
        enableSwipeToDismiss: _enableSwipe,
        animateIcon: _animateIcon,
        glassBlurIntensity: 20.0,
        defaultPosition: SnackToastPosition.bottom,
        successColor: _C.success,
        errorColor: _C.error,
        warningColor: _C.warning,
        infoColor: _C.info,
        // Sizing is managed per-call in the sizing section demos.
        wrapContent: true,
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Build
  // ─────────────────────────────────────────────────────────────────────────

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('SNACKTOAST / DEMO'),
        actions: [
          TextButton.icon(
            onPressed: SnackToastKit.dismissAll,
            icon: const Icon(Icons.clear_all_rounded, size: 16),
            label: const Text('Clear'),
            style: TextButton.styleFrom(
              foregroundColor: _C.textSecondary,
              textStyle: const TextStyle(fontSize: 12),
            ),
          ),
          const SizedBox(width: 8),
        ],
      ),
      body: ListView(
        padding: const EdgeInsets.only(bottom: 80),
        children: [
          _buildHero(),
          _buildConfigPanel(),
          _divider('NOTIFICATION TYPES'),
          _buildTypeSection(),
          _divider('VISUAL STYLES'),
          _buildVisualStyleSection(),
          _divider('ANIMATION FAMILIES'),
          _buildAnimFamilies(),
          _divider('COLOR & STYLE CUSTOMISATION'),
          _buildColorCustomisationSection(),
          _divider('SIZING CONTROL'),
          _buildSizingSection(),
          _divider('DECORATION OVERRIDE'),
          _buildDecorationOverrideSection(),
          _divider('SNACKBARS'),
          _buildSnackbarSection(),
          _divider('ADVANCED'),
          _buildAdvancedSection(),
          const SizedBox(height: 24),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Hero
  // ─────────────────────────────────────────────────────────────────────────

  Widget _buildHero() {
    return Container(
      margin: const EdgeInsets.fromLTRB(16, 20, 16, 4),
      padding: const EdgeInsets.all(22),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(16),
        gradient: const LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [Color(0xFF1A1040), Color(0xFF0E1220)],
        ),
        border: Border.all(color: _C.accentSoft),
      ),
      child: Row(
        children: [
          Container(
            width: 48,
            height: 48,
            decoration: BoxDecoration(
              color: _C.accentSoft,
              borderRadius: BorderRadius.circular(13),
            ),
            child: const Icon(Icons.layers_rounded, color: _C.accent, size: 26),
          ),
          const SizedBox(width: 14),
          const Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'SnackToast Kit',
                  style: TextStyle(
                    color: _C.textPrimary,
                    fontSize: 18,
                    fontWeight: FontWeight.w700,
                  ),
                ),
                SizedBox(height: 3),
                Text(
                  '18 animations · 5 styles · live config',
                  style: TextStyle(color: _C.textSecondary, fontSize: 12),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Config panel
  // ─────────────────────────────────────────────────────────────────────────

  Widget _buildConfigPanel() {
    return Container(
      margin: const EdgeInsets.fromLTRB(16, 12, 16, 4),
      decoration: BoxDecoration(
        color: _C.bgElevated,
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: _C.border),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
            child: Row(
              children: [
                const Icon(Icons.tune_rounded, size: 15, color: _C.accent),
                const SizedBox(width: 7),
                const Text(
                  'LIVE CONFIG',
                  style: TextStyle(
                    color: _C.accent,
                    fontSize: 11,
                    fontWeight: FontWeight.w700,
                    letterSpacing: 1.4,
                  ),
                ),
                const Spacer(),
                Text(
                  'affects all demos',
                  style: TextStyle(color: _C.textMuted, fontSize: 11),
                ),
              ],
            ),
          ),
          const Divider(color: _C.border, height: 1),
          // Visual style chips
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 10, 16, 4),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                _cfgLabel('Visual Style'),
                const SizedBox(height: 7),
                SingleChildScrollView(
                  scrollDirection: Axis.horizontal,
                  child: Row(
                    children: SnackToastVisualStyle.values.map((s) {
                      final active = s == _visualStyle;
                      return Padding(
                        padding: const EdgeInsets.only(right: 7),
                        child: GestureDetector(
                          onTap: () {
                            setState(() => _visualStyle = s);
                            _syncConfig();
                          },
                          child: AnimatedContainer(
                            duration: const Duration(milliseconds: 200),
                            padding: const EdgeInsets.symmetric(
                              horizontal: 13,
                              vertical: 6,
                            ),
                            decoration: BoxDecoration(
                              color: active ? _C.accentSoft : _C.surface,
                              borderRadius: BorderRadius.circular(7),
                              border: Border.all(
                                color: active ? _C.accent : _C.border,
                              ),
                            ),
                            child: Text(
                              s.name,
                              style: TextStyle(
                                color: active ? _C.accent : _C.textSecondary,
                                fontSize: 11.5,
                                fontWeight: active
                                    ? FontWeight.w700
                                    : FontWeight.w400,
                              ),
                            ),
                          ),
                        ),
                      );
                    }).toList(),
                  ),
                ),
              ],
            ),
          ),
          // Toggles
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 4, 16, 14),
            child: Row(
              children: [
                _cfgToggle('Progress Bar', _showProgressBar, (v) {
                  setState(() => _showProgressBar = v);
                  _syncConfig();
                }),
                const SizedBox(width: 7),
                _cfgToggle('Swipe Dismiss', _enableSwipe, (v) {
                  setState(() => _enableSwipe = v);
                  _syncConfig();
                }),
                const SizedBox(width: 7),
                _cfgToggle('Icon Anim', _animateIcon, (v) {
                  setState(() => _animateIcon = v);
                  _syncConfig();
                }),
              ],
            ),
          ),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Notification types
  // ─────────────────────────────────────────────────────────────────────────

  Widget _buildTypeSection() {
    final types = [
      (
        SnackToastType.success,
        'Success',
        _C.success,
        Icons.check_circle_rounded,
        'Profile saved!',
      ),
      (
        SnackToastType.error,
        'Error',
        _C.error,
        Icons.error_rounded,
        'Failed to save',
      ),
      (
        SnackToastType.warning,
        'Warning',
        _C.warning,
        Icons.warning_rounded,
        'Low battery!',
      ),
      (SnackToastType.info, 'Info', _C.info, Icons.info_rounded, 'New message'),
    ];

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Column(
        children: [
          Row(
            children: types.map((t) {
              final (type, label, color, icon, _) = t;
              final active = type == _activeType;
              return Expanded(
                child: Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 3),
                  child: GestureDetector(
                    onTap: () => setState(() => _activeType = type),
                    child: AnimatedContainer(
                      duration: const Duration(milliseconds: 200),
                      padding: const EdgeInsets.symmetric(vertical: 10),
                      decoration: BoxDecoration(
                        color: active
                            ? color.withValues(alpha: 0.16)
                            : _C.surface,
                        borderRadius: BorderRadius.circular(10),
                        border: Border.all(
                          color: active ? color : _C.border,
                          width: active ? 1.5 : 1,
                        ),
                      ),
                      child: Column(
                        children: [
                          Icon(
                            icon,
                            color: active ? color : _C.textMuted,
                            size: 19,
                          ),
                          const SizedBox(height: 4),
                          Text(
                            label,
                            style: TextStyle(
                              color: active ? color : _C.textSecondary,
                              fontSize: 11,
                              fontWeight: active
                                  ? FontWeight.w700
                                  : FontWeight.w400,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              );
            }).toList(),
          ),
          const SizedBox(height: 10),
          _fullBtn(
            label: 'Fire ${_activeType.name.toUpperCase()} toast',
            icon: Icons.send_rounded,
            color: _typeColor(_activeType),
            onTap: () => SnackToastKit.toast(
              _typeMessage(_activeType),
              type: _activeType,
            ),
          ),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Visual styles preview
  // ─────────────────────────────────────────────────────────────────────────

  Widget _buildVisualStyleSection() {
    final styles = [
      (
        SnackToastVisualStyle.standard,
        'Standard',
        Icons.layers_rounded,
        'Opaque gradient card',
      ),
      (
        SnackToastVisualStyle.glass,
        'Glass',
        Icons.blur_on_rounded,
        'Frosted blur',
      ),
      (
        SnackToastVisualStyle.gradient,
        'Gradient',
        Icons.gradient_rounded,
        'Rich 3-stop gradient',
      ),
      (
        SnackToastVisualStyle.minimal,
        'Minimal',
        Icons.view_sidebar_rounded,
        'White + accent bar',
      ),
      (
        SnackToastVisualStyle.outlined,
        'Outlined',
        Icons.border_outer_rounded,
        'Transparent + border',
      ),
    ];
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Column(
        children: styles.map((s) {
          final (style, label, icon, desc) = s;
          return Padding(
            padding: const EdgeInsets.only(bottom: 7),
            child: InkWell(
              borderRadius: BorderRadius.circular(12),
              onTap: () => SnackToastKit.toast(
                '$label style preview',
                title: desc,
                type: _activeType,
                visualStyle: style,
              ),
              child: Container(
                padding: const EdgeInsets.symmetric(
                  horizontal: 14,
                  vertical: 11,
                ),
                decoration: BoxDecoration(
                  color: _C.surface,
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: _C.border),
                ),
                child: Row(
                  children: [
                    Container(
                      width: 36,
                      height: 36,
                      decoration: BoxDecoration(
                        color: _C.surfaceHi,
                        borderRadius: BorderRadius.circular(9),
                      ),
                      child: Icon(icon, color: _C.accent, size: 17),
                    ),
                    const SizedBox(width: 11),
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            label,
                            style: const TextStyle(
                              color: _C.textPrimary,
                              fontSize: 13,
                              fontWeight: FontWeight.w600,
                            ),
                          ),
                          Text(
                            desc,
                            style: const TextStyle(
                              color: _C.textSecondary,
                              fontSize: 11.5,
                            ),
                          ),
                        ],
                      ),
                    ),
                    Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 9,
                        vertical: 4,
                      ),
                      decoration: BoxDecoration(
                        color: _C.accentSoft,
                        borderRadius: BorderRadius.circular(6),
                      ),
                      child: const Text(
                        'PREVIEW',
                        style: TextStyle(
                          color: _C.accent,
                          fontSize: 10,
                          fontWeight: FontWeight.w700,
                          letterSpacing: 0.8,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          );
        }).toList(),
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Animation families
  // ─────────────────────────────────────────────────────────────────────────

  Widget _buildAnimFamilies() {
    return Column(
      children: [
        _animFamily(
          icon: Icons.swap_vert_rounded,
          label: 'Vertical Origin',
          sublabel: 'Y-axis translate + opacity',
          color: const Color(0xFF6C63FF),
          items: [
            (
              'slideUp',
              'Slide Up',
              () => SnackToastKit.toast(
                'Sliding up!',
                animationStyle: SnackToastAnimation.slideUp,
              ),
            ),
            (
              'slideDown',
              'Slide Down',
              () => SnackToastKit.toast(
                'Sliding down!',
                animationStyle: SnackToastAnimation.slideDown,
                position: SnackToastPosition.top,
              ),
            ),
            (
              'fadeDrop',
              'Fade Drop',
              () => SnackToastKit.toast(
                'Dropping in!',
                animationStyle: SnackToastAnimation.fadeDrop,
                position: SnackToastPosition.top,
              ),
            ),
            (
              'fadeRise',
              'Fade Rise',
              () => SnackToastKit.toast(
                'Rising up!',
                animationStyle: SnackToastAnimation.fadeRise,
              ),
            ),
            (
              'bounceUp',
              'Bounce Up',
              () => SnackToastKit.toast(
                'Bounced!',
                animationStyle: SnackToastAnimation.bounceUp,
              ),
            ),
          ],
        ),
        _animFamily(
          icon: Icons.swap_horiz_rounded,
          label: 'Horizontal Edge Slides',
          sublabel: 'X-axis entry from screen edges',
          color: const Color(0xFF00BFA5),
          items: [
            (
              'slideLeft',
              'Slide Left',
              () => SnackToastKit.toast(
                'From the right!',
                animationStyle: SnackToastAnimation.slideLeft,
              ),
            ),
            (
              'slideRight',
              'Slide Right',
              () => SnackToastKit.toast(
                'From the left!',
                animationStyle: SnackToastAnimation.slideRight,
              ),
            ),
            (
              'slideFromLeft',
              'From Left Edge',
              () => SnackToastKit.toast(
                'Off-screen left!',
                animationStyle: SnackToastAnimation.slideFromLeft,
              ),
            ),
            (
              'slideFromRight',
              'From Right Edge',
              () => SnackToastKit.toast(
                'Off-screen right!',
                animationStyle: SnackToastAnimation.slideFromRight,
              ),
            ),
          ],
        ),
        _animFamily(
          icon: Icons.open_with_rounded,
          label: 'Corner Sweeps',
          sublabel: 'Diagonal origin → axis center',
          color: const Color(0xFFFF6B6B),
          items: [
            (
              'topLeftSweep',
              'Top Left',
              () => SnackToastKit.toast(
                'Top-left!',
                animationStyle: SnackToastAnimation.topLeftSweep,
                position: SnackToastPosition.top,
              ),
            ),
            (
              'topRightSweep',
              'Top Right',
              () => SnackToastKit.toast(
                'Top-right!',
                animationStyle: SnackToastAnimation.topRightSweep,
                position: SnackToastPosition.top,
              ),
            ),
            (
              'bottomLeftSweep',
              'Bottom Left',
              () => SnackToastKit.toast(
                'Bottom-left!',
                animationStyle: SnackToastAnimation.bottomLeftSweep,
              ),
            ),
            (
              'bottomRightSweep',
              'Bottom Right',
              () => SnackToastKit.toast(
                'Bottom-right!',
                animationStyle: SnackToastAnimation.bottomRightSweep,
              ),
            ),
          ],
        ),
        _animFamily(
          icon: Icons.auto_awesome_rounded,
          label: 'Physics & Atmospheric',
          sublabel: 'Spring, blur, flip & radial ring',
          color: const Color(0xFFFFB300),
          items: [
            (
              'scaleSpring',
              'Scale Spring',
              () => SnackToastKit.toast(
                'Spring!',
                animationStyle: SnackToastAnimation.scaleSpring,
                position: SnackToastPosition.center,
              ),
            ),
            (
              'fade',
              'Fade',
              () => SnackToastKit.toast(
                'Fading...',
                animationStyle: SnackToastAnimation.fade,
              ),
            ),
            (
              'centerPop',
              'Center Pop',
              () => SnackToastKit.toast(
                'Focus pop!',
                animationStyle: SnackToastAnimation.centerPop,
                position: SnackToastPosition.center,
              ),
            ),
            (
              'flipReveal',
              'Flip Reveal',
              () => SnackToastKit.toast(
                'Flipped!',
                animationStyle: SnackToastAnimation.flipReveal,
                position: SnackToastPosition.center,
              ),
            ),
            (
              'radialExpansion',
              'Radial Expansion',
              () => SnackToastKit.toast(
                'Expanding!',
                animationStyle: SnackToastAnimation.radialExpansion,
                position: SnackToastPosition.center,
              ),
            ),
          ],
        ),
      ],
    );
  }

  Widget _animFamily({
    required IconData icon,
    required String label,
    required String sublabel,
    required Color color,
    required List<(String, String, VoidCallback)> items,
  }) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Container(
                width: 30,
                height: 30,
                decoration: BoxDecoration(
                  color: color.withValues(alpha: 0.14),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Icon(icon, color: color, size: 15),
              ),
              const SizedBox(width: 9),
              Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    label,
                    style: const TextStyle(
                      color: _C.textPrimary,
                      fontSize: 13,
                      fontWeight: FontWeight.w700,
                    ),
                  ),
                  Text(
                    sublabel,
                    style: const TextStyle(
                      color: _C.textSecondary,
                      fontSize: 11,
                    ),
                  ),
                ],
              ),
            ],
          ),
          const SizedBox(height: 9),
          Wrap(
            spacing: 7,
            runSpacing: 7,
            children: items.map((item) {
              final (enumName, btnLabel, onTap) = item;
              return GestureDetector(
                onTap: onTap,
                child: Container(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 11,
                    vertical: 8,
                  ),
                  decoration: BoxDecoration(
                    color: _C.surface,
                    borderRadius: BorderRadius.circular(9),
                    border: Border.all(color: _C.border),
                  ),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(
                        btnLabel,
                        style: const TextStyle(
                          color: _C.textPrimary,
                          fontSize: 12,
                          fontWeight: FontWeight.w600,
                        ),
                      ),
                      Text(
                        '.$enumName',
                        style: TextStyle(
                          color: color.withValues(alpha: 0.8),
                          fontSize: 10,
                          fontFamily: 'monospace',
                        ),
                      ),
                    ],
                  ),
                ),
              );
            }).toList(),
          ),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // ── NEW: Color & Style Customisation ────────────────────────────────────
  // ─────────────────────────────────────────────────────────────────────────

  Widget _buildColorCustomisationSection() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _sectionNote(
            'Text and background colors can be customised globally via '
            'SnackToastConfig or per-notification via toast() parameters. '
            'Role-specific colors (titleColor, messageColor) let you style '
            'title and body independently.',
          ),
          const SizedBox(height: 12),

          // 1. Custom background + foreground
          _fullBtn(
            label: 'Custom Background + Foreground Colors',
            icon: Icons.color_lens_rounded,
            color: const Color(0xFF9C27B0),
            onTap: () => SnackToastKit.toast(
              'Both colors overridden per-call.',
              title: 'Custom Colors',
              type: SnackToastType.custom,
              backgroundColor: const Color(0xFF1A0033),
              foregroundColor: const Color(0xFFE040FB),
              animationStyle: SnackToastAnimation.scaleSpring,
              position: SnackToastPosition.center,
            ),
          ),
          const SizedBox(height: 8),

          // 2. Role-specific colors (title vs message different colors)
          _fullBtn(
            label: 'Independent Title & Message Colors',
            icon: Icons.text_fields_rounded,
            color: const Color(0xFF00BFA5),
            onTap: () => SnackToastKit.toast(
              'This message is in amber — different from the title.',
              title: 'Teal Title',
              type: SnackToastType.info,
              // Title in white, message in amber — fully independent
              titleColor: Colors.white,
              messageColor: Colors.amber.shade300,
              visualStyle: SnackToastVisualStyle.standard,
            ),
          ),
          const SizedBox(height: 8),

          // 3. Custom TextStyle per role
          _fullBtn(
            label: 'Custom TextStyle Per Role',
            icon: Icons.format_size_rounded,
            color: const Color(0xFFFF6B6B),
            onTap: () => SnackToastKit.toast(
              'Message in italic 14px with wide letter spacing.',
              title: 'Bold 16px Title',
              type: SnackToastType.warning,
              // Completely custom typography per role
              titleStyle: const TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w900,
                letterSpacing: 0.5,
              ),
              messageStyle: const TextStyle(
                fontSize: 14,
                fontStyle: FontStyle.italic,
                letterSpacing: 1.2,
              ),
            ),
          ),
          const SizedBox(height: 8),

          // 4. Global config with role-specific colors
          _fullBtn(
            label: 'Global Config: titleColor + messageColor',
            icon: Icons.settings_rounded,
            color: _C.accent,
            onTap: () {
              // Temporarily configure role-specific colors globally,
              // then restore after 6 seconds.
              SnackToastKit.configure(
                SnackToastKit.config.copyWith(
                  titleColor: Colors.yellow.shade200,
                  messageColor: Colors.white70,
                ),
              );
              SnackToastKit.toast(
                'Message in white70 — set globally via SnackToastConfig.',
                title: 'Yellow Title',
                type: SnackToastType.success,
                duration: const Duration(seconds: 4),
                onDismissed: _syncConfig, // restore on dismiss
              );
            },
          ),
          const SizedBox(height: 8),

          // 5. Snackbar with role-specific colors
          _fullBtn(
            label: 'Snackbar with Role-Specific Colors',
            icon: Icons.call_to_action_rounded,
            color: _C.info,
            onTap: () => SnackToastKit.snackbar(
              'Syncing data in the background.',
              title: 'Sync Active',
              type: SnackToastType.info,
              // Per-call snackbar color overrides
              titleColor: Colors.lightBlue.shade100,
              messageColor: Colors.white60,
            ),
          ),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // ── NEW: Sizing Control ──────────────────────────────────────────────────
  // ─────────────────────────────────────────────────────────────────────────

  Widget _buildSizingSection() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _sectionNote(
            'By default wrapContent=true — height fits content automatically. '
            'Set wrapContent=false and provide fixedHeight/fixedWidth for '
            'uniform fixed-size notifications.',
          ),
          const SizedBox(height: 12),

          // wrapContent toggle
          Row(
            children: [
              const Text(
                'wrapContent:',
                style: TextStyle(color: _C.textSecondary, fontSize: 13),
              ),
              const SizedBox(width: 10),
              GestureDetector(
                onTap: () => setState(() => _wrapContent = !_wrapContent),
                child: AnimatedContainer(
                  duration: const Duration(milliseconds: 200),
                  padding: const EdgeInsets.symmetric(
                    horizontal: 14,
                    vertical: 6,
                  ),
                  decoration: BoxDecoration(
                    color: _wrapContent ? _C.accentSoft : _C.surface,
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(
                      color: _wrapContent ? _C.accent : _C.border,
                    ),
                  ),
                  child: Text(
                    _wrapContent ? 'true  (default)' : 'false',
                    style: TextStyle(
                      color: _wrapContent ? _C.accent : _C.textSecondary,
                      fontSize: 12,
                      fontFamily: 'monospace',
                    ),
                  ),
                ),
              ),
            ],
          ),

          // Fixed size sliders — visible only when wrapContent=false
          if (!_wrapContent) ...[
            const SizedBox(height: 12),
            _sliderRow('fixedHeight', _fixedHeight, 48, 120, (v) {
              setState(() => _fixedHeight = v);
            }),
            const SizedBox(height: 6),
            _sliderRow('fixedWidth', _fixedWidth, 180, 400, (v) {
              setState(() => _fixedWidth = v);
            }),
          ],

          const SizedBox(height: 12),

          // Fire with current sizing settings
          _fullBtn(
            label: _wrapContent
                ? 'Fire Wrap-Content Toast (default)'
                : 'Fire Fixed ${_fixedHeight.round()}×${_fixedWidth.round()} Toast',
            icon: Icons.straighten_rounded,
            color: const Color(0xFF00BFA5),
            onTap: () => SnackToastKit.toast(
              _wrapContent
                  ? 'Height wraps to fit this content automatically.'
                  : 'Fixed size: ${_fixedHeight.round()}px × ${_fixedWidth.round()}px',
              title: _wrapContent ? 'Wrap Content' : 'Fixed Size',
              type: SnackToastType.info,
              wrapContent: _wrapContent,
              fixedHeight: _wrapContent ? null : _fixedHeight,
              fixedWidth: _wrapContent ? null : _fixedWidth,
            ),
          ),
          const SizedBox(height: 8),

          // minWidth demo
          _fullBtn(
            label: 'Min Width (prevents narrow cards)',
            icon: Icons.width_normal_rounded,
            color: const Color(0xFFFFB300),
            onTap: () => SnackToastKit.toast(
              'OK',
              type: SnackToastType.success,
              // Without minWidth, a 2-char message would produce a tiny card.
              minWidth: 220.0,
            ),
          ),
        ],
      ),
    );
  }

  Widget _sliderRow(
    String label,
    double value,
    double min,
    double max,
    ValueChanged<double> onChange,
  ) {
    return Row(
      children: [
        SizedBox(
          width: 90,
          child: Text(
            '$label:',
            style: const TextStyle(
              color: _C.textSecondary,
              fontSize: 12,
              fontFamily: 'monospace',
            ),
          ),
        ),
        Expanded(
          child: Slider(
            value: value,
            min: min,
            max: max,
            activeColor: _C.accent,
            inactiveColor: _C.border,
            onChanged: onChange,
          ),
        ),
        Text(
          '${value.round()}px',
          style: const TextStyle(
            color: _C.textMuted,
            fontSize: 11,
            fontFamily: 'monospace',
          ),
        ),
      ],
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // ── NEW: Decoration Override ─────────────────────────────────────────────
  // ─────────────────────────────────────────────────────────────────────────

  Widget _buildDecorationOverrideSection() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _sectionNote(
            'decorationOverride replaces the computed BoxDecoration entirely, '
            'giving you full control over gradients, borders, images, and '
            'shapes — while the icon, text, and progress bar are still rendered '
            'inside automatically.',
          ),
          const SizedBox(height: 12),

          // Neon border override
          _fullBtn(
            label: 'Neon Border + Dark Background',
            icon: Icons.auto_fix_high_rounded,
            color: const Color(0xFF00E5FF),
            onTap: () => SnackToastKit.toast(
              'Custom BoxDecoration — full control.',
              title: 'Decoration Override',
              type: SnackToastType.info,
              decorationOverride: BoxDecoration(
                color: const Color(0xFF001020),
                borderRadius: BorderRadius.circular(16),
                border: Border.all(color: const Color(0xFF00E5FF), width: 1.5),
                boxShadow: [
                  BoxShadow(
                    color: const Color(0xFF00E5FF).withValues(alpha: 0.35),
                    blurRadius: 20,
                    offset: const Offset(0, 4),
                  ),
                ],
              ),
              foregroundColor: const Color(0xFF00E5FF),
            ),
          ),
          const SizedBox(height: 8),

          // Purple-to-pink gradient override
          _fullBtn(
            label: 'Custom Gradient Override',
            icon: Icons.gradient_rounded,
            color: const Color(0xFFFF4081),
            onTap: () => SnackToastKit.toast(
              'Gradient defined entirely by decorationOverride.',
              title: 'Custom Gradient',
              type: SnackToastType.success,
              decorationOverride: BoxDecoration(
                borderRadius: BorderRadius.circular(20),
                gradient: const LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                  colors: [
                    Color(0xFF6A0DAD),
                    Color(0xFFFF4081),
                    Color(0xFFFF6E40),
                  ],
                  stops: [0.0, 0.55, 1.0],
                ),
                boxShadow: [
                  BoxShadow(
                    color: const Color(0xFFFF4081).withValues(alpha: 0.40),
                    blurRadius: 28,
                    offset: const Offset(0, 8),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Snackbars
  // ─────────────────────────────────────────────────────────────────────────

  Widget _buildSnackbarSection() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Column(
        children: [
          Row(
            children: [
              _typeBtn(
                'Success',
                _C.success,
                () => SnackToastKit.snackbarSuccess('Upload complete!'),
              ),
              const SizedBox(width: 7),
              _typeBtn(
                'Error',
                _C.error,
                () => SnackToastKit.snackbarError('Connection lost'),
              ),
              const SizedBox(width: 7),
              _typeBtn(
                'Warning',
                _C.warning,
                () => SnackToastKit.snackbarWarning('Storage full'),
              ),
              const SizedBox(width: 7),
              _typeBtn(
                'Info',
                _C.info,
                () => SnackToastKit.snackbarInfo('Update available'),
              ),
            ],
          ),
          const SizedBox(height: 8),
          _fullBtn(
            label: 'Undo Action Snackbar',
            icon: Icons.undo_rounded,
            color: _C.accent,
            onTap: () => SnackToastKit.snackbar(
              'File deleted permanently.',
              title: 'Deleted',
              type: SnackToastType.warning,
              action: SnackBarAction(
                label: 'UNDO',
                textColor: Colors.amber,
                onPressed: () => SnackToastKit.success('File restored!'),
              ),
            ),
          ),
          const SizedBox(height: 8),
          _fullBtn(
            label: 'Minimal Style Snackbar',
            icon: Icons.view_sidebar_rounded,
            color: _C.textSecondary,
            onTap: () => SnackToastKit.snackbar(
              'Syncing in the background.',
              title: 'Sync',
              type: SnackToastType.info,
              visualStyle: SnackToastVisualStyle.minimal,
            ),
          ),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Advanced
  // ─────────────────────────────────────────────────────────────────────────

  Widget _buildAdvancedSection() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Column(
        children: [
          _fullBtn(
            label: 'Custom Widget Toast',
            icon: Icons.widgets_rounded,
            color: const Color(0xFF9C27B0),
            onTap: _showCustomWidgetToast,
          ),
          const SizedBox(height: 8),
          _fullBtn(
            label: 'Queued Burst (5 toasts)',
            icon: Icons.queue_rounded,
            color: const Color(0xFF00BFA5),
            onTap: _showQueuedBurst,
          ),
          const SizedBox(height: 8),
          _fullBtn(
            label: 'Instant Unqueued Toast',
            icon: Icons.flash_on_rounded,
            color: const Color(0xFFFFB300),
            onTap: () => SnackToastKit.toast(
              'Bypasses queue — displayed immediately.',
              type: SnackToastType.warning,
              queued: false,
              animationStyle: SnackToastAnimation.flipReveal,
              position: SnackToastPosition.center,
            ),
          ),
          const SizedBox(height: 16),
          SizedBox(
            width: double.infinity,
            child: OutlinedButton.icon(
              onPressed: SnackToastKit.dismissAll,
              icon: const Icon(Icons.clear_all_rounded, size: 18),
              label: const Text('Dismiss All Notifications'),
              style: OutlinedButton.styleFrom(
                foregroundColor: _C.textSecondary,
                side: const BorderSide(color: _C.border),
                padding: const EdgeInsets.symmetric(vertical: 13),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Advanced callbacks
  // ─────────────────────────────────────────────────────────────────────────

  void _showCustomWidgetToast() {
    SnackToastKit.toast(
      '',
      position: SnackToastPosition.bottom,
      customWidget: Container(
        margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 48),
        padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
        decoration: BoxDecoration(
          gradient: const LinearGradient(
            colors: [Color(0xFF6C63FF), Color(0xFF9C27B0)],
          ),
          borderRadius: BorderRadius.circular(16),
          boxShadow: [
            BoxShadow(
              color: const Color(0xFF6C63FF).withValues(alpha: 0.45),
              blurRadius: 24,
              offset: const Offset(0, 8),
            ),
          ],
        ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              width: 38,
              height: 38,
              decoration: BoxDecoration(
                color: Colors.white.withValues(alpha: 0.2),
                shape: BoxShape.circle,
              ),
              child: const Icon(
                Icons.star_rounded,
                color: Colors.amber,
                size: 20,
              ),
            ),
            const SizedBox(width: 12),
            const Flexible(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Level Up!',
                    style: TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.w800,
                      fontSize: 14,
                    ),
                  ),
                  Text(
                    'You reached Level 10!',
                    style: TextStyle(color: Colors.white70, fontSize: 12.5),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  void _showQueuedBurst() {
    const messages = [
      ('Step 1 complete', SnackToastType.success),
      ('Step 2 complete', SnackToastType.success),
      ('Step 3 complete', SnackToastType.success),
      ('Warning on step 4', SnackToastType.warning),
      ('All steps done!', SnackToastType.info),
    ];
    for (final (msg, type) in messages) {
      SnackToastKit.toast(msg, type: type, queued: true);
    }
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Reusable widgets / helpers
  // ─────────────────────────────────────────────────────────────────────────

  Widget _divider(String label) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 24, 16, 12),
      child: Row(
        children: [
          Text(
            label,
            style: const TextStyle(
              color: _C.textMuted,
              fontSize: 10.5,
              fontWeight: FontWeight.w700,
              letterSpacing: 1.6,
            ),
          ),
          const SizedBox(width: 12),
          const Expanded(child: Divider(color: _C.border, height: 1)),
        ],
      ),
    );
  }

  Widget _fullBtn({
    required String label,
    required IconData icon,
    required Color color,
    required VoidCallback onTap,
  }) {
    return SizedBox(
      width: double.infinity,
      child: ElevatedButton.icon(
        onPressed: onTap,
        icon: Icon(icon, size: 15),
        label: Text(label),
        style: ElevatedButton.styleFrom(
          backgroundColor: color.withValues(alpha: 0.12),
          foregroundColor: color,
          elevation: 0,
          padding: const EdgeInsets.symmetric(vertical: 13),
          textStyle: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(12),
            side: BorderSide(color: color.withValues(alpha: 0.3)),
          ),
        ),
      ),
    );
  }

  Widget _typeBtn(String label, Color color, VoidCallback onTap) {
    return Expanded(
      child: ElevatedButton(
        onPressed: onTap,
        style: ElevatedButton.styleFrom(
          backgroundColor: color.withValues(alpha: 0.12),
          foregroundColor: color,
          elevation: 0,
          padding: const EdgeInsets.symmetric(vertical: 11),
          textStyle: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
            side: BorderSide(color: color.withValues(alpha: 0.3)),
          ),
        ),
        child: Text(label),
      ),
    );
  }

  Widget _cfgLabel(String text) => Text(
    text,
    style: const TextStyle(
      color: _C.textSecondary,
      fontSize: 11,
      fontWeight: FontWeight.w600,
      letterSpacing: 0.8,
    ),
  );

  Widget _cfgToggle(String label, bool value, ValueChanged<bool> onChange) {
    return Expanded(
      child: GestureDetector(
        onTap: () => onChange(!value),
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 200),
          padding: const EdgeInsets.symmetric(vertical: 8),
          decoration: BoxDecoration(
            color: value ? _C.accentSoft : _C.surface,
            borderRadius: BorderRadius.circular(8),
            border: Border.all(color: value ? _C.accent : _C.border),
          ),
          child: Column(
            children: [
              Icon(
                value ? Icons.toggle_on_rounded : Icons.toggle_off_rounded,
                color: value ? _C.accent : _C.textMuted,
                size: 18,
              ),
              const SizedBox(height: 3),
              Text(
                label,
                textAlign: TextAlign.center,
                style: TextStyle(
                  color: value ? _C.accent : _C.textSecondary,
                  fontSize: 10.5,
                  fontWeight: value ? FontWeight.w700 : FontWeight.w400,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _sectionNote(String text) {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: _C.accentSoft.withValues(alpha: 0.35),
        borderRadius: BorderRadius.circular(10),
        border: Border.all(color: _C.accentSoft),
      ),
      child: Text(
        text,
        style: const TextStyle(
          color: _C.textSecondary,
          fontSize: 12,
          height: 1.5,
        ),
      ),
    );
  }

  Color _typeColor(SnackToastType type) => switch (type) {
    SnackToastType.success => _C.success,
    SnackToastType.error => _C.error,
    SnackToastType.warning => _C.warning,
    SnackToastType.info => _C.info,
    SnackToastType.custom => _C.accent,
  };

  String _typeMessage(SnackToastType type) => switch (type) {
    SnackToastType.success => 'Profile saved successfully!',
    SnackToastType.error => 'Failed to save. Check your connection.',
    SnackToastType.warning => 'Low battery — plug in soon.',
    SnackToastType.info => 'New message received.',
    SnackToastType.custom => 'Custom notification.',
  };
}
3
likes
150
points
117
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A production-ready Flutter package for customizable Toast messages and Snackbars with queue management, theming, and zero-dependency design.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on snacktoast