taxgilde/lib/main.dart
2026-04-11 10:21:31 +05:30

240 lines
8.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:get/get.dart';
import 'package:file_picker/file_picker.dart';
import 'package:permission_handler/permission_handler.dart';
import 'firebase_options.dart';
import 'package:taxglide/router/router.dart';
import 'package:taxglide/services/notification_service.dart';
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();
/// ---------------------------------------------------------------------------
/// 🚨 BACKGROUND HANDLER - Must be top-level function (outside main)
/// This handles notifications when app is CLOSED/TERMINATED
/// ---------------------------------------------------------------------------
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
debugPrint("📩 Background Notification Received!");
}
Future<void> main() async {
try {
WidgetsFlutterBinding.ensureInitialized();
// 1. Initialize Firebase first (required)
debugPrint("🔥 Initializing Firebase...");
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
debugPrint("✅ Firebase Initialized");
// ✅ Register background handler as early as possible
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// 2. Fetch initial FCM message (terminated → tapped notification)
RemoteMessage? initialMessage;
try {
debugPrint("📩 Fetching initial message...");
initialMessage = await FirebaseMessaging.instance
.getInitialMessage()
.timeout(
const Duration(seconds: 2),
onTimeout: () {
debugPrint("⚠️ Firebase getInitialMessage timed out!");
return null;
},
);
debugPrint("📥 Initial message fetched: ${initialMessage != null}");
} catch (e) {
debugPrint("⚠️ Could not fetch initial message: $e");
}
Get.put<NotificationService>(
NotificationService(initialMessage),
permanent: true,
);
// 3. Initialize local notifications plugin (must happen before runApp)
await initLocalNotifications();
// 4. Start the app
debugPrint("🚀 Calling runApp...");
runApp(const ProviderScope(child: TaxglideApp()));
// 4. Run FCM/permission setup in background (non-blocking)
_runBackgroundTasks();
} catch (e, stack) {
debugPrint("❌ CRITICAL BOOT ERROR: $e");
debugPrint(stack.toString());
runApp(const ProviderScope(child: TaxglideApp()));
}
}
/// Helper to run non-critical startup tasks without blocking the UI
Future<void> _runBackgroundTasks() async {
try {
FirebaseMessaging messaging = FirebaseMessaging.instance;
// Request permissions (Non-blocking)
await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
provisional: false,
);
// Request notification permission for Android 13+
if (Platform.isAndroid) {
await Permission.notification.request();
}
// Setup messaging (tokens, etc)
await _setupMessaging(messaging);
// On iOS, set alert to true we show foreground FCM messages naturally.
await messaging.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
// Set up FCM foreground listener
_setupFcmListeners();
// Other plugins
await _initializePlugins();
debugPrint("✅ All background initializations complete");
} catch (e) {
debugPrint("⚠️ Background task failed: $e");
}
}
void _setupFcmListeners() {
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
debugPrint(
"🔔 Foreground FCM received! Notification block: ${message.notification}",
);
debugPrint("🔍 Foreground FCM Data block: ${message.data}");
// Show custom in-app notification for Android (since system doesn't show it in foreground)
// On iOS, setForegroundNotificationPresentationOptions handles this.
if (Platform.isAndroid) {
// Pass a dedup tag (chat/page id) so that if the WebSocket fires for the
// same event, the second notification replaces the first instead of
// showing a double notification.
final String? dedupTag =
message.data['page_id']?.toString() ??
message.data['chat_id']?.toString() ??
message.data['id']?.toString();
Get.find<NotificationService>().showForegroundNotification(
message,
tag: dedupTag,
);
}
});
// Background tap
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
debugPrint("📥 Notification tapped from background!");
Get.find<NotificationService>().handleNavigation(message.data);
});
}
/// ---------------------------------------------------------------------------
/// Non-blocking Messaging Setup
/// ---------------------------------------------------------------------------
Future<void> _setupMessaging(FirebaseMessaging messaging) async {
if (Platform.isIOS) {
debugPrint("🍎 iOS Detected: Requesting notification permissions...");
NotificationSettings settings = await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
debugPrint("✅ User granted notification permissions");
} else if (settings.authorizationStatus ==
AuthorizationStatus.provisional) {
debugPrint("✅ User granted provisional notification permissions");
} else {
debugPrint(
"❌ User declined or has not accepted notification permissions",
);
}
// Checking for APNS token - essential for FCM on iOS
String? apnsToken = await messaging.getAPNSToken();
if (apnsToken == null) {
debugPrint("⏳ APNS Token not ready. Retrying (Max 10s)...");
// Simulators will stay null here. Physical devices might take a few seconds.
for (int i = 0; i < 10; i++) {
await Future.delayed(const Duration(seconds: 1));
apnsToken = await messaging.getAPNSToken();
if (apnsToken != null) {
debugPrint("✅ APNS Token received after retry");
break;
}
}
}
if (apnsToken != null) {
debugPrint("✅ APNS Token: $apnsToken");
} else {
debugPrint(
"⚠️ APNS Token is NULL. "
"IMPORTANT: Push notifications require a PHYSICAL DEVICE and valid Xcode Push Capability. "
"Tokens are NOT available on Simulators.",
);
return;
}
}
// FCM Token generation is now handled EXCLUSIVELY during OTP verification in ApiRepository.
}
/// ---------------------------------------------------------------------------
/// Plugin Initializer
/// ---------------------------------------------------------------------------
Future<void> _initializePlugins() async {
try {
await FilePicker.platform.clearTemporaryFiles();
debugPrint("✅ Plugins initialized");
} catch (e) {
debugPrint("⚠️ Plugin init failed: $e");
}
}
class TaxglideApp extends StatefulWidget {
const TaxglideApp({super.key});
@override
State<TaxglideApp> createState() => _TaxglideAppState();
}
class _TaxglideAppState extends State<TaxglideApp> {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Taxglide App',
debugShowCheckedModeBanner: false,
scaffoldMessengerKey: rootScaffoldMessengerKey,
initialRoute: '/splash',
getPages: AppRoutes.routes,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
);
}
}