From b21142e4af17cafeef82881c0ff2d76ab3da14be Mon Sep 17 00:00:00 2001 From: "Aamir.Muhammad" Date: Mon, 20 Feb 2023 16:11:05 +0300 Subject: [PATCH] Webrtc Calling --- lib/classes/notifications.dart | 1 - lib/classes/voip_chat_call.dart | 155 +++++--- lib/config/routes.dart | 4 + lib/models/chat/incomingCall.dart | 425 ++++++++++++++++++++ lib/provider/chat_call_provider.dart | 66 +--- lib/provider/chat_provider_model.dart | 10 +- lib/ui/chat/call/start_call_screen.dart | 494 ++++++++++++++---------- lib/ui/login/login_screen.dart | 69 ++-- 8 files changed, 877 insertions(+), 347 deletions(-) create mode 100644 lib/models/chat/incomingCall.dart diff --git a/lib/classes/notifications.dart b/lib/classes/notifications.dart index 3d82570..30fc809 100644 --- a/lib/classes/notifications.dart +++ b/lib/classes/notifications.dart @@ -133,6 +133,5 @@ Future backgroundMessageHandler(RemoteMessage message) async { await Firebase.initializeApp(); Utils.saveStringFromPrefs("isAppOpendByChat", "false"); Utils.saveStringFromPrefs("notificationData", message.data["user_chat_history_response"].toString()); - ChatVoipCall().showCallkitIncoming(uuid: const Uuid().v4(), data: message); } diff --git a/lib/classes/voip_chat_call.dart b/lib/classes/voip_chat_call.dart index f96fb19..266dc01 100644 --- a/lib/classes/voip_chat_call.dart +++ b/lib/classes/voip_chat_call.dart @@ -1,10 +1,14 @@ import 'dart:convert'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_callkit_incoming/entities/entities.dart'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/main.dart'; import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.dart'; +import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart' as ALM; class ChatVoipCall { static final ChatVoipCall _instance = ChatVoipCall._internal(); @@ -14,51 +18,112 @@ class ChatVoipCall { factory ChatVoipCall() => _instance; Future showCallkitIncoming({required String uuid, required RemoteMessage data}) async { - logger.d(data.data["user_chat_history_response"]); - + await ChatVoipCall().listenerEvent(); SingleUserChatModel callerData = SingleUserChatModel.fromJson(jsonDecode(data.data["user_chat_history_response"])); - var params = CallKitParams( - id: uuid, - nameCaller: callerData.currentUserName, - appName: 'Mohemm', - avatar: 'https://i.pravatar.cc/100', - handle: '0123456789', - type: 0, - duration: 30000, - textAccept: 'Accept', - textDecline: 'Decline', - textMissedCall: 'Missed call', - textCallback: 'Call back', - extra: {'userId': '1a2b3c4d'}, - headers: {'apiKey': 'Abc@123!', 'platform': 'flutter'}, - android: const AndroidParams( - isCustomNotification: true, - isShowLogo: false, - isShowCallback: false, - isShowMissedCallNotification: true, - ringtonePath: 'system_ringtone_default', - backgroundColor: '#0955fa', - backgroundUrl: 'assets/test.png', - actionColor: '#4CAF50', - ), - ios: IOSParams( - iconName: 'CallKitLogo', - handleType: '', - supportsVideo: true, - maximumCallGroups: 2, - maximumCallsPerCallGroup: 1, - audioSessionMode: 'default', - audioSessionActive: true, - audioSessionPreferredSampleRate: 44100.0, - audioSessionPreferredIOBufferDuration: 0.005, - supportsDTMF: true, - supportsHolding: true, - supportsGrouping: false, - supportsUngrouping: false, - ringtonePath: 'system_ringtone_default', - ), - ); - logger.d(callerData.targetUserId); - await FlutterCallkitIncoming.showCallkitIncoming(params); + ALM.Response autoLoginData = ALM.Response.fromJson(jsonDecode(data.data["user_token_response"])); + var values = { + "loginDetails": autoLoginData.toJson(), + "callerDetails": callerData.toJson(), + }; + logger.d(values); + CallKitParams params = CallKitParams( + id: uuid, + nameCaller: callerData.targetUserName, + appName: 'Mohemm', + handle: '', + type: 0, + duration: 25000, + textAccept: 'Accept', + textDecline: 'Decline', + textMissedCall: 'Missed call', + textCallback: 'Call back', + extra: values, + android: const AndroidParams( + isCustomNotification: true, + isShowLogo: true, + isShowCallback: false, + isShowMissedCallNotification: true, + ringtonePath: 'system_ringtone_default', + backgroundColor: '#0955fa', + backgroundUrl: 'assets/test.png', + actionColor: '#4CAF50', + ), + ios: IOSParams( + iconName: 'Mohemm', + handleType: '', + supportsVideo: true, + maximumCallGroups: 2, + maximumCallsPerCallGroup: 1, + audioSessionMode: 'default', + audioSessionActive: true, + audioSessionPreferredSampleRate: 38000.0, + audioSessionPreferredIOBufferDuration: 0.005, + supportsDTMF: true, + supportsHolding: true, + supportsGrouping: false, + supportsUngrouping: false, + ringtonePath: 'system_ringtone_default', + ), + ); + if (callerData.chatEventId == 3) { + await Utils.saveStringFromPrefs("isIncomingCall", "true"); + await Utils.saveStringFromPrefs("inComingCallData",jsonEncode(params.toJson())); + await FlutterCallkitIncoming.showCallkitIncoming(params); + } + } + +//Function(CallEvent) callback + Future listenerEvent() async { + try { + FlutterCallkitIncoming.onEvent.listen((event) async { + switch (event!.event) { + case Event.ACTION_CALL_INCOMING: + // TODO: received an incoming call + break; + case Event.ACTION_CALL_START: + // TODO: started an outgoing call + // TODO: show screen calling in Flutter + break; + case Event.ACTION_CALL_ACCEPT: + break; + case Event.ACTION_CALL_DECLINE: + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + + break; + case Event.ACTION_CALL_ENDED: + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + + // TODO: ended an incoming/outgoing call + break; + case Event.ACTION_CALL_TIMEOUT: + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + break; + case Event.ACTION_CALL_CALLBACK: + // TODO: only Android - click action `Call back` from missed call notification + break; + case Event.ACTION_CALL_TOGGLE_HOLD: + // TODO: only iOS + break; + case Event.ACTION_CALL_TOGGLE_MUTE: + // TODO: only iOS + break; + case Event.ACTION_CALL_TOGGLE_DMTF: + // TODO: only iOS + break; + case Event.ACTION_CALL_TOGGLE_GROUP: + // TODO: only iOS + break; + case Event.ACTION_CALL_TOGGLE_AUDIO_SESSION: + // TODO: only iOS + break; + case Event.ACTION_DID_UPDATE_DEVICE_PUSH_TOKEN_VOIP: + // TODO: only iOS + break; + } + }); + } on Exception {} } } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 947bbcd..d5bb898 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -4,6 +4,7 @@ import 'package:mohem_flutter_app/ui/attendance/add_vacation_rule_screen.dart'; import 'package:mohem_flutter_app/ui/attendance/monthly_attendance_screen.dart'; import 'package:mohem_flutter_app/ui/attendance/vacation_rule_screen.dart'; import 'package:mohem_flutter_app/ui/bottom_sheets/attendence_details_bottom_sheet.dart'; +import 'package:mohem_flutter_app/ui/chat/call/start_call_screen.dart'; import 'package:mohem_flutter_app/ui/chat/chat_detailed_screen.dart'; import 'package:mohem_flutter_app/ui/chat/chat_home.dart'; import 'package:mohem_flutter_app/ui/chat/favorite_users_screen.dart'; @@ -182,6 +183,7 @@ class AppRoutes { static const String chat = "/chat"; static const String chatDetailed = "/chatDetailed"; static const String chatFavoriteUsers = "/chatFavoriteUsers"; + static const String chatStartCall = "/chatStartCall"; //Marathon static const String marathonIntroScreen = "/marathonIntroScreen"; @@ -294,6 +296,8 @@ class AppRoutes { chat: (BuildContext context) => ChatHome(), chatDetailed: (BuildContext context) => ChatDetailScreen(), chatFavoriteUsers: (BuildContext context) => ChatFavoriteUsersScreen(), + chatStartCall: (BuildContext context) => StartCallPage(), + // Marathon marathonIntroScreen: (BuildContext context) => MarathonIntroScreen(), diff --git a/lib/models/chat/incomingCall.dart b/lib/models/chat/incomingCall.dart new file mode 100644 index 0000000..c3aa11c --- /dev/null +++ b/lib/models/chat/incomingCall.dart @@ -0,0 +1,425 @@ +// To parse this JSON data, do +// +// final incomingCallDataPayload = incomingCallDataPayloadFromJson(jsonString); + +import 'dart:convert'; + +class IncomingCallDataPayload { + IncomingCallDataPayload({ + this.id, + this.nameCaller, + this.appName, + this.avatar, + this.handle, + this.type, + this.duration, + this.textAccept, + this.textDecline, + this.textMissedCall, + this.textCallback, + this.extra, + this.headers, + this.android, + this.ios, + }); + + String? id; + String? nameCaller; + String? appName; + dynamic avatar; + String? handle; + dynamic? type; + dynamic? duration; + String? textAccept; + String? textDecline; + String? textMissedCall; + String? textCallback; + Extra? extra; + dynamic headers; + Android? android; + Ios? ios; + + factory IncomingCallDataPayload.fromRawJson(String str) => IncomingCallDataPayload.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory IncomingCallDataPayload.fromJson(Map json) => IncomingCallDataPayload( + id: json["id"], + nameCaller: json["nameCaller"], + appName: json["appName"], + avatar: json["avatar"], + handle: json["handle"], + type: json["type"], + duration: json["duration"], + textAccept: json["textAccept"], + textDecline: json["textDecline"], + textMissedCall: json["textMissedCall"], + textCallback: json["textCallback"], + extra: json["extra"] == null ? null : Extra.fromJson(json["extra"]), + headers: json["headers"], + android: json["android"] == null ? null : Android.fromJson(json["android"]), + ios: json["ios"] == null ? null : Ios.fromJson(json["ios"]), + ); + + Map toJson() => { + "id": id, + "nameCaller": nameCaller, + "appName": appName, + "avatar": avatar, + "handle": handle, + "type": type, + "duration": duration, + "textAccept": textAccept, + "textDecline": textDecline, + "textMissedCall": textMissedCall, + "textCallback": textCallback, + "extra": extra?.toJson(), + "headers": headers, + "android": android?.toJson(), + "ios": ios?.toJson(), + }; +} + +class Android { + Android({ + this.isCustomNotification, + this.isShowLogo, + this.isShowCallback, + this.isShowMissedCallNotification, + this.ringtonePath, + this.backgroundColor, + this.backgroundUrl, + this.actionColor, + this.incomingCallNotificationChannelName, + this.missedCallNotificationChannelName, + }); + + bool? isCustomNotification; + bool? isShowLogo; + bool? isShowCallback; + bool? isShowMissedCallNotification; + String? ringtonePath; + String? backgroundColor; + String? backgroundUrl; + String? actionColor; + dynamic incomingCallNotificationChannelName; + dynamic missedCallNotificationChannelName; + + factory Android.fromRawJson(String str) => Android.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Android.fromJson(Map json) => Android( + isCustomNotification: json["isCustomNotification"], + isShowLogo: json["isShowLogo"], + isShowCallback: json["isShowCallback"], + isShowMissedCallNotification: json["isShowMissedCallNotification"], + ringtonePath: json["ringtonePath"], + backgroundColor: json["backgroundColor"], + backgroundUrl: json["backgroundUrl"], + actionColor: json["actionColor"], + incomingCallNotificationChannelName: json["incomingCallNotificationChannelName"], + missedCallNotificationChannelName: json["missedCallNotificationChannelName"], + ); + + Map toJson() => { + "isCustomNotification": isCustomNotification, + "isShowLogo": isShowLogo, + "isShowCallback": isShowCallback, + "isShowMissedCallNotification": isShowMissedCallNotification, + "ringtonePath": ringtonePath, + "backgroundColor": backgroundColor, + "backgroundUrl": backgroundUrl, + "actionColor": actionColor, + "incomingCallNotificationChannelName": incomingCallNotificationChannelName, + "missedCallNotificationChannelName": missedCallNotificationChannelName, + }; +} + +class Extra { + Extra({ + this.loginDetails, + this.callerDetails, + }); + + LoginDetails? loginDetails; + CallerDetails? callerDetails; + + factory Extra.fromRawJson(String str) => Extra.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Extra.fromJson(Map json) => Extra( + loginDetails: json["loginDetails"] == null ? null : LoginDetails.fromJson(json["loginDetails"]), + callerDetails: json["callerDetails"] == null ? null : CallerDetails.fromJson(json["callerDetails"]), + ); + + Map toJson() => { + "loginDetails": loginDetails?.toJson(), + "callerDetails": callerDetails?.toJson(), + }; +} + +class CallerDetails { + CallerDetails({ + this.userChatHistoryId, + this.userChatHistoryLineId, + this.contant, + this.contantNo, + this.currentUserId, + this.currentUserName, + this.targetUserId, + this.targetUserName, + this.encryptedTargetUserId, + this.encryptedTargetUserName, + this.currentUserEmail, + this.targetUserEmail, + this.chatEventId, + this.fileTypeId, + this.isSeen, + this.isDelivered, + this.createdDate, + this.chatSource, + this.conversationId, + this.fileTypeResponse, + this.userChatReplyResponse, + }); + + int? userChatHistoryId; + int? userChatHistoryLineId; + String? contant; + String? contantNo; + int? currentUserId; + String? currentUserName; + int? targetUserId; + String? targetUserName; + String? encryptedTargetUserId; + String? encryptedTargetUserName; + String? currentUserEmail; + String? targetUserEmail; + int? chatEventId; + dynamic fileTypeId; + bool? isSeen; + bool? isDelivered; + DateTime? createdDate; + int? chatSource; + String? conversationId; + FileTypeResponse? fileTypeResponse; + dynamic userChatReplyResponse; + + factory CallerDetails.fromRawJson(String str) => CallerDetails.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory CallerDetails.fromJson(Map json) => CallerDetails( + userChatHistoryId: json["userChatHistoryId"], + userChatHistoryLineId: json["userChatHistoryLineId"], + contant: json["contant"], + contantNo: json["contantNo"], + currentUserId: json["currentUserId"], + currentUserName: json["currentUserName"], + targetUserId: json["targetUserId"], + targetUserName: json["targetUserName"], + encryptedTargetUserId: json["encryptedTargetUserId"], + encryptedTargetUserName: json["encryptedTargetUserName"], + currentUserEmail: json["currentUserEmail"], + targetUserEmail: json["targetUserEmail"], + chatEventId: json["chatEventId"], + fileTypeId: json["fileTypeId"], + isSeen: json["isSeen"], + isDelivered: json["isDelivered"], + createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), + chatSource: json["chatSource"], + conversationId: json["conversationId"], + fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), + userChatReplyResponse: json["userChatReplyResponse"], + ); + + Map toJson() => { + "userChatHistoryId": userChatHistoryId, + "userChatHistoryLineId": userChatHistoryLineId, + "contant": contant, + "contantNo": contantNo, + "currentUserId": currentUserId, + "currentUserName": currentUserName, + "targetUserId": targetUserId, + "targetUserName": targetUserName, + "encryptedTargetUserId": encryptedTargetUserId, + "encryptedTargetUserName": encryptedTargetUserName, + "currentUserEmail": currentUserEmail, + "targetUserEmail": targetUserEmail, + "chatEventId": chatEventId, + "fileTypeId": fileTypeId, + "isSeen": isSeen, + "isDelivered": isDelivered, + "createdDate": createdDate?.toIso8601String(), + "chatSource": chatSource, + "conversationId": conversationId, + "fileTypeResponse": fileTypeResponse?.toJson(), + "userChatReplyResponse": userChatReplyResponse, + }; +} + +class FileTypeResponse { + FileTypeResponse({ + this.fileTypeId, + this.fileTypeName, + this.fileTypeDescription, + this.fileKind, + this.fileName, + }); + + int? fileTypeId; + dynamic fileTypeName; + dynamic fileTypeDescription; + dynamic fileKind; + dynamic fileName; + + factory FileTypeResponse.fromRawJson(String str) => FileTypeResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory FileTypeResponse.fromJson(Map json) => FileTypeResponse( + fileTypeId: json["fileTypeId"], + fileTypeName: json["fileTypeName"], + fileTypeDescription: json["fileTypeDescription"], + fileKind: json["fileKind"], + fileName: json["fileName"], + ); + + Map toJson() => { + "fileTypeId": fileTypeId, + "fileTypeName": fileTypeName, + "fileTypeDescription": fileTypeDescription, + "fileKind": fileKind, + "fileName": fileName, + }; +} + +class LoginDetails { + LoginDetails({ + this.id, + this.userName, + this.email, + this.phone, + this.title, + this.token, + this.isDomainUser, + this.isActiveCode, + this.encryptedUserId, + this.encryptedUserName, + }); + + int? id; + String? userName; + String? email; + dynamic phone; + String? title; + String? token; + bool? isDomainUser; + bool? isActiveCode; + String? encryptedUserId; + String? encryptedUserName; + + factory LoginDetails.fromRawJson(String str) => LoginDetails.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory LoginDetails.fromJson(Map json) => LoginDetails( + id: json["id"], + userName: json["userName"], + email: json["email"], + phone: json["phone"], + title: json["title"], + token: json["token"], + isDomainUser: json["isDomainUser"], + isActiveCode: json["isActiveCode"], + encryptedUserId: json["encryptedUserId"], + encryptedUserName: json["encryptedUserName"], + ); + + Map toJson() => { + "id": id, + "userName": userName, + "email": email, + "phone": phone, + "title": title, + "token": token, + "isDomainUser": isDomainUser, + "isActiveCode": isActiveCode, + "encryptedUserId": encryptedUserId, + "encryptedUserName": encryptedUserName, + }; +} + +class Ios { + Ios({ + this.iconName, + this.handleType, + this.supportsVideo, + this.maximumCallGroups, + this.maximumCallsPerCallGroup, + this.audioSessionMode, + this.audioSessionActive, + this.audioSessionPreferredSampleRate, + this.audioSessionPreferredIoBufferDuration, + this.supportsDtmf, + this.supportsHolding, + this.supportsGrouping, + this.supportsUngrouping, + this.ringtonePath, + }); + + String? iconName; + String? handleType; + bool? supportsVideo; + int? maximumCallGroups; + int? maximumCallsPerCallGroup; + String? audioSessionMode; + bool? audioSessionActive; + double? audioSessionPreferredSampleRate; + double? audioSessionPreferredIoBufferDuration; + bool? supportsDtmf; + bool? supportsHolding; + bool? supportsGrouping; + bool? supportsUngrouping; + String? ringtonePath; + + factory Ios.fromRawJson(String str) => Ios.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Ios.fromJson(Map json) => Ios( + iconName: json["iconName"], + handleType: json["handleType"], + supportsVideo: json["supportsVideo"], + maximumCallGroups: json["maximumCallGroups"], + maximumCallsPerCallGroup: json["maximumCallsPerCallGroup"], + audioSessionMode: json["audioSessionMode"], + audioSessionActive: json["audioSessionActive"], + audioSessionPreferredSampleRate: json["audioSessionPreferredSampleRate"], + audioSessionPreferredIoBufferDuration: json["audioSessionPreferredIOBufferDuration"]?.toDouble(), + supportsDtmf: json["supportsDTMF"], + supportsHolding: json["supportsHolding"], + supportsGrouping: json["supportsGrouping"], + supportsUngrouping: json["supportsUngrouping"], + ringtonePath: json["ringtonePath"], + ); + + Map toJson() => { + "iconName": iconName, + "handleType": handleType, + "supportsVideo": supportsVideo, + "maximumCallGroups": maximumCallGroups, + "maximumCallsPerCallGroup": maximumCallsPerCallGroup, + "audioSessionMode": audioSessionMode, + "audioSessionActive": audioSessionActive, + "audioSessionPreferredSampleRate": audioSessionPreferredSampleRate, + "audioSessionPreferredIOBufferDuration": audioSessionPreferredIoBufferDuration, + "supportsDTMF": supportsDtmf, + "supportsHolding": supportsHolding, + "supportsGrouping": supportsGrouping, + "supportsUngrouping": supportsUngrouping, + "ringtonePath": ringtonePath, + }; +} diff --git a/lib/provider/chat_call_provider.dart b/lib/provider/chat_call_provider.dart index af8b100..43584cc 100644 --- a/lib/provider/chat_call_provider.dart +++ b/lib/provider/chat_call_provider.dart @@ -4,13 +4,16 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.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_webrtc/flutter_webrtc.dart'; import 'package:just_audio/just_audio.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/main.dart'; import 'package:mohem_flutter_app/models/chat/call.dart'; +import 'package:mohem_flutter_app/models/chat/incomingCall.dart'; import 'package:mohem_flutter_app/models/chat/webrtc_payloads.dart'; import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/ui/chat/call/chat_incoming_call_screen.dart'; @@ -28,6 +31,7 @@ class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin { final AudioPlayer player = AudioPlayer(); late MediaStream localStream; late CallDataModel outGoingCallData; + late IncomingCallDataPayload incomingCallData; bool isMicOff = false; bool isLoudSpeaker = false; bool isCamOff = false; @@ -40,6 +44,8 @@ class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin { /// WebRTC Connection Variables bool _offer = false; + bool isIncomingCallLoader = true; + late BuildContext providerContext; void initCallListeners({required BuildContext context}) { @@ -258,7 +264,7 @@ class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin { Future invoke({required String invokeMethod, required int currentUserID, required int targetUserID, var data, int userStatus = 1}) async { List args = []; - // logger.w(currentUserID.toString() + " -- " + targetUserID.toString() + " -- " + isVideoCall.toString()); + Utils.showToast(currentUserID.toString() + " -- " + targetUserID.toString() + " -- " + isVideoCall.toString()); if (invokeMethod == "CallUserAsync") { args = [currentUserID, targetUserID, isVideoCall]; } else if (invokeMethod == "answerCallAsync") { @@ -425,57 +431,9 @@ class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); } - - - - - - CallKitParams callKitParams = CallKitParams( - id: "_currentUuid", - nameCaller: 'Hien Nguyen', - appName: 'Callkit', - avatar: 'https://i.pravatar.cc/100', - handle: '0123456789', - type: 0, - textAccept: 'Accept', - textDecline: 'Decline', - textMissedCall: 'Missed call', - textCallback: 'Call back', - duration: 30000, - extra: {'userId': '1a2b3c4d'}, - headers: {'apiKey': 'Abc@123!', 'platform': 'flutter'}, - android: const AndroidParams( - isCustomNotification: true, - isShowLogo: false, - isShowCallback: false, - isShowMissedCallNotification: true, - ringtonePath: 'system_ringtone_default', - backgroundColor: '#0955fa', - backgroundUrl: 'https://i.pravatar.cc/500', - actionColor: '#4CAF50', - incomingCallNotificationChannelName: "Incoming Call", - missedCallNotificationChannelName: "Missed Call"), - ios: IOSParams( - iconName: 'CallKitLogo', - handleType: 'generic', - supportsVideo: true, - maximumCallGroups: 2, - maximumCallsPerCallGroup: 1, - audioSessionMode: 'default', - audioSessionActive: true, - audioSessionPreferredSampleRate: 44100.0, - audioSessionPreferredIOBufferDuration: 0.005, - supportsDTMF: true, - supportsHolding: true, - supportsGrouping: false, - supportsUngrouping: false, - ringtonePath: 'system_ringtone_default', - ), - ); - - - - - - + Future startIncomingCallViaKit() async { + await startIncomingCall(); + Utils.showToast("Inside Incoming Call"); + await invoke(invokeMethod: "answerCallAsync", currentUserID: incomingCallData.extra!.loginDetails!.id!, targetUserID: incomingCallData.extra!.callerDetails!.targetUserId!); + } } diff --git a/lib/provider/chat_provider_model.dart b/lib/provider/chat_provider_model.dart index b04bcc6..f3747c0 100644 --- a/lib/provider/chat_provider_model.dart +++ b/lib/provider/chat_provider_model.dart @@ -84,6 +84,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { bool disbaleChatForThisUser = false; bool isCall = false; + + userLoginToken.UserAutoLoginModel userLoginData = userLoginToken.UserAutoLoginModel(); + + Future getUserAutoLoginToken() async { userLoginToken.UserAutoLoginModel userLoginResponse = await ChatApiClient().getUserLoginToken(); if (userLoginResponse.response != null) { @@ -99,7 +103,11 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } Future buildHubConnection({required BuildContext context, required ChatCallProvider ccProvider}) async { - chatHubConnection = await getHubConnection(); + try { + chatHubConnection = await getHubConnection(); + }catch(e){ + Utils.showToast(e.toString()); + } await chatHubConnection.start(); if (kDebugMode) { logger.i("Hub Conn: Startedddddddd"); diff --git a/lib/ui/chat/call/start_call_screen.dart b/lib/ui/chat/call/start_call_screen.dart index ceb66d0..3c4b54a 100644 --- a/lib/ui/chat/call/start_call_screen.dart +++ b/lib/ui/chat/call/start_call_screen.dart @@ -1,20 +1,25 @@ -import 'dart:async'; import 'dart:core'; import 'dart:ui'; import 'package:draggable_widget/draggable_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart'; +import 'package:mohem_flutter_app/models/chat/incomingCall.dart'; import 'package:mohem_flutter_app/provider/chat_call_provider.dart'; +import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; +import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:provider/provider.dart'; class StartCallPage extends StatefulWidget { - RTCVideoRenderer localRenderer; - RTCVideoRenderer remoteRenderer; + RTCVideoRenderer? localRenderer; + RTCVideoRenderer? remoteRenderer; - StartCallPage({required this.localRenderer, required this.remoteRenderer}); + StartCallPage({this.localRenderer, this.remoteRenderer}); @override _StartCallPageState createState() => _StartCallPageState(); @@ -22,245 +27,306 @@ class StartCallPage extends StatefulWidget { class _StartCallPageState extends State { final dragController = DragController(); - late ChatCallProvider callProvider; + late ChatProviderModel cPro; + late ChatCallProvider callPro; + IncomingCallDataPayload incomingCallData = IncomingCallDataPayload(); + bool isIncomingCall = false; +//userChatDetails @override void initState() { - callProvider = Provider.of(context, listen: false); + callPro = Provider.of(context, listen: false); + cPro = Provider.of(context, listen: false); super.initState(); - refresh(); } - void refresh() { - Future.delayed(const Duration(seconds: 1), () { - callProvider.notifyListeners(); + @override + void dispose() { + if (chatHubConnection != null) { + chatHubConnection.stop(); + } + super.dispose(); + } + + void connection() async { + isIncomingCall = (Utils.getStringFromPrefs("isIncomingCall") == "true" ? true : false); + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.showToast(incomingCallData.extra!.loginDetails!.toRawJson(), longDuration: false); + cPro.userLoginData = UserAutoLoginModel( + response: Response( + id: incomingCallData.extra!.loginDetails!.id, + userName: incomingCallData.extra!.loginDetails!.userName, + email: incomingCallData.extra!.loginDetails!.email, + phone: incomingCallData.extra!.loginDetails!.phone, + title: incomingCallData.extra!.loginDetails!.title, + token: incomingCallData.extra!.loginDetails!.token, + isActiveCode: incomingCallData.extra!.loginDetails!.isActiveCode, + isDomainUser: incomingCallData.extra!.loginDetails!.isDomainUser, + encryptedUserId: incomingCallData.extra!.loginDetails!.encryptedUserName, + encryptedUserName: incomingCallData.extra!.loginDetails!.encryptedUserId), + errorResponses: null, + ); + AppState().setchatUserDetails = cPro.userLoginData; + await cPro.buildHubConnection(context: context, ccProvider: callPro); + callPro.incomingCallData = incomingCallData; + callPro.isIncomingCallLoader = false; + Future.delayed(const Duration(seconds: 2)).then((value) { + callPro.startIncomingCallViaKit(); + callPro.notifyListeners(); }); } @override Widget build(BuildContext context) { - return Consumer(builder: (BuildContext context, ChatCallProvider provider, Widget? child) { + incomingCallData = ModalRoute.of(context)!.settings.arguments as IncomingCallDataPayload; + if (incomingCallData != null) { + connection(); + } + return Consumer2(builder: (BuildContext context, ChatCallProvider provider, ChatProviderModel cpm, Widget? child) { return SizedBox( width: double.infinity, height: double.infinity, - child: Stack( - alignment: FractionalOffset.center, - children: [ - if (provider.isVideoCall) - Positioned.fill( - child: RTCVideoView( - widget.remoteRenderer, - objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, - key: const Key('remote'), - ), - ), - if (provider.isVideoCall) - DraggableWidget( - bottomMargin: 20, - topMargin: 40, - intialVisibility: true, - horizontalSpace: 20, - shadowBorderRadius: 50, - initialPosition: AnchoringPosition.topLeft, - dragController: dragController, - normalShadow: const BoxShadow(spreadRadius: 0.0, blurRadius: 0.0), - draggingShadow: const BoxShadow(spreadRadius: 0.0, blurRadius: 0.0), - child: SizedBox( - height: 200, - width: 140, - child: RTCVideoView( - widget.localRenderer, - mirror: true, - objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, - ), - ), - ), - if (!provider.isVideoCall) - Positioned.fill( - child: ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), - child: Container( - decoration: BoxDecoration( - color: MyColors.grey57Color.withOpacity( - 0.3, + child: provider.isVideoCall + ? Stack( + alignment: FractionalOffset.center, + children: [ + if (provider.isVideoCall) + Positioned.fill( + child: RTCVideoView( + widget.remoteRenderer!, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + key: const Key('remote'), + ), + ), + if (provider.isVideoCall) + DraggableWidget( + bottomMargin: 20, + topMargin: 40, + intialVisibility: true, + horizontalSpace: 20, + shadowBorderRadius: 50, + initialPosition: AnchoringPosition.topLeft, + dragController: dragController, + normalShadow: const BoxShadow(spreadRadius: 0.0, blurRadius: 0.0), + draggingShadow: const BoxShadow(spreadRadius: 0.0, blurRadius: 0.0), + child: SizedBox( + height: 200, + width: 140, + child: RTCVideoView( + widget.localRenderer!, + mirror: true, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - 40.height, - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - margin: const EdgeInsets.all(21.0), - child: Container( - margin: const EdgeInsets.only( - left: 10.0, - right: 10.0, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - SvgPicture.asset( - "assets/images/user.svg", - height: 70, - width: 70, - fit: BoxFit.cover, - ), - 10.height, - Text( - callProvider.outGoingCallData.receiverName!, - style: const TextStyle( - fontSize: 21, - decoration: TextDecoration.none, - fontWeight: FontWeight.bold, - color: MyColors.white, - letterSpacing: -1.26, - height: 23 / 12, + ), + if (!provider.isVideoCall) + Positioned.fill( + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), + child: Container( + decoration: BoxDecoration( + color: MyColors.grey57Color.withOpacity( + 0.3, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + 40.height, + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.all(21.0), + child: Container( + margin: const EdgeInsets.only( + left: 10.0, + right: 10.0, ), - ), - const Text( - "On Call", - style: TextStyle( - fontSize: 16, - decoration: TextDecoration.none, - fontWeight: FontWeight.w600, - color: Color( - 0xffC6C6C6, - ), - letterSpacing: -0.48, - height: 23 / 24, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SvgPicture.asset( + "assets/images/user.svg", + height: 70, + width: 70, + fit: BoxFit.cover, + ), + 10.height, + Text( + callPro.outGoingCallData.receiverName!, + style: const TextStyle( + fontSize: 21, + decoration: TextDecoration.none, + fontWeight: FontWeight.bold, + color: MyColors.white, + letterSpacing: -1.26, + height: 23 / 12, + ), + ), + const Text( + "On Call", + style: TextStyle( + fontSize: 16, + decoration: TextDecoration.none, + fontWeight: FontWeight.w600, + color: Color( + 0xffC6C6C6, + ), + letterSpacing: -0.48, + height: 23 / 24, + ), + ), + const SizedBox( + height: 2, + ), + ], ), ), - const SizedBox( - height: 2, - ), - ], - ), + ), + ], ), - ), - ], + ], + ), ), - ], - ), - ), - ), - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - padding: const EdgeInsets.only( - bottom: 20, - left: 40, - right: 40, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // if (provider.isVideoCall) - RawMaterialButton( - constraints: BoxConstraints(), - onPressed: () { - callProvider.loudOn(); - }, - elevation: 2.0, - fillColor: callProvider.isLoudSpeaker ? MyColors.green2DColor : Colors.grey, - padding: const EdgeInsets.all( - 10.0, - ), - shape: const CircleBorder(), - child: const Icon( - Icons.volume_up, - color: MyColors.white, - size: 30.0, - ), - ), - RawMaterialButton( - constraints: BoxConstraints(), - onPressed: () { - provider.camOff(); - }, - elevation: 2.0, - fillColor: provider.isCamOff ? MyColors.green2DColor : Colors.grey, - padding: const EdgeInsets.all( - 10.0, - ), - shape: const CircleBorder(), - child: Icon( - provider.isCamOff ? Icons.videocam_off : Icons.videocam, - color: MyColors.white, - size: 30.0, + ), ), ), - RawMaterialButton( - constraints: BoxConstraints(), - onPressed: () { - provider.switchCamera(); - }, - elevation: 2.0, - fillColor: provider.isFrontCamera ? Colors.grey : MyColors.green2DColor, - padding: const EdgeInsets.all( - 10.0, + Align( + alignment: Alignment.bottomCenter, + child: Container( + padding: const EdgeInsets.only( + bottom: 20, + left: 40, + right: 40, ), - shape: const CircleBorder(), - child: Icon( - provider.isFrontCamera ? Icons.switch_camera_outlined : Icons.switch_camera, - color: MyColors.white, - size: 30.0, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // if (provider.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + callPro.loudOn(); + }, + elevation: 2.0, + fillColor: callPro.isLoudSpeaker ? MyColors.green2DColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: const Icon( + Icons.volume_up, + color: MyColors.white, + size: 30.0, + ), + ), + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + provider.camOff(); + }, + elevation: 2.0, + fillColor: provider.isCamOff ? MyColors.green2DColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + provider.isCamOff ? Icons.videocam_off : Icons.videocam, + color: MyColors.white, + size: 30.0, + ), + ), + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + provider.switchCamera(); + }, + elevation: 2.0, + fillColor: provider.isFrontCamera ? Colors.grey : MyColors.green2DColor, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + provider.isFrontCamera ? Icons.switch_camera_outlined : Icons.switch_camera, + color: MyColors.white, + size: 30.0, + ), + ), + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + provider.micOff(); + }, + elevation: 2.0, + fillColor: provider.isMicOff ? MyColors.green2DColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + provider.isMicOff ? Icons.mic_off : Icons.mic, + color: MyColors.white, + size: 30.0, + ), + ), + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + provider.endCall().then((value) { + if (value) { + Navigator.of(context).pop(); + } + }); + }, + elevation: 2.0, + fillColor: MyColors.redA3Color, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: const Icon( + Icons.call_end, + color: MyColors.white, + size: 30.0, + ), + ), + ], ), ), - RawMaterialButton( - constraints: BoxConstraints(), - onPressed: () { - provider.micOff(); - }, - elevation: 2.0, - fillColor: provider.isMicOff ? MyColors.green2DColor : Colors.grey, - padding: const EdgeInsets.all( - 10.0, - ), - shape: const CircleBorder(), - child: Icon( - provider.isMicOff ? Icons.mic_off : Icons.mic, - color: MyColors.white, - size: 30.0, - ), + ), + ], + ) + : provider.isIncomingCallLoader + ? SizedBox( + width: double.infinity, + height: 500, + child: Center( + child: CircularProgressIndicator(), ), - RawMaterialButton( - constraints: BoxConstraints(), - onPressed: () { - provider.endCall().then((value) { - if (value) { - Navigator.of(context).pop(); - } - }); - }, - elevation: 2.0, - fillColor: MyColors.redA3Color, - padding: const EdgeInsets.all( - 10.0, + ) + : Column( + children: const [ + Center( + child: Text( + "Testing", + ), ), - shape: const CircleBorder(), - child: const Icon( - Icons.call_end, - color: MyColors.white, - size: 30.0, + Center( + child: Text( + "Data", + ), ), - ), - ], - ), - ), - ), - ], - ), + ], + ), ); }); } diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index d1f9592..1efe4f2 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -21,6 +21,7 @@ import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/models/chat/incomingCall.dart'; import 'package:mohem_flutter_app/models/check_mobile_app_version_model.dart'; import 'package:mohem_flutter_app/models/get_mobile_login_info_list_model.dart'; import 'package:mohem_flutter_app/models/member_information_list_model.dart'; @@ -32,7 +33,7 @@ import 'package:mohem_flutter_app/widgets/input_widget.dart'; // import 'package:safe_device/safe_device.dart'; import 'package:wifi_iot/wifi_iot.dart'; -class LoginScreen extends StatefulWidget { +class LoginScreen extends StatefulWidget with WidgetsBindingObserver { LoginScreen({Key? key}) : super(key: key); @override @@ -60,7 +61,6 @@ class _LoginScreenState extends State { bool isDevelopmentModeEnable = false; late HmsApiAvailability hmsApiAvailability; - @override void initState() { super.initState(); @@ -96,39 +96,44 @@ class _LoginScreenState extends State { GetMobileLoginInfoListModel? loginInfo; Future checkFirebaseToken() async { - try { - Utils.showLoading(context); - if (Platform.isAndroid) { - try { - await hmsApiAvailability.isHMSAvailable().then((value) async { - if (value == 0) { - AppState().setIsHuawei = true; - AppNotifications().initHuaweiPush(checkLoginInfo); - } else { - await Firebase.initializeApp(); - _firebaseMessaging = FirebaseMessaging.instance; - firebaseToken = await _firebaseMessaging.getToken(); - AppNotifications().init(firebaseToken); - checkLoginInfo(); - FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; - await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); - } - }); - } catch (ex) {} - } else { - await Firebase.initializeApp(); - _firebaseMessaging = FirebaseMessaging.instance; - firebaseToken = await _firebaseMessaging.getToken(); - AppNotifications().init(firebaseToken); - checkLoginInfo(); + if (await Utils.getStringFromPrefs("isIncomingCall") == "true") { + Utils.hideLoading(context); + Navigator.pushNamed(context, AppRoutes.chatStartCall, arguments: IncomingCallDataPayload.fromRawJson(await Utils.getStringFromPrefs("inComingCallData"))); + } else { + try { + Utils.showLoading(context); + if (Platform.isAndroid) { + try { + await hmsApiAvailability.isHMSAvailable().then((value) async { + if (value == 0) { + AppState().setIsHuawei = true; + AppNotifications().initHuaweiPush(checkLoginInfo); + } else { + await Firebase.initializeApp(); + _firebaseMessaging = FirebaseMessaging.instance; + firebaseToken = await _firebaseMessaging.getToken(); + AppNotifications().init(firebaseToken); + checkLoginInfo(); + FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; + await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); + } + }); + } catch (ex) {} + } else { + await Firebase.initializeApp(); + _firebaseMessaging = FirebaseMessaging.instance; + firebaseToken = await _firebaseMessaging.getToken(); + AppNotifications().init(firebaseToken); + checkLoginInfo(); + FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; + await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); + } + } catch (ex) { + Utils.hideLoading(context); + Utils.handleException(ex, context, null); FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); } - } catch (ex) { - Utils.hideLoading(context); - Utils.handleException(ex, context, null); - FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; - await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); } }