From b26538b3d6d93d6b0d7b50e72d04f10a3f1cc0e8 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 29 Jul 2026 14:40:40 +0300 Subject: [PATCH] cont --- android/app/src/main/AndroidManifest.xml | 15 +- .../firebase_notification_manger.dart | 49 ++++-- lib/main.dart | 4 + .../chat/calling/audio_call_page.dart | 10 ++ .../cx_module/chat/calling/call_provider.dart | 150 ++++++++---------- lib/new_views/pages/land_page/land_page.dart | 3 +- 6 files changed, 135 insertions(+), 96 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4d4dcfee..9efe0945 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ - + @@ -43,6 +44,7 @@ + + + + firebaseMessagingBackgroundHandler(RemoteMessage message) async { if (message.notification == null && message.data != null) { - handleDataMessage(message.data); + handleDataMessage(message.data, true); + } +} + +@pragma('vm:entry-point') +Future onBackgroundMessage(CallEvent event) async { + // This executes when a background/killed action happens + + switch (event.eventName) { + case CallEventConstants.actionCallAccept: + "actionCallAccept".showToast; + break; + case CallEventConstants.actionCallDecline: + "actionCallDecline".showToast; + break; + default: + break; } } @@ -268,7 +286,7 @@ class FirebaseNotificationManger { } } if (message.notification == null && message.data != null) { - handleDataMessage(message.data); + handleDataMessage(message.data, false); } return; }); @@ -279,7 +297,7 @@ class FirebaseNotificationManger { } } -Future handleDataMessage(Map messageData) async { +Future handleDataMessage(Map messageData, bool isFromBackground) async { print("handleDataMessage:${messageData}"); // if (messageData["data"] != null) { // messageData = messageData["data"]; @@ -310,8 +328,7 @@ Future handleDataMessage(Map messageData) async { final referenceId = messageData['referenceId'] as String?; final conversationId = messageData['conversationId'] as String?; - log('📱 [FOREGROUND] Showing CallKit UI...', name: 'FirebaseNotificationManger'); - + WidgetsFlutterBinding.ensureInitialized(); Timer? _callTimeoutTimer; try { final callKitParams = CallKitParams( @@ -324,10 +341,16 @@ Future handleDataMessage(Map messageData) async { duration: 30000, missedCallNotification: const NotificationParams( showNotification: true, - isShowCallback: true, + isShowCallback: false, subtitle: 'Missed call', callbackText: 'Call back', ), + callingNotification: const NotificationParams( + showNotification: true, + isShowCallback: true, + subtitle: 'Calling...', + callbackText: 'Hang Up', + ), extra: { 'callId': callId, 'callerId': callerId, @@ -341,7 +364,7 @@ Future handleDataMessage(Map messageData) async { 'timestamp': DateTime.now().toIso8601String(), }, android: const AndroidParams( - isCustomNotification: true, + // isCustomNotification: true, isShowLogo: false, ringtonePath: 'system_ringtone_default', backgroundColor: '#0955fa', @@ -368,11 +391,11 @@ Future handleDataMessage(Map messageData) async { ringtonePath: 'system_ringtone_default', ), ); - // await FlutterCallkitIncoming.requestNotificationPermission({ - // "title": "Notification permission", - // "rationaleMessagePermission": "Notification permission is required, to show notification.", - // "postNotificationMessageRequired": "Notification permission is required, Please allow notification permission from setting." - // }); + await FlutterCallkitIncoming.requestNotificationPermission({ + // // "title": "Notification permission", + // // "rationaleMessagePermission": "Notification permission is required, to show notification.", + // // "postNotificationMessageRequired": "Notification permission is required, Please allow notification permission from setting." + }); await FlutterCallkitIncoming.canUseFullScreenIntent(); await FlutterCallkitIncoming.requestFullIntentPermission(); @@ -382,6 +405,8 @@ Future handleDataMessage(Map messageData) async { _callTimeoutTimer = Timer(const Duration(seconds: 30), () async { await FlutterCallkitIncoming.endCall(callId); await FlutterCallkitIncoming.endAllCalls(); + await FlutterCallkitIncoming.showMissCallNotification(callKitParams); + _callTimeoutTimer?.cancel(); _callTimeoutTimer = null; }); diff --git a/lib/main.dart b/lib/main.dart index d00c45a5..2d365cfb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:localization/localization.dart'; import 'package:flutter_timezone/flutter_timezone.dart'; import 'package:provider/provider.dart'; @@ -119,6 +120,7 @@ import 'package:test_sa/views/widgets/equipment/my_assets_page.dart'; import 'package:timezone/data/latest_all.dart' as tz; import 'package:timezone/timezone.dart' as tz; +import 'controllers/notification/firebase_notification_manger.dart'; import 'controllers/notification/notification_manger.dart'; import 'controllers/providers/api/gas_refill_comments.dart'; import 'controllers/providers/api/user_provider.dart'; @@ -156,6 +158,8 @@ import 'modules/cleaner_module/provider/cleaner_provider.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + FlutterCallkitIncoming.onBackgroundMessage(onBackgroundMessage); + FlutterCallkitIncoming.endAllCalls(); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); // HttpOverrides.global = MyHttpOverrides(); // for later use. _configureLocalTimeZone(); diff --git a/lib/modules/cx_module/chat/calling/audio_call_page.dart b/lib/modules/cx_module/chat/calling/audio_call_page.dart index d061d914..9fc4b787 100644 --- a/lib/modules/cx_module/chat/calling/audio_call_page.dart +++ b/lib/modules/cx_module/chat/calling/audio_call_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; @@ -25,6 +26,7 @@ class AudioCallPage extends StatefulWidget { class _AudioCallPageState extends State { late CallProvider callPro; late ChatProvider chatProvider; + // Participants? participants; String currentStatus = "Connecting..."; @@ -39,6 +41,14 @@ class _AudioCallPageState extends State { } void makeCall() async { + bool isConnected = await callPro.buildHubConnection(widget.sender.employeeNumber!); + if (!isConnected) { + context.showConfirmDialog("Error while connecting to call service. Please try again later.", title: "Connection Error", onTap: () { + Navigator.pop(context); + Navigator.pop(context); + }); + return; + } callPro.initAudioCallListeners(); callPro.startCall(widget.sender.employeeNumber ?? "", widget.recipient?.employeeNumber ?? "", false, chatProvider.moduleID, chatProvider.referenceID); diff --git a/lib/modules/cx_module/chat/calling/call_provider.dart b/lib/modules/cx_module/chat/calling/call_provider.dart index ed5607ce..8fbefeef 100644 --- a/lib/modules/cx_module/chat/calling/call_provider.dart +++ b/lib/modules/cx_module/chat/calling/call_provider.dart @@ -8,8 +8,13 @@ import 'package:signalr_netcore/http_connection_options.dart'; import 'package:signalr_netcore/hub_connection.dart'; import 'package:signalr_netcore/hub_connection_builder.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/modules/cx_module/chat/api_client.dart'; +import 'package:http/http.dart' as http; import '../chat_provider.dart'; +import '../model/chat_login_response_model.dart'; + +HubConnection? callHubConnection; class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { ///////////////////// Web RTC Video Calling ////////////////////// @@ -27,112 +32,93 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { bool isPeerMuted = false; bool isPeerCameraOn = true; + bool isLoading = false; + Future _disposeConnection() async { try { - if (chatHubConnection != null) { - await chatHubConnection!.stop(); - if (kDebugMode) { - print('🔌 SignalR connection closed successfully'); - } + if (callHubConnection != null) { + await callHubConnection!.stop(); } } catch (e) { - if (kDebugMode) { - print('⚠️ Error closing SignalR connection: $e'); - } } finally { - chatHubConnection = null; + callHubConnection = null; } + // isLoading = false; + // notifyListeners(); } @override void dispose() { - _disposeConnection().then((_) { - if (kDebugMode) { - print('✅ ChatProvider disposed'); - } - }).catchError((error) { - if (kDebugMode) { - print('⚠️ Error during ChatProvider disposal: $error'); - } - }); + _disposeConnection().then((_) {}).catchError((error) {}); super.dispose(); } - Future getHubConnection() async { - if (kDebugMode) { - print('🔧 Creating new SignalR hub connection...'); - } - + Future getHubConnection(String token) async { HubConnection hub; HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); - hub = HubConnectionBuilder().withUrl("${URLs.chatHubUrlChat}?accessTokenFactory=${URLs.chatApiKey}", options: httpOp).withAutomaticReconnect(retryDelays: [2000, 5000, 10000, 20000]).build(); - - if (kDebugMode) { - print('✅ SignalR hub connection created'); - } + hub = HubConnectionBuilder().withUrl("${URLs.chatHubUrlChat}?accessTokenFactory=$token}", options: httpOp).withAutomaticReconnect(retryDelays: [2000, 5000, 10000, 20000]).build(); return hub; } - Future buildHubConnection() async { + Future buildHubConnection(String employeeNumber) async { try { - // Dispose existing connection if any await _disposeConnection(); - - chatHubConnection = await getHubConnection(); - await chatHubConnection!.start(); - if (kDebugMode) { - print("🔌 SignalR Hub Connection: Started"); + // isLoading = true; + // notifyListeners(); + http.Response response = await ApiClient().postJsonForResponse(URLs.chatSdkToken, {"apiKey": URLs.chatApiKey, "employeeNumber": employeeNumber, "voIPToken": null}); + if (response.statusCode == 200) { + ChatLoginResponse chatResponse = ChatLoginResponse.fromJson(jsonDecode(response.body)); + print("chatResponse:${chatResponse.token}"); + callHubConnection = await getHubConnection(chatResponse.token!); + await callHubConnection!.start(); + // isLoading = false; + // notifyListeners(); + return true; } - return true; - - //group On message - - // chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived); + // isLoading = false; + // notifyListeners(); + return false; } catch (e) { - if (kDebugMode) { - print('⚠️ Error building SignalR connection: $e'); - } - // Clean up on error + print("buildHubConnectionE:$e"); await _disposeConnection(); return false; - rethrow; // Rethrow so caller knows about the error } } void initAudioCallListeners() { - chatHubConnection!.on("OnCallAcceptedAsync", onCallAcceptedAsync); - chatHubConnection!.on("OnIceCandidateAsync", onIceCandidateAsync); - chatHubConnection!.on("OnOfferAsync", onOfferAsync); - chatHubConnection!.on("OnAnswerOffer", onAnswerOffer); - chatHubConnection!.on("OnHangUpAsync", onHangUpAsync); - chatHubConnection!.on("OnCallDeclinedAsync", onCallDeclinedAsync); - chatHubConnection!.on('OnAudioToggle', onAudioToggle); + callHubConnection!.on("OnCallAcceptedAsync", onCallAcceptedAsync); + callHubConnection!.on("OnIceCandidateAsync", onIceCandidateAsync); + callHubConnection!.on("OnOfferAsync", onOfferAsync); + callHubConnection!.on("OnAnswerOffer", onAnswerOffer); + callHubConnection!.on("OnHangUpAsync", onHangUpAsync); + callHubConnection!.on("OnCallDeclinedAsync", onCallDeclinedAsync); + callHubConnection!.on('OnAudioToggle', onAudioToggle); } void initCallListeners() { - chatHubConnection!.on("OnCallAcceptedAsync", onCallAcceptedAsync); - chatHubConnection!.on("OnIceCandidateAsync", onIceCandidateAsync); - chatHubConnection!.on("OnOfferAsync", onOfferAsync); - chatHubConnection!.on("OnAnswerOffer", onAnswerOffer); - chatHubConnection!.on("OnHangUpAsync", onHangUpAsync); - chatHubConnection!.on("OnCallDeclinedAsync", onCallDeclinedAsync); - chatHubConnection!.on('OnAudioToggle', onAudioToggle); - chatHubConnection!.on('OnCameraToggle', onCameraToggle); - - // chatHubConnection!.on('OnIncomingCallAsync', onHandleIncomingCall); - // chatHubConnection!.on('OnCallAcceptedAsync', _handleCallAccepted); - // chatHubConnection!.on('OnCallDeclinedAsync', _handleCallDeclined); - // chatHubConnection!.on('OnHangUpAsync', onHangUpAsync); - // chatHubConnection!.on('OnOfferAsync', _handleOffer); - // chatHubConnection!.on('OnAnswerOfferAsync', onAnswerOffer); - // chatHubConnection!.on('OnIceCandidateAsync', _handleIceCandidate); - // chatHubConnection!.on('OnAudioToggle', _handleAudioToggle); - // chatHubConnection!.on('OnCameraToggle', _handleCameraToggle); - // chatHubConnection!.on('OnUserOnlineAsync', _handleUserOnline); - // chatHubConnection!.on('OnUserOfflineAsync', _handleUserOffline); - // chatHubConnection!.on('OnCallHistoryUpdated', _handleCallHistoryUpdated); - // chatHubConnection!.on('OnError', _handleError); + callHubConnection!.on("OnCallAcceptedAsync", onCallAcceptedAsync); + callHubConnection!.on("OnIceCandidateAsync", onIceCandidateAsync); + callHubConnection!.on("OnOfferAsync", onOfferAsync); + callHubConnection!.on("OnAnswerOffer", onAnswerOffer); + callHubConnection!.on("OnHangUpAsync", onHangUpAsync); + callHubConnection!.on("OnCallDeclinedAsync", onCallDeclinedAsync); + callHubConnection!.on('OnAudioToggle', onAudioToggle); + callHubConnection!.on('OnCameraToggle', onCameraToggle); + + // callHubConnection!.on('OnIncomingCallAsync', onHandleIncomingCall); + // callHubConnection!.on('OnCallAcceptedAsync', _handleCallAccepted); + // callHubConnection!.on('OnCallDeclinedAsync', _handleCallDeclined); + // callHubConnection!.on('OnHangUpAsync', onHangUpAsync); + // callHubConnection!.on('OnOfferAsync', _handleOffer); + // callHubConnection!.on('OnAnswerOfferAsync', onAnswerOffer); + // callHubConnection!.on('OnIceCandidateAsync', _handleIceCandidate); + // callHubConnection!.on('OnAudioToggle', _handleAudioToggle); + // callHubConnection!.on('OnCameraToggle', _handleCameraToggle); + // callHubConnection!.on('OnUserOnlineAsync', _handleUserOnline); + // callHubConnection!.on('OnUserOfflineAsync', _handleUserOffline); + // callHubConnection!.on('OnCallHistoryUpdated', _handleCallHistoryUpdated); + // callHubConnection!.on('OnError', _handleError); } //Video Constraints @@ -337,16 +323,16 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { } else if (invokeMethod == "AnswerOfferAsync") { args = [targetUserID, data]; } - await chatHubConnection!.invoke(invokeMethod, args: args); + await callHubConnection?.invoke(invokeMethod, args: args); } void stopListeners() async { - chatHubConnection!.off('OnCallDeclinedAsync'); - chatHubConnection!.off('OnCallAcceptedAsync'); - chatHubConnection!.off('OnIceCandidateAsync'); - chatHubConnection!.off('OnAnswerOffer'); - chatHubConnection!.off('OnOfferAsync'); - chatHubConnection!.off('OnHangUpAsync'); - chatHubConnection!.off('OnAudioToggle'); + callHubConnection?.off('OnCallDeclinedAsync'); + callHubConnection?.off('OnCallAcceptedAsync'); + callHubConnection?.off('OnIceCandidateAsync'); + callHubConnection?.off('OnAnswerOffer'); + callHubConnection?.off('OnOfferAsync'); + callHubConnection?.off('OnHangUpAsync'); + callHubConnection?.off('OnAudioToggle'); } } diff --git a/lib/new_views/pages/land_page/land_page.dart b/lib/new_views/pages/land_page/land_page.dart index 9d5b626c..ac765dfe 100644 --- a/lib/new_views/pages/land_page/land_page.dart +++ b/lib/new_views/pages/land_page/land_page.dart @@ -227,9 +227,10 @@ class _LandPageState extends State { break; case CallEventConstants.actionCallAccept: var activeCalls = await FlutterCallkitIncoming.activeCalls(); - if (activeCalls is List && activeCalls.isNotEmpty) { final lastCall = activeCalls.last; + await FlutterCallkitIncoming.endCall(lastCall.extra?["callId"]); + FlutterCallkitIncoming.endAllCalls(); Navigator.of(context).push(MaterialPageRoute( builder: (_) => AudioCallPage(sender: Participants(employeeNumber: lastCall.extra?["calleeEmployeeNumber"]), recipient: Participants(employeeNumber: lastCall.extra?["callerEmployeeNumber"]))));