diff --git a/flutter_app/lib/route_transitions.dart b/flutter_app/lib/route_transitions.dart index 43aac46..18a00bd 100644 --- a/flutter_app/lib/route_transitions.dart +++ b/flutter_app/lib/route_transitions.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:flutter/material.dart'; class FadeSlidePageRoute extends PageRouteBuilder { @@ -34,3 +35,154 @@ class FadeSlidePageRoute extends PageRouteBuilder { reverseTransitionDuration: const Duration(milliseconds: 200), ); } + +void showTopAlert( + BuildContext context, + String message, { + bool isError = false, + IconData? icon, +}) { + final overlayState = Overlay.of(context); + late OverlayEntry overlayEntry; + + overlayEntry = OverlayEntry( + builder: (context) => _TopAlertWidget( + message: message, + isError: isError, + icon: icon, + onDismiss: () { + overlayEntry.remove(); + }, + ), + ); + + overlayState.insert(overlayEntry); +} + +class _TopAlertWidget extends StatefulWidget { + final String message; + final bool isError; + final IconData? icon; + final VoidCallback onDismiss; + + const _TopAlertWidget({ + required this.message, + required this.isError, + this.icon, + required this.onDismiss, + }); + + @override + State<_TopAlertWidget> createState() => _TopAlertWidgetState(); +} + +class _TopAlertWidgetState extends State<_TopAlertWidget> with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _slideAnimation; + late Animation _opacityAnimation; + Timer? _dismissTimer; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 400), + ); + + _slideAnimation = CurvedAnimation( + parent: _controller, + curve: Curves.easeOutBack, + ); + + _opacityAnimation = CurvedAnimation( + parent: _controller, + curve: Curves.easeIn, + ); + + _controller.forward(); + + _dismissTimer = Timer(const Duration(milliseconds: 2800), () { + if (mounted) { + _controller.reverse().then((_) { + widget.onDismiss(); + }); + } + }); + } + + @override + void dispose() { + _dismissTimer?.cancel(); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final mediaQuery = MediaQuery.of(context); + final topPadding = mediaQuery.padding.top + 16.0; + + IconData alertIcon = widget.icon ?? + (widget.isError ? Icons.error_outline : Icons.check_circle_outline); + Color backgroundColor = widget.isError ? const Color(0xFFBA1A20) : const Color(0xFF2E7D32); + + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Positioned( + top: topPadding * _slideAnimation.value - 80.0 * (1.0 - _slideAnimation.value), + left: 16.0, + right: 16.0, + child: Opacity( + opacity: _opacityAnimation.value, + child: Material( + color: Colors.transparent, + child: Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 450), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.12), + blurRadius: 16, + offset: const Offset(0, 6), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + alertIcon, + color: Colors.white, + size: 20, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + widget.message, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/flutter_app/lib/screens/billing_dashboard_screen.dart b/flutter_app/lib/screens/billing_dashboard_screen.dart index 3adaba9..2067383 100644 --- a/flutter_app/lib/screens/billing_dashboard_screen.dart +++ b/flutter_app/lib/screens/billing_dashboard_screen.dart @@ -1,5 +1,6 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import '../route_transitions.dart'; class ProductItem { final String id; @@ -160,13 +161,7 @@ class _BillingDashboardScreenState extends State { } void _showErrorSnackBar(String message) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - backgroundColor: Colors.red[800], - behavior: SnackBarBehavior.floating, - ), - ); + showTopAlert(context, message, isError: true); } double get _subtotal { @@ -944,12 +939,10 @@ class _BillingDashboardScreenState extends State { const SizedBox(height: 16), ElevatedButton.icon( onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('๐Ÿงพ Thermal Printed: $_invoiceNumber successfully!'), - backgroundColor: const Color(0xFF10B981), - behavior: SnackBarBehavior.floating, - ), + showTopAlert( + context, + 'Thermal Printed: $_invoiceNumber successfully!', + icon: Icons.print, ); }, icon: const Icon(Icons.print, size: 16), diff --git a/flutter_app/lib/screens/business_setup_screen.dart b/flutter_app/lib/screens/business_setup_screen.dart index 12da895..5fb5ef4 100644 --- a/flutter_app/lib/screens/business_setup_screen.dart +++ b/flutter_app/lib/screens/business_setup_screen.dart @@ -90,29 +90,25 @@ class _BusinessSetupScreenState extends State { setState(() { _uploadedDocs[docName] = file.name; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('โœ… Successfully loaded $docName!'), - backgroundColor: Colors.green, - behavior: SnackBarBehavior.floating, - ), + showTopAlert( + context, + 'Successfully loaded $docName!', + icon: Icons.check_circle_outline, ); } } catch (e) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Failed to pick image: $e'), - behavior: SnackBarBehavior.floating, - ), + showTopAlert( + context, + 'Failed to pick image: $e', + isError: true, ); } } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Permission denied. Cannot load document image.'), - behavior: SnackBarBehavior.floating, - ), + showTopAlert( + context, + 'Permission denied. Cannot load document image.', + isError: true, ); } } @@ -298,11 +294,10 @@ class _BusinessSetupScreenState extends State { setState(() { _uploadedDocs.remove(docName); }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('$docName attachment removed.'), - behavior: SnackBarBehavior.floating, - ), + showTopAlert( + context, + '$docName attachment removed.', + icon: Icons.delete_outline, ); }, ), @@ -590,12 +585,10 @@ class _BusinessSetupScreenState extends State { const SizedBox(width: 12), ElevatedButton( onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('GSTIN Verified Successfully!'), - backgroundColor: Colors.green, - behavior: SnackBarBehavior.floating, - ), + showTopAlert( + context, + 'GSTIN Verified Successfully!', + icon: Icons.verified_outlined, ); }, style: ElevatedButton.styleFrom( @@ -628,12 +621,10 @@ class _BusinessSetupScreenState extends State { const SizedBox(height: 8), InkWell( onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('๐Ÿ“ Location detected: Sector 62, Noida, UP'), - backgroundColor: Colors.green, - behavior: SnackBarBehavior.floating, - ), + showTopAlert( + context, + 'Location detected: Sector 62, Noida, UP', + icon: Icons.my_location_outlined, ); }, borderRadius: BorderRadius.circular(16), diff --git a/flutter_app/lib/screens/mobile_login_screen.dart b/flutter_app/lib/screens/mobile_login_screen.dart index 8d80b39..5e14203 100644 --- a/flutter_app/lib/screens/mobile_login_screen.dart +++ b/flutter_app/lib/screens/mobile_login_screen.dart @@ -84,11 +84,10 @@ class _MobileLoginScreenState extends State { if (status.isGranted || kIsWeb) { _showContactPicker(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Contacts permission was denied. Please enter the number manually or enable it in Settings.'), - behavior: SnackBarBehavior.floating, - ), + showTopAlert( + context, + 'Contacts permission was denied. Please enter the number manually or enable it in Settings.', + isError: true, ); } } @@ -402,12 +401,10 @@ class _MobileLoginScreenState extends State { setState(() { _isValid = true; }); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('โšก Phone verified automatically with Truecaller! Joining PozoApp...'), - backgroundColor: Color(0xFF005FAF), - behavior: SnackBarBehavior.floating, - ), + showTopAlert( + context, + 'Phone verified automatically with Truecaller! Joining PozoApp...', + icon: Icons.verified_user_outlined, ); Navigator.push( context, @@ -610,11 +607,10 @@ class _MobileLoginScreenState extends State { SizedBox(height: gap / 2), OutlinedButton( onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('๐Ÿ’ฌ Connecting with Pozo Support... Please wait.'), - behavior: SnackBarBehavior.floating, - ), + showTopAlert( + context, + 'Connecting with Pozo Support... Please wait.', + icon: Icons.support_agent_outlined, ); }, style: OutlinedButton.styleFrom( diff --git a/flutter_app/lib/screens/setup_complete_screen.dart b/flutter_app/lib/screens/setup_complete_screen.dart index 602e262..039365a 100644 --- a/flutter_app/lib/screens/setup_complete_screen.dart +++ b/flutter_app/lib/screens/setup_complete_screen.dart @@ -293,13 +293,10 @@ class SetupCompleteScreen extends StatelessWidget { // Primary Launcher CTA ElevatedButton( onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('โšก Launching Register Dashboard... Enjoy billing!'), - behavior: SnackBarBehavior.floating, - backgroundColor: Colors.green, - duration: Duration(milliseconds: 1500), - ), + showTopAlert( + context, + 'Launching Register Dashboard... Enjoy billing!', + icon: Icons.rocket_launch_outlined, ); Navigator.push( context,