import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import '../../constant/colors.dart'; // ───────────────────────────────────────────────────────────────────────────── // Snackbar variant definition // ───────────────────────────────────────────────────────────────────────────── enum _SnackVariant { success, error, info, warning } extension _VariantProps on _SnackVariant { Color get baseColor => switch (this) { _SnackVariant.success => const Color(0xFF1A9E5C), _SnackVariant.error => const Color(0xFFD93025), _SnackVariant.info => const Color(0xFF1A73E8), _SnackVariant.warning => const Color(0xFFF29900), }; Color get surfaceColor => switch (this) { _SnackVariant.success => const Color(0xFFF0FBF5), _SnackVariant.error => const Color(0xFFFEF2F1), _SnackVariant.info => const Color(0xFFF0F6FF), _SnackVariant.warning => const Color(0xFFFFF8E6), }; IconData get icon => switch (this) { _SnackVariant.success => Icons.check_circle_rounded, _SnackVariant.error => Icons.error_rounded, _SnackVariant.info => Icons.info_rounded, _SnackVariant.warning => Icons.warning_amber_rounded, }; String get label => switch (this) { _SnackVariant.success => 'Success', _SnackVariant.error => 'Error', _SnackVariant.info => 'Info', _SnackVariant.warning => 'Warning', }; HapticFeedbackType get haptic => switch (this) { _SnackVariant.error => HapticFeedbackType.medium, _SnackVariant.warning => HapticFeedbackType.medium, _SnackVariant.success => HapticFeedbackType.light, _SnackVariant.info => HapticFeedbackType.selection, }; } enum HapticFeedbackType { light, medium, selection } // ───────────────────────────────────────────────────────────────────────────── // Core snackbar widget // ───────────────────────────────────────────────────────────────────────────── class _SnackContent extends StatefulWidget { final String message; final _SnackVariant variant; const _SnackContent({required this.message, required this.variant}); @override State<_SnackContent> createState() => _SnackContentState(); } class _SnackContentState extends State<_SnackContent> with TickerProviderStateMixin { late final AnimationController _ctrl; late final Animation _scaleAnim; late final Animation _progressAnim; static const Duration _displayDuration = Duration(seconds: 4); @override void initState() { super.initState(); _ctrl = AnimationController(vsync: this, duration: _displayDuration); _scaleAnim = CurvedAnimation( parent: AnimationController( vsync: this, duration: const Duration(milliseconds: 500), )..forward(), curve: Curves.elasticOut, ); _progressAnim = Tween(begin: 1.0, end: 0.0).animate( CurvedAnimation(parent: _ctrl, curve: Curves.linear), ); _ctrl.forward(); } @override void dispose() { _ctrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final v = widget.variant; final accent = v.baseColor; final surface = v.surfaceColor; return Container( margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: surface, borderRadius: BorderRadius.circular(18), border: Border.all(color: accent.withOpacity(0.18), width: 1.2), boxShadow: [ BoxShadow( color: accent.withOpacity(0.12), blurRadius: 20, spreadRadius: -2, offset: const Offset(0, 6), ), BoxShadow( color: Colors.black.withOpacity(0.06), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(18), child: Column( mainAxisSize: MainAxisSize.min, children: [ // ── Main row ────────────────────────────────────────────────── Padding( padding: const EdgeInsets.fromLTRB(14, 14, 10, 12), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Animated icon badge ScaleTransition( scale: _scaleAnim, child: Container( width: 40, height: 40, decoration: BoxDecoration( color: accent.withOpacity(0.12), shape: BoxShape.circle, ), child: Icon(v.icon, color: accent, size: 22), ), ), const SizedBox(width: 12), // Text content Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( v.label.tr, style: TextStyle( color: accent, fontSize: 13, fontWeight: FontWeight.w700, letterSpacing: 0.2, ), ), const SizedBox(height: 3), Text( widget.message, style: TextStyle( color: Colors.grey[800], fontSize: 13.5, height: 1.4, fontWeight: FontWeight.w400, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ), // Close button GestureDetector( onTap: () { HapticFeedback.lightImpact(); Get.closeCurrentSnackbar(); }, child: Container( width: 30, height: 30, margin: const EdgeInsets.only(left: 6), decoration: BoxDecoration( color: Colors.grey.withOpacity(0.1), shape: BoxShape.circle, ), child: Icon( Icons.close_rounded, size: 16, color: Colors.grey[500], ), ), ), ], ), ), // ── Thin progress strip ─────────────────────────────────────── AnimatedBuilder( animation: _progressAnim, builder: (_, __) => Stack( children: [ // Track Container( height: 3, color: accent.withOpacity(0.08), ), // Fill FractionallySizedBox( widthFactor: _progressAnim.value, child: Container( height: 3, decoration: BoxDecoration( color: accent.withOpacity(0.45), borderRadius: const BorderRadius.only( topRight: Radius.circular(4), bottomRight: Radius.circular(4), ), ), ), ), ], ), ), ], ), ), ); } } // ───────────────────────────────────────────────────────────────────────────── // Internal dispatcher — single source of truth // ───────────────────────────────────────────────────────────────────────────── SnackbarController _show(_SnackVariant variant, String message) { // Dismiss any existing snackbar first (no stacking) if (Get.isSnackbarOpen) Get.closeCurrentSnackbar(); switch (variant.haptic) { case HapticFeedbackType.light: HapticFeedback.lightImpact(); case HapticFeedbackType.medium: HapticFeedback.mediumImpact(); case HapticFeedbackType.selection: HapticFeedback.selectionClick(); } return Get.snackbar( '', '', snackPosition: SnackPosition.TOP, backgroundColor: Colors.transparent, margin: EdgeInsets.zero, padding: EdgeInsets.zero, duration: const Duration(seconds: 4), animationDuration: const Duration(milliseconds: 380), barBlur: 0, overlayBlur: 0, overlayColor: Colors.transparent, isDismissible: true, dismissDirection: DismissDirection.up, forwardAnimationCurve: Curves.easeOutCubic, reverseAnimationCurve: Curves.easeInCubic, snackStyle: SnackStyle.FLOATING, userInputForm: Form( child: _SnackContent(message: message, variant: variant), ), ); } // ───────────────────────────────────────────────────────────────────────────── // Public API — drop-in replacements for the old functions // ───────────────────────────────────────────────────────────────────────────── SnackbarController mySnackbarSuccess(String message) => _show(_SnackVariant.success, message); SnackbarController mySnackeBarError(String message) => _show(_SnackVariant.error, message); SnackbarController mySnackbarInfo(String message) => _show(_SnackVariant.info, message); SnackbarController mySnackbarWarning(String message) => _show(_SnackVariant.warning, message);