@ -9,17 +9,27 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import ' package:flutter/cupertino.dart ' ;
import ' package:flutter/material.dart ' ;
import ' package:flutter_callkit_incoming/entities/android_params.dart ' ;
import ' package:flutter_callkit_incoming/entities/call_event.dart ' ;
import ' package:flutter_callkit_incoming/entities/call_kit_params.dart ' ;
import ' package:flutter_callkit_incoming/entities/ios_params.dart ' ;
import ' package:flutter_callkit_incoming/entities/notification_params.dart ' ;
import ' package:flutter_callkit_incoming/flutter_callkit_incoming.dart ' ;
import ' package:flutter_ios_voip_kit_karmm/call_state_type.dart ' ;
import ' package:flutter_ios_voip_kit_karmm/flutter_ios_voip_kit.dart ' ;
import ' package:hmg_patient_app_new/core/api_consts.dart ' ;
import ' package:hmg_patient_app_new/core/cache_consts.dart ' ;
import ' package:hmg_patient_app_new/core/utils/utils.dart ' ;
import ' package:hmg_patient_app_new/core/api/api_client.dart ' ;
import ' package:hmg_patient_app_new/core/dependencies.dart ' ;
import ' package:permission_handler/permission_handler.dart ' ;
import ' package:uuid/uuid.dart ' ;
/ / Global tracking variables for call state
bool _isCallInProgress = false ;
String ? _currentCallId ;
Timer ? _callTimeoutTimer ;
String ? _currentSessionId ; / / Store session ID for API call
/ / | - - > Push Notification Background
@ pragma ( ' vm:entry-point ' )
Future < dynamic > backgroundMessageHandler ( dynamic message ) async {
@ -39,34 +49,36 @@ callPage(String sessionID, String token) async {}
_incomingCall ( Map < String , dynamic > data ) async {
print ( ' the value of the _incomingCall remote message is $ data ' ) ;
/ / LandingPage. incomingCallData = IncomingCallData . fromJson ( data ) ;
/ / var dataItem = await AppSharedPreferences ( ) . getObject ( ' call_data ' ) ;
/ / if ( dataItem ! = null ) return ; / / to stop repeated attempt to invoke the call
/ / if ( LandingPage . isOpenCallPage = = false ) {
/ / LandingPage . isOpenCallPage = true ;
/ / Check if there ' s already a call in progress to prevent duplicates
if ( _isCallInProgress & & _currentCallId ! = null ) {
print ( ' ⚠️ Call already in progress (ID: $ _currentCallId ), ignoring duplicate notification ' ) ;
return ;
}
String roomID = data [ ' session_id ' ] ? ? ' ' ;
String callTypeID = data [ ' AppointmentNo ' ] ? ? ' ' ;
/ / GetIt . instance < CacheService > ( ) . saveString ( key: CacheConst . zoomRoomID , value: roomID ) ;
/ / GetIt . instance < CacheService > ( ) . saveBool ( key: CacheConst . isAppOpenedFromCall , value: true ) ;
/ / Generate unique call ID
var _currentUuid = Uuid ( ) . v4 ( ) ;
_currentCallId = _currentUuid ;
_isCallInProgress = true ;
_currentSessionId = roomID ; / / Store session ID for decline API call
print ( ' 📞 Starting new call with ID: $ _currentCallId , Session: $ _currentSessionId ' ) ;
await Utils . saveStringFromPrefs ( CacheConst . zoomRoomID , roomID ) ;
await Utils . saveStringFromPrefs ( CacheConst . callTypeID , callTypeID ) ;
await Utils . saveBoolFromPrefs ( CacheConst . isAppOpenedFromCall , true ) ;
WidgetsFlutterBinding . ensureInitialized ( ) ;
int callKitType = callTypeID = = ' 2 ' ? 0 : 1 ;
var _currentUuid = Uuid ( ) . v4 ( ) ;
CallKitParams callKitParams = CallKitParams (
id: _currentUuid ,
nameCaller: " Dr Al Habib " ,
appName: ' Dr Al Habib ' ,
avatar: ' https://play-lh.googleusercontent.com/FBNNpxb7m6eM6wtW7MV1Ffp6OXOGLI38q47zcvP29OCYA1yhYH5mZzl5itZi0TgOyZpG ' ,
/ / handle: LandingPage . incomingCallData . name ,
type: callKitType ,
textAccept: ' Accept ' ,
textDecline: ' Decline ' ,
@ -85,8 +97,8 @@ _incomingCall(Map<String, dynamic> data) async {
duration: 30000 ,
extra: < String , dynamic > {
' userId ' : ' 1a2b3c4d ' ,
' callTypeID ' : callTypeID , / / Store actual callTypeID here
' roomID ' : roomID , / / Store roomID here for backup
' callTypeID ' : callTypeID ,
' roomID ' : roomID ,
} ,
headers: < String , dynamic > { ' apiKey ' : ' Abc@123! ' , ' platform ' : ' flutter ' } ,
android: const AndroidParams (
@ -96,10 +108,10 @@ _incomingCall(Map<String, dynamic> data) async {
isShowLogo: false ,
logoUrl: " https://play-lh.googleusercontent.com/FBNNpxb7m6eM6wtW7MV1Ffp6OXOGLI38q47zcvP29OCYA1yhYH5mZzl5itZi0TgOyZpG " ,
ringtonePath: ' system_ringtone_default ' ,
backgroundColor: ' # ED1C2B ' ,
backgroundColor: ' # 2E3039 ' ,
backgroundUrl: ' https://saudipedia.com/en/saudipediaen/uploads/images/2024/08/02/97358.jpg ' ,
actionColor: ' #4CAF50 ' ,
textColor: ' # 000000 ' ,
textColor: ' # FFFFFF ' ,
incomingCallNotificationChannelName: " Incoming Call " ,
missedCallNotificationChannelName: " Missed Call " ,
isShowCallID: false ) ,
@ -120,12 +132,89 @@ _incomingCall(Map<String, dynamic> data) async {
ringtonePath: ' system_ringtone_default ' ,
) ,
) ;
FlutterCallkitIncoming . unsilenceEvents ( ) ;
await FlutterCallkitIncoming . showCallkitIncoming ( callKitParams ) ;
/ / Start 60 - second timeout timer to auto - dismiss call
_callTimeoutTimer ? . cancel ( ) ; / / Cancel any existing timer
_callTimeoutTimer = Timer ( Duration ( seconds: 40 ) , ( ) async {
print ( ' ⏱️ Call timeout (60 seconds) - auto dismissing call notification ' ) ;
await _endCallAndCleanup ( _currentUuid ) ;
} ) ;
await Future . delayed ( Duration ( milliseconds: 500 ) ) ;
}
/ / Helper method to end call and cleanup
Future < void > _endCallAndCleanup ( String callId , { bool isDeclined = false } ) async {
try {
print ( ' 🧹 Cleaning up call: $ callId ' ) ;
/ / If call was declined , update session status via API
if ( isDeclined & & _currentSessionId ! = null & & _currentSessionId ! . isNotEmpty ) {
await _updateSessionStatus ( _currentSessionId ! , 3 , ' Patient ' ) ;
}
/ / End the specific call
await FlutterCallkitIncoming . endCall ( callId ) ;
/ / Clear all calls to ensure clean state
await FlutterCallkitIncoming . endAllCalls ( ) ;
/ / Reset tracking flags
_isCallInProgress = false ;
_currentCallId = null ;
_currentSessionId = null ;
/ / Cancel timer
_callTimeoutTimer ? . cancel ( ) ;
_callTimeoutTimer = null ;
/ / Clear cache
await Utils . saveBoolFromPrefs ( CacheConst . isAppOpenedFromCall , false ) ;
await Utils . saveStringFromPrefs ( CacheConst . zoomRoomID , ' ' ) ;
await Utils . saveStringFromPrefs ( CacheConst . callTypeID , ' ' ) ;
print ( ' ✅ Call and push notification cleanup completed successfully ' ) ;
} catch ( e ) {
print ( ' ❌ Error during call cleanup: $ e ' ) ;
/ / Force reset flags even if there ' s an error
_isCallInProgress = false ;
_currentCallId = null ;
_currentSessionId = null ;
_callTimeoutTimer ? . cancel ( ) ;
_callTimeoutTimer = null ;
}
}
/ / Helper method to update session status via API
Future < void > _updateSessionStatus ( String sessionId , int sessionStatus , String sessionEndedBy ) async {
try {
final apiClient = getIt . get < ApiClient > ( ) ;
Map < String , dynamic > requestBody = {
" Open_SessionID " : sessionId ,
" SessionStatus " : sessionStatus ,
" SessionEndedBy " : sessionEndedBy
} ;
await apiClient . post (
CHANGE_PATIENT_ER_SESSION ,
body: requestBody ,
onSuccess: ( response , statusCode , { messageStatus , errorMessage } ) {
print ( ' ✅ Session status updated successfully: $ response ' ) ;
} ,
onFailure: ( error , statusCode , { messageStatus , failureType } ) {
print ( ' ❌ Failed to update session status: $ error ' ) ;
} ,
) ;
} catch ( e ) {
print ( ' ❌ Exception updating session status: $ e ' ) ;
}
}
Future < void > openCallPage ( BuildContext context ) async {
try {
/ / if ( incomingCallData ! . background = = " 0 " ) {
@ -291,6 +380,10 @@ class PushNotificationHandler {
try {
print ( ' 🎈 example: onDidRejectIncomingCall $ uuid - $ callerId ' ) ;
timeOutTimer . cancel ( ) ;
/ / Cleanup on reject with API call
if ( _currentCallId ! = null ) {
await _endCallAndCleanup ( _currentCallId ! , isDeclined: true ) ;
}
} catch ( err ) { }
} ;
@ -301,6 +394,12 @@ class PushNotificationHandler {
print ( ' 🎈 example: onDidAcceptIncomingCall $ uuid - $ callerId ' ) ;
await voIPKit . acceptIncomingCall ( callerState: CallStateType . calling ) ;
await voIPKit . callConnected ( ) ;
/ / Cleanup on accept - cancel timer and reset flags
_callTimeoutTimer ? . cancel ( ) ;
_callTimeoutTimer = null ;
_isCallInProgress = false ;
_currentCallId = null ;
await Future . delayed ( Duration ( seconds: 2 ) ) . then ( ( val ) async {
await voIPKit . endCall ( ) ;
} ) ;