import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'dart:ui' as ui; import 'dart:typed_data'; import 'package:app_tracking_transparency/app_tracking_transparency.dart'; import 'package:awesome_notifications/awesome_notifications.dart'; import 'package:wanisclient/providers/language_provider.dart'; import 'package:wanisclient/routes/app_routes.dart'; import 'package:wanisclient/services/Sockets/PubNubManager.dart'; import 'package:wanisclient/utils/dependency_injection.dart'; import 'package:wanisclient/utils/firebase_options.dart'; import 'package:wanisclient/utils/routes.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:get/get_navigation/src/root/get_material_app.dart'; import 'package:image/image.dart' as img; import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart'; import 'package:overlay_support/overlay_support.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'config/constants/app_constant.dart'; import 'config/language/language_enum.dart'; import 'config/screen_size.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'screens/NewUiScreen/PermissionModals/NotificationsPermissionModal.dart'; extension on LanguageProvider { Locale get currentLocale { switch (currentLangCode) { case Language.ar: return const Locale('ar', 'SA'); case Language.en: return const Locale('en', 'US'); default: return const Locale('fr', 'FR'); } } } // NOTE: Ensure your Base64 string is correct and does not contain errors. const String carIconBase64 = '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'; @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(); log("--- ✅ BACKGROUND HANDLER TRIGGERED with data: ${message.data}"); final notificationType = message.data['notification_type']; final rideId = message.data['rideId']; final data = message.data; if (rideId == null || notificationType == null) { log("--- ERROR: Missing rideId or notification_type."); return; } final int notificationId = rideId.hashCode; final carInfo = "${message.data['licensePlate']} · ${message.data['carModel']}"; final eta = message.data['eta'] ?? '...'; double progress = 0.0; if (notificationType == 'LIVE_ACTIVITY_UPDATE') { progress = double.tryParse(message.data['progress'] ?? '0.0') ?? 0.0; } final String imagePath = await _createRichNotificationImage( progress: progress, eta: eta, carInfo: carInfo, ); if (data['type'] == 'DRIVER_ARRIVED') { final rideId = data['rideId']; final plate = data['licensePlate'] ?? ''; final car = data['carModel'] ?? ''; await AwesomeNotifications().createNotification( content: NotificationContent( id: rideId.hashCode, channelKey: 'ride_arrived_channel', title: 'Your driver has arrived', body: plate.isNotEmpty ? 'Waiting outside • $plate' : 'Waiting outside', category: NotificationCategory.Alarm, fullScreenIntent: true, wakeUpScreen: true, criticalAlert: true, autoDismissible: true, locked: true, displayOnBackground: true, displayOnForeground: true, payload: {'ride_id': rideId.toString()}, ), actionButtons: [ NotificationActionButton( key: 'OPEN', label: 'Open', actionType: ActionType.Default, autoDismissible: true, ), ], ); } switch (notificationType) { case 'LIVE_ACTIVITY_START': // Create the notification using the "loud" channel await AwesomeNotifications().createNotification( content: NotificationContent( id: notificationId, channelKey: 'ride_progress_channel', title: 'Your driver is on the way!', body: 'Arriving in $eta', summary: carInfo, notificationLayout: NotificationLayout.BigPicture, bigPicture: 'file://$imagePath', largeIcon: 'resource://drawable/res_app_icon', locked: true, ), ); break; case 'LIVE_ACTIVITY_UPDATE': // Update the notification using the "silent" channel await AwesomeNotifications().createNotification( content: NotificationContent( id: notificationId, // SAME ID channelKey: 'ride_update_channel', // SILENT channel title: 'Your driver is on the way!', body: 'Arriving in $eta', summary: carInfo, notificationLayout: NotificationLayout.BigPicture, bigPicture: 'file://$imagePath', largeIcon: 'resource://drawable/res_app_icon', locked: true, ), ); break; } log("--- ✅ Notification created/updated ---"); } Future _createRichNotificationImage({required double progress, required String eta, required String carInfo,}) async { final String rawBase64 = carIconBase64.split(',').last; final Uint8List carIconBytes = base64Decode(rawBase64); final img.Image originalCarIcon = img.decodeImage(carIconBytes)!; final img.Image carIcon = img.copyResize(originalCarIcon, width: 70); const int width = 600; const int height = 180; final img.ColorRgb8 bgColor = img.ColorRgb8(255, 255, 255); final img.ColorRgb8 trackColor = img.ColorRgb8(224, 224, 224); final img.ColorRgb8 progressColor = img.ColorRgb8(34, 197, 94); final img.ColorRgb8 textColor = img.ColorRgb8(0, 0, 0); final img.ColorRgb8 subTextColor = img.ColorRgb8(100, 100, 100); final font = img.arial48; final smallFont = img.arial24; final canvas = img.Image(width: width, height: height, numChannels: 4); // Draw rounded rectangle background img.fill(canvas, color: bgColor); img.fillCircle(canvas, x: 20, y: 20, radius: 20, color: bgColor); img.fillCircle(canvas, x: width - 20, y: 20, radius: 20, color: bgColor); img.fillCircle(canvas, x: 20, y: height - 20, radius: 20, color: bgColor); img.fillCircle(canvas, x: width - 20, y: height - 20, radius: 20, color: bgColor); img.fillRect(canvas, x1: 20, y1: 0, x2: width - 20, y2: height, color: bgColor); img.fillRect(canvas, x1: 0, y1: 20, x2: width, y2: height - 20, color: bgColor); // Draw text img.drawString(canvas, 'Arriving in $eta', font: font, x: 40, y: 30, color: textColor); img.drawString(canvas, 'Wanis', font: font, x: 450, y: 30, color: progressColor); img.drawString(canvas, carInfo, font: smallFont, x: 40, y: 85, color: subTextColor); // Draw progress bar final int progressBarY = 140; img.drawLine(canvas, x1: 40, y1: progressBarY, x2: width - 40, y2: progressBarY, color: trackColor, thickness: 8); if (progress > 0) { final int progressWidth = (progress * (width - 80)).toInt(); img.drawLine(canvas, x1: 40, y1: progressBarY, x2: 40 + progressWidth, y2: progressBarY, color: progressColor, thickness: 8); } // Draw car icon on progress bar final int carX = (40 + (progress * (width - 80)) - (carIcon.width ~/ 2)) .clamp(20, width - carIcon.width) .toInt(); final int carY = progressBarY - (carIcon.height ~/ 2); img.compositeImage(canvas, carIcon, dstX: carX, dstY: carY); // Save to file final Directory tempDir = await getTemporaryDirectory(); final String filePath = '${tempDir.path}/rich_notification_${DateTime.now().millisecondsSinceEpoch}.png'; await File(filePath).writeAsBytes(img.encodePng(canvas)); return filePath; } late SharedPreferences sharedPref; void main() async { final Int64List kArrivedVibration = Int64List.fromList([0, 400, 180, 400, 180, 600]); WidgetsFlutterBinding.ensureInitialized(); SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( statusBarColor: Colors.white, statusBarIconBrightness: Brightness.dark, statusBarBrightness: Brightness.light, systemNavigationBarColor: Colors.white, systemNavigationBarIconBrightness: Brightness.dark, )); try { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); } catch (e) { if (e.toString().contains('duplicate-app')) { // Firebase already initialized, continue print('Firebase already initialized'); } else { rethrow; } } MapboxOptions.setAccessToken(mapBoxApikey); await AwesomeNotifications().initialize( 'resource://drawable/res_app_icon', [ NotificationChannel( channelKey: 'ride_progress_channel', channelName: 'Ride Progress', channelDescription: 'Notification for initial ride status.', defaultColor: Colors.purple, ledColor: Colors.white, importance: NotificationImportance.High, channelShowBadge: true, locked: true, ), NotificationChannel( channelKey: 'ride_update_channel', channelName: 'Ride Updates (Silent)', channelDescription: 'Silent updates for ongoing ride progress.', defaultColor: Colors.purple, ledColor: Colors.white, importance: NotificationImportance.Default, playSound: false, enableVibration: false, channelShowBadge: false, locked: true, ), NotificationChannel( channelKey: 'ride_arrived_channel', channelName: 'Ride Arrived (Max)', channelDescription: 'Driver arrival alerts that wake screen and vibrate.', defaultColor: Colors.purple, ledColor: Colors.white, importance: NotificationImportance.Max, criticalAlerts: true, playSound: true, enableVibration: true, channelShowBadge: true, locked: true, defaultRingtoneType: DefaultRingtoneType.Alarm, // <— defaultPrivacy: NotificationPrivacy.Public, // <— ), ], debug: true, ); await ScreenUtil.ensureScreenSize(); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); FirebaseMessaging.onMessage.listen((RemoteMessage message) async { log('--- Got a message whilst in the foreground!'); log('--- Message data: ${message.data}'); final data = message.data; // Guard: permission (Android 13+ / iOS) final allowed = await AwesomeNotifications().isNotificationAllowed(); if (!allowed) { // Optionally prompt once somewhere in your app flow // await AwesomeNotifications().requestPermissionToSendNotifications(); } // Show local notif while app is OPEN if (data['type'] == 'DRIVER_ARRIVED') { final rideId = data['rideId'] ?? '0'; final plate = data['licensePlate'] ?? ''; await AwesomeNotifications().createNotification( content: NotificationContent( id: rideId.hashCode, channelKey: 'ride_arrived_channel', // High/Max importance channel title: 'Your driver has arrived', body: plate.isNotEmpty ? 'Waiting outside • $plate' : 'Waiting outside', category: NotificationCategory.Alarm, fullScreenIntent: true, wakeUpScreen: true, criticalAlert: true, autoDismissible: true, locked: true, displayOnForeground: true, displayOnBackground: true, payload: {'ride_id': rideId.toString()}, ), actionButtons: [ NotificationActionButton( key: 'OPEN', label: 'Open', actionType: ActionType.Default, autoDismissible: true, ), ], ); } }); sharedPref = await SharedPreferences.getInstance(); runApp( MultiProvider( providers: [ Provider.value( value: PubNubManager.instance, // Use singleton instance ), ...DependencyInjector.instance.providers, ], child: const MyApp(), ), ); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State { String? _deviceBrand; static const platform = MethodChannel('com.camelsoft.wanisclient/background'); @override void initState() { super.initState(); _initializeAll(); } Future _initializeDeviceSpecificSettings() async { final deviceInfo = DeviceInfoPlugin(); if (Platform.isAndroid) { final androidInfo = await deviceInfo.androidInfo; _deviceBrand = androidInfo.brand.toLowerCase(); print('Device brand: $_deviceBrand'); // Request to disable battery optimization // await _requestBatteryOptimizationException(); } } // Future _requestBatteryOptimizationException() async { // try { // await platform.invokeMethod('requestBatteryOptimization'); // } catch (e) { // print('Battery optimization request failed: $e'); // } // } void _initializeAll() { WidgetsBinding.instance.addPostFrameCallback((_) async { await AppTrackingTransparency.requestTrackingAuthorization(); await _initializeDeviceSpecificSettings(); // ✅ IMPROVED: Check both permission status AND if we've asked before Future.delayed(const Duration(seconds: 1), () async { if (!mounted) return; final prefs = await SharedPreferences.getInstance(); final wasAsked = prefs.getBool('notification_permission_asked') ?? false; final isAllowed = await AwesomeNotifications().isNotificationAllowed(); if (!isAllowed && !wasAsked) { // Not granted AND never asked, show modal _showNotificationPermissionModal(); } else if (isAllowed) { // Already granted print('✅ Notification permission already granted'); } else { // Was asked before but declined print('⚠️ Notification permission was declined previously'); } }); }); } void _showNotificationPermissionModal() { if (Get.context != null) { final languageProvider = Get.context!.read(); final isArabic = languageProvider.currentLangCode == Language.ar; Get.dialog( Dialog( backgroundColor: Colors.transparent, child: NotificationsPermissionModal( isArabic: isArabic, onAllow: () async { Get.back(); // Mark as asked final prefs = await SharedPreferences.getInstance(); await prefs.setBool('notification_permission_asked', true); // Request permission await AwesomeNotifications().requestPermissionToSendNotifications( channelKey: 'ride_progress_channel', permissions: [ NotificationPermission.Alert, NotificationPermission.Sound, NotificationPermission.Vibration, NotificationPermission.FullScreenIntent, NotificationPermission.CriticalAlert, ], ); }, onMaybeLater: () async { Get.back(); // Mark as asked and declined SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setBool('notification_permission_asked', true); await prefs.setBool('notification_permission_declined', true); }, ), ), barrierDismissible: false, ); } } @override Widget build(BuildContext context) { final languageProvider = context.watch(); ScreenConfiguration().initialize(context); return ScreenUtilInit( useInheritedMediaQuery: true, minTextAdapt: true, designSize: _getDesignSize(context), builder: (context, child) => OverlaySupport.global( child: WillPopScope( onWillPop: () async => false, child: GestureDetector( onTap: () => FocusManager.instance.primaryFocus?.unfocus(), child: GetMaterialApp( key: ValueKey(languageProvider.currentLocale), locale: languageProvider.currentLocale, supportedLocales: const [ Locale('fr', 'FR'), Locale('en', 'US'), Locale('ar', 'SA'), ], localizationsDelegates: const [ GlobalWidgetsLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], debugShowCheckedModeBanner: false, getPages: routes, initialRoute: AppRoutes.SplashScreen, builder: (context, child) => MediaQuery.removePadding( context: context, removeTop: false, removeBottom: false, child: child!, ), ), ), ), ), ); } } ui.Size _getDesignSize(BuildContext context) { final width = MediaQuery.of(context).size.width; if (width < 400) return const ui.Size(360, 690); if (width < 450) return const ui.Size(412, 892); if (width < 650) return const ui.Size(480, 892); if (width < 700) return const ui.Size(500, 932); return const ui.Size(612, 1024); // Tablet or large screen }