From 97d60b30317e5c8e002e8f77ec45f469eab4c859 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 8 Oct 2026 08:57:04 +0300 Subject: [PATCH] huawei calling added --- .../firebase_notification_manger.dart | 66 ++++++++++++++----- lib/main.dart | 6 ++ lib/modules/cm_module/cm_request_utils.dart | 16 +++-- 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/lib/controllers/notification/firebase_notification_manger.dart b/lib/controllers/notification/firebase_notification_manger.dart index c9ddab66..2d1d5c1d 100644 --- a/lib/controllers/notification/firebase_notification_manger.dart +++ b/lib/controllers/notification/firebase_notification_manger.dart @@ -46,8 +46,42 @@ Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { } } -/// Android: receives callkit events while no UI listener is active (app killed / not on LandPage). -/// When the app is in the foreground, events go to `callingEvents` in land_page.dart instead. +@pragma('vm:entry-point') +Future backgroundMessageCallback(h_push.RemoteMessage remoteMessage) async { + final payload = huaweiMessageData(remoteMessage); + if (payload == null || payload["type"] != "incoming_call") return; + // Start the callkit background engine so a Decline tapped while killed reaches the caller. + try { + await FlutterCallkitIncoming.onBackgroundMessage(onBackgroundMessage); + } catch (_) {} + await handleDataMessage(payload, true); +} + +Map? huaweiMessageData(h_push.RemoteMessage remoteMessage) { + try { + Map? map; + final raw = remoteMessage.data; + if (raw != null && raw.isNotEmpty) { + final decoded = json.decode(raw); + if (decoded is Map) map = Map.from(decoded); + } + map ??= remoteMessage.dataOfMap == null ? null : Map.from(remoteMessage.dataOfMap!); + if (map == null) return null; + + if (map["type"] == null && map["data"] != null) { + final inner = map["data"] is String ? json.decode(map["data"]) : map["data"]; + if (inner is Map) map = Map.from(inner); + } + return { + for (final e in map.entries) + if (e.value != null) e.key: e.value is String ? e.value : (e.value is Map || e.value is List ? json.encode(e.value) : e.value.toString()), + }; + } catch (e) { + print("HMS data parse error: $e"); + return null; + } +} + @pragma('vm:entry-point') Future onBackgroundMessage(CallEvent event) async { switch (event) { @@ -59,16 +93,6 @@ Future onBackgroundMessage(CallEvent event) async { } } -/// iOS: the single app-wide subscription to [FlutterCallkitIncoming.onEvent], started in main(). -/// -/// When the app is killed, a VoIP push launches it in the background with no UI: the splash -/// animation never completes, LandPage is never built and its listener never exists, so a -/// Decline tapped on the CallKit screen was dropped and the caller kept ringing. -/// Starting this in main() means a listener exists before the user can respond. -/// -/// It must be the ONLY onEvent subscription: every `onEvent` getter call creates a new -/// EventChannel stream, and listening again replaces the previous native handler. -/// LandPage therefore receives events through [uiHandler] instead of listening itself. class CallKitEvents { static StreamSubscription? _subscription; @@ -113,6 +137,7 @@ class FirebaseNotificationManger { static String? voipToken; static Timer? _missedCallTimer; static String? _activeMissedCallId; + static StreamSubscription? _hmsMessageSubscription; static String? _acceptedCallInProgress; @@ -124,6 +149,7 @@ class FirebaseNotificationManger { } static void endAcceptedCall() => _acceptedCallInProgress = null; + /// iOS only. The VoIP token is registered natively in AppDelegate (PushKit) and stored /// by flutter_callkit_incoming. It is sent to the backend as `voIPToken` on chat login. static Future refreshVoipToken() async { @@ -159,11 +185,11 @@ class FirebaseNotificationManger { if (!(await isGoogleServicesAvailable())) { h_push.Push.enableLogger(); final result = await h_push.Push.setAutoInitEnabled(true); - h_push.Push.onMessageReceivedStream.listen(_onMessageReceived, onError: _onMessageReceiveError); + listenHmsForegroundMessages(); h_push.Push.getTokenStream.listen((hToken) { // onToken(token); - // print("Huawei Token: ${hToken}"); + print("Huawei Token: ${hToken}"); token = hToken; }, onError: (e) { print("Huawei TokenError" + e.toString()); @@ -191,8 +217,17 @@ class FirebaseNotificationManger { //print("pushToken:$token"); } + static Stream? _hmsMessageStream; + static Stream get hmsMessageStream => _hmsMessageStream ??= h_push.Push.onMessageReceivedStream.asBroadcastStream(); + static void listenHmsForegroundMessages() { + _hmsMessageSubscription ??= hmsMessageStream.listen(_onMessageReceived, onError: _onMessageReceiveError); + } + static void _onMessageReceived(h_push.RemoteMessage remoteMessage) { - print("onMessageReceivedStream:${remoteMessage.toMap()}"); + final payload = huaweiMessageData(remoteMessage); + if (payload != null && payload["type"] == "incoming_call") { + handleDataMessage(payload, false); + } } static void _onMessageReceiveError(Object error) { @@ -332,6 +367,7 @@ class FirebaseNotificationManger { } if (!(await isGoogleServicesAvailable()) && Platform.isAndroid) { + listenHmsForegroundMessages(); var initialNotification = await h_push.Push.getInitialNotification(); if (initialNotification != null) { Map remoteData = Map.from(initialNotification["extras"] as Map); diff --git a/lib/main.dart b/lib/main.dart index 47268a1d..cec66259 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -148,6 +148,7 @@ import 'providers/service_request_providers/loan_availability_provider.dart'; import 'providers/service_request_providers/reject_reason_provider.dart'; import 'modules/cleaner_module/pages/cleaner_search_form_view.dart'; import 'modules/cleaner_module/provider/cleaner_provider.dart'; +import 'package:huawei_push/huawei_push.dart' as h_push; // class MyHttpOverrides extends HttpOverrides { // @override @@ -160,6 +161,11 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); if (Platform.isAndroid) { FlutterCallkitIncoming.onBackgroundMessage(onBackgroundMessage); + if (!(await FirebaseNotificationManger.isGoogleServicesAvailable())) { + try { + await h_push.Push.registerBackgroundMessageHandler(backgroundMessageCallback); + } catch (_) {} + } } // iOS: listen for CallKit events before anything else, so a Decline on a call that launched // the app from killed state (no UI yet) still reaches the caller. diff --git a/lib/modules/cm_module/cm_request_utils.dart b/lib/modules/cm_module/cm_request_utils.dart index 2fec18c6..29ec07c2 100644 --- a/lib/modules/cm_module/cm_request_utils.dart +++ b/lib/modules/cm_module/cm_request_utils.dart @@ -11,6 +11,7 @@ import 'package:flutter_timezone/flutter_timezone.dart'; import 'package:google_api_availability/google_api_availability.dart'; import 'package:huawei_push/huawei_push.dart' as h_push; import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/notification/firebase_notification_manger.dart'; import 'package:test_sa/controllers/notification/notification_manger.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/string_extensions.dart'; @@ -438,21 +439,26 @@ class CMRequestUtils { Completer completer = Completer(); if (Platform.isAndroid && !(await isGoogleServicesAvailable())) { - // If not using Firebase (i.e., if Google Services is unavailable) - h_push.Push.onMessageReceivedStream.listen((h_push.RemoteMessage remoteMessage) { + StreamSubscription? subscription; + void finish(bool value) { + if (completer.isCompleted) return; + completer.complete(value); + subscription?.cancel(); + } + subscription = FirebaseNotificationManger.hmsMessageStream.listen((h_push.RemoteMessage remoteMessage) { ConfirmArrivalNotificationModel notificationModel = ConfirmArrivalNotificationModel.fromJson(remoteMessage.toMap()); if (notificationModel.requestId != null && notificationModel.accept != null) { if (notificationModel.accept == 'True') { isVerified = true; - completer.complete(isVerified); + finish(isVerified); } else if (notificationModel.accept == 'False') { isVerified = false; - completer.complete(isVerified); + finish(isVerified); } } }, onError: (Object error) { log("onMessageReceivedStream:${error.toString()}"); - completer.complete(false); // Handle any error + finish(false); // Handle any error }); } else { // Using Firebase