import 'package:wanisdriver/utils/colors.dart'; import 'package:wanisdriver/widgets/custom_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; import 'package:wanisdriver/models/notification_model.dart' as general; import '../Provider/notification_provider.dart'; import '../config/custom_loader.dart'; import '../config/enum/action_enum.dart'; import '../config/enum/notification_status_enum.dart'; import '../Provider/language_provider.dart'; import '../config/language/language_enum.dart'; class NotificationScreen extends StatefulWidget { @override _NotificationScreenState createState() => _NotificationScreenState(); } class _NotificationScreenState extends State with TickerProviderStateMixin { late NotificationProvider _notificationProvider; final ScrollController _scrollController = ScrollController(); late AnimationController _fadeController; late AnimationController _slideController; late Animation _fadeAnimation; late Animation _slideAnimation; @override void initState() { super.initState(); _notificationProvider = Provider.of(context, listen: false); // Initialize animations _fadeController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _slideController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _fadeController, curve: Curves.easeInOut, )); _slideAnimation = Tween( begin: const Offset(0, 0.3), end: Offset.zero, ).animate(CurvedAnimation( parent: _slideController, curve: Curves.easeOutCubic, )); // Mark all notifications as read and fetch initial notifications WidgetsBinding.instance.addPostFrameCallback((_) async { await _notificationProvider.markAllAsRead(); await _notificationProvider.fetchNotifications(); _fadeController.forward(); _slideController.forward(); }); // Listen to scroll events for infinite scrolling _scrollController.addListener(() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent * 0.9 && !_notificationProvider.isLoading && _notificationProvider.hasMore) { _notificationProvider.loadMoreNotifications(); } }); } @override void dispose() { _scrollController.dispose(); _fadeController.dispose(); _slideController.dispose(); super.dispose(); } String _getTranslatedContent( general.NotificationModel notification, BuildContext context) { final languageProvider = Provider.of(context, listen: false); String content = notification.content ?? ''; String contentNormalized = content.trim().toLowerCase(); // Check for known templates and interpolate {amount} if (contentNormalized .contains('you have completed a ride. the total fee is')) { // Extract the amount using RegExp final match = RegExp(r'the total fee is\s*:?\s*(\d+(?:\.\d+)?)', caseSensitive: false) .firstMatch(content); String amount = match != null ? match.group(1) ?? '' : ''; String template = languageProvider .translate('You have completed a ride. The total fee is : {amount}'); return template.replaceAll('{amount}', amount); } // Direct match for other templates (robust to case and whitespace) for (final key in [ 'You have requested a new ride', 'You have scheduled a new ride', 'You have an update', ]) { if (contentNormalized.contains(key.toLowerCase())) { return languageProvider.translate(key); } } // Fallback: return the original content return content; } @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; final languageProvider = context.watch(); bool isArabic = languageProvider.currentLangCode == Language.ar; return Scaffold( body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox( height: 30, ), Txt( text: 'Notifications', style: TextStyle( fontSize: 18, fontFamily: isArabic ? "Readex_Pro" : 'Inter', fontWeight: FontWeight.w600, height: 1.33, ), textAlign: TextAlign.start, ), Expanded( child: Consumer( builder: (context, provider, child) { if (provider.isLoading && provider.notifications.isEmpty) { return const Center( child: CustomLoader( logoSize: 60.0, dotSize: 10.0, ), ); } List newNotifications = []; List historyNotifications = []; if (provider.notifications.isNotEmpty) { newNotifications.add(provider.notifications.first); if (provider.notifications.length > 1) { historyNotifications = provider.notifications.sublist(1); } } if (!provider.isLoading && provider.notifications.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.1), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: SvgPicture.asset( 'assets/icons/nodatafind.svg', width: screenWidth * 0.3, ), ), ], ), ); } return RefreshIndicator( onRefresh: () async { await provider.refreshNotifications(); }, color: Theme.of(context).brightness == Brightness.dark ? AppColors.darkPrimaryColor : AppColors.primaryColor, backgroundColor: Colors.white, child: ListView( controller: _scrollController, physics: const BouncingScrollPhysics(), children: [ // New Section const SizedBox(height: 16), ...historyNotifications .asMap() .entries .map((entry) { final index = entry.key; final notification = entry.value; return _buildAnimatedNotificationCard( notification, index + newNotifications.length, false, ); }).toList(), // Loading Indicator at the bottom if (provider.isLoading && provider.notifications.isNotEmpty) Padding( padding: const EdgeInsets.all(24), child: Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( Theme.of(context).brightness == Brightness.dark ? AppColors.darkPrimaryColor : AppColors.primaryColor, ), ), ), ), // Bottom spacing const SizedBox(height: 20), ], ), ); }, ), ), ], ), ), ); } Widget _buildAnimatedNotificationCard( general.NotificationModel notification, int index, bool isNew, ) { return TweenAnimationBuilder( duration: Duration(milliseconds: 300 + (index * 100)), tween: Tween(begin: 0.0, end: 1.0), builder: (context, value, child) { return Transform.translate( offset: Offset(0, (1 - value) * 50), child: Opacity( opacity: value, child: Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Theme.of(context).brightness == Brightness.dark ? AppColors.darkCardColor : Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.1), blurRadius: 15, offset: const Offset(0, 5), ), ], border: Border.all( color: Theme.of(context).brightness == Brightness.dark ? AppColors.whiteCardColor.withOpacity(0.3) : AppColors.darkCardColor.withOpacity(0.3)) , ), child: EnhancedNotificationCard( icon: _getIconFromSubject(notification.subject), iconColor: _getIconColorFromSubject(notification.subject), title: _getReadableSubject(notification.subject), description: _getTranslatedContent(notification, context), imageUrl: _getImageUrl(notification.action), isNew: isNew, ), ), ), ); }, ); } /// Helper method to get icon based on subject IconData _getIconFromSubject(String subject) { switch (subject) { case 'NEW_RIDE_REQUESTED': return Icons.directions_car; case 'COMPLETED_RIDE': return Icons.check_circle; case 'CLIENT_CANCEL_RIDE': return Icons.cancel; case 'COMPLETE_INSTANT_RIDE': return Icons.flash_on; case 'IDLE': return Icons.pause_circle; case 'NEW_INSTANT_RIDE': return Icons.bolt; case 'NEW_SCHEDULE_RIDE': return Icons.schedule; case 'TIME_OUT': return Icons.timer_off; case 'UPDATE_SCHEDULE_RIDE': return Icons.update; default: return Icons.notifications_none; } } /// Helper method to get icon color based on subject Color _getIconColorFromSubject(String subject) { switch (subject) { case 'NEW_RIDE_REQUESTED': return Colors.blue; case 'COMPLETED_RIDE': return Colors.green; case 'CLIENT_CANCEL_RIDE': return Colors.red; case 'COMPLETE_INSTANT_RIDE': return Colors.purple; case 'IDLE': return Color(0xffFF6A2F); case 'NEW_INSTANT_RIDE': return Colors.orange; case 'NEW_SCHEDULE_RIDE': return Colors.indigo; case 'TIME_OUT': return Colors.amber; case 'UPDATE_SCHEDULE_RIDE': return Colors.teal; default: return Color(0xffFF6A2F); } } /// Helper method to get image URL based on action String? _getImageUrl(ActionEnum action) { if (action == ActionEnum.NEW_INSTANT_RIDE) { return 'assets/images/logo.png'; } return null; } /// Helper method to convert subject enum to readable text String _getReadableSubject(String subject) { switch (subject) { case 'NEW_RIDE_REQUESTED': return 'New ride requested'; case 'COMPLETED_RIDE': return 'Completed ride'; case 'CLIENT_CANCEL_RIDE': return 'Client cancel ride'; case 'COMPLETE_INSTANT_RIDE': return 'Complete instant ride'; case 'IDLE': return 'Idle'; case 'NEW_INSTANT_RIDE': return 'New instant ride'; case 'NEW_SCHEDULE_RIDE': return 'New schedule ride'; case 'TIME_OUT': return 'Time out'; case 'UPDATE_SCHEDULE_RIDE': return 'Update schedule ride'; default: // Fallback: convert any enum-like string to readable format return subject .split('_') .map((word) => word.isEmpty ? '' : word[0].toUpperCase() + word.substring(1).toLowerCase()) .join(' '); } } } // Enhanced NotificationCard widget class EnhancedNotificationCard extends StatelessWidget { final IconData icon; final Color iconColor; final String title; final String description; final String? imageUrl; final bool isNew; const EnhancedNotificationCard({ Key? key, required this.icon, required this.iconColor, required this.title, required this.description, this.imageUrl, required this.isNew, }) : super(key: key); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(20), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Icon container Container( width: 50, height: 50, decoration: BoxDecoration( color:Theme.of(context).brightness == Brightness.dark ? AppColors.whiteCardColor : iconColor.withOpacity(0.1), borderRadius: BorderRadius.circular(30), ), child: Icon( icon, color: Color(0xff000000), size: 24, ), ), const SizedBox(width: 16), // Content Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Txt( text: title, style: const TextStyle( fontSize: 16, fontFamily: 'Poppins', fontWeight: FontWeight.w600, ), ), ), ], ), const SizedBox(height: 8), Txt( text: description, style: TextStyle( fontSize: 14, fontFamily: 'Poppins', color: Colors.grey[600], height: 1.4, ), ), ], ), ), ], ), ); } }