import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:new_version_plus/new_version_plus.dart'; import 'package:http/http.dart' as http; import 'Controller/Internetcheck/check_internet.dart'; import 'Globalwidgets/Localnotificationservice.dart'; import 'Globalwidgets/binding.dart'; import 'Helper/Constants/Apiconstants.dart'; import 'Helper/Constants/Colorconstants.dart'; import 'Helper/Constants/api_config.dart'; import 'Helper/Logger.dart'; import 'Helper/Locationservice/app_config_service.dart'; import 'View/Home/Homeview.dart'; import 'View/Introscreen/Introscreenview.dart'; import 'View/Update/Updateview.dart'; /// --------------------------------------------------------------------------- /// 1. GLOBAL NOTIFICATION OBJECTS /// --------------------------------------------------------------------------- final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); final AndroidNotificationChannel channel = AndroidNotificationChannel( 'Nearlexpress Business', // id 'Nearlexpress Business Notifications', // name description: 'Nearlexpress Business', importance: Importance.high, sound: const RawResourceAndroidNotificationSound('ring'), // raw/ring.mp3 enableLights: true, enableVibration: true, playSound: true, showBadge: true, vibrationPattern: Int64List.fromList(const [500, 1000, 500]), ); /// --------------------------------------------------------------------------- /// 2. BACKGROUND MESSAGE HANDLER (must be top-level) /// --------------------------------------------------------------------------- @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(); // Initialize local notifications in background isolate const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); const DarwinInitializationSettings initializationSettingsIOS = DarwinInitializationSettings(); const InitializationSettings initializationSettings = InitializationSettings( android: initializationSettingsAndroid, iOS: initializationSettingsIOS, ); await flutterLocalNotificationsPlugin.initialize(initializationSettings); // Create notification channel final AndroidNotificationChannel channel = AndroidNotificationChannel( 'Nearlexpress Business', 'Nearlexpress Business Notifications', description: 'Nearlexpress Business', importance: Importance.high, sound: const RawResourceAndroidNotificationSound('ring'), playSound: true, enableVibration: true, vibrationPattern: Int64List.fromList(const [500, 1000, 500]), ); final androidPlugin = flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation(); await androidPlugin?.createNotificationChannel(channel); // === LOAD LOGO FROM ASSETS IN BACKGROUND === final ByteData logoData = await rootBundle.load('assets/images/nearlebusiness.png'); final Uint8List logoBytes = logoData.buffer.asUint8List(); // Show notification if it has title/body if (message.notification != null) { await flutterLocalNotificationsPlugin.show( message.hashCode, message.notification!.title, message.notification!.body, NotificationDetails( android: AndroidNotificationDetails( channel.id, channel.name, channelDescription: channel.description, importance: Importance.high, priority: Priority.high, icon: '@mipmap/ic_launcher', sound: const RawResourceAndroidNotificationSound('ring'), enableVibration: true, vibrationPattern: Int64List.fromList([500, 1000, 500]), playSound: true, color: const Color(0xFF662582), largeIcon: ByteArrayAndroidBitmap(logoBytes), // LOGO ON RIGHT styleInformation: BigPictureStyleInformation( ByteArrayAndroidBitmap(logoBytes), largeIcon: ByteArrayAndroidBitmap(logoBytes), contentTitle: message.notification!.title, htmlFormatContentTitle: true, ), ), iOS: const DarwinNotificationDetails( sound: 'ring.wav', ), ), payload: jsonEncode(message.data.isNotEmpty ? message.data : { "type": message.notification?.android?.tag ?? "" }), ); } } /// --------------------------------------------------------------------------- /// 3. LOCAL NOTIFICATION HELPER (foreground + background) /// --------------------------------------------------------------------------- Future _showLocalNotification(RemoteMessage message) async { final notification = message.notification; if (notification == null) return; // === LOAD LOGO FROM ASSETS === final ByteData logoData = await rootBundle.load('assets/images/nearle_logo.jpeg'); final Uint8List logoBytes = logoData.buffer.asUint8List(); final AndroidNotificationDetails androidDetails = AndroidNotificationDetails( channel.id, channel.name, channelDescription: channel.description, importance: channel.importance, priority: Priority.high, sound: channel.sound, enableVibration: channel.enableVibration, vibrationPattern: channel.vibrationPattern, playSound: channel.playSound, enableLights: channel.enableLights, icon: '@mipmap/ic_launcher', color: const Color(0xFF662582), largeIcon: ByteArrayAndroidBitmap(logoBytes), // LOGO ON RIGHT styleInformation: BigPictureStyleInformation( ByteArrayAndroidBitmap(logoBytes), largeIcon: ByteArrayAndroidBitmap(logoBytes), contentTitle: notification.title, summaryText: notification.body, htmlFormatContentTitle: true, htmlFormatSummaryText: true, ), ); const DarwinNotificationDetails iOSDetails = DarwinNotificationDetails( sound: 'ring.mp3', presentAlert: true, presentBadge: true, presentSound: true, ); final NotificationDetails platformDetails = NotificationDetails(android: androidDetails, iOS: iOSDetails); await flutterLocalNotificationsPlugin.show( notification.hashCode, notification.title, notification.body, platformDetails, payload: jsonEncode(message.data.isNotEmpty ? message.data : { "type": message.notification?.android?.tag ?? "" }), ); } /// --------------------------------------------------------------------------- /// 4. NOTIFICATION TAP HANDLER /// --------------------------------------------------------------------------- void _handleNotificationTap(Map data) { final type = data['type']?.toString(); print('Notification tapped – type: $type'); if (type == 'tojoin') { Get.toNamed('/join'); } } /// --------------------------------------------------------------------------- /// 5. HTTP OVERRIDE (dev only – remove in production) /// --------------------------------------------------------------------------- class MyHttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext? context) { return super.createHttpClient(context) ..badCertificateCallback = (cert, host, port) => true; } } /// --------------------------------------------------------------------------- /// 6. MAIN /// --------------------------------------------------------------------------- Future main() async { WidgetsFlutterBinding.ensureInitialized(); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.transparent, // or your color statusBarIconBrightness: Brightness.dark, // Android icons statusBarBrightness: Brightness.light, // iOS icons ), ); // Allow self-signed certs (dev only) HttpOverrides.global = MyHttpOverrides(); await Firebase.initializeApp(); // ---------- LOCAL NOTIFICATIONS INITIALISATION ---------- const AndroidInitializationSettings androidInit = AndroidInitializationSettings('@mipmap/ic_launcher'); const DarwinInitializationSettings iOSInit = DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true, ); const InitializationSettings initSettings = InitializationSettings(android: androidInit, iOS: iOSInit); await flutterLocalNotificationsPlugin.initialize( initSettings, ); // Create Android channel (mandatory for Android 8+) final androidPlugin = flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>(); await androidPlugin?.createNotificationChannel(channel); // ---------- FCM ---------- FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); await FirebaseMessaging.instance .setForegroundNotificationPresentationOptions( alert: true, badge: true, sound: true, ); SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); ApiConfig.setLive(); // 👈 clean call // Dependency injection Get.put(ConnectivityController()); runApp(const MyApp()); } /// --------------------------------------------------------------------------- /// 7. APP WIDGET /// --------------------------------------------------------------------------- class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return GetMaterialApp( title: 'Nearlexpress Business', debugShowCheckedModeBanner: false, initialBinding: GlobalBinding(), navigatorKey: MyHomePageOne.mNavigationState, theme: ThemeData( primaryColor: ColorConstants.primaryColor, useMaterial3: false, scaffoldBackgroundColor: Colors.grey.shade200, appBarTheme: AppBarTheme( iconTheme: IconThemeData(color: ColorConstants.secondaryColor), color: ColorConstants.secondaryColor, elevation: 0, titleTextStyle: TextStyle( color: ColorConstants.secondaryColor, fontSize: 18, ), ), fontFamily: 'Lato', visualDensity: VisualDensity.adaptivePlatformDensity, ), home: const SafeArea(top: false, child: MyHomePageOne()), ); } } /// --------------------------------------------------------------------------- /// 8. HOME PAGE (FCM + UI logic) /// --------------------------------------------------------------------------- class MyHomePageOne extends StatefulWidget { static final GlobalKey mNavigationState = GlobalKey(); const MyHomePageOne({Key? key}) : super(key: key); @override State createState() => _MyHomePageOneState(); } class _MyHomePageOneState extends State { final AppConfigurationService _appConfig = AppConfigurationService(); String? _tenantFcmToken; String? _tenantContact; int? _moduleId; int? _tenantId; @override void initState() { super.initState(); _initApp(); } Future _initApp() async { await _loadPrefs(); await _setupFCM(); await _getAppVersion(); _checkForUpdate(context); _fetchLocation(); _appConfig.getAppConfig("${ApiConstants.configUrl}?configid=1"); LocalNotificationService.initialize(context); } Future _loadPrefs() async { final prefs = await SharedPreferences.getInstance(); setState(() { _tenantFcmToken = prefs.getString('tenantFcmToken'); _tenantContact = prefs.getString('tenantContactNo'); _moduleId = prefs.getInt('moduleId'); _tenantId = prefs.getInt('tenantId'); }); } Future _setupFCM() async { final settings = await FirebaseMessaging.instance.requestPermission( alert: true, badge: true, sound: true, ); print('FCM permission: ${settings.authorizationStatus}'); final token = await FirebaseMessaging.instance.getToken(); print('=== FCM TOKEN ===\n$token\n================='); final prefs = await SharedPreferences.getInstance(); await prefs.setString('fcmToken', token!); FirebaseMessaging.onMessage.listen((RemoteMessage msg) async { print('FOREGROUND: ${msg.notification?.title}'); await _showLocalNotification(msg); }); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage msg) { print('OPENED FROM NOTIFICATION'); _handleNotificationTap(msg.data); }); } String currentVersion = ''; Future _getAppVersion() async { final packageInfo = await PackageInfo.fromPlatform(); currentVersion = packageInfo.version; final prefs = await SharedPreferences.getInstance(); await prefs.setString('CurrentVersion', currentVersion); logger.i('Current version: $currentVersion'); } Future _checkForUpdate(BuildContext context) async { final newVersion = NewVersionPlus( iOSId: '284882215', androidId: "com.nearle.bond", ); final status = await newVersion.getVersionStatus(); if (status != null && status.canUpdate) { Get.to(() => UpdateAppPage( mCurrentVersion: status.localVersion, mUpdateVersion: status.storeVersion, )); } } Future _fetchLocation() async { final url = Uri.http('ip-api.com', '/json'); try { final response = await http.get(url); if (response.statusCode == 200) { final data = jsonDecode(response.body); final country = data['countryCode'].toString(); final city = data['city'].toString(); final prefs = await SharedPreferences.getInstance(); await prefs.setString('location_Country', country); await prefs.setString('MainCity', city); await prefs.setString('location_CountryCode', country); } } catch (e) { print('Location error: $e'); } } @override Widget build(BuildContext context) { return (_tenantFcmToken == null && _tenantContact == null) ? IntroScreenView() : HomeView(selectedIndex: 0); } }