import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:get/get.dart'; import 'package:taxglide/consts/local_store.dart'; import 'package:taxglide/view/Mahi_chat/live_chat_screen.dart'; import 'package:taxglide/view/Main_controller/main_controller.dart'; import 'package:taxglide/view/screens/history/detail_screen.dart'; import 'package:taxglide/view/screens/notification_screen.dart'; import 'package:taxglide/router/consts_routers.dart'; // ───────────────────────────────────────────────────────────────────────────── // Local Notifications setup (singleton plugin instance) // ───────────────────────────────────────────────────────────────────────────── final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); /// Android notification channel used for all foreground chat/app notifications. const AndroidNotificationChannel _channel = AndroidNotificationChannel( 'taxglide_foreground', // id 'TaxGlide Notifications', // name shown in system settings description: 'Foreground notifications for TaxGlide', importance: Importance.high, playSound: true, enableVibration: true, ); /// Call once in main() after Firebase.initializeApp(). Future initLocalNotifications() async { const initSettings = InitializationSettings( android: AndroidInitializationSettings('@mipmap/launcher_icon'), iOS: DarwinInitializationSettings(), ); await flutterLocalNotificationsPlugin.initialize( initSettings, onDidReceiveNotificationResponse: (NotificationResponse response) { // Notification tapped while app is open – handled inside NotificationService. debugPrint('🔔 Local notification tapped: payload=${response.payload}'); Get.find().handleNavigationFromPayload( response.payload, ); }, ); // Create the Android channel (no-op on iOS). await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin >() ?.createNotificationChannel(_channel); debugPrint('✅ LocalNotifications initialized'); } // ───────────────────────────────────────────────────────────────────────────── // NotificationService // ───────────────────────────────────────────────────────────────────────────── class NotificationService extends GetxController { final RemoteMessage? initialMessage; NotificationService(this.initialMessage); /// Running notification ID counter so multiple notifications can stack. int _notifId = 0; /// Shows a native Android system notification for a foreground message. /// Works from any context – WebSocket callbacks, FCM listeners, etc. /// /// [tag] is used as a deduplication key: if two sources (FCM + WebSocket) /// fire for the same chat/event, passing the same tag means Android will /// *replace* the first notification instead of showing a second one. void showForegroundNotification(RemoteMessage message, {String? tag}) { final String title = message.notification?.title ?? message.data['title']?.toString() ?? 'New Notification'; final String body = message.notification?.body ?? message.data['body']?.toString() ?? 'You have a new update'; // Build a simple payload string so tapping routes correctly. final String? type = message.data['type']?.toString(); final String? id = message.data['page_id']?.toString() ?? message.data['id']?.toString(); final String payload = (type != null && id != null) ? '$type:$id' : ''; // Use the tag as dedup key; fall back to an incrementing ID so unrelated // notifications still stack correctly. final int notifId = tag != null ? tag.hashCode.abs() % 100000 : _notifId++; debugPrint( '🔔 NotificationService: showing local notification ' '[id=$notifId tag=$tag title=$title]', ); flutterLocalNotificationsPlugin.show( notifId, title, body, NotificationDetails( android: AndroidNotificationDetails( _channel.id, _channel.name, channelDescription: _channel.description, importance: Importance.high, priority: Priority.high, icon: '@mipmap/launcher_icon', playSound: true, enableVibration: true, styleInformation: BigTextStyleInformation(body), tag: tag, // same tag → replaces instead of duplicating ), iOS: const DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, ), ), payload: payload, ); } // ── Navigation helpers ────────────────────────────────────────────────────── /// Called when a local notification is tapped (payload = "type:id"). void handleNavigationFromPayload(String? payload) { if (payload == null || payload.isEmpty) { _safeNavigate(() => Get.offAll(() => MainController())); return; } final parts = payload.split(':'); final type = parts.isNotEmpty ? parts[0] : null; final idStr = parts.length > 1 ? parts[1] : null; handleNavigation({'type': type, 'id': idStr}); } Future handleNavigation(Map data) async { final LocalStore localStore = LocalStore(); try { // ── Auth check ────────────────────────────────────────────────────────── final String? token = await localStore.getToken(); if (token == null || token.isEmpty || token == 'null') { debugPrint('🔒 No valid token → Login'); _safeNavigate(() => Get.offAllNamed(ConstRouters.login)); return; } // Support both 'page'+'page_id' and 'type'+'id' payload formats. final String? type = data['page']?.toString() ?? data['type']?.toString(); final String? idStr = data['page_id']?.toString() ?? data['pageId']?.toString() ?? data['id']?.toString(); if (type == null || type.isEmpty) { debugPrint('⚠️ Invalid notification data: type is empty'); _safeNavigate(() => Get.offAll(() => MainController())); return; } debugPrint('📍 Notification nav → type=$type id=$idStr'); if (type == 'chat') { final int chatId = int.tryParse(idStr ?? '') ?? 0; if (chatId == 0) { _safeNavigate(() => Get.offAll(() => MainController())); return; } debugPrint('💬 Navigating to Chat $chatId'); _safeNavigate(() { try { if (Get.isDialogOpen ?? false) Get.back(); if (Get.isBottomSheetOpen ?? false) Get.back(); Navigator.of(Get.context!).popUntil((route) { return route.isFirst || route.settings.name == '/splash' || route.settings.name == '/MainController'; }); } catch (_) {} Future.delayed(const Duration(milliseconds: 200), () { if (Get.currentRoute == '/LiveChatScreen') { Get.off( () => LiveChatScreen(chatid: chatId, fileid: '0'), preventDuplicates: false, ); } else { Get.to( () => LiveChatScreen(chatid: chatId, fileid: '0'), preventDuplicates: false, ); } }); }); } else if (type == 'service') { final int serviceId = int.tryParse(idStr ?? '') ?? 0; if (serviceId == 0) { _safeNavigate(() => Get.offAll(() => MainController())); return; } debugPrint('📋 Navigating to Service $serviceId'); _safeNavigate( () => Get.offAll( () => MainController( initialIndex: 2, child: DetailScreen(id: serviceId, sourceTabIndex: 2), ), ), ); } else { debugPrint("❓ Unknown type '$type' → NotificationScreen"); _safeNavigate(() { Get.offAll(() => const MainController()); Future.delayed( const Duration(milliseconds: 300), () => Get.to(() => const NotificationScreen()), ); }); } } catch (e) { debugPrint('❌ Error handling notification nav: $e'); try { final String? token = await localStore.getToken(); if (token != null && token.isNotEmpty && token != 'null') { _safeNavigate(() => Get.offAll(() => MainController())); } else { _safeNavigate(() => Get.offAllNamed(ConstRouters.login)); } } catch (_) { _safeNavigate(() => Get.offAllNamed(ConstRouters.login)); } } } void _safeNavigate(Function action) { if (Get.key.currentState != null) { action(); return; } int attempts = 0; Future.doWhile(() async { await Future.delayed(const Duration(milliseconds: 100)); attempts++; if (Get.key.currentState != null) { action(); return false; } return attempts < 20; }); } }