From e834bf322b492977ed78c686ba8e6c13432f9d8f Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 30 Jul 2026 15:16:26 +0300 Subject: [PATCH] cont --- .../chat/calling/audio_call_page.dart | 43 +++++- .../cx_module/chat/calling/call_provider.dart | 94 +++++++++---- lib/new_views/pages/land_page/land_page.dart | 123 ++++++++++-------- 3 files changed, 181 insertions(+), 79 deletions(-) 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 afc15325..e1d2386d 100644 --- a/lib/modules/cx_module/chat/calling/audio_call_page.dart +++ b/lib/modules/cx_module/chat/calling/audio_call_page.dart @@ -6,6 +6,7 @@ import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/modules/cx_module/chat/model/chat_participant_model.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/swipe_module/dialoge/single_btn_dialog.dart'; import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import '../chat_provider.dart'; @@ -14,8 +15,9 @@ import 'call_provider.dart'; class AudioCallPage extends StatefulWidget { Participants sender; Participants recipient; + bool isAccept; - AudioCallPage({Key? key, required this.sender, required this.recipient}) : super(key: key); + AudioCallPage({Key? key, required this.sender, required this.recipient, this.isAccept = false}) : super(key: key); @override _AudioCallPageState createState() { @@ -30,6 +32,7 @@ class _AudioCallPageState extends State { // Participants? participants; String currentStatus = "Connecting..."; + bool isDialogOpen = false; @override void initState() { @@ -50,7 +53,41 @@ class _AudioCallPageState extends State { return; } callPro.initAudioCallListeners(); - callPro.startCall(widget.sender.employeeNumber ?? "", widget.recipient?.employeeNumber ?? "", false, null, null); + if (widget.isAccept) { + callPro.acceptCall(widget.sender.employeeNumber ?? "", widget.recipient?.employeeNumber ?? "", false); + } else { + callPro.startCall(widget.sender.employeeNumber ?? "", widget.recipient?.employeeNumber ?? "", false, null, null, onSuccess: (String msg) { + callPro.dispose(); + Navigator.pop(context); + }, onFailed: (String msg) { + if (isDialogOpen) { + return; + } + isDialogOpen = true; + showDialog( + context: context, + barrierDismissible: false, + useRootNavigator: false, + builder: (BuildContext cxt) => PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) { + if (didPop) return; + }, + child: SingleBtnDialog( + title: "Failed", + message: msg, + okTitle: "Go Back", + onTap: () { + Navigator.pop(context); + callPro.dispose(); + Navigator.pop(context); + }), + ), + ).then((val) { + isDialogOpen = false; + }); + }); + } Map json = { // "callerID": AppState().chatDetails!.response!.id!.toString(), @@ -148,7 +185,7 @@ class _AudioCallPageState extends State { callPro.toggleMute(); }), callButton("calling/end_call", () async { - callPro.endCall(); + callPro.endCall(widget.sender.employeeNumber ?? "", widget.recipient.employeeNumber ?? ""); 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 8eafc71c..e1a008b8 100644 --- a/lib/modules/cx_module/chat/calling/call_provider.dart +++ b/lib/modules/cx_module/chat/calling/call_provider.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:math'; import 'dart:ui'; import 'package:flutter/cupertino.dart'; @@ -61,26 +62,24 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { return hub; } - Future buildHubConnection(String employeeNumber) async { + Future buildHubConnection(String employeeNumber, {HubConnection? hubC}) async { + if (hubC != null) { + callHubConnection = hubC; + await callHubConnection!.start(); + return true; + } try { await _disposeConnection(); - // 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; } - // isLoading = false; - // notifyListeners(); + return false; } catch (e) { - print("buildHubConnectionE:$e"); await _disposeConnection(); return false; } @@ -214,35 +213,40 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); } - late String senderN; - late String recipientN; - late bool isVideo; + String? senderN; + String? recipientN; + bool? isVideo; int? moduleId; int? referenceID; - void startCall(String senderN, String recipientN, bool isVideo, int? moduleId, int? referenceID) async { + Function(String message)? onFailed; + Function(String message)? onSuccess; + + void startCall(String? senderN, String? recipientN, bool? isVideo, int? moduleId, int? referenceID, {Function(String message)? onFailed, Function(String message)? onSuccess}) async { this.senderN = senderN; this.recipientN = recipientN; this.isVideo = isVideo; this.moduleId = moduleId; this.referenceID = referenceID; - invoke(invokeMethod: "CallUserAsync", currentUserID: senderN, targetUserID: recipientN, isVideoCall: isVideo); + this.onFailed = onFailed; + this.onSuccess = onSuccess; + invoke(invokeMethod: "CallUserAsync", currentUserID: senderN!, targetUserID: recipientN!, isVideoCall: isVideo!); } - void acceptCall(String senderN, String recipientN, bool isVideo, int moduleId, int? referenceID) async { + void acceptCall(String senderN, String recipientN, bool isVideo) async { invoke(invokeMethod: "AnswerCallAsync", currentUserID: senderN, targetUserID: recipientN, isVideoCall: isVideo); } - void declineCall(String senderN, String recipientN, bool isVideo, int moduleId, int? referenceID) async { - invoke(invokeMethod: "CallDeclinedAsync", currentUserID: senderN, targetUserID: recipientN, isVideoCall: isVideo); + Future declineCall(String senderN, String recipientN) async { + await invoke(invokeMethod: "CallDeclinedAsync", currentUserID: senderN, targetUserID: recipientN, isVideoCall: false); } - void endCall() { - invoke(invokeMethod: "HangUpAsync", currentUserID: senderN, targetUserID: recipientN, data: moduleId); + void endCall(String senderN, String recipientN) { + invoke(invokeMethod: "HangUpAsync", currentUserID: senderN, targetUserID: recipientN); } void toggleMute() { - invoke(invokeMethod: "AudioToggle", currentUserID: senderN, targetUserID: recipientN, isVideoCall: isVideo); + invoke(invokeMethod: "AudioToggle", currentUserID: senderN!, targetUserID: recipientN!, isVideoCall: isVideo!); } void checkCall(Map message) { @@ -280,6 +284,7 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { void onOfferAsync(List? params) { print("onCallAcceptedAsync: $params"); + print("onCallAcceptedAsync: $params"); } void onAnswerOffer(List? params) { @@ -288,10 +293,32 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { void onHangUpAsync(List? params) { print("onHangUpAsync: $params"); + handleResponse(params); } void onCallDeclinedAsync(List? params) { print("onCallDeclinedAsync: $params"); + handleResponse(params); + // try { + // if (params?.isNotEmpty ?? false) { + // print("onCallDeclinedAsync:${params?.length}:${params}"); + // if (params!.length > 2) { + // print("runTimeData:${params[1].runtimeType}"); + // var data = jsonDecode(jsonEncode(params[1].toString())); + // print("runTimeData:${data.runtimeType}"); + // onFailed?.call("Call Declined, ${data["targetUserId"]} has declined your call."); + // return; + // } else { print("runTimeData:${params[0].runtimeType}"); + // var data = jsonDecode(params[0].toString()); + // print("runTimeData:${data.runtimeType}"); + // onFailed?.call("Call Declined, ${data["targetUserId"]} has declined your call."); + // return; + // } + // } + // } catch (ex) { + // print("Ex:$ex"); + // onFailed?.call("Call Declined, User has declined your call."); + // } } void onAudioToggle(List? params) { @@ -308,12 +335,14 @@ 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 (invokeMethod == "answerCallAsync") { + if (invokeMethod == "AnswerCallAsync") { + args = [currentUserID, targetUserID]; + } else if (invokeMethod == 'CallDeclinedAsync') { args = [currentUserID, targetUserID]; } else if (invokeMethod == "CallUserAsync") { args = [currentUserID, targetUserID, isVideoCall]; - } else if (invokeMethod == "HangUpAsync ") { - args = [currentUserID, targetUserID, data]; + } else if (invokeMethod == "HangUpAsync") { + args = [currentUserID, targetUserID]; } else if (invokeMethod == "AudioToggle") { args = [currentUserID, targetUserID, isVideoCall]; } else if (invokeMethod == "IceCandidateAsync") { @@ -323,6 +352,7 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { } else if (invokeMethod == "AnswerOfferAsync") { args = [targetUserID, data]; } + print("Invoking:$invokeMethod:${args}"); await callHubConnection?.invoke(invokeMethod, args: args); } @@ -335,4 +365,22 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { callHubConnection?.off('OnHangUpAsync'); callHubConnection?.off('OnAudioToggle'); } + + handleResponse(List? params) { + if (params?.isNotEmpty ?? false) { + if (params![0] == "NO_ANSWER") { + onFailed?.call("No Answer, User did not answer your call."); + return; + } else if (params[0] == "NO_ANSWER") { + onFailed?.call("Call Declined, User has declined your call."); + return; + } else if (params[0] == 'USER_BUSY') { + onFailed?.call("The user is currently busy on another call"); + } else if (params[0] == 'USER_OFFLINE') { + onFailed?.call("The user is currently offline"); + } else { + onSuccess?.call(""); + } + } + } } diff --git a/lib/new_views/pages/land_page/land_page.dart b/lib/new_views/pages/land_page/land_page.dart index ac765dfe..3b6b0de7 100644 --- a/lib/new_views/pages/land_page/land_page.dart +++ b/lib/new_views/pages/land_page/land_page.dart @@ -18,6 +18,7 @@ import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/models/enums/user_types.dart'; import 'package:test_sa/modules/cx_module/chat/calling/audio_call_page.dart'; +import 'package:test_sa/modules/cx_module/chat/calling/call_provider.dart'; import 'package:test_sa/modules/cx_module/chat/model/chat_participant_model.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; @@ -216,60 +217,76 @@ class _LandPageState extends State { } callingEvents(BuildContext context) { - FlutterCallkitIncoming.onEvent.listen((CallEvent? event) async { - switch (event!.eventName) { - case CallEventConstants.actionCallIncoming: - // TODO: received an incoming call - break; - case CallEventConstants.actionCallStart: - // TODO: started an outgoing call - // TODO: show screen calling in Flutter - 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"])))); - } - break; - case CallEventConstants.actionCallDecline: - // TODO: declined an incoming call - break; - case CallEventConstants.actionCallEnded: - // TODO: ended an incoming/outgoing call - break; - case CallEventConstants.actionCallTimeout: - // TODO: missed an incoming call - break; - case CallEventConstants.actionCallCallback: - // TODO: click action `Call back` from missed call notification - break; - case CallEventConstants.actionCallToggleHold: - // TODO: only iOS - break; - case CallEventConstants.actionCallToggleMute: - // TODO: only iOS - break; - case CallEventConstants.actionCallToggleDmtf: - // TODO: only iOS - break; - case CallEventConstants.actionCallToggleGroup: - // TODO: only iOS - break; - case CallEventConstants.actionCallToggleAudioSession: - // TODO: only iOS - break; - case CallEventConstants.actionDidUpdateDevicePushTokenVoip: - // TODO: only iOS - break; - case CallEventConstants.actionCallCustom: - // TODO: for custom action - break; + FlutterCallkitIncoming.onEvent.listen((CallEvent? event) { + if (event == null) return; + + switch (event) { + case CallEventActionCallAccept(:final callKitParams): + _handleCallAccepted(callKitParams); + + case CallEventActionCallDecline(:final callKitParams): + _handleCallDeclined(callKitParams); + + case CallEventActionDidUpdateDevicePushTokenVoip(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallIncoming(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallStart(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallEnded(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallTimeout(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallConnected(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallCallback(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallToggleHold(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallToggleMute(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallToggleDmtf(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallToggleGroup(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallToggleAudioSession(): + // TODO: Handle this case. + throw UnimplementedError(); + case CallEventActionCallCustom(): + // TODO: Handle this case. + throw UnimplementedError(); } }); } + + Future _handleCallDeclined(CallKitParams callKitParams) async { + FlutterCallkitIncoming.endCall(callKitParams.extra?["callId"]); + 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"]); + calPro.dispose(); + } + } + + void _handleCallAccepted(CallKitParams callKitParams) { + FlutterCallkitIncoming.endCall(callKitParams.extra?["callId"]); + FlutterCallkitIncoming.endAllCalls(); + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => AudioCallPage( + sender: Participants(employeeNumber: callKitParams.extra?["calleeEmployeeNumber"]), + recipient: Participants(employeeNumber: callKitParams.extra?["callerEmployeeNumber"]), + isAccept: true))); + } }