diff --git a/lib/controllers/notification/firebase_notification_manger.dart b/lib/controllers/notification/firebase_notification_manger.dart index 51fde1d1..913f7757 100644 --- a/lib/controllers/notification/firebase_notification_manger.dart +++ b/lib/controllers/notification/firebase_notification_manger.dart @@ -324,10 +324,18 @@ Future handleDataMessage(Map messageData, bool isFromBack return false; }(); - final moduleId = messageData['moduleId'] as String? ?? messageData['applicationId']?.toString(); + String? moduleId = messageData['moduleId'] as String? ?? messageData['applicationId']?.toString(); + + if (messageData['moduleId'] != null && messageData['moduleId'].toString().isNotEmpty) { + moduleId = messageData['moduleId']?.toString(); + } else if (messageData['applicationId'] != null && messageData['applicationId'].toString().isNotEmpty) { + moduleId = messageData['applicationId']?.toString(); + } final referenceId = messageData['referenceId'] as String?; final conversationId = messageData['conversationId'] as String?; + print("handleDataMessage:moduleId:$moduleId"); + WidgetsFlutterBinding.ensureInitialized(); Timer? _callTimeoutTimer; try { diff --git a/lib/main.dart b/lib/main.dart index 2d365cfb..3c151786 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -158,7 +158,9 @@ import 'modules/cleaner_module/provider/cleaner_provider.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - FlutterCallkitIncoming.onBackgroundMessage(onBackgroundMessage); + if (Platform.isAndroid) { + FlutterCallkitIncoming.onBackgroundMessage(onBackgroundMessage); + } FlutterCallkitIncoming.endAllCalls(); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); // HttpOverrides.global = MyHttpOverrides(); // for later use. 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 e1d2386d..34c76582 100644 --- a/lib/modules/cx_module/chat/calling/audio_call_page.dart +++ b/lib/modules/cx_module/chat/calling/audio_call_page.dart @@ -15,9 +15,11 @@ import 'call_provider.dart'; class AudioCallPage extends StatefulWidget { Participants sender; Participants recipient; + int moduleId; + int? referenceID; bool isAccept; - AudioCallPage({Key? key, required this.sender, required this.recipient, this.isAccept = false}) : super(key: key); + AudioCallPage({Key? key, required this.sender, required this.recipient, this.isAccept = false, required this.moduleId, this.referenceID}) : super(key: key); @override _AudioCallPageState createState() { @@ -31,7 +33,6 @@ class _AudioCallPageState extends State { // Participants? participants; - String currentStatus = "Connecting..."; bool isDialogOpen = false; @override @@ -54,12 +55,14 @@ class _AudioCallPageState extends State { } callPro.initAudioCallListeners(); if (widget.isAccept) { - callPro.acceptCall(widget.sender.employeeNumber ?? "", widget.recipient?.employeeNumber ?? "", false); + callPro.acceptCall(widget.sender.employeeNumber ?? "", widget.recipient?.employeeNumber ?? "", false, moduleId: widget.moduleId); } else { - callPro.startCall(widget.sender.employeeNumber ?? "", widget.recipient?.employeeNumber ?? "", false, null, null, onSuccess: (String msg) { + callPro.startCall(widget.sender.employeeNumber ?? "", widget.recipient?.employeeNumber ?? "", false, widget.moduleId, widget.referenceID, onSuccess: (String msg) { callPro.dispose(); Navigator.pop(context); }, onFailed: (String msg) { + callPro.dispose(); + print("isDialogOpen:$isDialogOpen"); if (isDialogOpen) { return; } @@ -79,7 +82,6 @@ class _AudioCallPageState extends State { okTitle: "Go Back", onTap: () { Navigator.pop(context); - callPro.dispose(); Navigator.pop(context); }), ), @@ -174,18 +176,29 @@ class _AudioCallPageState extends State { 24.height, InfoTextLabelWidget(label: chatProvider.recipient?.userName ?? "", textStyle: AppTextStyles.heading4.copyWith(fontWeight: FontWeight.w600), color: Colors.white), 4.height, - InfoTextLabelWidget(label: currentStatus, textStyle: AppTextStyles.heading6, color: Colors.white) + Selector( + selector: (_, myModel) => myModel.currentStatus, // Selects only the userName + builder: (_, currentStatus, __) => InfoTextLabelWidget(label: currentStatus, textStyle: AppTextStyles.heading6, color: Colors.white), + ), + 4.height, + Selector( + selector: (_, myModel) => myModel.isPeerMuted, + builder: (_, isPeerMuted, __) => isPeerMuted ? InfoTextLabelWidget(label: "Mic Mute", textStyle: AppTextStyles.tinyFont, color: AppColor.red30) : const SizedBox(), + ), ], ), const Spacer(), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - callButton("calling/mic_enable", () { - callPro.toggleMute(); - }), + Selector( + selector: (_, myModel) => myModel.isMuted, // Selects only the userName + builder: (_, isMuted, __) => callButton(isMuted ? "calling/mic_disable" : "calling/mic_enable", () { + callPro.toggleMute(); + }), + ), callButton("calling/end_call", () async { - callPro.endCall(widget.sender.employeeNumber ?? "", widget.recipient.employeeNumber ?? ""); + callPro.endCall(widget.sender.employeeNumber ?? "", widget.recipient.employeeNumber ?? "", moduleId: widget.moduleId); Navigator.pop(context); }, isEndCall: true, size: 68), callButton("calling/speaker_enable", () {}), diff --git a/lib/modules/cx_module/chat/calling/call_provider.dart b/lib/modules/cx_module/chat/calling/call_provider.dart index e1a008b8..defbc775 100644 --- a/lib/modules/cx_module/chat/calling/call_provider.dart +++ b/lib/modules/cx_module/chat/calling/call_provider.dart @@ -35,6 +35,8 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { bool isLoading = false; + String currentStatus = "Connecting..."; + Future _disposeConnection() async { try { if (callHubConnection != null) { @@ -216,7 +218,7 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { String? senderN; String? recipientN; bool? isVideo; - int? moduleId; + int moduleId = 1004; int? referenceID; Function(String message)? onFailed; @@ -226,27 +228,29 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { this.senderN = senderN; this.recipientN = recipientN; this.isVideo = isVideo; - this.moduleId = moduleId; + // this.moduleId = moduleId; this.referenceID = referenceID; this.onFailed = onFailed; this.onSuccess = onSuccess; - invoke(invokeMethod: "CallUserAsync", currentUserID: senderN!, targetUserID: recipientN!, isVideoCall: isVideo!); + invoke(invokeMethod: "CallUserAsync", currentUserID: senderN!, targetUserID: recipientN!, isVideoCall: isVideo!, data: [this.moduleId, referenceID]); } - void acceptCall(String senderN, String recipientN, bool isVideo) async { - invoke(invokeMethod: "AnswerCallAsync", currentUserID: senderN, targetUserID: recipientN, isVideoCall: isVideo); + void acceptCall(String senderN, String recipientN, bool isVideo, {required int moduleId}) async { + invoke(invokeMethod: "AnswerCallAsync", currentUserID: senderN, targetUserID: recipientN, isVideoCall: isVideo, data: [this.moduleId]); } - Future declineCall(String senderN, String recipientN) async { - await invoke(invokeMethod: "CallDeclinedAsync", currentUserID: senderN, targetUserID: recipientN, isVideoCall: false); + Future declineCall(String senderN, String recipientN, {required int moduleId}) async { + await invoke(invokeMethod: "CallDeclinedAsync", currentUserID: senderN, targetUserID: recipientN, isVideoCall: false, data: [this.moduleId]); } - void endCall(String senderN, String recipientN) { - invoke(invokeMethod: "HangUpAsync", currentUserID: senderN, targetUserID: recipientN); + void endCall(String senderN, String recipientN, {required int moduleId}) { + invoke(invokeMethod: "HangUpAsync", currentUserID: senderN, targetUserID: recipientN, data: [moduleId]); } - void toggleMute() { - invoke(invokeMethod: "AudioToggle", currentUserID: senderN!, targetUserID: recipientN!, isVideoCall: isVideo!); + void toggleMute() async { + await invoke(invokeMethod: "AudioToggle", currentUserID: senderN!, targetUserID: recipientN!); + isMuted = !isMuted; + notifyListeners(); } void checkCall(Map message) { @@ -323,7 +327,7 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { void onAudioToggle(List? params) { print("onAudioToggle: $params"); - isMuted = !isMuted; + isPeerMuted = !isPeerMuted; notifyListeners(); } @@ -335,16 +339,19 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { Future invoke({required String invokeMethod, required String currentUserID, required String targetUserID, bool isVideoCall = false, var data}) async { List args = []; + if (data != null) { + data = data.toString().replaceFirst("[", "").replaceFirst("]", ""); + } if (invokeMethod == "AnswerCallAsync") { - args = [currentUserID, targetUserID]; + args = [currentUserID, targetUserID, data]; } else if (invokeMethod == 'CallDeclinedAsync') { - args = [currentUserID, targetUserID]; + args = [currentUserID, targetUserID, data]; } else if (invokeMethod == "CallUserAsync") { args = [currentUserID, targetUserID, isVideoCall]; } else if (invokeMethod == "HangUpAsync") { - args = [currentUserID, targetUserID]; + args = [currentUserID, targetUserID, data]; } else if (invokeMethod == "AudioToggle") { - args = [currentUserID, targetUserID, isVideoCall]; + args = [currentUserID, targetUserID]; } else if (invokeMethod == "IceCandidateAsync") { args = [targetUserID, data]; } else if (invokeMethod == "OfferAsync") { @@ -376,6 +383,8 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { return; } else if (params[0] == 'USER_BUSY') { onFailed?.call("The user is currently busy on another call"); + } else if (params[0] == 'USER_DECLINE') { + onFailed?.call("Call Declined, User has declined your call."); } else if (params[0] == 'USER_OFFLINE') { onFailed?.call("The user is currently offline"); } else { diff --git a/lib/modules/cx_module/chat/chat_page.dart b/lib/modules/cx_module/chat/chat_page.dart index 53c0ce8a..66b0aa56 100644 --- a/lib/modules/cx_module/chat/chat_page.dart +++ b/lib/modules/cx_module/chat/chat_page.dart @@ -202,75 +202,95 @@ class _ChatPageState extends State { appBar: DefaultAppBar( title: widget.title, actions: [ - IconButton( - icon: "calling/audio_call_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), - padding: EdgeInsets.zero, - visualDensity: VisualDensity.compact, - tooltip: "Audio", - onPressed: () async { - bool audioPermission = await Provider.of(context, listen: false).checkAudioPermissions(); - if (!audioPermission) { - context.showConfirmDialog("To perform audio call, we need permission to access microphone.", title: "Permission Required", onTap: () async { - Navigator.pop(context); - audioPermission = await Provider.of(context, listen: false).requestAudioPermissions(); - if (!audioPermission) { - bool openSetting = await showDialog( - context: context, - builder: (_) => const AAlertDialog( - title: "Permission denied", - content: "Permission is denied. Please go to application Setting to enable it.", - button1Text: "Cancel", - button2Text: "Open Setting", + Selector( + selector: (_, provider) => _ChatConnectionState( + isLoading: provider.chatLoginTokenLoading, + loginResponse: provider.chatLoginResponse, + ), + builder: (context, connectionState, child) { + if (!connectionState.isLoading && connectionState.loginResponse == null) { + return SizedBox(); + } else { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: "calling/audio_call_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + tooltip: "Audio", + onPressed: () async { + bool audioPermission = await Provider.of(context, listen: false).checkAudioPermissions(); + if (!audioPermission) { + context.showConfirmDialog("To perform audio call, we need permission to access microphone.", title: "Permission Required", onTap: () async { + Navigator.pop(context); + audioPermission = await Provider.of(context, listen: false).requestAudioPermissions(); + if (!audioPermission) { + bool openSetting = await showDialog( + context: context, + builder: (_) => const AAlertDialog( + title: "Permission denied", + content: "Permission is denied. Please go to application Setting to enable it.", + button1Text: "Cancel", + button2Text: "Open Setting", + ), + ); + if (!openSetting) PermissionHandler.openAppSettings(); + return; + } else { + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => AudioCallPage( + sender: Provider.of(context, listen: false).sender!, + recipient: Provider.of(context, listen: false).recipient!, + moduleId: widget.moduleId, + referenceID: widget.requestId, + ))); + } + }); + } else { + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => AudioCallPage( + sender: Provider.of(context, listen: false).sender!, + recipient: Provider.of(context, listen: false).recipient!, + moduleId: widget.moduleId, + referenceID: widget.requestId, + ))); + } + }, ), - ); - if (!openSetting) PermissionHandler.openAppSettings(); - return; - } else { - Navigator.of(context).push(MaterialPageRoute( - builder: (_) => AudioCallPage( - sender: Provider.of(context, listen: false).sender!, - recipient: Provider.of(context, listen: false).recipient!, - ))); - } - }); - } else { - Navigator.of(context).push(MaterialPageRoute( - builder: (_) => AudioCallPage( - sender: Provider.of(context, listen: false).sender!, - recipient: Provider.of(context, listen: false).recipient!, - ))); - } - }, - ), - IconButton( - icon: "calling/video_call_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), - padding: EdgeInsets.zero, - visualDensity: VisualDensity.compact, - tooltip: "Video", - onPressed: () async { - bool videoPermission = await Provider.of(context, listen: false).checkVideoPermissions(); - if (!videoPermission) { - context.showConfirmDialog("To perform Video call, we need permission to access microphone & Camera.", title: "Permission Required", onTap: () async { - Navigator.pop(context); - videoPermission = await Provider.of(context, listen: false).requestVideoPermissions(); - if (videoPermission) { - bool openSetting = await showDialog( - context: context, - builder: (_) => const AAlertDialog( - title: "Permission denied", - content: "Permission is denied. Please go to application Setting to enable it.", - button1Text: "Cancel", - button2Text: "Open Setting", + IconButton( + icon: "calling/video_call_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + tooltip: "Video", + onPressed: () async { + bool videoPermission = await Provider.of(context, listen: false).checkVideoPermissions(); + if (!videoPermission) { + context.showConfirmDialog("To perform Video call, we need permission to access microphone & Camera.", title: "Permission Required", onTap: () async { + Navigator.pop(context); + videoPermission = await Provider.of(context, listen: false).requestVideoPermissions(); + if (videoPermission) { + bool openSetting = await showDialog( + context: context, + builder: (_) => const AAlertDialog( + title: "Permission denied", + content: "Permission is denied. Please go to application Setting to enable it.", + button1Text: "Cancel", + button2Text: "Open Setting", + ), + ); + if (!openSetting) PermissionHandler.openAppSettings(); + return; + } + }); + } + Navigator.of(context).push(MaterialPageRoute(builder: (_) => CallPage(isAudio: false))); + }, ), - ); - if (!openSetting) PermissionHandler.openAppSettings(); - return; - } - }); - } - Navigator.of(context).push(MaterialPageRoute(builder: (_) => CallPage(isAudio: false))); - }, - ), + ], + ); + } + }), ], ), body: Selector( diff --git a/lib/new_views/pages/land_page/land_page.dart b/lib/new_views/pages/land_page/land_page.dart index 3b6b0de7..de170023 100644 --- a/lib/new_views/pages/land_page/land_page.dart +++ b/lib/new_views/pages/land_page/land_page.dart @@ -219,7 +219,7 @@ class _LandPageState extends State { callingEvents(BuildContext context) { FlutterCallkitIncoming.onEvent.listen((CallEvent? event) { if (event == null) return; - + print("callingEvents:$event"); switch (event) { case CallEventActionCallAccept(:final callKitParams): _handleCallAccepted(callKitParams); @@ -236,9 +236,8 @@ class _LandPageState extends State { case CallEventActionCallStart(): // TODO: Handle this case. throw UnimplementedError(); - case CallEventActionCallEnded(): - // TODO: Handle this case. - throw UnimplementedError(); + case CallEventActionCallEnded(:final callKitParams): + _handleCallEnded(callKitParams); case CallEventActionCallTimeout(): // TODO: Handle this case. throw UnimplementedError(); @@ -275,11 +274,16 @@ class _LandPageState extends State { var calPro = Provider.of(context, listen: false); bool isConnected = await calPro.buildHubConnection(callKitParams.extra?["calleeEmployeeNumber"]); if (isConnected) { - await calPro.declineCall(callKitParams.extra?["calleeEmployeeNumber"], callKitParams.extra?["callerEmployeeNumber"]); + await calPro.declineCall(callKitParams.extra?["calleeEmployeeNumber"], callKitParams.extra?["callerEmployeeNumber"], moduleId: int.parse(callKitParams.extra!["moduleId"])); calPro.dispose(); } } + Future _handleCallEnded(CallKitParams callKitParams) async { + FlutterCallkitIncoming.endCall(callKitParams.extra?["callId"]); + FlutterCallkitIncoming.endAllCalls(); + } + void _handleCallAccepted(CallKitParams callKitParams) { FlutterCallkitIncoming.endCall(callKitParams.extra?["callId"]); FlutterCallkitIncoming.endAllCalls(); @@ -287,6 +291,7 @@ class _LandPageState extends State { builder: (_) => AudioCallPage( sender: Participants(employeeNumber: callKitParams.extra?["calleeEmployeeNumber"]), recipient: Participants(employeeNumber: callKitParams.extra?["callerEmployeeNumber"]), + moduleId: int.parse(callKitParams.extra!["moduleId"]), isAccept: true))); } }