diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 15b0c54..d092ef9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -19,6 +19,8 @@ + + diff --git a/android/app/src/main/kotlin/com/mohem_flutter_app/CallDecline.kt b/android/app/src/main/kotlin/com/mohem_flutter_app/CallDecline.kt new file mode 100644 index 0000000..73df479 --- /dev/null +++ b/android/app/src/main/kotlin/com/mohem_flutter_app/CallDecline.kt @@ -0,0 +1,318 @@ +//package com.mohem_flutter_app +//import kotlinx.coroutines.Dispatchers +//import kotlinx.coroutines.GlobalScope +//import kotlinx.coroutines.launch +//import okhttp3.* +//import okhttp3.MediaType.Companion.toMediaType +//import okhttp3.RequestBody.Companion.toRequestBody +//import java.io.IOException +// +//class NativeIncomingCallDecline { +// fun declineCall(currentUserID: String, targetUserID: String, token: String) { +// println("--------------- Inside Decline Call ----------------") +// val url = "https://apiderichat.hmg.com/api/user/calldecline" +// val payload = """ +// { +// "currentUserId": "$targetUserID", +// "targetUserId": "$currentUserID", +// "secretKey": "derichatmobileuser", +// "targetUserToken": "$token" +// } +// """.trimIndent() +// +// val jsonMediaType = "application/json".toMediaType() +// +// GlobalScope.launch(Dispatchers.IO) { +// val client = OkHttpClient() +// val requestBody = payload.toRequestBody(jsonMediaType) +// val request = Request.Builder() +// .url(url) +// .post(requestBody) +// .build() +// +// client.newCall(request).enqueue(object : Callback { +// override fun onResponse(call: Call, response: Response) { +// if (response.isSuccessful) { +// val responseData = response.body?.string() +// println("API Successful. Response data: $responseData") +// } else { +// val errorMessage = response.body?.string() +// println("API Failed. Error message: $errorMessage") +// } +// } +// +// override fun onFailure(call: Call, e: IOException) { +// println("API Request Failed. Exception: ${e.message}") +// } +// }) +// } +// } +//} +// + + + +//This code is included into CallkitIncomingBroadcastReceiver.kt file under Flutter_callKit_incoming Package +//Below is the Whole Code + +// Implemented Libraries into Kotlin +// implementation 'com.squareup.okhttp3:okhttp:4.9.1' +// implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0' + +//package com.hiennv.flutter_callkit_incoming +//import android.annotation.SuppressLint +//import android.content.BroadcastReceiver +//import android.content.Context +//import android.content.Intent +//import android.os.Build +//import android.os.Bundle +//import android.util.Log +////http +//import okhttp3.MediaType.Companion.toMediaType +//import okhttp3.OkHttpClient +//import okhttp3.Request +//import okhttp3.RequestBody.Companion.toRequestBody +// +//// Async +//import kotlinx.coroutines.Dispatchers +//import kotlinx.coroutines.GlobalScope +//import kotlinx.coroutines.launch +// +//class CallkitIncomingBroadcastReceiver : BroadcastReceiver() { +// +// companion object { +// private const val TAG = "CallkitIncomingReceiver" +// +// fun getIntent(context: Context, action: String, data: Bundle?) = +// Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply { +// this.action = "${context.packageName}.${action}" +// putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data) +// } +// +// fun getIntentIncoming(context: Context, data: Bundle?) = +// Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply { +// action = "${context.packageName}.${CallkitConstants.ACTION_CALL_INCOMING}" +// putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data) +// } +// +// fun getIntentStart(context: Context, data: Bundle?) = +// Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply { +// action = "${context.packageName}.${CallkitConstants.ACTION_CALL_START}" +// putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data) +// } +// +// fun getIntentAccept(context: Context, data: Bundle?) = +// Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply { +// action = "${context.packageName}.${CallkitConstants.ACTION_CALL_ACCEPT}" +// putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data) +// } +// +// fun getIntentDecline(context: Context, data: Bundle?) = +// Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply { +// action = "${context.packageName}.${CallkitConstants.ACTION_CALL_DECLINE}" +// putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data) +// } +// +// fun getIntentEnded(context: Context, data: Bundle?) = +// Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply { +// action = "${context.packageName}.${CallkitConstants.ACTION_CALL_ENDED}" +// putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data) +// } +// +// fun getIntentTimeout(context: Context, data: Bundle?) = +// Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply { +// action = "${context.packageName}.${CallkitConstants.ACTION_CALL_TIMEOUT}" +// putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data) +// } +// +// fun getIntentCallback(context: Context, data: Bundle?) = +// Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply { +// action = "${context.packageName}.${CallkitConstants.ACTION_CALL_CALLBACK}" +// putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data) +// } +// } +// +// +// @SuppressLint("MissingPermission") +// override fun onReceive(context: Context, intent: Intent) { +// val callkitNotificationManager = CallkitNotificationManager(context) +// val action = intent.action ?: return +// val data = intent.extras?.getBundle(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA) ?: return +// +// when (action) { +// "${context.packageName}.${CallkitConstants.ACTION_CALL_INCOMING}" -> { +// try { +// callkitNotificationManager.showIncomingNotification(data) +// sendEventFlutter(CallkitConstants.ACTION_CALL_INCOMING, data) +// addCall(context, Data.fromBundle(data)) +// if (callkitNotificationManager.incomingChannelEnabled()) { +// val soundPlayerServiceIntent = +// Intent(context, CallkitSoundPlayerService::class.java) +// soundPlayerServiceIntent.putExtras(data) +// context.startService(soundPlayerServiceIntent) +// } +// } catch (error: Exception) { +// Log.e(TAG, null, error) +// } +// } +// +// "${context.packageName}.${CallkitConstants.ACTION_CALL_START}" -> { +// try { +// sendEventFlutter(CallkitConstants.ACTION_CALL_START, data) +// addCall(context, Data.fromBundle(data), true) +// } catch (error: Exception) { +// Log.e(TAG, null, error) +// } +// } +// +// "${context.packageName}.${CallkitConstants.ACTION_CALL_ACCEPT}" -> { +// try { +// sendEventFlutter(CallkitConstants.ACTION_CALL_ACCEPT, data) +// context.stopService(Intent(context, CallkitSoundPlayerService::class.java)) +// callkitNotificationManager.clearIncomingNotification(data, true) +// addCall(context, Data.fromBundle(data), true) +// } catch (error: Exception) { +// Log.e(TAG, null, error) +// } +// } +// +// "${context.packageName}.${CallkitConstants.ACTION_CALL_DECLINE}" -> { +// try { +// sendEventFlutter(CallkitConstants.ACTION_CALL_DECLINE, data) +// context.stopService(Intent(context, CallkitSoundPlayerService::class.java)) +// callkitNotificationManager.clearIncomingNotification(data, false) +// removeCall(context, Data.fromBundle(data)) +// println("----------- Code By Aamir on 2222-----------------------"); +// var callData = data.getSerializable(CallkitConstants.EXTRA_CALLKIT_EXTRA) as HashMap +// val token = (callData["loginDetails"] as HashMap<*, *>)["token"] as? String +// val targetUserId = (callData["callerDetails"] as HashMap<*, *>)["targetUserId"] as? Int +// val currentUserId = (callData["callerDetails"] as HashMap<*, *>)["currentUserId"] as? Int +// delineCall(currentUserID = currentUserId, targetUserID = targetUserId, token = token) +// println("----------- Code By Aamir On BroadCast -----------------------"); +// } catch (error: Exception) { +// Log.e(TAG, null, error) +// } +// } +// +// "${context.packageName}.${CallkitConstants.ACTION_CALL_ENDED}" -> { +// try { +// sendEventFlutter(CallkitConstants.ACTION_CALL_ENDED, data) +// context.stopService(Intent(context, CallkitSoundPlayerService::class.java)) +// callkitNotificationManager.clearIncomingNotification(data, false) +// removeCall(context, Data.fromBundle(data)) +// } catch (error: Exception) { +// Log.e(TAG, null, error) +// } +// } +// +// "${context.packageName}.${CallkitConstants.ACTION_CALL_TIMEOUT}" -> { +// try { +// sendEventFlutter(CallkitConstants.ACTION_CALL_TIMEOUT, data) +// context.stopService(Intent(context, CallkitSoundPlayerService::class.java)) +// if (data.getBoolean(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_SHOW, true)) { +// callkitNotificationManager.showMissCallNotification(data) +// } +// removeCall(context, Data.fromBundle(data)) +// } catch (error: Exception) { +// Log.e(TAG, null, error) +// } +// } +// +// "${context.packageName}.${CallkitConstants.ACTION_CALL_CALLBACK}" -> { +// try { +// callkitNotificationManager.clearMissCallNotification(data) +// sendEventFlutter(CallkitConstants.ACTION_CALL_CALLBACK, data) +// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { +// val closeNotificationPanel = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) +// context.sendBroadcast(closeNotificationPanel) +// } +// } catch (error: Exception) { +// Log.e(TAG, null, error) +// } +// } +// } +// } +// +// +// private fun delineCall(currentUserID: Int?, targetUserID: Int?, token: String?) { +// println("--------------- Inside Decline Call ----------------"); +// val url = "https://apiderichat.hmg.com/api/user/calldecline" +// val payload = """ +// { +// "currentUserId": $targetUserID, +// "targetUserId": $currentUserID, +// "secretKey": "derichatmobileuser", +// "targetUserToken": "$token" +// } +// """.trimIndent() +// +// +// val jsonMediaType = "application/json".toMediaType() +// +// GlobalScope.launch(Dispatchers.IO) { +// val client = OkHttpClient() +// val requestBody = payload.toRequestBody(jsonMediaType) +// val request = Request.Builder() +// .url(url) +// .post(requestBody) +// .build() +// +// client.newCall(request).execute().use { response -> +// if (response.isSuccessful) { +// val responseData = response.body?.string() +// println("API Successful. Response data: $responseData") +// } else { +// val errorMessage = response.body?.string() +// println("API Failed. Error message: $errorMessage") +// } +// } +// } +// +// +// } +// +// +// private fun sendEventFlutter(event: String, data: Bundle) { +// val android = mapOf( +// "isCustomNotification" to data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_NOTIFICATION, false), +// "isCustomSmallExNotification" to data.getBoolean( +// CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_SMALL_EX_NOTIFICATION, +// false +// ), +// "ringtonePath" to data.getString(CallkitConstants.EXTRA_CALLKIT_RINGTONE_PATH, ""), +// "backgroundColor" to data.getString(CallkitConstants.EXTRA_CALLKIT_BACKGROUND_COLOR, ""), +// "backgroundUrl" to data.getString(CallkitConstants.EXTRA_CALLKIT_BACKGROUND_URL, ""), +// "actionColor" to data.getString(CallkitConstants.EXTRA_CALLKIT_ACTION_COLOR, ""), +// "incomingCallNotificationChannelName" to data.getString( +// CallkitConstants.EXTRA_CALLKIT_INCOMING_CALL_NOTIFICATION_CHANNEL_NAME, +// "" +// ), +// "missedCallNotificationChannelName" to data.getString( +// CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_NOTIFICATION_CHANNEL_NAME, +// "" +// ), +// ) +// val notification = mapOf( +// "id" to data.getInt(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_ID), +// "showNotification" to data.getBoolean(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_SHOW), +// "count" to data.getInt(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_COUNT), +// "subtitle" to data.getString(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_SUBTITLE), +// "callbackText" to data.getString(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_TEXT), +// "isShowCallback" to data.getBoolean(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_SHOW), +// ) +// val forwardData = mapOf( +// "id" to data.getString(CallkitConstants.EXTRA_CALLKIT_ID, ""), +// "nameCaller" to data.getString(CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, ""), +// "avatar" to data.getString(CallkitConstants.EXTRA_CALLKIT_AVATAR, ""), +// "number" to data.getString(CallkitConstants.EXTRA_CALLKIT_HANDLE, ""), +// "type" to data.getInt(CallkitConstants.EXTRA_CALLKIT_TYPE, 0), +// "duration" to data.getLong(CallkitConstants.EXTRA_CALLKIT_DURATION, 0L), +// "textAccept" to data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_ACCEPT, ""), +// "textDecline" to data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_DECLINE, ""), +// "extra" to data.getSerializable(CallkitConstants.EXTRA_CALLKIT_EXTRA)!!, +// "missedCallNotification" to notification, +// "android" to android +// ) +// FlutterCallkitIncomingPlugin.sendEvent(event, forwardData) +// } +//} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/mohem_flutter_app/MainActivity.kt b/android/app/src/main/kotlin/com/mohem_flutter_app/MainActivity.kt index 0023ec9..aafee53 100644 --- a/android/app/src/main/kotlin/com/mohem_flutter_app/MainActivity.kt +++ b/android/app/src/main/kotlin/com/mohem_flutter_app/MainActivity.kt @@ -9,9 +9,18 @@ package com.mohem_flutter_app import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant +import io.flutter.plugin.common.MethodChannel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.IOException + +class MainActivity : FlutterFragmentActivity() { -class MainActivity: FlutterFragmentActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } + + } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/mohem_flutter_app/VoIPCenter.swift b/android/app/src/main/kotlin/com/mohem_flutter_app/VoIPCenter.swift new file mode 100644 index 0000000..8f0ca9b --- /dev/null +++ b/android/app/src/main/kotlin/com/mohem_flutter_app/VoIPCenter.swift @@ -0,0 +1,420 @@ +// VoIPCenter.swift +// +// +// // +// // VoIPCenter.swift +// // flutter_ios_voip_kit +// // +// // Created by ้ ˆ่—คๅฐ†ๅฒ on 2020/07/02. +// // +// +// import Foundation +// import Flutter +// import PushKit +// import CallKit +// import AVFoundation +// +// extension String { +// internal init(deviceToken: Data) { +// self = deviceToken.map { String(format: "%.2hhx", $0) }.joined() +// } +// } +// +// class VoIPCenter: NSObject, URLSessionDelegate { +// +// +// +// +// // MARK: - event channel +// +// private let eventChannel: FlutterEventChannel +// private var eventSink: FlutterEventSink? +// +// private enum EventChannel: String { +// case onDidReceiveIncomingPush +// case onDidAcceptIncomingCall +// case onDidRejectIncomingCall +// +// case onDidUpdatePushToken +// case onDidActivateAudioSession +// case onDidDeactivateAudioSession +// } +// +// // MARK: - PushKit +// +// private let didUpdateTokenKey = "Did_Update_VoIP_Device_Token" +// private let pushRegistry: PKPushRegistry +// +// var token: String? { +// if let didUpdateDeviceToken = UserDefaults.standard.data(forKey: didUpdateTokenKey) { +// let token = String(deviceToken: didUpdateDeviceToken) +// print("๐ŸŽˆ VoIP didUpdateDeviceToken: \(token)") +// return token +// } +// +// guard let cacheDeviceToken = self.pushRegistry.pushToken(for: .voIP) else { +// return nil +// } +// +// let token = String(deviceToken: cacheDeviceToken) +// print("๐ŸŽˆ VoIP cacheDeviceToken: \(token)") +// return token +// } +// +// // MARK: - CallKit +// +// let callKitCenter: CallKitCenter +// +// fileprivate var audioSessionMode: AVAudioSession.Mode +// fileprivate let ioBufferDuration: TimeInterval +// fileprivate let audioSampleRate: Double +// +// init(eventChannel: FlutterEventChannel) { +// self.eventChannel = eventChannel +// self.pushRegistry = PKPushRegistry(queue: .main) +// self.pushRegistry.desiredPushTypes = [.voIP] +// self.callKitCenter = CallKitCenter() +// +// if let path = Bundle.main.path(forResource: "Info", ofType: "plist"), let plist = NSDictionary(contentsOfFile: path) { +// self.audioSessionMode = ((plist["FIVKAudioSessionMode"] as? String) ?? "audio") == "video" ? .videoChat : .voiceChat +// self.ioBufferDuration = plist["FIVKIOBufferDuration"] as? TimeInterval ?? 0.005 +// self.audioSampleRate = plist["FIVKAudioSampleRate"] as? Double ?? 44100.0 +// } else { +// self.audioSessionMode = .voiceChat +// self.ioBufferDuration = TimeInterval(0.005) +// self.audioSampleRate = 44100.0 +// } +// +// super.init() +// self.eventChannel.setStreamHandler(self) +// self.pushRegistry.delegate = self +// self.callKitCenter.setup(delegate: self) +// } +// } +// +// extension VoIPCenter: PKPushRegistryDelegate { +// +// // MARK: - PKPushRegistryDelegate +// +// public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { +// print("๐ŸŽˆ VoIP didUpdate pushCredentials") +// UserDefaults.standard.set(pushCredentials.token, forKey: didUpdateTokenKey) +// +// self.eventSink?(["event": EventChannel.onDidUpdatePushToken.rawValue, +// "token": pushCredentials.token.hexString]) +// } +// +// // NOTE: iOS11 or more support +// +// public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { +// print("๐ŸŽˆ VoIP didRecyeiveIncomingPushWith completion: \(payload.dictionaryPayload)") +// +// let info = self.parse(payload: payload) +// let callerName = info?["incoming_caller_name"] as! String +// self.callKitCenter.incomingCall(uuidString: info?["uuid"] as! String, +// callerId: info?["incoming_caller_id"] as! String, +// callerName: callerName) { error in +// if let error = error { +// print("โŒ reportNewIncomingCall error: \(error.localizedDescription)") +// return +// } +// self.eventSink?(["event": EventChannel.onDidReceiveIncomingPush.rawValue, +// "payload": info as Any, +// "incoming_caller_name": callerName]) +// completion() +// } +// } +// +// // NOTE: iOS10 support +// +// public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { +// print("๐ŸŽˆ VoIP didReceiveIncomingPushWith: \(payload.dictionaryPayload)") +// +// +// let info = self.parse(payload: payload) +// let callerName = info?["incoming_caller_name"] as! String +// self.callKitCenter.incomingCall(uuidString: info?["uuid"] as! String, +// callerId: info?["incoming_caller_id"] as! String, +// callerName: callerName) { error in +// if let error = error { +// print("โŒ reportNewIncomingCall error: \(error.localizedDescription)") +// return +// } +// self.eventSink?(["event": EventChannel.onDidReceiveIncomingPush.rawValue, +// "payload": info as Any, +// "incoming_caller_name": callerName]) +// } +// } +// +// private func parse(payload: PKPushPayload) -> [String: Any]? { +// do { +// let data = try JSONSerialization.data(withJSONObject: payload.dictionaryPayload, options: .prettyPrinted) +// let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] +// +// if let aps = json?["aps"] as? [String: Any] { +// if let alertString = aps["alert"] as? String { +// let alertData = alertString.data(using: .utf8) +// let alertJson = try JSONSerialization.jsonObject(with: alertData!, options: []) as? [String: Any] +// return alertJson +// } else if let alertDictionary = aps["alert"] as? [String: Any] { +// return alertDictionary +// } +// } +// +// return nil +// } catch let error as NSError { +// print("โŒ VoIP parsePayload: \(error.localizedDescription)") +// return nil +// } +// } +// +// +// +// // private func parse(payload: PKPushPayload) -> [String: Any]? { +// // do { +// // let data = try JSONSerialization.data(withJSONObject: payload.dictionaryPayload, options: .prettyPrinted) +// // let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] +// // let aps = json?["aps"] as? [String: Any] +// // return aps?["alert"] as? [String: Any] +// // } catch let error as NSError { +// // print("โŒ VoIP parsePayload: \(error.localizedDescription)") +// // return nil +// // } +// // } +// +// +// } +// +// extension VoIPCenter: CXProviderDelegate , APIDelegate { +// func didReceiveAPIResponse(data: Data?) { +// print("didReceiveAPIResponse") +// } +// +// func didFailAPIRequest(error: Error) { +// print("didFailAPIRequest") +// } +// +// +// // MARK: - CXProviderDelegate +// +// public func providerDidReset(_ provider: CXProvider) { +// print("๐Ÿšซ VoIP providerDidReset") +// } +// +// public func provider(_ provider: CXProvider, perform action: CXStartCallAction) { +// print("๐Ÿค™ VoIP CXStartCallAction") +// self.callKitCenter.connectingOutgoingCall() +// action.fulfill() +// } +// +// public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { +// print("โœ… VoIP CXAnswerCallAction") +// self.callKitCenter.answerCallAction = action +// self.configureAudioSession() +// self.eventSink?(["event": EventChannel.onDidAcceptIncomingCall.rawValue, +// "uuid": self.callKitCenter.uuidString as Any, +// "incoming_caller_id": self.callKitCenter.incomingCallerId as Any]) +// } +// +// public func provider(_ provider: CXProvider, perform action: CXEndCallAction) { +// print("โŽ VoIP CXEndCallAction") +// if (self.callKitCenter.isCalleeBeforeAcceptIncomingCall) { +// self.eventSink?(["event": EventChannel.onDidRejectIncomingCall.rawValue, +// "uuid": self.callKitCenter.uuidString as Any, +// "incoming_caller_id": self.callKitCenter.incomingCallerId as Any]) +// if let callerId = self.callKitCenter.incomingCallerId { +// let components = callerId.components(separatedBy: "-") +// if components.count == 3 { +// let targetUserID = components[0] +// let currentUserID = components[1] +// print("โŽ VoIP CXEndCallBeforeApi") +// APIClient.shared.postRequest(delegate: self, currentUserID: currentUserID,targetUserID:targetUserID) +// self.callKitCenter.disconnected(reason: .remoteEnded) +// action.fulfill() +// print("โŽ VoIP CXEndCallAfterApi") +// } else { +// print("Invalid input string format") +// } +// } else { +// print("incomingCallerId is not a String") +// } +// } +// +// } +// +// +// +// public func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { +// print("๐Ÿ”ˆ VoIP didActivate audioSession") +// self.eventSink?(["event": EventChannel.onDidActivateAudioSession.rawValue]) +// } +// +// public func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { +// print("๐Ÿ”‡ VoIP didDeactivate audioSession") +// self.eventSink?(["event": EventChannel.onDidDeactivateAudioSession.rawValue]) +// } +// +// // This is a workaround for known issue, when audio doesn't start from lockscreen call +// // https://stackoverflow.com/questions/55391026/no-sound-after-connecting-to-webrtc-when-app-is-launched-in-background-using-pus +// private func configureAudioSession() { +// let sharedSession = AVAudioSession.sharedInstance() +// do { +// try sharedSession.setCategory(.playAndRecord, +// options: [AVAudioSession.CategoryOptions.allowBluetooth, +// AVAudioSession.CategoryOptions.defaultToSpeaker]) +// try sharedSession.setMode(audioSessionMode) +// try sharedSession.setPreferredIOBufferDuration(ioBufferDuration) +// try sharedSession.setPreferredSampleRate(audioSampleRate) +// } catch { +// print("โŒ VoIP Failed to configure `AVAudioSession`") +// } +// } +// } +// +// // Aamir Work +// +// protocol APIDelegate: AnyObject { +// func didReceiveAPIResponse(data: Data?) +// func didFailAPIRequest(error: Error) +// } +// +// +// class APIClient { +// static let shared = APIClient() +// +// func postRequest(delegate: APIDelegate, currentUserID: String, targetUserID: String) { +// DispatchQueue.global(qos: .background).async { +// self.getApiToken(currentUserID: currentUserID) { data, error in +// if let error = error { +// print("Error: \(error.localizedDescription)") +// } else if let data = data { +// do{ +// let sem = DispatchSemaphore(value: 0) +// do { +// let json = try JSONSerialization.jsonObject(with: data, options: []) +// guard let dictionary = json as? [String: Any] else { +// print("Error parsing JSON") +// return +// } +// guard let responseDictionary = dictionary["response"] as? [String: Any] else { +// print("Error parsing response") +// return +// } +// +// guard let token = responseDictionary["token"] as? String else { +// print("Error getting token") +// return +// } +// print("OneSignal User Token After Login JSON: \(token)") +// +// self.makeEndCallRequest(currentUserID: currentUserID, targetUserID: targetUserID, token: token) { data, error in +// if let error = error { +// print("Error: \(error.localizedDescription)") +// } else if let data = data { +// sem.signal() +// _ = String(data: data, encoding: .utf8) +// print("Call Ended Successfully") +// } +// } +// } +// sem.wait() +// }catch{ +// print("Error parsing JSON: \(error)") +// } +// } +// +// } +// } +// +// } +// +// private func getApiToken(currentUserID: String, completion: @escaping (Data?, Error?) -> Void) { +// let parameters = """ +// { +// "employeeNumber": "\(currentUserID)", +// "password": "FxIu26rWIKoF8n6mpbOmAjDLphzFGmpG" +// } +// """ +// let postData = parameters.data(using: .utf8) +// var request = URLRequest(url: URL(string: "https://apiderichat.hmg.com/api/user/`externaluserlogin")!, +// timeoutInterval: Double.infinity) +// request.addValue("application/json", forHTTPHeaderField: "Content-Type") +// request.httpMethod = "POST" +// request.httpBody = postData +// let task = URLSession.shared.dataTask(with: request) { data, response, error in +// if let error = error { +// completion(nil, error) +// return +// } +// guard let httpResponse = response as? HTTPURLResponse else { +// let error = NSError(domain: "InvalidResponse", code: 0, userInfo: nil) +// completion(nil, error) +// return +// } +// guard (200...299).contains(httpResponse.statusCode) else { +// let error = NSError(domain: "HTTPError", code: httpResponse.statusCode, userInfo: nil) +// completion(nil, error) +// return +// } +// completion(data, nil) +// } +// +// task.resume() +// } +// +// +// private func makeEndCallRequest(currentUserID: String, targetUserID: String, token: String, completion: @escaping (Data?, Error?) -> Void) { +// let parameters = """ +// { +// "currentUserId": \(currentUserID), +// "targetUserId": \(targetUserID), +// "secretKey": "derichatmobileuser", +// "targetUserToken": "\(token)" +// } +// """ +// print("OneSignal Params: \(parameters)") +// let postData = parameters.data(using: .utf8) +// var request = URLRequest(url: URL(string: "https://apiderichat.hmg.com/api/user/calldecline")!, +// timeoutInterval: Double.infinity) +// request.addValue("application/json", forHTTPHeaderField: "Content-Type") +// request.httpMethod = "POST" +// request.httpBody = postData +// let task = URLSession.shared.dataTask(with: request) { data, response, error in +// if let error = error { +// completion(nil, error) +// return +// } +// guard let httpResponse = response as? HTTPURLResponse else { +// let error = NSError(domain: "InvalidResponse", code: 0, userInfo: nil) +// completion(nil, error) +// return +// } +// guard (200...299).contains(httpResponse.statusCode) else { +// let error = NSError(domain: "HTTPError", code: httpResponse.statusCode, userInfo: nil) +// completion(nil, error) +// return +// } +// completion(data, nil) +// } +// +// task.resume() +// } +// +// } +// +// +// // End Aamir Work +// extension VoIPCenter: FlutterStreamHandler { +// +// // MARK: - FlutterStreamHandler๏ผˆevent channel๏ผ‰ +// +// public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { +// self.eventSink = events +// return nil +// } +// +// public func onCancel(withArguments arguments: Any?) -> FlutterError? { +// self.eventSink = nil +// return nil +// } +// } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 2f8758c..2d15a3d 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +#distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip +#distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip + distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip \ No newline at end of file diff --git a/assets/audio/ring_30Sec.caf b/assets/audio/ring_30Sec.caf new file mode 100644 index 0000000..b5bb4b8 Binary files /dev/null and b/assets/audio/ring_30Sec.caf differ diff --git a/assets/audio/ring_60Sec.mp3 b/assets/audio/ring_60Sec.mp3 new file mode 100644 index 0000000..ac32394 Binary files /dev/null and b/assets/audio/ring_60Sec.mp3 differ diff --git a/assets/icons/chat/avi.svg b/assets/icons/chat/avi.svg new file mode 100644 index 0000000..e99eaef --- /dev/null +++ b/assets/icons/chat/avi.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/chat/flv.svg b/assets/icons/chat/flv.svg new file mode 100644 index 0000000..febd584 --- /dev/null +++ b/assets/icons/chat/flv.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/chat/mov.svg b/assets/icons/chat/mov.svg new file mode 100644 index 0000000..22e41fe --- /dev/null +++ b/assets/icons/chat/mov.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/chat/mp4.svg b/assets/icons/chat/mp4.svg new file mode 100644 index 0000000..5d8e9ec --- /dev/null +++ b/assets/icons/chat/mp4.svg @@ -0,0 +1,14 @@ + + + + + + + + + + 4 + + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index b4ad5b3..fab432d 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,9 +1,11 @@ import UIKit +import PushKit import Flutter import Firebase +import flutter_callkit_incoming import flutter_local_notifications - +// PKPushRegistryDelegate @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( @@ -14,10 +16,49 @@ import flutter_local_notifications FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in GeneratedPluginRegistrant.register(with: registry) } - if #available(iOS 10.0, *) { - UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate - } +// if #available(iOS 10.0, *) { +// UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate +// } GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } + + //Setup VOIP +// let mainQueue = DispatchQueue.main +// let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue) +// voipRegistry.delegate = self +// voipRegistry.desiredPushTypes = [PKPushType.voIP] + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + // Handle updated push credentials +// func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { +// print(credentials.token) +// let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined() +// //Save deviceToken to your server +// SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken) +// } + +// func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { +// print("didInvalidatePushTokenFor") +// SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("") +// } + +// // Handle incoming pushes +// func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { +// print("didReceiveIncomingPushWith") +// guard type == .voIP else { return } +// print(payload.dictionaryPayload) +//// let id = payload.dictionaryPayload["id"] as? String ?? "" +//// let nameCaller = payload.dictionaryPayload["nameCaller"] as? String ?? "" +//// let handle = payload.dictionaryPayload["handle"] as? String ?? "" +// let isVideo = payload.dictionaryPayload["isVideo"] as? Bool ?? false +//// +//// +// let data = flutter_callkit_incoming.Data(id: "1", nameCaller: "Mohemm", handle: "handle", type: isVideo ? 1 : 0) +//// data.extra = ["user": "abc@123", "platform": "ios"] +//// data.iconName = "Mohemm" +// SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true) +// } + + } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index cb62b88..729a8ef 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -22,6 +22,14 @@ ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) + FIVKIconName + AppIcon-VoIPKit + FIVKLocalizedName + VoIP-Kit + FIVKSkipRecallScreen + + FIVKSupportVideo + FirebaseAppDelegateProxyEnabled ITSAppUsesNonExemptEncryption @@ -63,6 +71,8 @@ UIBackgroundModes + voip + processing fetch remote-notification diff --git a/lib/api/api_client.dart b/lib/api/api_client.dart index 3fbe57c..a82b520 100644 --- a/lib/api/api_client.dart +++ b/lib/api/api_client.dart @@ -111,6 +111,8 @@ class ApiClient { Future postJsonForResponse(String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0, bool isFormData = false}) async { String? requestBody; + print(url); + print(jsonObject); late Map stringObj; if (jsonObject != null) { requestBody = jsonEncode(jsonObject); @@ -124,6 +126,7 @@ class ApiClient { if (isFormData) { headers = {'Content-Type': 'application/x-www-form-urlencoded'}; stringObj = ((jsonObject ?? {}) as Map).map((key, value) => MapEntry(key, value?.toString() ?? "")); + print(stringObj); } return await _postForResponse(url, isFormData ? stringObj : requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); diff --git a/lib/api/chat/chat_api_client.dart b/lib/api/chat/chat_api_client.dart index daafa92..3feec0c 100644 --- a/lib/api/chat/chat_api_client.dart +++ b/lib/api/chat/chat_api_client.dart @@ -1,9 +1,8 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:http/http.dart'; import 'package:mohem_flutter_app/api/api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; @@ -34,12 +33,13 @@ class ChatApiClient { "${ApiConsts.chatLoginTokenUrl}externaluserlogin", { "employeeNumber": AppState().memberInformationList!.eMPLOYEENUMBER.toString(), + // "employeeNumber": ApiConsts.baseUrl.contains("uat") ? "266642" : AppState().memberInformationList!.eMPLOYEENUMBER.toString(), "password": "FxIu26rWIKoF8n6mpbOmAjDLphzFGmpG", "isMobile": true, - "platform": Platform.isIOS ? "ios" : "android", "deviceToken": AppState().getIsHuawei ? AppState().getHuaweiPushToken : AppState().getDeviceToken, "isHuaweiDevice": AppState().getIsHuawei, - "voipToken": "", + "platform": Platform.isIOS ? "ios" : "android", // ios, android + "voipToken": Platform.isIOS ? AppState().iosVoipPlayerID : "" }, ); @@ -49,7 +49,12 @@ class ChatApiClient { if (response.statusCode == 200) { userLoginResponse = user.userAutoLoginModelFromJson(response.body); } else if (response.statusCode == 501 || response.statusCode == 502 || response.statusCode == 503 || response.statusCode == 504) { - getUserLoginToken(); + try { + await getUserLoginToken(); + } on TimeoutException catch (e) { + return userLoginResponse; + } + ; } else { userLoginResponse = user.userAutoLoginModelFromJson(response.body); Utils.showToast(userLoginResponse.errorResponses!.first.message!); @@ -78,9 +83,7 @@ class ChatApiClient { if (!kReleaseMode) { logger.i("res: " + response.body); } - return ChatUserModel.fromJson( - json.decode(response.body), - ); + return ChatUserModel.fromJson(json.decode(response.body)); } catch (e) { throw e; } @@ -145,14 +148,14 @@ class ChatApiClient { } // Upload Chat Media - Future uploadMedia(String userId, File file) async { + Future uploadMedia(String userId, File file, String fileSource) async { if (kDebugMode) { print("${ApiConsts.chatMediaImageUploadUrl}upload"); print(AppState().chatDetails!.response!.token); } dynamic request = MultipartRequest('POST', Uri.parse('${ApiConsts.chatMediaImageUploadUrl}upload')); - request.fields.addAll({'userId': userId, 'fileSource': '1'}); + request.fields.addAll({'userId': userId, 'fileSource': fileSource}); request.files.add(await MultipartFile.fromPath('files', file.path)); request.headers.addAll({'Authorization': 'Bearer ${AppState().chatDetails!.response!.token}'}); StreamedResponse response = await request.send(); @@ -164,10 +167,10 @@ class ChatApiClient { } // Download File For Chat - Future downloadURL({required String fileName, required String fileTypeDescription}) async { + Future downloadURL({required String fileName, required String fileTypeDescription, required int fileSource}) async { Response response = await ApiClient().postJsonForResponse( "${ApiConsts.chatMediaImageUploadUrl}download", - {"fileType": fileTypeDescription, "fileName": fileName, "fileSource": 1}, + {"fileType": fileTypeDescription, "fileName": fileName, "fileSource": fileSource}, token: AppState().chatDetails!.response!.token, ); Uint8List data = Uint8List.fromList(response.bodyBytes); @@ -203,6 +206,7 @@ class ChatApiClient { "${ApiConsts.getGroupByUserId}${AppState().chatDetails!.response!.id}", token: AppState().chatDetails!.response!.token, ); + if (!kReleaseMode) { logger.i("res: " + response.body); } @@ -313,4 +317,65 @@ class ChatApiClient { throw e; } } + + // CallUser Login Token + + Future getUserCallToken({required String userid}) async { + user.UserAutoLoginModel userLoginResponse = user.UserAutoLoginModel(); + Response response = await ApiClient().postJsonForResponse( + "${ApiConsts.chatLoginTokenUrl}externaluserlogin", + { + "employeeNumber": userid, + "password": "FxIu26rWIKoF8n6mpbOmAjDLphzFGmpG", + }, + ); + + if (!kReleaseMode) { + logger.i("login-res: " + response.body); + } + if (response.statusCode == 200) { + userLoginResponse = user.userAutoLoginModelFromJson(response.body); + } else if (response.statusCode == 501 || response.statusCode == 502 || response.statusCode == 503 || response.statusCode == 504) { + await getUserCallToken(userid: userid); + } else { + userLoginResponse = user.userAutoLoginModelFromJson(response.body); + Utils.showToast(userLoginResponse.errorResponses!.first.message!); + } + return userLoginResponse; + } + + // Call Decline On App Terminated State + Future callDecline({required int cUserID, required int tUserID, String? targetUsertoken}) async { + Response response = await ApiClient().postJsonForResponse( + "${ApiConsts.chatLoginTokenUrl}calldecline", + {"currentUserId": cUserID, "targetUserId": tUserID, "secretKey": "derichatmobileuser", "targetUserToken": targetUsertoken}, + ); + print("res: " + response.body); + if (!kReleaseMode) { + logger.i({"currentUserId": cUserID, "targetUserId": tUserID, "secretKey": "derichatmobileuser", "targetUserToken": targetUsertoken}); + print("res: " + response.body); + } + return response; + } + + Future oneSignalVoip(String value) async { + String id = ""; + Response response = await ApiClient().postJsonForResponse( + "${ApiConsts.oneSignalCall}players", + { + "app_id": ApiConsts.oneSignalAppID, + "identifier": value, + "device_type": 0, + "test_type": !kReleaseMode || kDebugMode ? 1 : 0 + // 1 + }, + ); + + Map values = jsonDecode(response.body) as Map; + id = values["id"]; + if (!kReleaseMode) { + print("res: " + response.body); + } + return id; + } } diff --git a/lib/app_state/app_state.dart b/lib/app_state/app_state.dart index 51469c2..e441fa1 100644 --- a/lib/app_state/app_state.dart +++ b/lib/app_state/app_state.dart @@ -193,4 +193,28 @@ class AppState { } bool cancelRequestTrancsection = true; + + String _iosVoipPlayerID = ""; + + String get iosVoipPlayerID => _iosVoipPlayerID; + + set setiosVoipPlayerID(String value) { + _iosVoipPlayerID = value; + } + + bool _isUserOnline = false; + + bool get getisUserOnline => _isUserOnline; + + set setisUserOnline(bool value) { + _isUserOnline = value; + } + + bool _isBackgroundCall = false; + + bool get isBackgroundCall => _isBackgroundCall; + + set isBackgroundCall(bool value) { + _isBackgroundCall = value; + } } diff --git a/lib/classes/chat_call_kit.dart b/lib/classes/chat_call_kit.dart new file mode 100644 index 0000000..5b47492 --- /dev/null +++ b/lib/classes/chat_call_kit.dart @@ -0,0 +1,200 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:firebase_messaging/firebase_messaging.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/consts.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/get_single_user_chat_list_model.dart'; +import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart' as ALM; +import 'package:mohem_flutter_app/models/chat/incoming_call_model.dart'; +import 'package:mohem_flutter_app/provider/chat_call_provider.dart'; +import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; +import 'package:signalr_netcore/signalr_client.dart'; + +class ChatVoipCall { + static final ChatVoipCall _instance = ChatVoipCall._internal(); + + ChatVoipCall._internal(); + + factory ChatVoipCall() => _instance; + + late ChatProviderModel prov; + late ChatCallProvider cProv; + dynamic inCallData; + bool isUserOnline = false; + dynamic callData; + + Timer? _timer; + int _start = 25; + + void startTimer() { + Duration oneSec = const Duration(seconds: 1); + _timer = Timer.periodic( + oneSec, + (Timer timer) { + if (_start == 0) { + timer.cancel(); + } else { + _start--; + } + }, + ); + } + + Future showCallkitIncoming({required String uuid, RemoteMessage? data, CallDataModel? incomingCallData, bool background = false}) async { + await FlutterCallkitIncoming.endAllCalls(); + ALM.Response autoLoginData; + SingleUserChatModel callerData; + if (data!.data["user_token_response"] == null || data.data["user_token_response"].isEmpty) { + // Online & App Logged In + ALM.Response sharedDetails = ALM.Response.fromJson(jsonDecode(await Utils.getStringFromPrefs("userLoginChatDetails"))); + autoLoginData = ALM.Response.fromJson(AppState().getchatUserDetails == null ? sharedDetails.toJson() : AppState().getchatUserDetails!.response!.toJson()); + dynamic items = jsonDecode(data.data["user_chat_history_response"]); + callerData = SingleUserChatModel( + targetUserId: items["CurrentUserId"], + targetUserEmail: items["CurrentUserEmail"], + targetUserName: items["CurrentUserName"].split("@").first, + currentUserId: autoLoginData.id, + currentUserEmail: autoLoginData.email, + currentUserName: autoLoginData.userName, + chatEventId: 3); + isUserOnline = true; + } else { + // Offline or App in Background or App is At Verify Screen + autoLoginData = ALM.Response.fromJson(jsonDecode(data.data["user_token_response"])); + callerData = SingleUserChatModel.fromJson(json.decode(data.data["user_chat_history_response"])); + } + CallKitParams params = CallKitParams( + id: uuid, + nameCaller: callerData.targetUserName, + appName: 'Mohemm', + handle: '', + type: 0, + duration: 20000, + textAccept: 'Accept', + textDecline: 'Decline', + extra: { + "loginDetails": autoLoginData.toJson(), + "callerDetails": callerData.toJson(), + 'isIncomingCall': true, + 'isUserOnline': isUserOnline, + 'callType': data.data["callType"], + }, + android: const AndroidParams( + isCustomNotification: true, + isShowLogo: false, + ringtonePath: 'system_ringtone_default', + backgroundColor: '#0955fa', + backgroundUrl: 'assets/test.png', + actionColor: '#4CAF50', + ), + ios: const 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) { + startTimer(); + await Utils.saveStringFromPrefs("isIncomingCall", "true"); + await FlutterCallkitIncoming.showCallkitIncoming(params); + + if (_start == 0) { + _timer!.cancel(); + //await FlutterCallkitIncoming.showMissCallNotification(params); + await FlutterCallkitIncoming.endAllCalls(); + } + } + } + + Future declineCall({payload}) async { + IncomingCallModel data = IncomingCallModel.fromJson(jsonDecode(payload)); + + if (isUserOnline) { + HubConnection _hc = await makeHub(sessionData: data); + await _hc.start(); + if (_hc.state == HubConnectionState.Connected) { + if (data.extra != null) { + await _hc.invoke("HangUpAsync", args: [data.extra!.callerDetails!.currentUserId!, data.extra!.callerDetails!.targetUserId!]); + await _hc.invoke("UpdateUserStatusAsync", args: [int.parse(data.extra!.callerDetails!.currentUserId.toString()), 1]); + FlutterCallkitIncoming.endAllCalls(); + chatHubConnection = _hc; + } + } + } + //else { + // Future.delayed(const Duration(seconds: 3), () { + // ChatApiClient().callDecline(cUserID: data.extra!.callerDetails!.targetUserId!, tUserID: data.extra!.callerDetails!.currentUserId!, targetUsertoken: data.extra!.loginDetails!.token!); + // }); + // HubConnection _hc = await makeHub(sessionData: data); + // await _hc.start(); + // if (_hc.state == HubConnectionState.Connected) { + // logger.log(Level.info, "HUB-EVENT"); + // await _hc.invoke("HangUpAsync", args: [ + // data.extra!.callerDetails!.currentUserId!, + // data.extra!.callerDetails!.targetUserId!, + // ]); + // FlutterCallkitIncoming.endAllCalls(); + // await _hc.stop(); + // } + // } + + // + // try { + // var response = await platform.invokeMethod( + // 'executeHttpPostRequest', {"currentUserID": data.extra!.callerDetails!.targetUserId, "targetUserID": data.extra!.callerDetails!.currentUserId, "token": data.extra!.loginDetails!.token}); + // print('HTTP POST response: $response'); + // Future.delayed(Duration(seconds: 3), () { + // ChatApiClient().callDecline(cUserID: data.extra!.callerDetails!.targetUserId!, tUserID: data.extra!.callerDetails!.currentUserId!, targetUsertoken: data.extra!.loginDetails!.token!); + // }); + // } on PlatformException catch (e) { + // print('Error invoking method: ${e.message}'); + // } + + //await ChatApiClient().callDecline(cUserID: data.extra!.callerDetails!.targetUserId!, tUserID: data.extra!.callerDetails!.currentUserId!, targetUsertoken: data.extra!.loginDetails!.token!); + // logger.log(Level.error, "API-EVENT-END"); + } + + // Future voipDeclineCall(IosCallPayload? iosCallPayload) async { + // try { + // print("DeclineVia Flutter"); + // print(iosCallPayload!.toRawJson()); + // IosCallPayload _iosCallPayload = IosCallPayload(incomingCallerId: iosCallPayload.incomingCallerId!.split("-")[0], incomingCallReciverId: iosCallPayload.incomingCallerId!.split("-")[1]); + // ALM.UserAutoLoginModel model = await ChatApiClient().getUserCallToken(userid: iosCallPayload.incomingCallerId!.split("-")[1]); + // await ChatApiClient() + // .callDecline(cUserID: int.parse(_iosCallPayload.incomingCallerId!), tUserID: int.parse(_iosCallPayload.incomingCallReciverId.toString()), targetUsertoken: model.response!.token); + // } catch (err) { + // print(err); + // } + // } + + Future makeHub({required IncomingCallModel sessionData}) async { + late HubConnection hc; + try { + HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); + hc = HubConnectionBuilder() + .withUrl(ApiConsts.chatHubConnectionUrl + "?UserId=${sessionData.extra!.loginDetails!.id}&source=Desktop&access_token=${sessionData.extra?.loginDetails!.token}", options: httpOp) + .withAutomaticReconnect(retryDelays: [2000, 5000, 10000, 20000]).build(); + return hc; + } catch (e) { + print(e); + return hc; + } + } +} diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index bffc7ad..8ec614a 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -3,8 +3,8 @@ import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; class ApiConsts { //static String baseUrl = "http://10.200.204.20:2801/"; // Local server // static String baseUrl = "https://erptstapp.srca.org.sa"; // SRCA server - static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver - // static String baseUrl = "https://hmgwebservices.com"; // Live server + //static String baseUrl = "https://uat.hmgwebservices.com"; // UAT server + static String baseUrl = "https://hmgwebservices.com"; // Live server static String baseUrlServices = baseUrl + "/Services/"; // server // static String baseUrlServices = "https://api.cssynapses.com/tangheem/"; // Live server static String utilitiesRest = baseUrlServices + "Utilities.svc/REST/"; @@ -34,13 +34,11 @@ class ApiConsts { static String chatMediaImageUploadUrl = chatServerBaseApiUrl + "shared/"; static String chatFavUser = chatServerBaseApiUrl + "FavUser/"; static String chatUserImages = chatServerBaseUrl + "empservice/api/employee/"; + static String oneSignalCall = "https://onesignal.com/api/v1/"; + static String oneSignalAppID = "472e4582-5c44-47ab-a5f5-9369b8967107"; //Brain Marathon Constants - static String marathonBaseUrlLive = "https://marathoon.com/service/api/"; - static String marathonBaseUrlUAT = "https://marathoon.com/uatservice/api/"; - - static String marathonBaseUrl = marathonBaseUrlLive; - // static String marathonBaseUrl = marathonBaseUrlUAT; + static String marathonBaseUrl = "https://marathoon.com/service/api/"; static String marathonBaseUrlServices = "https://marathoon.com/service/"; static String marathonParticipantLoginUrl = marathonBaseUrl + "auth/participantlogin"; static String marathonProjectGetUrl = marathonBaseUrl + "Project/Project_Get"; @@ -53,6 +51,7 @@ class ApiConsts { static String marathonGetMarathonersCount = marathonBaseUrl + "Participant/GetRemainingParticipants"; //DummyCards for the UI + static CardContent dummyQuestion = const CardContent(); static int tabletMinLength = 500; } @@ -70,5 +69,3 @@ class SharedPrefsConsts { static String mohemmWifiPassword = "mohemmWifiPassword"; static String editItemForSale = "editItemForSale"; } - - diff --git a/lib/classes/notifications.dart b/lib/classes/notifications.dart index 8f432d7..220e56f 100644 --- a/lib/classes/notifications.dart +++ b/lib/classes/notifications.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:firebase_core/firebase_core.dart'; @@ -5,15 +6,16 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; - // import 'package:huawei_hmsavailability/huawei_hmsavailability.dart'; import 'package:huawei_push/huawei_push.dart' as huawei_push; import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/chat_call_kit.dart'; import 'package:mohem_flutter_app/classes/date_uitl.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/models/get_notifications_response_model.dart'; import 'package:mohem_flutter_app/ui/notifications/notification_details_page.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:uuid/uuid.dart'; final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); @@ -47,12 +49,8 @@ class AppNotifications { } void init(String? firebaseToken, BuildContext context) async { - // if (Platform.isAndroid) { - // hmsApiAvailability = HmsApiAvailability(); - // } - this.context = context; - + debugPrint("Firebase init"); await requestPermissions(); AppState().setDeviceToken = firebaseToken; await Permission.notification.isDenied.then((bool value) { @@ -60,12 +58,16 @@ class AppNotifications { Permission.notification.request(); } }); + + await FirebaseMessaging.instance.setAutoInitEnabled(true); + await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true); + RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage(); if (initialMessage != null) _handleMessage(initialMessage); FirebaseMessaging.onMessage.listen((RemoteMessage message) { - if (message.notification != null) _handleMessage(message); + _handleMessage(message); }); FirebaseMessaging.onMessageOpenedApp.listen(_handleOpenApp); @@ -76,30 +78,60 @@ class AppNotifications { AppState().setDeviceToken = token; }); - if (Platform.isAndroid) { - // await hmsApiAvailability.isHMSAvailable().then((value) async { - if (!(await Utils.isGoogleServicesAvailable())) { - huawei_push.Push.enableLogger(); - var result = await huawei_push.Push.setAutoInitEnabled(true); - - huawei_push.Push.onNotificationOpenedApp.listen((message) { - // newMessage(toFirebaseRemoteMessage(message)); - }, onError: (e) => print(e.toString())); - - huawei_push.Push.onMessageReceivedStream.listen((message) { - // newMessage(toFirebaseRemoteMessage(message)); - }, onError: (e) => print(e.toString())); - } - // }).catchError((err) { - // print(err); - // }); - } + // if (Platform.isAndroid) { + // // await hmsApiAvailability.isHMSAvailable().then((value) async { + // if (!(await Utils.isGoogleServicesAvailable())) { + // huawei_push.Push.enableLogger(); + // var result = await huawei_push.Push.setAutoInitEnabled(true); + // + // huawei_push.Push.onNotificationOpenedApp.listen((message) { + // print("onNotificationOpenedApp"); + // print(message); + // // newMessage(toFirebaseRemoteMessage(message)); + // }, onError: (e) => print(e.toString())); + // + // huawei_push.Push.onMessageReceivedStream.listen((message) { + // print("onMessageReceivedStream"); + // print(message); + // // newMessage(toFirebaseRemoteMessage(message)); + // }, onError: (e) => print(e.toString())); + // } + // // }).catchError((err) { + // // print(err); + // // }); + // } } - void initHuaweiPush(Function loginCallback) { + Future initHuaweiPush(Function loginCallback) async { AppState().setIsHuawei = true; - initTokenStream(loginCallback); + huawei_push.Push.enableLogger(); huawei_push.Push.getToken(""); + var result = await huawei_push.Push.setAutoInitEnabled(true); + debugPrint("HUAWEI PUSH TOKEN RESULT: $result"); + huawei_push.Push.onMessageReceivedStream.listen(onMessageReceived); + //huawei_push.Push.registerBackgroundMessageHandler(huaweiBackgroundMessage); + bool backgroundMessageHandler = await huawei_push.Push.registerBackgroundMessageHandler(huaweiBackgroundMessage); + debugPrint("Huawei Background Message Handler Registered: $backgroundMessageHandler"); + initTokenStream(loginCallback); + } + + void onMessageReceived(huawei_push.RemoteMessage remoteMessage) { + dynamic data = remoteMessage.data; + if (data != null) { + huawei_push.Push.localNotification( + { + huawei_push.HMSLocalNotificationAttr.TITLE: 'DataMessage Received', + huawei_push.HMSLocalNotificationAttr.MESSAGE: data, + }, + ); + print('onMessageReceived' + 'Data: $data'); + RemoteMessage message = RemoteMessage( + data: jsonDecode(remoteMessage.data!), + ); + _handleMessage(message); + } else { + print('onMessageReceived' + 'No data is present.'); + } } // HUAWEI PUSH TOKEN IMPLEMENTATION @@ -114,8 +146,8 @@ class AppNotifications { Utils.hideLoading(context); } - Future initTokenStream(Function loginCallback) async { - huawei_push.Push.getTokenStream.listen(_onTokenEvent, onError: _onTokenError).onData((data) { + void initTokenStream(Function loginCallback) { + huawei_push.Push.getTokenStream.listen(_onTokenEvent, onError: _onTokenError).onData((data) async { AppState().setHuaweiPushToken = data; debugPrint("HUAWEI PUSH TOKEN: $data"); loginCallback(); @@ -126,23 +158,34 @@ class AppNotifications { Utils.saveStringFromPrefs("isAppOpendByChat", "false"); GetNotificationsResponseModel notification = GetNotificationsResponseModel(); - notification.createdOn = DateUtil.getMonthDayYearDateFormatted(DateTime.now()); - notification.messageTypeData = message.data['picture']; - notification.message = message.data['message']; - notification.notificationType = message.data["NotificationType"].toString(); - if (message.data["NotificationType"] == "2") { - notification.videoURL = message.data["VideoUrl"]; - } + if (message.notification != null) { + notification.createdOn = DateUtil.getMonthDayYearDateFormatted(DateTime.now()); + notification.messageTypeData = message.data['picture']; + notification.message = message.data['message']; + notification.notificationType = message.data["NotificationType"].toString(); + if (message.data["NotificationType"] == "2") { + notification.videoURL = message.data["VideoUrl"]; + } - Future.delayed(Duration(seconds: 5), () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) => NotificationsDetailsPage( - notification: notification, + Future.delayed(Duration(seconds: 5), () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => NotificationsDetailsPage( + notification: notification, + ), ), - ), - ); - }); + ); + }); + } + + if (message.data.isNotEmpty && message.data["messageType"] == 'chat') { + Utils.saveStringFromPrefs("isAppOpendByChat", "true"); + Utils.saveStringFromPrefs("notificationData", message.data["user_chat_history_response"].toString()); + } else if (message.data.isNotEmpty && message.data["messageType"] == 'call') { + if (Platform.isAndroid) { + ChatVoipCall().showCallkitIncoming(uuid: const Uuid().v4(), data: message); + } + } } void _handleOpenApp(RemoteMessage message) { @@ -173,17 +216,41 @@ class AppNotifications { } } -AndroidNotificationChannel channel = const AndroidNotificationChannel( - "high_importance_channel", - "High Importance Notifications", +const AndroidNotificationChannel channel = AndroidNotificationChannel( + 'high_importance_channel', // id + 'High Importance Notifications', // title + description: 'This channel is used for important notifications.', // description importance: Importance.high, ); -Future backgroundMessageHandler(RemoteMessage message) async { +@pragma('vm:entry-point') +Future backgroundMessageHandler(RemoteMessage message) async { await Firebase.initializeApp(); - Utils.saveStringFromPrefs("isAppOpendByChat", "false"); - Utils.saveStringFromPrefs("notificationData", message.data["user_chat_history_response"].toString()); - if (message.data.isNotEmpty && message.data["type"] == 'call') { - // ChatVoipCall().showCallkitIncoming(uuid: const Uuid().v4(), data: message); + if (message.data.isNotEmpty && message.data["messageType"] == 'chat') { + Utils.saveStringFromPrefs("isAppOpendByChat", "false"); + Utils.saveStringFromPrefs("notificationData", message.data["user_chat_history_response"].toString()); + } else if (message.data.isNotEmpty && message.data["messageType"] == 'call') { + if (Platform.isAndroid) { + ChatVoipCall().showCallkitIncoming(uuid: const Uuid().v4(), data: message, background: true); + } + } +} + +void huaweiBackgroundMessage(huawei_push.RemoteMessage remoteMessage) async { + dynamic data = remoteMessage.data; + if (data != null) { + print( + 'Background message is received, sending local notification.', + ); + huawei_push.Push.localNotification( + { + huawei_push.HMSLocalNotificationAttr.TITLE: '[Headless] DataMessage Received', + huawei_push.HMSLocalNotificationAttr.MESSAGE: data, + }, + ); + } else { + print( + 'Background message is received. There is no data in the message.', + ); } } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 07a927d..6517deb 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -5,6 +5,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/chat_incoming_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'; @@ -192,6 +193,7 @@ class AppRoutes { static const String chat = "/chat"; static const String chatDetailed = "/chatDetailed"; static const String chatFavoriteUsers = "/chatFavoriteUsers"; + static const String chatStartCall = "/chatStartCall"; //Group Chat static const String manageGroup = "/manageGroup"; @@ -308,6 +310,8 @@ class AppRoutes { chat: (BuildContext context) => ChatHome(), chatDetailed: (BuildContext context) => ChatDetailScreen(), chatFavoriteUsers: (BuildContext context) => ChatFavoriteUsersScreen(), + chatStartCall: (BuildContext context) => StartCallPage(), + //Group Chat manageGroup: (BuildContext context) => ManageGroupScreen(), diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart index b59eec2..141f577 100644 --- a/lib/generated_plugin_registrant.dart +++ b/lib/generated_plugin_registrant.dart @@ -7,35 +7,28 @@ // ignore_for_file: depend_on_referenced_packages import 'package:audio_session/audio_session_web.dart'; -import 'package:camera_web/camera_web.dart'; import 'package:file_picker/_internal/file_picker_web.dart'; import 'package:firebase_core_web/firebase_core_web.dart'; import 'package:firebase_messaging_web/firebase_messaging_web.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:fluttertoast/fluttertoast_web.dart'; import 'package:geolocator_web/geolocator_web.dart'; -import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; import 'package:image_picker_for_web/image_picker_for_web.dart'; import 'package:just_audio_web/just_audio_web.dart'; -// import 'package:record_web/record_web.dart'; import 'package:shared_preferences_web/shared_preferences_web.dart'; import 'package:url_launcher_web/url_launcher_web.dart'; import 'package:video_player_web/video_player_web.dart'; -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - // ignore: public_member_api_docs void registerPlugins(Registrar registrar) { AudioSessionWeb.registerWith(registrar); - CameraPlugin.registerWith(registrar); FilePickerWeb.registerWith(registrar); FirebaseCoreWeb.registerWith(registrar); FirebaseMessagingWeb.registerWith(registrar); FluttertoastWebPlugin.registerWith(registrar); GeolocatorPlugin.registerWith(registrar); - GoogleMapsPlugin.registerWith(registrar); ImagePickerPlugin.registerWith(registrar); JustAudioPlugin.registerWith(registrar); - //RecordPluginWeb.registerWith(registrar); SharedPreferencesPlugin.registerWith(registrar); UrlLauncherPlugin.registerWith(registrar); VideoPlayerPlugin.registerWith(registrar); diff --git a/lib/main.dart b/lib/main.dart index 3eabd68..9cc9a68 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/generated/codegen_loader.g.dart'; import 'package:mohem_flutter_app/models/post_params_model.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/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/provider/eit_provider_model.dart'; @@ -17,8 +18,10 @@ import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:month_year_picker/month_year_picker.dart'; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; +import 'package:signalr_netcore/hub_connection.dart'; import 'package:sizer/sizer.dart'; +HubConnection? chatHubConnection; // test uat account // username 199067 @@ -74,9 +77,9 @@ Future main() async { ChangeNotifierProvider( create: (_) => MarathonProvider(), ), - // ChangeNotifierProvider( - // create: (_) => ChatCallProvider(), - // ), + ChangeNotifierProvider( + create: (_) => ChatCallProvider(), + ), ], child: const MyApp(), ), diff --git a/lib/models/chat/call.dart b/lib/models/chat/call.dart index ce58ae3..7deaccb 100644 --- a/lib/models/chat/call.dart +++ b/lib/models/chat/call.dart @@ -7,19 +7,31 @@ import 'dart:convert'; class CallDataModel { CallDataModel({ this.callerId, - this.callerDetails, + this.callerName, + this.callerEmail, + this.callerTitle, + this.callerPhone, this.receiverId, - this.receiverDetails, + this.receiverName, + this.receiverEmail, + this.receiverTitle, + this.receiverPhone, this.title, - this.calltype, + this.callType, }); - String? callerId; - CallerDetails? callerDetails; - String? receiverId; - ReceiverDetails? receiverDetails; - dynamic title; - String? calltype; + int? callerId; + String? callerName; + String? callerEmail; + String? callerTitle; + dynamic callerPhone; + int? receiverId; + String? receiverName; + String? receiverEmail; + dynamic receiverTitle; + dynamic receiverPhone; + String? title; + String? callType; factory CallDataModel.fromRawJson(String str) => CallDataModel.fromJson(json.decode(str)); @@ -27,171 +39,135 @@ class CallDataModel { factory CallDataModel.fromJson(Map json) => CallDataModel( callerId: json["callerID"], - callerDetails: json["callerDetails"] == null ? null : CallerDetails.fromJson(json["callerDetails"]), + callerName: json["callerName"], + callerEmail: json["callerEmail"], + callerTitle: json["callerTitle"], + callerPhone: json["callerPhone"], receiverId: json["receiverID"], - receiverDetails: json["receiverDetails"] == null ? null : ReceiverDetails.fromJson(json["receiverDetails"]), + receiverName: json["receiverName"], + receiverEmail: json["receiverEmail"], + receiverTitle: json["receiverTitle"], + receiverPhone: json["receiverPhone"], title: json["title"], - calltype: json["calltype"], + callType: json["callType"], ); Map toJson() => { "callerID": callerId, - "callerDetails": callerDetails?.toJson(), + "callerName": callerName, + "callerEmail": callerEmail, + "callerTitle": callerTitle, + "callerPhone": callerPhone, "receiverID": receiverId, - "receiverDetails": receiverDetails?.toJson(), + "receiverName": receiverName, + "receiverEmail": receiverEmail, + "receiverTitle": receiverTitle, + "receiverPhone": receiverPhone, "title": title, - "calltype": calltype, + "callType": callType, }; } -class CallerDetails { - CallerDetails({ - this.response, - this.errorResponses, + + + + +// To parse this JSON data, do +// +// final callSessionPayLoad = callSessionPayLoadFromJson(jsonString); + + +class CallSessionPayLoad { + CallSessionPayLoad({ + this.target, + this.caller, + this.sdp, }); - Response? response; - dynamic errorResponses; + int? target; + int? caller; + Sdp? sdp; - factory CallerDetails.fromRawJson(String str) => CallerDetails.fromJson(json.decode(str)); + factory CallSessionPayLoad.fromRawJson(String str) => CallSessionPayLoad.fromJson(json.decode(str)); String toRawJson() => json.encode(toJson()); - factory CallerDetails.fromJson(Map json) => CallerDetails( - response: json["response"] == null ? null : Response.fromJson(json["response"]), - errorResponses: json["errorResponses"], + factory CallSessionPayLoad.fromJson(Map json) => CallSessionPayLoad( + target: json["target"], + caller: json["caller"], + sdp: json["sdp"] == null ? null : Sdp.fromJson(json["sdp"]), ); Map toJson() => { - "response": response?.toJson(), - "errorResponses": errorResponses, + "target": target, + "caller": caller, + "sdp": sdp?.toJson(), }; } -class Response { - Response({ - this.id, - this.userName, - this.email, - this.phone, - this.title, - this.token, - this.isDomainUser, - this.isActiveCode, - this.encryptedUserId, - this.encryptedUserName, +class Sdp { + Sdp({ + this.type, + this.sdp, }); - int? id; - String? userName; - String? email; - dynamic phone; - String? title; - String? token; - bool? isDomainUser; - bool? isActiveCode; - String? encryptedUserId; - String? encryptedUserName; + String? type; + String? sdp; - factory Response.fromRawJson(String str) => Response.fromJson(json.decode(str)); + factory Sdp.fromRawJson(String str) => Sdp.fromJson(json.decode(str)); String toRawJson() => json.encode(toJson()); - factory Response.fromJson(Map json) => Response( - 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"], + factory Sdp.fromJson(Map json) => Sdp( + type: json["type"], + sdp: json["sdp"], ); Map toJson() => { - "id": id, - "userName": userName, - "email": email, - "phone": phone, - "title": title, - "token": token, - "isDomainUser": isDomainUser, - "isActiveCode": isActiveCode, - "encryptedUserId": encryptedUserId, - "encryptedUserName": encryptedUserName, + "type": type, + "sdp": sdp, }; } -class ReceiverDetails { - ReceiverDetails({ - this.id, - this.userName, - this.email, - this.phone, - this.title, - this.userStatus, - this.image, - this.unreadMessageCount, - this.userAction, - this.isPin, - this.isFav, - this.isAdmin, - this.rKey, - this.totalCount, + + + + +// final iosCallPayload = iosCallPayloadFromJson(jsonString); + +class IosCallPayload { + String? incomingCallType; + String? incomingCallerId; + String? incomingCallReciverId; + String? incomingCallerName; + String? callData; + String? uuid; + + IosCallPayload({ + this.incomingCallType, + this.incomingCallerId, + this.incomingCallReciverId, + this.incomingCallerName, + this.callData, + this.uuid, }); - int? id; - String? userName; - String? email; - dynamic phone; - dynamic title; - int? userStatus; - String? image; - int? unreadMessageCount; - dynamic userAction; - bool? isPin; - bool? isFav; - bool? isAdmin; - String? rKey; - int? totalCount; - - factory ReceiverDetails.fromRawJson(String str) => ReceiverDetails.fromJson(json.decode(str)); + factory IosCallPayload.fromRawJson(String str) => IosCallPayload.fromJson(json.decode(str)); String toRawJson() => json.encode(toJson()); - factory ReceiverDetails.fromJson(Map json) => ReceiverDetails( - id: json["id"], - userName: json["userName"], - email: json["email"], - phone: json["phone"], - title: json["title"], - userStatus: json["userStatus"], - image: json["image"], - unreadMessageCount: json["unreadMessageCount"], - userAction: json["userAction"], - isPin: json["isPin"], - isFav: json["isFav"], - isAdmin: json["isAdmin"], - rKey: json["rKey"], - totalCount: json["totalCount"], + factory IosCallPayload.fromJson(Map json) => IosCallPayload( + incomingCallType: json["incoming_call_type"], + incomingCallerId: json["incoming_caller_id"], + incomingCallerName: json["incoming_caller_name"], + incomingCallReciverId: null, + uuid: json["uuid"], ); Map toJson() => { - "id": id, - "userName": userName, - "email": email, - "phone": phone, - "title": title, - "userStatus": userStatus, - "image": image, - "unreadMessageCount": unreadMessageCount, - "userAction": userAction, - "isPin": isPin, - "isFav": isFav, - "isAdmin": isAdmin, - "rKey": rKey, - "totalCount": totalCount, + "incoming_call_type": incomingCallType, + "incoming_caller_id": incomingCallerId, + "incoming_caller_name": incomingCallerName, + "uuid": uuid, }; } diff --git a/lib/models/chat/create_group_request.dart b/lib/models/chat/create_group_request.dart index b33297f..1f5bfd2 100644 --- a/lib/models/chat/create_group_request.dart +++ b/lib/models/chat/create_group_request.dart @@ -4,6 +4,8 @@ class CreateGroupRequest { String? groupName; int? adminUserId; List? groupUserList; + List? addedUsers; + List? removedUsers; bool? canAttach; bool? canAudioC; bool? canShareS; @@ -15,6 +17,8 @@ class CreateGroupRequest { {this.groupName, this.adminUserId, this.groupUserList, + this.addedUsers, + this.removedUsers, this.canAttach, this.canAudioC, this.canShareS, @@ -27,12 +31,24 @@ class CreateGroupRequest { CreateGroupRequest.fromJson(Map json) { groupName = json['groupName']; adminUserId = json['adminUserId']; + if (json['removedUsers'] != null) { + groupUserList = []; + json['removedUsers'].forEach((v) { + groupUserList!.add(new ChatUser.fromJson(v)); + }); + } if (json['groupUserList'] != null) { groupUserList = []; json['groupUserList'].forEach((v) { groupUserList!.add(new ChatUser.fromJson(v)); }); } + if (json['addedUsers'] != null) { + groupUserList = []; + json['addedUsers'].forEach((v) { + groupUserList!.add(new ChatUser.fromJson(v)); + }); + } canAttach = json['canAttach']; canAudioC = json['canAudioC']; canShareS = json['canShareS']; @@ -50,6 +66,14 @@ class CreateGroupRequest { data['groupUserList'] = this.groupUserList!.map((v) => v.toJson()).toList(); } + if (this.addedUsers != null) { + data['addedUsers'] = + this.addedUsers!.map((v) => v.toJson()).toList(); + } + if (this.removedUsers != null) { + data['removedUsers'] = + this.removedUsers!.map((v) => v.toJson()).toList(); + } data['canAttach'] = this.canAttach; data['canAudioC'] = this.canAudioC; data['canShareS'] = this.canShareS; diff --git a/lib/models/chat/get_user_groups_by_id.dart b/lib/models/chat/get_user_groups_by_id.dart index 2520c43..e0219a5 100644 --- a/lib/models/chat/get_user_groups_by_id.dart +++ b/lib/models/chat/get_user_groups_by_id.dart @@ -1,7 +1,7 @@ import 'dart:convert'; class GetUserGroups { - List? groupresponse; + List? groupresponse =[]; Null? errorResponses; GetUserGroups({this.groupresponse, this.errorResponses}); diff --git a/lib/models/chat/incoming_call_model.dart b/lib/models/chat/incoming_call_model.dart new file mode 100644 index 0000000..9494505 --- /dev/null +++ b/lib/models/chat/incoming_call_model.dart @@ -0,0 +1,333 @@ +// To parse this JSON data, do +// +// final incomingCallModel = incomingCallModelFromJson(jsonString); + +import 'dart:convert'; + +class IncomingCallModel { + String? actionColor; + String? appName; + Args? args; + String? avatar; + String? backgroundColor; + String? backgroundUrl; + int? duration; + Extra? extra; + String? from; + String? handle; + Args? headers; + String? id; + bool? isAccepted; + bool? isCustomNotification; + bool? isCustomSmallExNotification; + bool? isShowCallback; + bool? isShowLogo; + bool? isShowMissedCallNotification; + String? nameCaller; + String? ringtonePath; + String? textAccept; + String? textCallback; + String? textDecline; + String? textMissedCall; + int? type; + String? uuid; + + IncomingCallModel({ + this.actionColor, + this.appName, + this.args, + this.avatar, + this.backgroundColor, + this.backgroundUrl, + this.duration, + this.extra, + this.from, + this.handle, + this.headers, + this.id, + this.isAccepted, + this.isCustomNotification, + this.isCustomSmallExNotification, + this.isShowCallback, + this.isShowLogo, + this.isShowMissedCallNotification, + this.nameCaller, + this.ringtonePath, + this.textAccept, + this.textCallback, + this.textDecline, + this.textMissedCall, + this.type, + this.uuid, + }); + + factory IncomingCallModel.fromRawJson(String str) => IncomingCallModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory IncomingCallModel.fromJson(Map json) => IncomingCallModel( + actionColor: json["actionColor"], + appName: json["appName"], + args: json["args"] == null ? null : Args.fromJson(json["args"]), + avatar: json["avatar"], + backgroundColor: json["backgroundColor"], + backgroundUrl: json["backgroundUrl"], + duration: json["duration"] == null ? null : json["duration"].toInt(), + extra: json["extra"] == null ? null : Extra.fromJson(json["extra"]), + from: json["from"], + handle: json["handle"], + headers: json["headers"] == null ? null : Args.fromJson(json["headers"]), + id: json["id"], + isAccepted: json["isAccepted"], + isCustomNotification: json["isCustomNotification"], + isCustomSmallExNotification: json["isCustomSmallExNotification"], + isShowCallback: json["isShowCallback"], + isShowLogo: json["isShowLogo"], + isShowMissedCallNotification: json["isShowMissedCallNotification"], + nameCaller: json["nameCaller"], + ringtonePath: json["ringtonePath"], + textAccept: json["textAccept"], + textCallback: json["textCallback"], + textDecline: json["textDecline"], + textMissedCall: json["textMissedCall"], + type: json["type"] == null ? null : json["type"].toInt(), + uuid: json["uuid"], + ); + + Map toJson() => { + "actionColor": actionColor, + "appName": appName, + "args": args?.toJson(), + "avatar": avatar, + "backgroundColor": backgroundColor, + "backgroundUrl": backgroundUrl, + "duration": duration, + "extra": extra?.toJson(), + "from": from, + "handle": handle, + "headers": headers?.toJson(), + "id": id, + "isAccepted": isAccepted, + "isCustomNotification": isCustomNotification, + "isCustomSmallExNotification": isCustomSmallExNotification, + "isShowCallback": isShowCallback, + "isShowLogo": isShowLogo, + "isShowMissedCallNotification": isShowMissedCallNotification, + "nameCaller": nameCaller, + "ringtonePath": ringtonePath, + "textAccept": textAccept, + "textCallback": textCallback, + "textDecline": textDecline, + "textMissedCall": textMissedCall, + "type": type, + "uuid": uuid, + }; +} + +class Args { + Args(); + + factory Args.fromRawJson(String str) => Args.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Args.fromJson(Map json) => Args(); + + Map toJson() => {}; +} + +class Extra { + LoginDetails? loginDetails; + bool? isIncomingCall; + CallerDetails? callerDetails; + String? callType; + + Extra({ + this.loginDetails, + this.isIncomingCall, + this.callerDetails, + this.callType, + }); + + 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"]), + isIncomingCall: json["isIncomingCall"], + callType: json["callType"], + callerDetails: json["callerDetails"] == null ? null : CallerDetails.fromJson(json["callerDetails"]), + ); + + Map toJson() => { + "loginDetails": loginDetails?.toJson(), + "isIncomingCall": isIncomingCall, + "callType": callType, + "callerDetails": callerDetails?.toJson(), + }; +} + +class CallerDetails { + int? userChatHistoryId; + String? contant; + FileTypeResponse? fileTypeResponse; + String? currentUserName; + String? targetUserEmail; + String? conversationId; + String? encryptedTargetUserId; + int? targetUserId; + bool? isSeen; + int? userChatHistoryLineId; + bool? isDelivered; + String? targetUserName; + int? currentUserId; + DateTime? createdDate; + String? currentUserEmail; + String? contantNo; + int? chatEventId; + String? encryptedTargetUserName; + int? chatSource; + + CallerDetails({ + this.userChatHistoryId, + this.contant, + this.fileTypeResponse, + this.currentUserName, + this.targetUserEmail, + this.conversationId, + this.encryptedTargetUserId, + this.targetUserId, + this.isSeen, + this.userChatHistoryLineId, + this.isDelivered, + this.targetUserName, + this.currentUserId, + this.createdDate, + this.currentUserEmail, + this.contantNo, + this.chatEventId, + this.encryptedTargetUserName, + this.chatSource, + }); + + factory CallerDetails.fromRawJson(String str) => CallerDetails.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory CallerDetails.fromJson(Map json) => CallerDetails( + userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"].toInt(), + contant: json["contant"], + fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), + currentUserName: json["currentUserName"], + targetUserEmail: json["targetUserEmail"], + conversationId: json["conversationId"], + encryptedTargetUserId: json["encryptedTargetUserId"], + targetUserId: json["targetUserId"] == null ? null : json["targetUserId"].toInt(), + isSeen: json["isSeen"], + userChatHistoryLineId: json["userChatHistoryLineId"] == null ? null : json["userChatHistoryLineId"].toInt(), + isDelivered: json["isDelivered"], + targetUserName: json["targetUserName"], + currentUserId: json["currentUserId"] == null ? null : json["currentUserId"].toInt(), + createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), + currentUserEmail: json["currentUserEmail"], + contantNo: json["contantNo"], + chatEventId: json["chatEventId"] == null ? null : json["chatEventId"].toInt(), + encryptedTargetUserName: json["encryptedTargetUserName"], + chatSource: json["chatSource"] == null ? null : json["chatSource"].toInt(), + ); + + Map toJson() => { + "userChatHistoryId": userChatHistoryId, + "contant": contant, + "fileTypeResponse": fileTypeResponse?.toJson(), + "currentUserName": currentUserName, + "targetUserEmail": targetUserEmail, + "conversationId": conversationId, + "encryptedTargetUserId": encryptedTargetUserId, + "targetUserId": targetUserId, + "isSeen": isSeen, + "userChatHistoryLineId": userChatHistoryLineId, + "isDelivered": isDelivered, + "targetUserName": targetUserName, + "currentUserId": currentUserId, + "createdDate": createdDate?.toIso8601String(), + "currentUserEmail": currentUserEmail, + "contantNo": contantNo, + "chatEventId": chatEventId, + "encryptedTargetUserName": encryptedTargetUserName, + "chatSource": chatSource, + }; +} + +class FileTypeResponse { + int? fileTypeId; + + FileTypeResponse({ + this.fileTypeId, + }); + + factory FileTypeResponse.fromRawJson(String str) => FileTypeResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory FileTypeResponse.fromJson(Map json) => FileTypeResponse( + fileTypeId: json["fileTypeId"].toInt(), + ); + + Map toJson() => { + "fileTypeId": fileTypeId, + }; +} + +class LoginDetails { + bool? isActiveCode; + int? id; + String? encryptedUserName; + String? userName; + String? title; + String? encryptedUserId; + String? email; + bool? isDomainUser; + String? token; + + LoginDetails({ + this.isActiveCode, + this.id, + this.encryptedUserName, + this.userName, + this.title, + this.encryptedUserId, + this.email, + this.isDomainUser, + this.token, + }); + + factory LoginDetails.fromRawJson(String str) => LoginDetails.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory LoginDetails.fromJson(Map json) => LoginDetails( + isActiveCode: json["isActiveCode"], + id: json["id"] == null ? null : json["id"].toInt(), + encryptedUserName: json["encryptedUserName"], + userName: json["userName"], + title: json["title"], + encryptedUserId: json["encryptedUserId"], + email: json["email"], + isDomainUser: json["isDomainUser"], + token: json["token"], + ); + + Map toJson() => { + "isActiveCode": isActiveCode, + "id": id, + "encryptedUserName": encryptedUserName, + "userName": userName, + "title": title, + "encryptedUserId": encryptedUserId, + "email": email, + "isDomainUser": isDomainUser, + "token": token, + }; +} diff --git a/lib/models/chat/webrtc_payloads.dart b/lib/models/chat/webrtc_payloads.dart new file mode 100644 index 0000000..1a3463f --- /dev/null +++ b/lib/models/chat/webrtc_payloads.dart @@ -0,0 +1,61 @@ +// To parse this JSON data, do +// +// final remoteIceCandidatePayLoad = remoteIceCandidatePayLoadFromJson(jsonString); + +import 'dart:convert'; + +class RemoteIceCandidatePayLoad { + RemoteIceCandidatePayLoad({ + this.target, + this.candidate, + }); + + int? target; + Candidate? candidate; + + factory RemoteIceCandidatePayLoad.fromRawJson(String str) => RemoteIceCandidatePayLoad.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RemoteIceCandidatePayLoad.fromJson(Map json) => RemoteIceCandidatePayLoad( + target: json["target"], + candidate: json["candidate"] == null ? null : Candidate.fromJson(json["candidate"]), + ); + + Map toJson() => { + "target": target, + "candidate": candidate?.toJson(), + }; +} + +class Candidate { + Candidate({ + this.candidate, + this.sdpMid, + this.sdpMLineIndex, + this.usernameFragment, + }); + + String? candidate; + String? sdpMid; + int? sdpMLineIndex; + String? usernameFragment; + + factory Candidate.fromRawJson(String str) => Candidate.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Candidate.fromJson(Map json) => Candidate( + candidate: json["candidate"], + sdpMid: json["sdpMid"], + sdpMLineIndex: json["sdpMLineIndex"], + usernameFragment: json["usernameFragment"], + ); + + Map toJson() => { + "candidate": candidate, + "sdpMid": sdpMid, + "sdpMLineIndex": sdpMLineIndex, + "usernameFragment": usernameFragment, + }; +} diff --git a/lib/provider/chat_call_provider.dart b/lib/provider/chat_call_provider.dart index 45205df..d768552 100644 --- a/lib/provider/chat_call_provider.dart +++ b/lib/provider/chat_call_provider.dart @@ -1,52 +1,70 @@ +import 'dart:async'; import 'dart:convert'; -import 'dart:ui'; +import 'dart:io'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; +import 'package:flutter_ios_voip_kit/flutter_ios_voip_kit.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; -import 'package:mohem_flutter_app/ui/landing/dashboard_screen.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/config/routes.dart'; +import 'package:mohem_flutter_app/main.dart'; +import 'package:mohem_flutter_app/models/chat/call.dart'; +import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.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'; +import 'package:signalr_netcore/hub_connection.dart'; class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin { ///////////////////// Web RTC Video Calling ////////////////////// // Video Call - late RTCPeerConnection _peerConnection; - RTCVideoRenderer _localVideoRenderer = RTCVideoRenderer(); - final RTCVideoRenderer _remoteRenderer = RTCVideoRenderer(); + late RTCPeerConnection _pc; + late ChatProviderModel chatProvModel; + RTCVideoRenderer? localVideoRenderer; + RTCVideoRenderer? remoteRenderer; + final AudioPlayer player = AudioPlayer(); + final FlutterIOSVoIPKit voIPKit = FlutterIOSVoIPKit.instance; MediaStream? _localStream; - MediaStream? _remoteStream; - - 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); - } - - //Video Constraints - var videoConstraints = { - "video": { - "mandatory": { - "width": {"min": 320}, - "height": {"min": 180} - }, - "optional": [ - { - "width": {"max": 1280} - }, - {"frameRate": 25}, - {"facingMode": "user"} - ] - }, - "frameRate": 25, - "width": 420, //420,//640,//1280, - "height": 240 //240//480//720 - }; + late CallDataModel outGoingCallData; + bool isMicOff = false; + bool isLoudSpeaker = false; + bool isCamOff = false; + bool isCallEnded = false; + bool isVideoCall = false; + bool isAudioCall = false; + bool isCallStarted = false; + bool isFrontCamera = true; + late SingleUserChatModel incomingCallData; + + /// WebRTC Connection Variables + bool isIncomingCallLoader = true; + bool isIncomingCall = false; + bool isOutGoingCall = false; + bool isUserOnline = false; + + late BuildContext providerContext; + + void initCallListeners({required BuildContext context}) { + providerContext = context; + if (kDebugMode) { + print("=================== Call Listeners Registered ======================="); + } + 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("OnIncomingCallAsync", OnIncomingCallAsync); + } // Audio Constraints - var audioConstraints = { + Map audioConstraints = { "sampleRate": 8000, "sampleSize": 16, "channelCount": 2, @@ -54,134 +72,769 @@ class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin { "audio": true, }; - Future _createPeerConnection() async { - // {"url": "stun:stun.l.google.com:19302"}, - Map configuration = { - "iceServers": [ - {"urls": 'stun:15.185.116.59:3478'}, - {"urls": "turn:15.185.116.59:3479", "username": "admin", "credential": "admin"} - ] - }; + Future init() async { + _pc = await creatOfferWithCon(); + Future.delayed(const Duration(seconds: 2), () { + connectIncomingCall(); + }); + } - Map offerSdpConstraints = { - "mandatory": { - "OfferToReceiveAudio": true, - "OfferToReceiveVideo": true, - }, - "optional": [], - }; + ///////////////////////////////////////////////OutGoing Call//////////////////////////////////////////////////// - RTCPeerConnection pc = await createPeerConnection(configuration, offerSdpConstraints); - // if (pc != null) print(pc); - //pc.addStream(widget.localStream); + Future initLocalCamera({required ChatProviderModel chatProvmodel, required callData, required BuildContext context, bool isIncomingCall = false}) async { + isCallEnded = false; + chatProvModel = chatProvmodel; + outGoingCallData = callData; + await initStreams(); - pc.onIceCandidate = (e) { - if (e.candidate != null) { - print(json.encode({ - 'candidate': e.candidate.toString(), - 'sdpMid': e.sdpMid.toString(), - 'sdpMlineIndex': e.sdpMLineIndex, - })); - } - }; - pc.onIceConnectionState = (e) { - print(e); - }; - pc.onAddStream = (stream) { - print('addStream: ' + stream.id); - _remoteRenderer.srcObject = stream; - }; - return pc; + await startCall(callType: isVideoCall ? "Video" : "Audio", context: context); + _pc = await creatOfferWithCon(); + connectOutgoing(); + notifyListeners(); } - void init() { - initRenderers(); - _createPeerConnection().then((pc) { - _peerConnection = pc; - // _setRemoteDescription(widget.info); - }); + void connectOutgoing() { + isOutGoingCall = true; + // notifyListeners(); } - void initRenderers() { - _localVideoRenderer.initialize(); - _remoteRenderer.initialize(); - initLocalCamera(); + Future startCall({required String callType, required BuildContext context}) async { + chatProvModel.isTextMsg = true; + chatProvModel.isAttachmentMsg = false; + chatProvModel.isVoiceMsg = false; + chatProvModel.isReplyMsg = false; + chatProvModel.isCall = true; + chatProvModel.message.text = "Start $callType call ${outGoingCallData.receiverName.toString().replaceAll(".", " ")}"; + chatProvModel.sendChatMessage( + context, + targetUserId: outGoingCallData.receiverId!, + userStatus: 1, + userEmail: outGoingCallData.receiverEmail!, + targetUserName: outGoingCallData.receiverName!, + ); + await invoke( + invokeMethod: "CallUserAsync", + currentUserID: outGoingCallData.callerId!, + targetUserID: outGoingCallData.receiverId!, + ); + await invoke(invokeMethod: "UpdateUserStatusAsync", currentUserID: outGoingCallData.callerId!, targetUserID: outGoingCallData.receiverId!, userStatus: 4); + } + + // OutGoing Listeners + void onCallAcceptedAsync(List? params) async { + if (_timer!.isActive && _start > 5) { + _timer!.cancel(); + _start = 25; + } + dynamic items = params!.toList(); + RTCSessionDescription description = await _createOffer(); + await _pc.setLocalDescription(description); + dynamic payload = {"target": items[0]["id"], "caller": outGoingCallData.callerId, "sdp": description.toMap()}; + invoke(invokeMethod: "OfferAsync", currentUserID: outGoingCallData.callerId!, targetUserID: items[0]["id"], data: jsonEncode(payload)); + } + + Future onIceCandidateAsync(List? params) async { + dynamic items = params!.toList(); + if (isIncomingCall) { + RemoteIceCandidatePayLoad data = RemoteIceCandidatePayLoad.fromJson(jsonDecode(items.first.toString())); + if (_pc != null) { + await _pc.addCandidate(RTCIceCandidate(data.candidate!.candidate, data.candidate!.sdpMid, data.candidate!.sdpMLineIndex)); + } + } else { + if (kDebugMode) { + logger.i("res: " + items.toString()); + } + RemoteIceCandidatePayLoad data = RemoteIceCandidatePayLoad.fromJson(jsonDecode(items.first.toString())); + if (_pc != null) { + await _pc.addCandidate(RTCIceCandidate(data.candidate!.candidate, data.candidate!.sdpMid, data.candidate!.sdpMLineIndex)); + if (!isCallStarted) { + isCallStarted = true; + notifyListeners(); + if (isCallStarted) { + isIncomingCallLoader = false; + isOutGoingCall = true; + if (Platform.isIOS) { + Future.delayed(Duration(seconds: 2), () { + Navigator.pushReplacement( + providerContext, + MaterialPageRoute( + builder: (BuildContext context) => StartCallPage(), + )); + }); + } else { + Navigator.pushReplacement( + providerContext, + MaterialPageRoute( + builder: (BuildContext context) => StartCallPage(), + )); + } + } + } + } + notifyListeners(); + } } - void initLocalCamera() async { - _localStream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': true}); - _localVideoRenderer.srcObject = _localStream; - // _localVideoRenderer.srcObject = await navigator.mediaDevices - // .getUserMedia({'video': true, 'audio': true}); - print('this source Object'); - print('this suarce ${_localVideoRenderer.srcObject != null}'); + Future onOfferAsync(List? params) async { + dynamic items = params!.toList(); + var data = jsonDecode(items.toString()); + if (isIncomingCall) { + _pc.setRemoteDescription(RTCSessionDescription(data[0]["sdp"]["sdp"], data[0]["sdp"]["type"])); + RTCSessionDescription description = await _createAnswer(); + await _pc.setLocalDescription(description); + dynamic payload = {"target": data[0]["caller"], "caller": AppState().chatDetails!.response!.id, "sdp": description.toMap()}; + invoke(invokeMethod: "AnswerOfferAsync", currentUserID: AppState().chatDetails!.response!.id!, targetUserID: incomingCallData.targetUserId!, data: jsonEncode(payload)); + } + // else { + // RTCSessionDescription description = await _createAnswer(); + // await _pc.setLocalDescription(description); + // var payload = {"target": items[0]["id"], "caller": outGoingCallData.callerId, "sdp": description.toMap()}; + // invoke(invokeMethod: "AnswerOffer", currentUserID: outGoingCallData.callerId!, targetUserID: items[0]["id"], data: jsonEncode(payload)); + // } notifyListeners(); } - void startCall({required String callType}) {} - - void endCall() {} - - void checkCall(Map message) { - switch (message["callStatus"]) { - case 'connected': - {} - break; - case 'offer': - {} - break; - case 'accept': - {} - break; - case 'candidate': - {} - break; - case 'bye': - {} - break; - case 'leave': - {} - break; + //////////////////////////// OutGoing Call End /////////////////////////////////////// + + Future endCall({required bool isUserOnline}) async { + if (isIncomingCall) { + logger.i("-----------------------Endeddddd By Me---------------------------"); + if (chatHubConnection!.state == HubConnectionState.Connected) { + await invoke(invokeMethod: "HangUpAsync", currentUserID: AppState().chatDetails!.response!.id!, targetUserID: incomingCallData.targetUserId!, userStatus: 0); + await invoke(invokeMethod: "UpdateUserStatusAsync", currentUserID: AppState().chatDetails!.response!.id!, targetUserID: incomingCallData.targetUserId!, userStatus: 1); + } + isCallStarted = false; + isVideoCall = false; + isCamOff = false; + isMicOff = false; + isLoudSpeaker = false; + isIncomingCall = false; + isOutGoingCall = false; + isAudioCall = false; + + if (isCallConnected) { + if (_pc.connectionState == RTCPeerConnectionState.RTCPeerConnectionStateConnected) { + if (kDebugMode) { + print("------------------ PC Stopped ----------------------------"); + } + _pc.close(); + _pc.dispose(); + } + } + if (remoteRenderer != null) { + remoteRenderer!.dispose(); + remoteRenderer = null; + } + if (localVideoRenderer != null) { + localVideoRenderer!.dispose(); + localVideoRenderer = null; + } + + if (_localStream != null) { + _localStream!.dispose(); + _localStream = null; + } + if (chatHubConnection != null && !isUserOnline) { + chatHubConnection!.stop(); + } + if (Platform.isAndroid) await FlutterCallkitIncoming.endAllCalls(); + if (Platform.isIOS) await voIPKit.endCall(); + return true; + } else { + if (isOutGoingCall) { + await invoke(invokeMethod: "HangUpAsync", currentUserID: outGoingCallData.callerId!, targetUserID: outGoingCallData.receiverId!, userStatus: 1); + await invoke(invokeMethod: "UpdateUserStatusAsync", currentUserID: outGoingCallData.callerId!, targetUserID: outGoingCallData.receiverId!, userStatus: 1); + } else if (isIncomingCall) { + await invoke(invokeMethod: "UpdateUserStatusAsync", currentUserID: AppState().chatDetails!.response!.id!, targetUserID: incomingCallData.targetUserId!, userStatus: 1); + } + isCallStarted = false; + isVideoCall = false; + isCamOff = false; + isMicOff = false; + isLoudSpeaker = false; + if (isCallConnected) { + if (_pc.connectionState == RTCPeerConnectionState.RTCPeerConnectionStateConnected) { + _pc.close(); + _pc.dispose(); + } + } + if (remoteRenderer != null) { + remoteRenderer!.dispose(); + remoteRenderer = null; + } + if (localVideoRenderer != null) { + localVideoRenderer!.dispose(); + localVideoRenderer = null; + } + + if (_localStream != null) { + _localStream!.dispose(); + _localStream = null; + } + isOutGoingCall = false; + isIncomingCall = false; + isAudioCall = false; + return true; + } + } + + // Incoming Listeners + + void onAnswerOffer(List? payload) async { + // if (isIncomingCall) { + // // print("--------------------- On Answer Offer Async ---------------------------------------"); + // //await invoke(invokeMethod: "InvokeMobile", currentUserID: AppState().getchatUserDetails!.response!.id!, targetUserID: incomingCallData.targetUserId!, debugData: {"On Answer Offer Async"}); + // } else { + var items = payload!.toList(); + if (kDebugMode) { + logger.i("res: " + items.toString()); } + CallSessionPayLoad data = CallSessionPayLoad.fromJson(jsonDecode(items.first.toString())); + RTCSessionDescription description = RTCSessionDescription(data.sdp!.sdp, 'answer'); + _pc.setRemoteDescription(description); + // } } - //// Listeners Methods //// + Future onHangUpAsync(List? params) async { + print("--------------------- onHangUp ASYNC ---------------------------------"); - void onCallAcceptedAsync(List? params) {} + dynamic items = params!.toList(); + if (items[0]["id"] != AppState().chatDetails!.response!.id) { + if (Platform.isIOS) await voIPKit.endCall(); + if (Platform.isAndroid) await FlutterCallkitIncoming.endAllCalls(); + if (isIncomingCall) { + endCall(isUserOnline: isUserOnline).then((bool value) { + if (isCallConnected && isUserOnline) { + isCallConnected = false; + if (!AppState().isLogged) { + Navigator.of(AppRoutes.navigatorKey.currentContext!).canPop(); + } else { + Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.dashboard)); + } + } else { + Navigator.of(AppRoutes.navigatorKey.currentContext!).canPop(); + } + }); + } else { + if (isOutGoingCall) { + endCall(isUserOnline: isUserOnline).then((bool value) { + if (isCallConnected && isUserOnline) { + isCallConnected = false; + Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.dashboard)); + } else { + // Navigator.of(AppRoutes.navigatorKey.currentContext!).canPop(); + Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.dashboard)); + } + }); + } + } + } else { + if (kDebugMode) { + print("Call Ended By Me"); + } + if (isOutGoingCall) { + // if (!isCallConnected) { + // if (_timer!.isActive && _start == 5) { + // _timer!.cancel(); + // _start = 25; + // Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.chatDetailed)); + // endCall(isUserOnline: isUserOnline); + // return Utils.showToast("${items[0]["userName"]}" + " has declined call", longDuration: true); + // } + // } + if (isCallConnected && isUserOnline) { + isCallConnected = false; + Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.chatDetailed)); + } else { + Navigator.of(AppRoutes.navigatorKey.currentContext!).canPop(); + } + } + } + // endCall(isUserOnline: isUserOnline).then((bool value) { + // if (isCallConnected && isUserOnline) { + // Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.chatDetailed)); + // isCallConnected = false; + // } + // if (items[0]["id"] != AppState().chatDetails!.response!.id && !AppState().isBackgroundCall) { + // if (kDebugMode) { + // print("Popped Due to Another User"); + // } + // if (AppState().isBackgroundCall) { + // Navigator.of(AppRoutes.navigatorKey.currentContext!).pop(); + // // Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.login)); + // } else { + // Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.chat)); + // } + // } + // if (AppState().isBackgroundCall) { + // Navigator.of(AppRoutes.navigatorKey.currentContext!).pop(); + // } + // + notifyListeners(); + isCallEnded = true; + // }); + } - void onIceCandidateAsync(List? params) {} + //Outgoing Timer for Decline Call - void onOfferAsync(List? params) {} + Timer? _timer; + int _start = 25; - void onAnswerOffer(List? params) {} + void startTimer() { + Duration oneSec = const Duration(seconds: 1); + _timer = Timer.periodic( + oneSec, + (Timer timer) { + if (_start == 0) { + timer.cancel(); + } else { + print(_start); + _start--; + if (isOutGoingCall) { + if (!isCallConnected) { + if (_timer!.isActive && _start == 5) { + _timer!.cancel(); + _start = 25; + Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.chatDetailed)); + endCall(isUserOnline: isUserOnline); + return Utils.showToast("${outGoingCallData.receiverName}" + " has declined call", longDuration: true); + } + } else { + if (isCallConnected) { + _timer!.cancel(); + _start = 25; + } + } + notifyListeners(); + } + } + }, + ); + } - void onHangUpAsync(List? params) {} + // Future OnIncomingCallAsync(List? params) async { + // print("--------------------- On Incoming Call ---------------------------------------"); + // dynamic items = params!.toList(); + // logger.d(items); + // // Map json = { + // // "callerID": items[0]["id"], + // // "callerName": items[0]["userName"], + // // "callerEmail": items[0]["email"], + // // "callerTitle": items[0]["title"], + // // "callerPhone": null, + // // "receiverID": AppState().chatDetails!.response!.id, + // // "receiverName": AppState().chatDetails!.response!.userName, + // // "receiverEmail": AppState().chatDetails!.response!.email, + // // "receiverTitle": AppState().chatDetails!.response!.title, + // // "receiverPhone": AppState().chatDetails!.response!.phone, + // // "title": AppState().chatDetails!.response!.userName!.replaceAll(".", " "), + // // "callType": items[1] ? "Video" : "Audio", + // // }; + // // CallDataModel callData = CallDataModel.fromJson(json); + // // ChatVoipCall().showCallkitIncoming(uuid: const Uuid().v4(), isOnline: true, incomingCallData: callData); + // // + // // if (!isOnIncomingCallPage) { + // // Map json = { + // // "callerID": items[0]["id"], + // // "callerName": items[0]["userName"], + // // "callerEmail": items[0]["email"], + // // "callerTitle": items[0]["title"], + // // "callerPhone": null, + // // "receiverID": AppState().chatDetails!.response!.id, + // // "receiverName": AppState().chatDetails!.response!.userName, + // // "receiverEmail": AppState().chatDetails!.response!.email, + // // "receiverTitle": AppState().chatDetails!.response!.title, + // // "receiverPhone": AppState().chatDetails!.response!.phone, + // // "title": AppState().chatDetails!.response!.userName!.replaceAll(".", " "), + // // "callType": items[1] ? "Video" : "Audio", + // // }; + // // CallDataModel callData = CallDataModel.fromJson(json); + // // await Navigator.push( + // // providerContext, + // // MaterialPageRoute( + // // builder: (BuildContext context) => IncomingCall( + // // isVideoCall: items[1] ? true : false, + // // outGoingCallData: callData, + // // ), + // // ), + // // ); + // // isOnIncomingCallPage = true; + // // } + // } - void onCallDeclinedAsync(List? params) {} + Future onCallDeclinedAsync(List? params) async { + print("================= On Declined ========================"); + dynamic items = params!.toList(); + if (isOutGoingCall) { + Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.chatDetailed)); + bool val = await endCall(isUserOnline: isUserOnline); + if (Platform.isIOS) await voIPKit.endCall(); + if (Platform.isAndroid) await FlutterCallkitIncoming.endAllCalls(); + return Utils.showToast("${items[1]["userName"]}" + " has declined call", longDuration: true); + } + } //// Invoke Methods - Future invoke({required String invokeMethod, required String currentUserID, required String targetUserID, bool isVideoCall = false, var data}) async { + Future invoke({required String invokeMethod, required int currentUserID, required int targetUserID, var data, int userStatus = 1, var debugData}) async { List args = []; - if (invokeMethod == "answerCallAsync") { - args = [currentUserID, targetUserID]; - } else if (invokeMethod == "CallUserAsync") { + if (invokeMethod == "CallUserAsync") { args = [currentUserID, targetUserID, isVideoCall]; + } else if (invokeMethod == "answerCallAsync") { + args = [currentUserID, targetUserID]; } else if (invokeMethod == "IceCandidateAsync") { args = [targetUserID, data]; } else if (invokeMethod == "OfferAsync") { args = [targetUserID, data]; } else if (invokeMethod == "AnswerOfferAsync") { args = [targetUserID, data]; - //json In Data + // json In Data + } else if (invokeMethod == "UpdateUserStatusAsync") { + args = [currentUserID, userStatus]; + } else if (invokeMethod == "HangUpAsync") { + args = [currentUserID, targetUserID]; + } else if (invokeMethod == "InvokeMobile") { + args = [debugData]; + } + try { + await chatHubConnection!.invoke("$invokeMethod", args: args); + } catch (e) { + logger.w(e); } - await chatHubConnection.invoke(invokeMethod, args: args); } void stopListeners() async { - chatHubConnection.off('OnCallDeclinedAsync'); - chatHubConnection.off('OnCallAcceptedAsync'); - chatHubConnection.off('OnIceCandidateAsync'); - chatHubConnection.off('OnAnswerOffer'); + chatHubConnection!.off('OnCallDeclinedAsync'); + chatHubConnection!.off('OnCallAcceptedAsync'); + chatHubConnection!.off('OnIceCandidateAsync'); + chatHubConnection!.off('OnAnswerOffer'); + } + + void playRingtone() async { + player.stop(); + await player.setVolume(1.0); + String audioAsset = ""; + if (Platform.isAndroid) { + audioAsset = "assets/audio/ring_60Sec.mp3"; + } else { + audioAsset = "assets/audio/ring_30Sec.caf"; + } + try { + await player.setAsset(audioAsset); + await player.load(); + player.play(); + } catch (e) { + print("Error: $e"); + } + } + + //////////////////// Web RTC Offers & Connections //////////////////////// + + Future creatOfferWithCon() async { + Map configuration = { + "sdpSemantics": "plan-b", + 'iceServers': [ + { + 'urls': 'stun:15.185.116.59:3478', + }, + { + 'urls': 'turn:15.185.116.59:3479', + 'username': 'admin', + 'credential': 'admin', + }, + ] + }; + Map offerSdpConstraints = { + 'mandatory': { + 'OfferToReceiveAudio': true, + 'OfferToReceiveVideo': true, + }, + 'optional': [] + }; + RTCPeerConnection pc = await createPeerConnection(configuration, offerSdpConstraints); + // await pc.addStream(_localStream!); + //Changed By Aamir + _localStream?.getTracks().forEach((track) { + pc.addTrack(track, _localStream!); + }); + pc.onConnectionState = (RTCPeerConnectionState state) {}; + pc.onAddStream = (MediaStream stream) { + remoteRenderer!.srcObject = stream; + notifyListeners(); + }; + pc.onIceCandidate = (RTCIceCandidate e) async { + if (isIncomingCall) { + if (e.candidate != null) { + var payload = {"target": incomingCallData.targetUserId, "candidate": e.toMap()}; + invoke(invokeMethod: "IceCandidateAsync", currentUserID: AppState().chatDetails!.response!.id!, targetUserID: incomingCallData.targetUserId!, data: jsonEncode(payload)); + notifyListeners(); + } + } else { + if (e.candidate != null) { + var payload = {"target": outGoingCallData.callerId, "candidate": e.toMap()}; + invoke(invokeMethod: "IceCandidateAsync", currentUserID: outGoingCallData.callerId!, targetUserID: outGoingCallData.receiverId!, data: jsonEncode(payload)); + } + } + }; + // pc!.onTrack = (RTCTrackEvent event) async { + // + // String streamId = const Uuid().toString(); + // MediaStream remoteStream = await createLocalMediaStream(streamId); + // event.streams[0].getTracks().forEach((MediaStreamTrack element) { + // logger.i("Stream Track: " + element.id.toString()); + // // remoteRenderer.srcObject = element; + // remoteStream.addTrack(element); + // }); + // }; + pc.onSignalingState = (RTCSignalingState state) { + logger.i("signaling state: " + state.name); + // invoke( + // invokeMethod: "InvokeMobile", + // currentUserID: AppState().getchatUserDetails!.response!.id!, + // targetUserID: incomingCallData.targetUserId!, + // debugData: {"location": "Signaling", "parms": state.name}); + }; + pc.onIceGatheringState = (RTCIceGatheringState state) { + logger.i("rtc ice gathering state: " + state.name); + }; + pc.onIceConnectionState = (RTCIceConnectionState state) { + if (RTCIceConnectionState.RTCIceConnectionStateFailed == state || + RTCIceConnectionState.RTCIceConnectionStateDisconnected == state || + RTCIceConnectionState.RTCIceConnectionStateClosed == state) { + logger.i("Ice Connection State:" + state.name); + + // endCall().then((value) { + // notifyListeners(); + // }); + } + }; + // pc!.onRenegotiationNeeded = _onRenegotiate; + return pc; + } + + // void _onRenegotiate() async { + // try { + // print('onRenegotiationNeeded start'); + // // makingOffer = true; + // await _pc.setLocalDescription(await _pc.createOffer(videoConstraints)); + // print('onRenegotiationNeeded state after setLocalDescription: ' + _pc.signalingState.toString()); + // // send offer via callManager + // var localDesc = await _pc.getLocalDescription(); + // // callManager.sendCallMessage(MsgType.rtc_offer, RtcOfferAnswer(localDesc.sdp, localDesc.type)); + // print('onRenegotiationNeeded; offer sent'); + // } catch (e) { + // print("onRenegotiationNeeded error: " + e.toString()); + // } finally { + // // makingOffer = false; + // print('onRenegotiationNeeded done'); + // } + // } + + Future _createOffer() async { + RTCSessionDescription description = await _pc.createOffer(); + // _offer = true; + return description; + } + + Future _createAnswer() async { + RTCSessionDescription description = await _pc!.createAnswer(); + // _offer = false; + return description; } + + //////////////////// Web RTC End Offers //////////////////// + + //////////////////// CallPage Buttons ////////////////////// + + void micOff() { + isMicOff = !isMicOff; + _localStream!.getAudioTracks().forEach((track) { + track.enabled = !track.enabled; + }); + notifyListeners(); + } + + void camOff() { + isCamOff = !isCamOff; + _localStream!.getVideoTracks().forEach((track) { + track.enabled = !track.enabled; + }); + // if (isCamOff) { + // isVideoCall = false; + // } else { + // isVideoCall = true; + // } + notifyListeners(); + } + + void loudOn() { + isLoudSpeaker = !isLoudSpeaker; + remoteRenderer!.srcObject?.getAudioTracks().forEach((track) { + if (isLoudSpeaker) { + track.enableSpeakerphone(true); + } else { + track.enableSpeakerphone(false); + } + }); + notifyListeners(); + } + + void switchCamera() { + isFrontCamera = !isFrontCamera; + Helper.switchCamera(_localStream!.getVideoTracks()[0]); + notifyListeners(); + } + + ///////////////// Incoming Call /////////////////////////////// + + Future initStreams() async { + List devices = await navigator.mediaDevices.enumerateDevices(); + remoteRenderer = RTCVideoRenderer(); + localVideoRenderer ??= RTCVideoRenderer(); + await localVideoRenderer!.initialize(); + try { + _localStream = await navigator.mediaDevices.getUserMedia({ + 'audio': true, + 'video': isVideoCall + ? { + 'mandatory': { + 'minWidth': '640', // Provide your own width, height and frame rate here + 'minHeight': '480', + 'minFrameRate': '30', + }, + 'facingMode': 'user', + 'optional': [], + } + : false + }); + if (kDebugMode) { + print(_localStream..toString()); + } + localVideoRenderer!.srcObject = _localStream; + localVideoRenderer!.value = (const RTCVideoValue(width: 200, height: 200, renderVideo: true)); + print("Working localStream"); + } catch (e) { + print("Failed to get user media: $e"); + } + // _localStream = await navigator.mediaDevices.getUserMedia(isVideoCall + // ? Platform.isIOS + // ? { + // 'audio': true, + // 'video': { + // 'facingMode': 'user', // Use 'user' for front camera, 'environment' for back camera + // 'width': { + // 'ideal': 1080, // Set the ideal width (maximum quality) + // }, + // 'height': { + // 'ideal': 1920, // Set the ideal height (maximum quality) + // }, + // 'frameRate': { + // 'ideal': 30, // Set the ideal frame rate (adjust as needed) + // }, + // }, + // } + // : { + // 'audio': true, + // 'video': { + // 'facingMode': 'user', // Use 'user' for front camera, 'environment' for back camera + // 'width': { + // 'ideal': 1920, // Set the ideal width (maximum quality) + // }, + // 'height': { + // 'ideal': 1080, // Set the ideal height (maximum quality) + // }, + // 'frameRate': { + // 'ideal': 30, // Set the ideal frame rate (adjust as needed) + // }, + // }, + // } + // + // // ? { + // // "video": { + // // "mandatory": { + // // "width": {"min": 1080}, + // // "height": {"min": 1920} + // // }, + // // "optional": Platform.isAndroid + // // ? [ + // // {'sourceId': devices[1].deviceId}, + // // { + // // "width": {"max": 1080} + // // }, + // // {"frameRate": 30}, + // // {"facingMode": "user"} + // // ] + // // : [ + // // {"frameRate": 30}, + // // {"facingMode": "user"} + // // ] + // // }, + // // "frameRate": 30, + // // "width": 1080, //420,//640,//1280, + // // "height": 1920, //240//480//720 + // // "audio": true, + // // } + // : audioConstraints); + + // localVideoRenderer.srcObject = _localStream; + await remoteRenderer!.initialize(); + notifyListeners(); + } + + Future startIncomingCallViaKit({bool isVCall = true, required var inCallData}) async { + Utils.saveStringFromPrefs("isIncomingCall", "false"); + if (isVCall) { + isVideoCall = isVCall; + } else { + isAudioCall = true; + } + await initStreams(); + isIncomingCall = true; + incomingCallData = SingleUserChatModel.fromJson(inCallData); + loudOn(); + // notifyListeners(); + } + + void connectIncomingCall() { + invoke(invokeMethod: "answerCallAsync", currentUserID: AppState().getchatUserDetails!.response!.id!, targetUserID: incomingCallData.targetUserId!); + isIncomingCallLoader = false; + isIncomingCall = true; + // isVideoCall = true; + + notifyListeners(); + } + + MediaRecorder? mobileRecoder; + +// void startRecording() async { +// print("=-=-=-=-=-=-= Call Recoding Started -=-=-=-=-=-=-=-=-==-="); +// if (_localStream == null) throw Exception('Stream is not initialized'); +// if (Platform.isIOS) { +// print('Recording is not available on iOS'); +// return; +// } +// Directory appDirectory = await getApplicationDocumentsDirectory(); +// String dirPath = '${appDirectory.path}/webrtc_sample'; +// if (!await Directory(dirPath).exists()) { +// await Directory(dirPath).create(); +// await File('$dirPath/.nomedia').create(); +// } +// if (appDirectory == null) throw Exception('Can\'t find storagePath'); +// String filePath = dirPath + '/mobile.mp4'; +// mobileRecoder = MediaRecorder(); +// notifyListeners(); +// MediaStreamTrack videoTrack = _localStream!.getVideoTracks().firstWhere((track) => track.kind == 'video'); +// await mobileRecoder!.start(filePath, videoTrack: videoTrack); +// +// Future.delayed(Duration(minutes: 1), () { +// stopRecording(); +// }); +// } +// +// void stopRecording() async { +// print("=-=-=-=-=-=-= Call Recoding Stopped -=-=-=-=-=-=-=-=-==-="); +// await mobileRecoder!.stop(); +// mobileRecoder = null; +// notifyListeners(); +// } } diff --git a/lib/provider/chat_provider_model.dart b/lib/provider/chat_provider_model.dart index 00545b2..af0f620 100644 --- a/lib/provider/chat_provider_model.dart +++ b/lib/provider/chat_provider_model.dart @@ -1,11 +1,12 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; + import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart' as Material; import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:just_audio/just_audio.dart' as JustAudio; @@ -19,31 +20,24 @@ import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/main.dart'; import 'package:mohem_flutter_app/models/chat/chat_user_image_model.dart'; -import 'package:mohem_flutter_app/models/chat/create_group_request.dart' - as createGroup; -import 'package:mohem_flutter_app/models/chat/get_group_chat_history.dart' - as groupchathistory; +import 'package:mohem_flutter_app/models/chat/create_group_request.dart' as createGroup; +import 'package:mohem_flutter_app/models/chat/get_group_chat_history.dart' as groupchathistory; import 'package:mohem_flutter_app/models/chat/get_search_user_chat_model.dart'; import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.dart'; -import 'package:mohem_flutter_app/models/chat/get_user_groups_by_id.dart' - as groups; +import 'package:mohem_flutter_app/models/chat/get_user_groups_by_id.dart' as groups; import 'package:mohem_flutter_app/models/chat/get_user_groups_by_id.dart'; -import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart' - as userLoginToken; -import 'package:mohem_flutter_app/models/chat/make_user_favotire_unfavorite_chat_model.dart' - as fav; +import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart' as userLoginToken; +import 'package:mohem_flutter_app/models/chat/make_user_favotire_unfavorite_chat_model.dart' as fav; import 'package:mohem_flutter_app/models/chat/target_users.dart'; import 'package:mohem_flutter_app/models/my_team/get_employee_subordinates_list.dart'; +import 'package:mohem_flutter_app/provider/chat_call_provider.dart'; import 'package:mohem_flutter_app/ui/chat/chat_detailed_screen.dart'; -import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/widgets/image_picker.dart'; import 'package:open_file/open_file.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:signalr_netcore/hub_connection.dart'; import 'package:signalr_netcore/signalr_client.dart'; import 'package:uuid/uuid.dart'; -import 'package:flutter/material.dart' as Material; class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { ScrollController scrollController = ScrollController(); @@ -64,10 +58,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { List favUsersList = []; int paginationVal = 0; int? cTypingUserId = 0; - bool isTextMsg = false, - isReplyMsg = false, - isAttachmentMsg = false, - isVoiceMsg = false; + bool isTextMsg = false, isReplyMsg = false, isAttachmentMsg = false, isVoiceMsg = false; // Audio Recoding Work Timer? _timer; @@ -95,84 +86,104 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { /// Search Provider List? chatUsersList = []; int pageNo = 1; - - bool disbaleChatForThisUser = false; List? uGroups = [], searchGroups = []; + bool disbaleChatForThisUser = false; + bool disableChatForThisUser = false; + bool isUserOnline = false; + bool isCall = false; + userLoginToken.UserAutoLoginModel userLoginData = userLoginToken.UserAutoLoginModel(); Future getUserAutoLoginToken() async { - userLoginToken.UserAutoLoginModel userLoginResponse = - await ChatApiClient().getUserLoginToken(); - if (userLoginResponse.response != null) { - AppState().setchatUserDetails = userLoginResponse; - } else { - AppState().setchatUserDetails = userLoginResponse; - Utils.showToast( - userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr", - ); - disbaleChatForThisUser = true; + try { + userLoginToken.UserAutoLoginModel userLoginResponse = await ChatApiClient().getUserLoginToken(); + if (userLoginResponse.response != null) { + AppState().setchatUserDetails = userLoginResponse; + Utils.saveStringFromPrefs("userLoginChatDetails", jsonEncode(userLoginResponse.response)); + isUserOnline = true; + if (Platform.isIOS) { + AppState().setisUserOnline = true; + } + } else { + AppState().setchatUserDetails = userLoginResponse; + Utils.saveStringFromPrefs("userLoginChatDetails", "null"); + Utils.showToast( + userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr", + ); + disableChatForThisUser = false; + + isUserOnline = false; + if (Platform.isIOS) { + AppState().setisUserOnline = false; + } + notifyListeners(); + } + } catch (e) { + disableChatForThisUser = true; + isUserOnline = false; + if (Platform.isIOS) { + AppState().setisUserOnline = false; + } notifyListeners(); } } - Future buildHubConnection() async { - chatHubConnection = await getHubConnection(); - await chatHubConnection.start(); + Future buildHubConnection({required BuildContext context, required ChatCallProvider ccProvider}) async { + try { + chatHubConnection = await getHubConnection(); + } catch (e) { + Utils.showToast(e.toString()); + } + await chatHubConnection!.start(); if (kDebugMode) { logger.i("Hub Conn: Startedddddddd"); } - chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); - chatHubConnection.on("OnGetChatConversationCount", onNewChatConversion); + chatHubConnection!.on("OnDeliveredChatUserAsync", onMsgReceived); + chatHubConnection!.on("OnGetChatConversationCount", onNewChatConversion); //group On message - chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived); + chatHubConnection!.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived); + ccProvider.initCallListeners(context: context); } Future getHubConnection() async { HubConnection hub; - HttpConnectionOptions httpOp = - HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); + HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); hub = HubConnectionBuilder() - .withUrl( - ApiConsts.chatHubConnectionUrl + - "?UserId=${AppState().chatDetails!.response!.id}&source=Desktop&access_token=${AppState().chatDetails!.response!.token}", - options: httpOp) - .withAutomaticReconnect( - retryDelays: [2000, 5000, 10000, 20000]).build(); + .withUrl(ApiConsts.chatHubConnectionUrl + "?UserId=${AppState().chatDetails!.response!.id}&source=Desktop&access_token=${AppState().chatDetails!.response!.token}", options: httpOp) + .withAutomaticReconnect(retryDelays: [2000, 5000, 10000, 20000]).build(); return hub; } void registerEvents() { - chatHubConnection.on("OnUpdateUserStatusAsync", changeStatus); - // chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); - - chatHubConnection.on("OnSubmitChatAsync", OnSubmitChatAsync); - chatHubConnection.on("OnUserTypingAsync", onUserTyping); - chatHubConnection.on("OnUserCountAsync", userCountAsync); - // chatHubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow); - chatHubConnection.on( - "OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered); - chatHubConnection.on( - "OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus); - chatHubConnection.on( - "OnGetGroupUserStatusAsync", getGroupUserStatus); - - // - // {"type":1,"target":"","arguments":[[{"id":217869,"userName":"Sultan.Khan","email":"Sultan.Khan@cloudsolutions.com.sa","phone":null,"title":"Sultan.Khan","userStatus":1,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":false,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null},{"id":15153,"userName":"Tamer.Fanasheh","email":"Tamer.F@cloudsolutions.com.sa","phone":null,"title":"Tamer Fanasheh","userStatus":2,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":true,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null}]]} + if (chatHubConnection != null) { + chatHubConnection!.on("OnUpdateUserStatusAsync", changeStatus); + // chatHubConnection!.on("OnDeliveredChatUserAsync", onMsgReceived); + + chatHubConnection!.on("OnSubmitChatAsync", OnSubmitChatAsync); + chatHubConnection!.on("OnUserTypingAsync", onUserTyping); + chatHubConnection!.on("OnUserCountAsync", userCountAsync); + // chatHubConnection!.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow); + chatHubConnection!.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered); + chatHubConnection!.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus); + chatHubConnection!.on("OnGetGroupUserStatusAsync", getGroupUserStatus); + chatHubConnection!.on("OnAddGroupChatHistoryAsync", groupChatHistoryAsync); + // + // {"type":1,"target":"","arguments":[[{"id":217869,"userName":"Sultan.Khan","email":"Sultan.Khan@cloudsolutions.com.sa","phone":null,"title":"Sultan.Khan","userStatus":1,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":false,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null},{"id":15153,"userName":"Tamer.Fanasheh","email":"Tamer.F@cloudsolutions.com.sa","phone":null,"title":"Tamer Fanasheh","userStatus":2,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":true,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null}]]} - if (kDebugMode) { - logger.i("All listeners registered"); + if (kDebugMode) { + logger.i("All listeners registered"); + } } } Future getUserRecentChats() async { ChatUserModel recentChat = await ChatApiClient().getRecentChats(); ChatUserModel favUList = await ChatApiClient().getFavUsers(); - // userGroups = await ChatApiClient().getGroupsByUserId(); + userGroups = await ChatApiClient().getGroupsByUserId(); if (favUList.response != null && recentChat.response != null) { favUsersList = favUList.response!; - favUsersList.sort((ChatUser a, ChatUser b) => - a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase())); + favUsersList.sort((ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase())); for (dynamic user in recentChat.response!) { for (dynamic favUser in favUList.response!) { if (user.id == favUser.id) { @@ -183,12 +194,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } pChatHistory = recentChat.response ?? []; uGroups = userGroups.groupresponse ?? []; - pChatHistory!.sort((ChatUser a, ChatUser b) => - a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase())); + pChatHistory!.sort((ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase())); searchedChats = pChatHistory; isLoading = false; - await invokeUserChatHistoryNotDeliveredAsync( - userId: int.parse(AppState().chatDetails!.response!.id.toString())); + await invokeUserChatHistoryNotDeliveredAsync(userId: int.parse(AppState().chatDetails!.response!.id.toString())); sort(); notifyListeners(); if (searchedChats!.isNotEmpty || favUsersList.isNotEmpty) { @@ -197,38 +206,27 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } Future invokeUserChatHistoryNotDeliveredAsync({required int userId}) async { - await chatHubConnection - .invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]); + await chatHubConnection!.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]); return ""; } - void getSingleUserChatHistory( - {required int senderUID, - required int receiverUID, - required bool loadMore, - bool isNewChat = false}) async { + void getSingleUserChatHistory({required int senderUID, required int receiverUID, required bool loadMore, bool isNewChat = false}) async { isLoading = true; if (isNewChat) userChatHistory = []; if (!loadMore) paginationVal = 0; isChatScreenActive = true; receiverID = receiverUID; - Response response = await ChatApiClient().getSingleUserChatHistory( - senderUID: senderUID, - receiverUID: receiverUID, - loadMore: loadMore, - paginationVal: paginationVal); + Response response = await ChatApiClient().getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal); if (response.statusCode == 204) { if (isNewChat) { userChatHistory = []; } else if (loadMore) {} } else { if (loadMore) { - List temp = - getSingleUserChatModel(response.body).reversed.toList(); + List temp = getSingleUserChatModel(response.body).reversed.toList(); userChatHistory.addAll(temp); } else { - userChatHistory = - getSingleUserChatModel(response.body).reversed.toList(); + userChatHistory = getSingleUserChatModel(response.body).reversed.toList(); } } isLoading = false; @@ -255,9 +253,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { dynamic data = [ { "userChatHistoryId": element.userChatHistoryId, - "TargetUserId": element.currentUserId == receiverID - ? element.currentUserId - : element.targetUserId, + "TargetUserId": element.currentUserId == receiverID ? element.currentUserId : element.targetUserId, "isDelivered": true, "isSeen": true, } @@ -279,8 +275,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { void updateUserChatHistoryStatusAsync(List data) { try { - chatHubConnection - .invoke("UpdateUserChatHistoryStatusAsync", args: [data]); + chatHubConnection!.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); } catch (e) { throw e; } @@ -288,26 +283,21 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { void updateUserChatHistoryOnMsg(List data) { try { - chatHubConnection - .invoke("UpdateUserChatHistoryStatusAsync", args: [data]); + chatHubConnection!.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); } catch (e) { throw e; } } - List getSingleUserChatModel(String str) => - List.from( - json.decode(str).map((x) => SingleUserChatModel.fromJson(x))); + List getSingleUserChatModel(String str) => List.from(json.decode(str).map((x) => SingleUserChatModel.fromJson(x))); List getGroupChatHistoryAsync(String str) => - List.from( - json.decode(str).map((x) => groupchathistory.GetGroupChatHistoryAsync.fromJson(x))); - + List.from(json.decode(str).map((x) => groupchathistory.GetGroupChatHistoryAsync.fromJson(x))); - Future uploadAttachments(String userId, File file) async { + Future uploadAttachments(String userId, File file, String fileSource) async { dynamic result; try { - Object? response = await ChatApiClient().uploadMedia(userId, file); + Object? response = await ChatApiClient().uploadMedia(userId, file, fileSource); if (response != null) { result = response; } else { @@ -332,9 +322,15 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); } - void getGroupUserStatus(List? args){ - //note: need to implement this function... - print(args); + void getGroupUserStatus(List? args) { + //note: need to implement this function when group user status + + print(args); + } + + void groupChatHistoryAsync(List? args) { + //need to imlement this event when any group details updated. + print(args); } void onChatSeen(List? args) { @@ -363,8 +359,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { void updateChatHistoryWindow(List? args) { dynamic items = args!.toList(); if (kDebugMode) { - logger.i( - "---------------------------------Update Chat History Windows Async -------------------------------------"); + logger.i("---------------------------------Update Chat History Windows Async -------------------------------------"); } logger.d(items); // for (var user in searchedChats!) { @@ -423,6 +418,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { Future onMsgReceived(List? parameters) async { List data = [], temp = []; + for (dynamic msg in parameters!) { data = getSingleUserChatModel(jsonEncode(msg)); temp = getSingleUserChatModel(jsonEncode(msg)); @@ -432,27 +428,14 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { data.first.currentUserId = temp.first.targetUserId; data.first.currentUserName = temp.first.targetUserName; data.first.currentUserEmail = temp.first.targetUserEmail; - - if (data.first.fileTypeId == 12 || - data.first.fileTypeId == 4 || - data.first.fileTypeId == 3) { - data.first.image = await ChatApiClient().downloadURL( - fileName: data.first.contant!, - fileTypeDescription: - data.first.fileTypeResponse!.fileTypeDescription ?? - "image/jpg"); + if (data.first.fileTypeId == 12 || data.first.fileTypeId == 4 || data.first.fileTypeId == 3) { + data.first.image = await ChatApiClient().downloadURL(fileName: data.first.contant!, fileTypeDescription: data.first.fileTypeResponse!.fileTypeDescription ?? "image/jpg", fileSource: 1); } if (data.first.userChatReplyResponse != null) { if (data.first.fileTypeResponse != null) { - if (data.first.userChatReplyResponse!.fileTypeId == 12 || - data.first.userChatReplyResponse!.fileTypeId == 4 || - data.first.userChatReplyResponse!.fileTypeId == 3) { + if (data.first.userChatReplyResponse!.fileTypeId == 12 || data.first.userChatReplyResponse!.fileTypeId == 4 || data.first.userChatReplyResponse!.fileTypeId == 3) { data.first.userChatReplyResponse!.image = await ChatApiClient() - .downloadURL( - fileName: data.first.userChatReplyResponse!.contant!, - fileTypeDescription: - data.first.fileTypeResponse!.fileTypeDescription ?? - "image/jpg"); + .downloadURL(fileName: data.first.userChatReplyResponse!.contant!, fileTypeDescription: data.first.fileTypeResponse!.fileTypeDescription ?? "image/jpg", fileSource: 1); data.first.userChatReplyResponse!.isImageLoaded = true; } } @@ -460,14 +443,11 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } if (searchedChats != null) { - dynamic contain = searchedChats! - .where((ChatUser element) => element.id == data.first.currentUserId); + dynamic contain = searchedChats!.where((ChatUser element) => element.id == data.first.currentUserId); if (contain.isEmpty) { List emails = []; - emails.add(await EmailImageEncryption() - .encrypt(val: data.first.currentUserEmail!)); - List chatImages = - await ChatApiClient().getUsersImages(encryptedEmails: emails); + emails.add(await EmailImageEncryption().encrypt(val: data.first.currentUserEmail!)); + List chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails); searchedChats!.add( ChatUser( id: data.first.currentUserId, @@ -479,14 +459,14 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isImageLoaded: true, userStatus: 1, isTyping: false, - userLocalDownlaodedImage: await downloadImageLocal( - chatImages.first.profilePicture, - data.first.currentUserId.toString()), + userLocalDownlaodedImage: await downloadImageLocal(chatImages.first.profilePicture, data.first.currentUserId.toString()), ), ); } } - setMsgTune(); + if (data.first.chatEventId != 3) { + setMsgTune(); + } if (isChatScreenActive && data.first.currentUserId == receiverID) { userChatHistory.insert(0, data.first); } else { @@ -506,9 +486,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { "userChatHistoryId": data.first.userChatHistoryId, "TargetUserId": temp.first.targetUserId, "isDelivered": true, - "isSeen": isChatScreenActive && data.first.currentUserId == receiverID - ? true - : false + "isSeen": isChatScreenActive && data.first.currentUserId == receiverID ? true : false } ]; updateUserChatHistoryOnMsg(list); @@ -519,38 +497,24 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { Future onGroupMsgReceived(List? parameters) async { List data = [], temp = []; - for (dynamic msg in parameters!) { - // groupChatHistory.add(groupchathistory.GetGroupChatHistoryAsync.fromJson(msg)); + // groupChatHistory.add(groupchathistory.GetGroupChatHistoryAsync.fromJson(msg)); data.add(groupchathistory.GetGroupChatHistoryAsync.fromJson(msg)); - temp =data; + temp = data; // data.first.currentUserId = temp.first.currentUserId; // data.first.currentUserName = temp.first.currentUserName; // // data.first.currentUserId = temp.first.currentUserId; // data.first.currentUserName = temp.first.currentUserName; - - if (data.first.fileTypeId == 12 || - data.first.fileTypeId == 4 || - data.first.fileTypeId == 3) { - data.first.image = await ChatApiClient().downloadURL( - fileName: data.first.contant!, - fileTypeDescription: - data.first.fileTypeResponse!.fileTypeDescription ?? - "image/jpg"); + if (data.first.fileTypeId == 12 || data.first.fileTypeId == 4 || data.first.fileTypeId == 3) { + data.first.image = await ChatApiClient().downloadURL(fileName: data.first.contant!, fileTypeDescription: data.first.fileTypeResponse!.fileTypeDescription ?? "image/jpg", fileSource: 2); } if (data.first.groupChatReplyResponse != null) { if (data.first.fileTypeResponse != null) { - if (data.first.groupChatReplyResponse!.fileTypeId == 12 || - data.first.groupChatReplyResponse!.fileTypeId == 4 || - data.first.groupChatReplyResponse!.fileTypeId == 3) { + if (data.first.groupChatReplyResponse!.fileTypeId == 12 || data.first.groupChatReplyResponse!.fileTypeId == 4 || data.first.groupChatReplyResponse!.fileTypeId == 3) { data.first.groupChatReplyResponse!.image = await ChatApiClient() - .downloadURL( - fileName: data.first.groupChatReplyResponse!.contant!, - fileTypeDescription: - data.first.fileTypeResponse!.fileTypeDescription ?? - "image/jpg"); + .downloadURL(fileName: data.first.groupChatReplyResponse!.contant!, fileTypeDescription: data.first.fileTypeResponse!.fileTypeDescription ?? "image/jpg", fileSource: 2); data.first.groupChatReplyResponse!.isImageLoaded = true; } } @@ -583,10 +547,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { // ), // ); // } - // } + // } groupChatHistory.insert(0, data.first); setMsgTune(); - // if (isChatScreenActive && data.first.currentUserId == receiverID) { + // if (isChatScreenActive && data.first.currentUserId == receiverID) { // } else { // if (searchedChats != null) { @@ -596,8 +560,8 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { // user.unreadMessageCount = tempCount + 1; // } // } - sort(); - //} + sort(); + //} //} // // List list = [ @@ -610,18 +574,12 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { // : false // } // ]; - // updateUserChatHistoryOnMsg(list); - // invokeChatCounter(userId: AppState().chatDetails!.response!.id!); + // updateUserChatHistoryOnMsg(list); + // invokeChatCounter(userId: AppState().chatDetails!.response!.id!); notifyListeners(); } - - void OnSubmitChatAsync(List? parameters) { - print(isChatScreenActive); - print(receiverID); - print(isChatScreenActive); - logger.i(parameters); List data = [], temp = []; for (dynamic msg in parameters!) { data = getSingleUserChatModel(jsonEncode(msg)); @@ -634,9 +592,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { data.first.currentUserEmail = temp.first.targetUserEmail; } if (isChatScreenActive && data.first.currentUserId == receiverID) { - int index = userChatHistory.indexWhere( - (SingleUserChatModel element) => element.userChatHistoryId == 0); - logger.d(index); + int index = userChatHistory.indexWhere((SingleUserChatModel element) => element.userChatHistoryId == 0); userChatHistory[index] = data.first; } @@ -645,8 +601,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { void sort() { searchedChats!.sort( - (ChatUser a, ChatUser b) => - b.unreadMessageCount!.compareTo(a.unreadMessageCount!), + (ChatUser a, ChatUser b) => b.unreadMessageCount!.compareTo(a.unreadMessageCount!), ); } @@ -698,6 +653,14 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { return 13; case ".mp3": return 14; + case ".mp4": + return 16; + case ".mov": + return 16; + case ".avi": + return 16; + case ".flv": + return 16; default: return 0; } @@ -735,6 +698,14 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { return "audio/aac"; case ".mp3": return "audio/mp3"; + case ".mp4": + return "video/mp4"; + case ".avi": + return "video/avi"; + case ".flv": + return "video/flv"; + case ".mov": + return "video/mov"; default: return ""; } @@ -761,7 +732,6 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { msg = voiceFile!.path.split("/").last; } else { msg = message.text; - logger.w(msg); } SingleUserChatModel data = SingleUserChatModel( userChatHistoryId: 0, @@ -777,22 +747,14 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { targetUserName: targetUserName, isReplied: false, fileTypeId: fileTypeId, - userChatReplyResponse: isReply - ? UserChatReplyResponse.fromJson(repliedMsg.first.toJson()) - : null, + userChatReplyResponse: isReply ? UserChatReplyResponse.fromJson(repliedMsg.first.toJson()) : null, fileTypeResponse: isAttachment ? FileTypeResponse( fileTypeId: fileTypeId, - fileTypeName: isVoiceMsg - ? getFileExtension(voiceFile!.path).toString() - : getFileExtension(selectedFile.path).toString(), + fileTypeName: isVoiceMsg ? getFileExtension(voiceFile!.path).toString() : getFileExtension(selectedFile.path).toString(), fileKind: "file", fileName: isVoiceMsg ? msg : selectedFile.path.split("/").last, - fileTypeDescription: isVoiceMsg - ? getFileTypeDescription( - getFileExtension(voiceFile!.path).toString()) - : getFileTypeDescription( - getFileExtension(selectedFile.path).toString()), + fileTypeDescription: isVoiceMsg ? getFileTypeDescription(getFileExtension(voiceFile!.path).toString()) : getFileTypeDescription(getFileExtension(selectedFile.path).toString()), ) : null, image: image, @@ -814,8 +776,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { String chatData = '{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId": $fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"chatReplyId":$chatReplyId,"conversationId":"$chatCID"}'; - await chatHubConnection - .invoke("AddChatUserAsync", args: [json.decode(chatData)]); + await chatHubConnection!.invoke("AddChatUserAsync", args: [json.decode(chatData)]); } //groupChatMessage @@ -834,8 +795,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { int? userStatus, File? voiceFile, required bool isVoiceAttached, - required List userList - }) async { + required List userList}) async { Uuid uuid = const Uuid(); String contentNo = uuid.v4(); String msg; @@ -845,36 +805,36 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { msg = message.text; logger.w(msg); } - groupchathistory.GetGroupChatHistoryAsync data = - groupchathistory.GetGroupChatHistoryAsync( - //userChatHistoryId: 0, - chatEventId: chatEventId, - chatSource: 1, - contant: msg, - contantNo: contentNo, - conversationId: chatCID, - createdDate: DateTime.now().toString(), - currentUserId: AppState().chatDetails!.response!.id, - currentUserName: AppState().chatDetails!.response!.userName, - groupId: targetGroupId, - groupName: targetUserName, - isReplied: false, - fileTypeId: fileTypeId, - fileTypeResponse: isAttachment - ? groupchathistory.FileTypeResponse( - fileTypeId: fileTypeId, - fileTypeName: isVoiceMsg ? getFileExtension(voiceFile!.path).toString() : getFileExtension(selectedFile.path).toString(), - fileKind: "file", - fileName: isVoiceMsg ? msg : selectedFile.path.split("/").last, - fileTypeDescription: isVoiceMsg ? getFileTypeDescription(getFileExtension(voiceFile!.path).toString()) : getFileTypeDescription(getFileExtension(selectedFile.path).toString())) : null, - image: image, - isImageLoaded: isImageLoaded, - voice: isVoiceMsg ? voiceFile! : null, - voiceController: isVoiceMsg ? AudioPlayer() : null); + groupchathistory.GetGroupChatHistoryAsync data = groupchathistory.GetGroupChatHistoryAsync( + //userChatHistoryId: 0, + chatEventId: chatEventId, + chatSource: 1, + contant: msg, + contantNo: contentNo, + conversationId: chatCID, + createdDate: DateTime.now().toString(), + currentUserId: AppState().chatDetails!.response!.id, + currentUserName: AppState().chatDetails!.response!.userName, + groupId: targetGroupId, + groupName: targetUserName, + isReplied: false, + fileTypeId: fileTypeId, + fileTypeResponse: isAttachment + ? groupchathistory.FileTypeResponse( + fileTypeId: fileTypeId, + fileTypeName: isVoiceMsg ? getFileExtension(voiceFile!.path).toString() : getFileExtension(selectedFile.path).toString(), + fileKind: "file", + fileName: isVoiceMsg ? msg : selectedFile.path.split("/").last, + fileTypeDescription: isVoiceMsg ? getFileTypeDescription(getFileExtension(voiceFile!.path).toString()) : getFileTypeDescription(getFileExtension(selectedFile.path).toString())) + : null, + image: image, + isImageLoaded: isImageLoaded, + voice: isVoiceMsg ? voiceFile! : null, + voiceController: isVoiceMsg ? AudioPlayer() : null); if (kDebugMode) { logger.i("model data: " + jsonEncode(data)); } - groupChatHistory.insert(0, data); + // groupChatHistory.insert(0, data); isTextMsg = false; isReplyMsg = false; isAttachmentMsg = false; @@ -883,27 +843,26 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { message.clear(); notifyListeners(); - List targetUsers =[]; + List targetUsers = []; for (GroupUserList element in userList) { - targetUsers.add(TargetUsers(isDelivered: false,isSeen: false, targetUserId: element.id, userAction: element.userAction, userStatus: element.userStatus)); - + targetUsers.add(TargetUsers(isDelivered: false, isSeen: false, targetUserId: element.id, userAction: element.userAction, userStatus: element.userStatus)); } String chatData = '{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId":$fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"groupId":$targetGroupId,"groupChatHistoryLineRequestList":${json.encode(targetUsers)},"chatReplyId": $chatReplyId,"conversationId":"${uuid.v4()}"}'; - await chatHubConnection.invoke("AddGroupChatHistoryAsync", - args: [json.decode(chatData)]); + await chatHubConnection!.invoke("AddGroupChatHistoryAsync", args: [json.decode(chatData)]); } - void sendGroupChatMessage(BuildContext context, - {required int targetUserId, - required int userStatus, - required String userEmail, - required String targetUserName, - required List userList, - }) async { + void sendGroupChatMessage( + BuildContext context, { + required int targetUserId, + required int userStatus, + required String userEmail, + required String targetUserName, + required List userList, + }) async { if (isTextMsg && !isAttachmentMsg && !isVoiceMsg && !isReplyMsg) { logger.d("// Normal Text Message"); if (message.text.isEmpty) { @@ -922,8 +881,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isVoiceAttached: false, userEmail: userEmail, userStatus: userStatus, - userList:userList - ); + userList: userList); } else if (isTextMsg && !isAttachmentMsg && !isVoiceMsg && isReplyMsg) { logger.d("// Text Message as Reply"); if (message.text.isEmpty) { @@ -943,15 +901,13 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { voiceFile: null, userEmail: userEmail, userStatus: userStatus, - userList:userList - ); + userList: userList); } // Attachment else if (!isTextMsg && isAttachmentMsg && !isVoiceMsg && !isReplyMsg) { logger.d("// Normal Image Message"); Utils.showLoading(context); - dynamic value = await uploadAttachments( - AppState().chatDetails!.response!.id.toString(), selectedFile); + dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile, '2'); String? ext = getFileExtension(selectedFile.path); Utils.hideLoading(context); sendGroupChatToServer( @@ -967,19 +923,16 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isVoiceAttached: false, userEmail: userEmail, userStatus: userStatus, - userList:userList - ); + userList: userList); } else if (!isTextMsg && isAttachmentMsg && !isVoiceMsg && isReplyMsg) { logger.d("// Image as Reply Msg"); Utils.showLoading(context); - dynamic value = await uploadAttachments( - AppState().chatDetails!.response!.id.toString(), selectedFile); + dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile, '2'); String? ext = getFileExtension(selectedFile.path); Utils.hideLoading(context); sendGroupChatToServer( chatEventId: 2, fileTypeId: getFileType(ext.toString()), - targetGroupId: targetUserId, targetUserName: targetUserName, isAttachment: true, @@ -990,8 +943,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isVoiceAttached: false, userEmail: userEmail, userStatus: userStatus, - userList:userList - ); + userList: userList); } //Voice @@ -1011,9 +963,8 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isPlaying = false; isRecoding = false; Utils.showLoading(context); - dynamic value = await uploadAttachments( - AppState().chatDetails!.response!.id.toString(), voiceFile); - String? ext = getFileExtension(voiceFile.path); + dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile, '2'); + String? ext = getFileExtension(voiceFile.path); Utils.hideLoading(context); sendGroupChatToServer( chatEventId: 2, @@ -1029,8 +980,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isVoiceAttached: true, userEmail: userEmail, userStatus: userStatus, - userList:userList - ); + userList: userList); notifyListeners(); } else if (!isTextMsg && !isAttachmentMsg && isVoiceMsg && isReplyMsg) { logger.d("// Voice as Reply Msg"); @@ -1049,9 +999,8 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isRecoding = false; Utils.showLoading(context); - dynamic value = await uploadAttachments( - AppState().chatDetails!.response!.id.toString(), voiceFile); - String? ext = getFileExtension(voiceFile.path); + dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile, '2'); + String? ext = getFileExtension(voiceFile.path); Utils.hideLoading(context); sendGroupChatToServer( chatEventId: 2, @@ -1066,18 +1015,15 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isVoiceAttached: true, userEmail: userEmail, userStatus: userStatus, - userList:userList - ); + userList: userList); notifyListeners(); } if (searchedChats != null) { - dynamic contain = searchedChats! - .where((ChatUser element) => element.id == targetUserId); + dynamic contain = searchedChats!.where((ChatUser element) => element.id == targetUserId); if (contain.isEmpty) { List emails = []; emails.add(await EmailImageEncryption().encrypt(val: userEmail)); - List chatImages = - await ChatApiClient().getUsersImages(encryptedEmails: emails); + List chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails); searchedChats!.add( ChatUser( id: targetUserId, @@ -1098,13 +1044,13 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } } - void sendChatMessage(BuildContext context, - {required int targetUserId, - required int userStatus, - required String userEmail, - required String targetUserName, - - }) async { + void sendChatMessage( + BuildContext context, { + required int targetUserId, + required int userStatus, + required String userEmail, + required String targetUserName, + }) async { if (kDebugMode) { print("====================== Values ============================"); print("Is Text " + isTextMsg.toString()); @@ -1119,7 +1065,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { return; } sendChatToServer( - chatEventId: 1, + chatEventId: isCall ? 3 : 1, fileTypeId: null, targetUserId: targetUserId, targetUserName: targetUserName, @@ -1155,8 +1101,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { else if (!isTextMsg && isAttachmentMsg && !isVoiceMsg && !isReplyMsg) { logger.d("// Normal Image Message"); Utils.showLoading(context); - dynamic value = await uploadAttachments( - AppState().chatDetails!.response!.id.toString(), selectedFile); + dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile, '1'); String? ext = getFileExtension(selectedFile.path); Utils.hideLoading(context); sendChatToServer( @@ -1175,8 +1120,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } else if (!isTextMsg && isAttachmentMsg && !isVoiceMsg && isReplyMsg) { logger.d("// Image as Reply Msg"); Utils.showLoading(context); - dynamic value = await uploadAttachments( - AppState().chatDetails!.response!.id.toString(), selectedFile); + dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile, '1'); String? ext = getFileExtension(selectedFile.path); Utils.hideLoading(context); sendChatToServer( @@ -1211,8 +1155,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isPlaying = false; isRecoding = false; Utils.showLoading(context); - dynamic value = await uploadAttachments( - AppState().chatDetails!.response!.id.toString(), voiceFile); + dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile, '1'); String? ext = getFileExtension(voiceFile.path); Utils.hideLoading(context); sendChatToServer( @@ -1246,8 +1189,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isRecoding = false; Utils.showLoading(context); - dynamic value = await uploadAttachments( - AppState().chatDetails!.response!.id.toString(), voiceFile); + dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile, '1'); String? ext = getFileExtension(voiceFile.path); Utils.hideLoading(context); sendChatToServer( @@ -1266,13 +1208,11 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); } if (searchedChats != null) { - dynamic contain = searchedChats! - .where((ChatUser element) => element.id == targetUserId); + dynamic contain = searchedChats!.where((ChatUser element) => element.id == targetUserId); if (contain.isEmpty) { List emails = []; emails.add(await EmailImageEncryption().encrypt(val: userEmail)); - List chatImages = - await ChatApiClient().getUsersImages(encryptedEmails: emails); + List chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails); searchedChats!.add( ChatUser( id: targetUserId, @@ -1285,8 +1225,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isTyping: false, isFav: false, userStatus: userStatus, - userLocalDownlaodedImage: await downloadImageLocal( - chatImages.first.profilePicture, targetUserId.toString()), + userLocalDownlaodedImage: await downloadImageLocal(chatImages.first.profilePicture, targetUserId.toString()), ), ); notifyListeners(); @@ -1316,8 +1255,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } void selectImageToUpload(BuildContext context) { - ImageOptions.showImageOptionsNew(context, true, - (String image, File file) async { + ImageOptions.showImageOptionsNew(context, true, (String image, File file) async { if (checkFileSize(file.path)) { selectedFile = file; isAttachmentMsg = true; @@ -1351,10 +1289,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } bool checkFileSize(String path) { - int fileSizeLimit = 1024; + int fileSizeLimit = 5000; File f = File(path); - double fileSizeInKB = f.lengthSync() / 1024; - double fileSizeInMB = fileSizeInKB / 1024; + double fileSizeInKB = f.lengthSync() / 5000; + double fileSizeInMB = fileSizeInKB / 5000; if (fileSizeInKB > fileSizeLimit) { return false; } else { @@ -1393,7 +1331,15 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { case ".aac": return "assets/icons/chat/aac.svg"; case ".mp3": - return "assets/icons/chat/zip.mp3"; + return "assets/icons/chat/mp3.svg"; + case ".mp4": + return "assets/icons/chat/mp4.svg"; + case ".flv": + return "assets/icons/chat/flv.svg"; + case ".avi": + return "assets/icons/chat/avi.svg"; + case ".mov": + return "assets/icons/chat/mov.svg"; default: return "assets/images/thumb.svg"; } @@ -1406,6 +1352,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { repliedMsg.add(data); notifyListeners(); } + void groupChatReply(groupchathistory.GetGroupChatHistoryAsync data) { groupChatReplyData = []; data.isReplied = true; @@ -1413,6 +1360,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { groupChatReplyData.add(data); notifyListeners(); } + void closeMe() { repliedMsg = []; isReplyMsg = false; @@ -1425,18 +1373,13 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { return f.format(data); } - Future favoriteUser( - {required int userID, - required int targetUserID, - required bool fromSearch}) async { - fav.FavoriteChatUser favoriteChatUser = await ChatApiClient() - .favUser(userID: userID, targetUserID: targetUserID); + Future favoriteUser({required int userID, required int targetUserID, required bool fromSearch}) async { + fav.FavoriteChatUser favoriteChatUser = await ChatApiClient().favUser(userID: userID, targetUserID: targetUserID); if (favoriteChatUser.response != null) { for (ChatUser user in searchedChats!) { if (user.id == favoriteChatUser.response!.targetUserId!) { user.isFav = favoriteChatUser.response!.isFav; - dynamic contain = favUsersList!.where((ChatUser element) => - element.id == favoriteChatUser.response!.targetUserId!); + dynamic contain = favUsersList!.where((ChatUser element) => element.id == favoriteChatUser.response!.targetUserId!); if (contain.isEmpty) { favUsersList.add(user); } @@ -1446,8 +1389,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { for (ChatUser user in chatUsersList!) { if (user.id == favoriteChatUser.response!.targetUserId!) { user.isFav = favoriteChatUser.response!.isFav; - dynamic contain = favUsersList!.where((ChatUser element) => - element.id == favoriteChatUser.response!.targetUserId!); + dynamic contain = favUsersList!.where((ChatUser element) => element.id == favoriteChatUser.response!.targetUserId!); if (contain.isEmpty) { favUsersList.add(user); } @@ -1466,10 +1408,8 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); } - Future unFavoriteUser( - {required int userID, required int targetUserID}) async { - fav.FavoriteChatUser favoriteChatUser = await ChatApiClient() - .unFavUser(userID: userID, targetUserID: targetUserID); + Future unFavoriteUser({required int userID, required int targetUserID}) async { + fav.FavoriteChatUser favoriteChatUser = await ChatApiClient().unFavUser(userID: userID, targetUserID: targetUserID); if (favoriteChatUser.response != null) { for (ChatUser user in searchedChats!) { @@ -1523,7 +1463,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } void disposeData() { - if (!disbaleChatForThisUser) { + if (!disableChatForThisUser) { search.clear(); isChatScreenActive = false; receiverID = 0; @@ -1541,7 +1481,8 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { pChatHistory?.clear(); uGroups?.clear(); searchGroup?.clear(); - chatHubConnection.stop(); + // callP.stopListeners(); + chatHubConnection!.stop(); AppState().chatDetails = null; } } @@ -1574,14 +1515,12 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { emails.add(await EmailImageEncryption().encrypt(val: element.email!)); } - List chatImages = - await ChatApiClient().getUsersImages(encryptedEmails: emails); + List chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails); for (ChatUser user in searchedChats!) { for (ChatUserImageModel uImage in chatImages) { if (user.email == uImage.email) { user.image = uImage.profilePicture ?? ""; - user.userLocalDownlaodedImage = await downloadImageLocal( - uImage.profilePicture, user.id.toString()); + user.userLocalDownlaodedImage = await downloadImageLocal(uImage.profilePicture, user.id.toString()); user.isImageLoading = false; user.isImageLoaded = true; } @@ -1591,8 +1530,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { for (ChatUserImageModel uImage in chatImages) { if (favUser.email == uImage.email) { favUser.image = uImage.profilePicture ?? ""; - favUser.userLocalDownlaodedImage = await downloadImageLocal( - uImage.profilePicture, favUser.id.toString()); + favUser.userLocalDownlaodedImage = await downloadImageLocal(uImage.profilePicture, favUser.id.toString()); favUser.isImageLoading = false; favUser.isImageLoaded = true; } @@ -1609,8 +1547,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } else { await deleteFile(userID); Uint8List decodedBytes = base64Decode(encodedBytes); - Directory appDocumentsDirectory = - await getApplicationDocumentsDirectory(); + Directory appDocumentsDirectory = await getApplicationDocumentsDirectory(); String dirPath = '${appDocumentsDirectory.path}/chat_images'; if (!await Directory(dirPath).exists()) { await Directory(dirPath).create(); @@ -1633,8 +1570,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { Future downChatMedia(Uint8List bytes, String ext) async { String dir = (await getApplicationDocumentsDirectory()).path; - File file = File( - "$dir/" + DateTime.now().millisecondsSinceEpoch.toString() + "." + ext); + File file = File("$dir/" + DateTime.now().millisecondsSinceEpoch.toString() + "." + ext); await file.writeAsBytes(bytes); return file.path; } @@ -1657,20 +1593,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } } - Future getChatMedia(BuildContext context, - {required String fileName, - required String fileTypeName, - required int fileTypeID}) async { + Future getChatMedia(BuildContext context, {required String fileName, required String fileTypeName, required int fileTypeID, required int fileSource}) async { Utils.showLoading(context); - if (fileTypeID == 1 || - fileTypeID == 5 || - fileTypeID == 7 || - fileTypeID == 6 || - fileTypeID == 8 || - fileTypeID == 2) { - Uint8List encodedString = await ChatApiClient().downloadURL( - fileName: fileName, - fileTypeDescription: getFileTypeDescription(fileTypeName)); + if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8 || fileTypeID == 2 || fileTypeID == 16) { + Uint8List encodedString = await ChatApiClient().downloadURL(fileName: fileName, fileTypeDescription: getFileTypeDescription(fileTypeName), fileSource: fileSource); try { String path = await downChatMedia(encodedString, fileTypeName ?? ""); Utils.hideLoading(context); @@ -1688,23 +1614,19 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } Future invokeChatCounter({required int userId}) async { - await chatHubConnection.invoke("GetChatCounversationCount", args: [userId]); + await chatHubConnection!.invoke("GetChatCounversationCount", args: [userId]); return ""; } - void userTypingInvoke( - {required int currentUser, required int reciptUser}) async { - await chatHubConnection - .invoke("UserTypingAsync", args: [reciptUser, currentUser]); + void userTypingInvoke({required int currentUser, required int reciptUser}) async { + await chatHubConnection!.invoke("UserTypingAsync", args: [reciptUser, currentUser]); } - void groupTypingInvoke( - {required GroupResponse groupDetails, required int groupId}) async { + + void groupTypingInvoke({required GroupResponse groupDetails, required int groupId}) async { var data = json.decode(json.encode(groupDetails.groupUserList)); - await chatHubConnection - .invoke("GroupTypingAsync", args: ["${groupDetails.adminUser!.userName}",data, groupId ]); + await chatHubConnection!.invoke("GroupTypingAsync", args: ["${groupDetails.adminUser!.userName}", data, groupId]); } - //////// Audio Recoding Work //////////////////// Future initAudio({required int receiverId}) async { @@ -1718,8 +1640,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { await Directory(dirPath).create(); await File('$dirPath/.nomedia').create(); } - path = - "$dirPath/${AppState().chatDetails!.response!.id}-$receiverID-${DateTime.now().microsecondsSinceEpoch}.aac"; + path = "$dirPath/${AppState().chatDetails!.response!.id}-$receiverID-${DateTime.now().microsecondsSinceEpoch}.aac"; recorderController = RecorderController() ..androidEncoder = AndroidEncoder.aac ..androidOutputFormat = AndroidOutputFormat.mpeg4 @@ -1849,19 +1770,15 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { return numberStr; } - Future downChatVoice( - Uint8List bytes, String ext, SingleUserChatModel data) async { + Future downChatVoice(Uint8List bytes, String ext, SingleUserChatModel data) async { File file; try { - String dirPath = - '${(await getApplicationDocumentsDirectory()).path}/chat_audios'; + String dirPath = '${(await getApplicationDocumentsDirectory()).path}/chat_audios'; if (!await Directory(dirPath).exists()) { await Directory(dirPath).create(); await File('$dirPath/.nomedia').create(); } - file = File( - "$dirPath/${data.currentUserId}-${data.targetUserId}-${DateTime.now().microsecondsSinceEpoch}" + - ext); + file = File("$dirPath/${data.currentUserId}-${data.targetUserId}-${DateTime.now().microsecondsSinceEpoch}" + ext); await file.writeAsBytes(bytes); } catch (e) { if (kDebugMode) { @@ -1873,14 +1790,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } void scrollToMsg(SingleUserChatModel data) { - if (data.userChatReplyResponse != null && - data.userChatReplyResponse!.userChatHistoryId != null) { - int index = userChatHistory.indexWhere((SingleUserChatModel element) => - element.userChatHistoryId == - data.userChatReplyResponse!.userChatHistoryId); + if (data.userChatReplyResponse != null && data.userChatReplyResponse!.userChatHistoryId != null) { + int index = userChatHistory.indexWhere((SingleUserChatModel element) => element.userChatHistoryId == data.userChatReplyResponse!.userChatHistoryId); if (index >= 1) { - double contentSize = scrollController.position.viewportDimension + - scrollController.position.maxScrollExtent; + double contentSize = scrollController.position.viewportDimension + scrollController.position.maxScrollExtent; double target = contentSize * index / userChatHistory.length; scrollController.position.animateTo( target, @@ -1912,18 +1825,14 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isImageLoading: false, image: element.eMPLOYEEIMAGE ?? "", isImageLoaded: element.eMPLOYEEIMAGE == null ? false : true, - userLocalDownlaodedImage: element.eMPLOYEEIMAGE == null - ? null - : await downloadImageLocal( - element.eMPLOYEEIMAGE ?? "", element.eMPLOYEENUMBER!), + userLocalDownlaodedImage: element.eMPLOYEEIMAGE == null ? null : await downloadImageLocal(element.eMPLOYEEIMAGE ?? "", element.eMPLOYEENUMBER!), ), ); } } } } else { - getEmployeeSubordinatesList = - await MyTeamApiClient().getEmployeeSubordinates("", "", ""); + getEmployeeSubordinatesList = await MyTeamApiClient().getEmployeeSubordinates("", "", ""); AppState().setemployeeSubordinatesList = getEmployeeSubordinatesList; for (GetEmployeeSubordinatesList element in getEmployeeSubordinatesList) { if (element.eMPLOYEEEMAILADDRESS != null) { @@ -1941,10 +1850,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isImageLoading: false, image: element.eMPLOYEEIMAGE ?? "", isImageLoaded: element.eMPLOYEEIMAGE == null ? false : true, - userLocalDownlaodedImage: element.eMPLOYEEIMAGE == null - ? null - : await downloadImageLocal( - element.eMPLOYEEIMAGE ?? "", element.eMPLOYEENUMBER!), + userLocalDownlaodedImage: element.eMPLOYEEIMAGE == null ? null : await downloadImageLocal(element.eMPLOYEEIMAGE ?? "", element.eMPLOYEENUMBER!), ), ); } @@ -2013,14 +1919,12 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { SingleUserChatModel nUser = SingleUserChatModel(); Utils.saveStringFromPrefs("isAppOpendByChat", "false"); if (await Utils.getStringFromPrefs("notificationData") != "null") { - nUser = SingleUserChatModel.fromJson( - jsonDecode(await Utils.getStringFromPrefs("notificationData"))); + nUser = SingleUserChatModel.fromJson(jsonDecode(await Utils.getStringFromPrefs("notificationData"))); Utils.saveStringFromPrefs("notificationData", "null"); Future.delayed(const Duration(seconds: 2)); for (ChatUser user in searchedChats!) { if (user.id == nUser.targetUserId) { - Navigator.pushNamed(context, AppRoutes.chatDetailed, - arguments: ChatDetailedScreenParams(user, false)); + Navigator.pushNamed(context, AppRoutes.chatDetailed, arguments: ChatDetailedScreenParams(user, false)); return; } } @@ -2057,9 +1961,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { Future getGroupChatHistory(groups.GroupResponse groupDetails) async { isLoading = true; - groupChatHistory = await ChatApiClient().getGroupChatHistory( - groupDetails.groupId, - groupDetails.groupUserList as List); + groupChatHistory = await ChatApiClient().getGroupChatHistory(groupDetails.groupId, groupDetails.groupUserList as List); isLoading = false; @@ -2076,12 +1978,12 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { Future addGroupAndUsers(createGroup.CreateGroupRequest request) async { isLoading = true; var groups = await ChatApiClient().addGroupAndUsers(request); - userGroups.groupresponse! - .add(GroupResponse.fromJson(json.decode(groups)['response'])); + userGroups.groupresponse!.add(GroupResponse.fromJson(json.decode(groups)['response'])); isLoading = false; notifyListeners(); } + Future updateGroupAndUsers(createGroup.CreateGroupRequest request) async { isLoading = true; await ChatApiClient().updateGroupAndUsers(request); diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index 586c3aa..57bb094 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/theme/colors.dart'; class AppTheme { static ThemeData getTheme(isArabic) => ThemeData( + useMaterial3: false, fontFamily: isArabic ? 'Cairo' : 'Poppins', primarySwatch: Colors.red, + visualDensity: VisualDensity.adaptivePlatformDensity, brightness: Brightness.light, pageTransitionsTheme: const PageTransitionsTheme( @@ -26,7 +29,7 @@ class AppTheme { splashColor: Colors.transparent, primaryColor: primaryColor, primaryColorDark: primaryColor, - buttonColor: Colors.black, + cardColor: Colors.black, toggleableActiveColor: secondaryColor, indicatorColor: secondaryColor, bottomSheetTheme: const BottomSheetThemeData( @@ -44,7 +47,11 @@ class AppTheme { floatingActionButtonTheme: const FloatingActionButtonThemeData(highlightElevation: 2, disabledElevation: 0, elevation: 2), appBarTheme: AppBarTheme( color: const Color(0xff515A5D), - brightness: Brightness.light, + systemOverlayStyle: const SystemUiOverlayStyle( + statusBarBrightness: Brightness.light, + statusBarIconBrightness: Brightness.light, + systemNavigationBarIconBrightness: Brightness.light, + ), elevation: 0.0, actionsIconTheme: IconThemeData( color: Colors.grey[800], diff --git a/lib/ui/attendance/monthly_attendance_screen.dart b/lib/ui/attendance/monthly_attendance_screen.dart index 1753c29..875c8ca 100644 --- a/lib/ui/attendance/monthly_attendance_screen.dart +++ b/lib/ui/attendance/monthly_attendance_screen.dart @@ -16,7 +16,7 @@ import 'package:mohem_flutter_app/models/get_schedule_shifts_details_list_model. import 'package:mohem_flutter_app/models/get_time_card_summary_list_model.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/circular_step_progress_bar.dart'; -import 'package:month_picker_dialog_2/month_picker_dialog_2.dart'; +import 'package:month_picker_dialog/month_picker_dialog.dart'; import 'package:pie_chart/pie_chart.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; @@ -117,8 +117,10 @@ class _MonthlyAttendanceScreenState extends State { initialDate: formattedDate, firstDate: DateTime(searchYear - 2), lastDate: DateTime.now(), - confirmText: Text(LocaleKeys.confirm.tr()), - cancelText: Text(LocaleKeys.cancel.tr()), + confirmWidget: Text(LocaleKeys.confirm.tr()), + cancelWidget: Text(LocaleKeys.cancel.tr()), + // confirmText: Text(LocaleKeys.confirm.tr()), + // cancelText: Text(LocaleKeys.cancel.tr()), ).then((selectedDate) { if (selectedDate != null) { searchMonth = getMonth(selectedDate.month); diff --git a/lib/ui/chat/call/chat_incoming_call_screen.dart b/lib/ui/chat/call/chat_incoming_call_screen.dart index 1b6a5aa..b7fb734 100644 --- a/lib/ui/chat/call/chat_incoming_call_screen.dart +++ b/lib/ui/chat/call/chat_incoming_call_screen.dart @@ -1,381 +1,623 @@ +import 'dart:convert'; +import 'dart:core'; +import 'dart:io'; import 'dart:ui'; -import 'package:camera/camera.dart'; +import 'package:draggable_widget/draggable_widget.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'package:mohem_flutter_app/api/chat/chat_api_client.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/main.dart'; import 'package:mohem_flutter_app/models/chat/call.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'; +import 'package:mohem_flutter_app/models/chat/incoming_call_model.dart'; +import 'package:mohem_flutter_app/provider/chat_call_provider.dart'; +import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; +import 'package:provider/provider.dart'; -class IncomingCall extends StatefulWidget { - CallDataModel incomingCallData; - bool? isVideoCall; +bool isCallConnected = false; - IncomingCall({Key? key, required this.incomingCallData, this.isVideoCall}) : super(key: key); +class StartCallPage extends StatefulWidget { + IosCallPayload? payload; + + StartCallPage({this.payload}); @override - _IncomingCallState createState() => _IncomingCallState(); + _StartCallPageState createState() => _StartCallPageState(); } -class _IncomingCallState extends State with SingleTickerProviderStateMixin { - AnimationController? _animationController; - CameraController? _controller; - Future? _initializeControllerFuture; - bool isCameraReady = false; +class _StartCallPageState extends State { + DragController dragController = DragController(); + late ChatCallProvider cProv; + late ChatProviderModel provider; @override void initState() { - _animationController = AnimationController( - vsync: this, - duration: const Duration( - milliseconds: 500, - ), - ); - //_runAnimation(); - // connectSignaling(); - WidgetsBinding.instance.addPostFrameCallback( - (_) => _runAnimation(), + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + void startCall() async { + IncomingCallModel? sessionData; + dynamic calls = await FlutterCallkitIncoming.activeCalls(); + if (calls.isNotEmpty) { + sessionData = IncomingCallModel.fromRawJson(jsonEncode(calls[0])); + if (kDebugMode) { + print(sessionData.toRawJson()); + } + if (provider.isUserOnline) { + AppState().isBackgroundCall = true; + cProv.isUserOnline = provider.isUserOnline; + if (kDebugMode) { + print("====== Processing Incoming Call in Online State ========="); + } + await cProv.startIncomingCallViaKit(inCallData: sessionData.extra!.callerDetails!.toJson(), isVCall: sessionData.extra!.callType == "video" ? true : false); + cProv.init(); + isCallConnected = true; + } else { + AppState().isBackgroundCall = true; + if (kDebugMode) { + print("====== Processing Incoming Call ========="); + } + cProv.isUserOnline = provider.isUserOnline; + await cProv.startIncomingCallViaKit(inCallData: sessionData.extra!.callerDetails!.toJson(), isVCall: sessionData.extra!.callType == "video" ? true : false); + try { + AppState().setchatUserDetails = UserAutoLoginModel(response: Response.fromJson(sessionData.extra!.loginDetails!.toJson()), errorResponses: null); + await provider.buildHubConnection(context: context, ccProvider: cProv).whenComplete(() { + cProv.init(); + isCallConnected = true; + }); + } catch (e) { + logger.w(e); + } + } + } + + // cProv.startRecording(); + } + + void startIosCall() async { + print("๐ŸŽˆ Call Payload:" + widget.payload!.toRawJson()); + // IosCallPayload? iosCallPayload = IosCallPayload.fromRawJson(await Utils.getStringFromPrefs("iosCallPayload")); + IosCallPayload? iosCallPayload = widget.payload; + var userID = iosCallPayload!.incomingCallReciverId; + var callType = iosCallPayload.incomingCallType; + SingleUserChatModel inCallData = SingleUserChatModel( + targetUserName: iosCallPayload.incomingCallerName, + chatEventId: 3, + targetUserId: int.parse(iosCallPayload.incomingCallerId!), + currentUserId: int.parse(userID.toString()), ); + if (provider.isUserOnline) { + AppState().isBackgroundCall = true; + cProv.isUserOnline = provider.isUserOnline; + if (kDebugMode) { + print("====== Processing Incoming Call in Online State ========="); + } + await cProv.startIncomingCallViaKit(inCallData: inCallData.toJson(), isVCall: callType == "video" ? true : false); + cProv.init(); + isCallConnected = true; + } else { + if (kDebugMode) { + print("====== Processing Incoming Call ========="); + } + AppState().isBackgroundCall = true; + cProv.isUserOnline = provider.isUserOnline; + UserAutoLoginModel userLoginResponse = await ChatApiClient().getUserCallToken(userid: userID.toString()); + if (userLoginResponse.response != null) { + AppState().setchatUserDetails = userLoginResponse; + Utils.saveStringFromPrefs("userLoginChatDetails", jsonEncode(userLoginResponse.response)); - super.initState(); + await cProv.startIncomingCallViaKit(inCallData: inCallData.toJson(), isVCall: callType == "video" ? true : false); + try { + AppState().setchatUserDetails = UserAutoLoginModel(response: userLoginResponse.response, errorResponses: null); + await provider.buildHubConnection(context: context, ccProvider: cProv).whenComplete(() { + cProv.init(); + isCallConnected = true; + }); + } catch (e) { + logger.w(e); + } + } + } } @override Widget build(BuildContext context) { + cProv = context.read(); + provider = context.read(); + if (!cProv.isOutGoingCall) { + if (Platform.isAndroid) { + startCall(); + } else if (Platform.isIOS) { + startIosCall(); + } + } + return Scaffold( - body: FutureBuilder( - future: _initializeControllerFuture, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - return Stack( - alignment: FractionalOffset.center, - children: [ - if (widget.isVideoCall!) - Positioned.fill( - child: AspectRatio( - aspectRatio: _controller!.value.aspectRatio, - child: CameraPreview( - _controller!, - ), - ), - ), - Positioned.fill( - child: ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: Container( - decoration: BoxDecoration( - color: MyColors.grey57Color.withOpacity( - 0.7, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Container( - margin: const EdgeInsets.all(21.0), - child: Row( - children: [ - Image.asset( - "assets/images/logos/main_mohemm_logo.png", - height: 70, - width: 70, - ), - Container( - margin: const EdgeInsets.only( - left: 10.0, - right: 10.0, + extendBody: true, + body: Consumer2( + builder: (BuildContext context, ChatCallProvider prov, ChatProviderModel cpm, Widget? child) { + return prov.isIncomingCallLoader + ? const SizedBox( + width: double.infinity, + height: double.infinity, + child: Center(child: CircularProgressIndicator()), + ) + : prov.isIncomingCall + ? Container( + width: double.infinity, + height: double.infinity, + color: Colors.black, + child: Stack( + alignment: FractionalOffset.center, + children: [ + if (!prov.isAudioCall && prov.isVideoCall) + RTCVideoView( + prov.remoteRenderer!, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + // filterQuality: FilterQuality.high, + key: const Key('remote'), + ), + if (prov.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( + prov.localVideoRenderer!, + mirror: true, + // filterQuality: FilterQuality.high, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + ), + ), + ), + if (!prov.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.min, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: const [ - - // todo @aamir, need to use extension mehtods - Text( - "Aamir Saleem Ahmad", - style: TextStyle( - fontSize: 21, - fontWeight: FontWeight.bold, - color: MyColors.white, - letterSpacing: -1.26, - height: 23 / 12, - ), - ), - Text( - "Calling...", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Color( - 0xffC6C6C6, + 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( + prov.incomingCallData.targetUserName ?? "", + 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, + ), + ], + ), + ), ), - letterSpacing: -0.48, - height: 23 / 24, - ), - ), - SizedBox( - height: 2, + ], ), ], ), ), - ], + ), ), ), - // Container( - // margin: const EdgeInsets.all(21.0), - // width: MediaQuery.of(context).size.width, - // decoration: cardRadius(15.0, color: MyColors.black, elevation: null), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // mainAxisSize: MainAxisSize.min, - // children: [ - // Container( - // padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 6.0), - // child: Text( - // "TranslationBase.of(context).appoInfo", - // style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: MyColors.white, letterSpacing: -0.64, height: 23 / 12), - // ), - // ), - // Container( - // padding: const EdgeInsets.only(left: 16.0, right: 16.0), - // child: Text( - // "widget.incomingCallData.appointmentdate + widget.incomingCallData.appointmenttime", - // style: TextStyle(fontSize: 12.0, letterSpacing: -0.48, color: Color(0xff8E8E8E), fontWeight: FontWeight.w600), - // ), - // ), - // Container( - // padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 21.0), - // child: Text( - // "widget.incomingCallData.clinicname", - // style: TextStyle(fontSize: 12.0, letterSpacing: -0.48, color: Color(0xff8E8E8E), fontWeight: FontWeight.w600), - // ), - // ), - // ], - // ), - // ), - const Spacer(), - Container( - margin: const EdgeInsets.only( - bottom: 70.0, - left: 49, - right: 49, + 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: [ - RotationTransition( - turns: Tween( - begin: 0.0, - end: -.1, - ) - .chain( - CurveTween( - curve: Curves.elasticIn, - ), - ) - .animate( - _animationController!, - ), - child: RawMaterialButton( + // if (provider.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.loudOn(); + }, + elevation: 2.0, + fillColor: prov.isLoudSpeaker ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: const Icon( + Icons.volume_up, + color: MyColors.white, + size: 30.0, + ), + ), + if (prov.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.camOff(); + }, + elevation: 2.0, + fillColor: prov.isCamOff ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + prov.isCamOff ? Icons.videocam_off : Icons.videocam, + color: MyColors.white, + size: 30.0, + ), + ), + if (prov.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), onPressed: () { - _submit(); + prov.switchCamera(); }, elevation: 2.0, - fillColor: MyColors.green2DColor, + fillColor: prov.isFrontCamera ? Colors.grey : MyColors.textMixColor, padding: const EdgeInsets.all( - 15.0, + 10.0, ), shape: const CircleBorder(), - child: const Icon( - Icons.call, + child: Icon( + prov.isFrontCamera ? Icons.switch_camera_outlined : Icons.switch_camera, color: MyColors.white, - size: 35.0, + size: 30.0, ), ), + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.micOff(); + }, + elevation: 2.0, + fillColor: prov.isMicOff ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + prov.isMicOff ? Icons.mic_off : Icons.mic, + color: MyColors.white, + size: 30.0, + ), ), RawMaterialButton( + constraints: const BoxConstraints(), onPressed: () { - backToHome(); + prov.endCall(isUserOnline: cpm.isUserOnline).then((bool value) { + if (value) { + Navigator.of(context).pop(); + // print("Reintiiiiiiitttiiiiiiii"); + // provider.initStreams(); + } + }); }, elevation: 2.0, fillColor: MyColors.redA3Color, padding: const EdgeInsets.all( - 15.0, + 10.0, ), shape: const CircleBorder(), child: const Icon( Icons.call_end, color: MyColors.white, - size: 35.0, + size: 30.0, ), ), ], ), ), - ], - ), + ), + ], ), - ), - ), - ), - ], - ); - } else { - return const Center( - child: CircularProgressIndicator(), - ); - } - }, - ), - ); - } - - void _runAnimation() async { - List cameras = await availableCameras(); - CameraDescription firstCamera = cameras[1]; - _controller = CameraController( - firstCamera, - ResolutionPreset.medium, - ); - _initializeControllerFuture = _controller!.initialize(); - setState(() {}); - // setAudioFile(); - for (int i = 0; i < 100; i++) { - await _animationController!.forward(); - await _animationController!.reverse(); - } - } - - Future _submit() async { - try { - // backToHome(); - // final roomModel = RoomModel(name: widget.incomingCallData.name, token: widget.incomingCallData.sessionId, identity: widget.incomingCallData.identity); - await _controller?.dispose(); - - // changeCallStatusAPI(4); - - // if (_session != null && _signaling != null) { - // await Navigator.of(context).pushReplacement( - // MaterialPageRoute( - // // fullscreenDialog: true, - // builder: (BuildContext context) { - // // if (widget.incomingCallData.isWebRTC == "true") { - // return StartVideoCall(signaling: _signaling, session: _session); - // - // // else { - // // return OpenTokConnectCallPage(apiKey: OPENTOK_API_KEY, sessionId: widget.incomingCallData.sessionId, token: widget.incomingCallData.token); - // // } - // - // // return VideoCallWebPage(receiverId: widget.incomingCallData.receiverID, callerId: widget.incomingCallData.callerID); // Web WebRTC VideoCall - // - // // return CallHomePage(receiverId: widget.incomingCallData.receiverID, callerId: widget.incomingCallData.callerID); // App WebRTC VideoCall - // }, - // ), - // ); - // } else { - // // Invalid Params/Data - // Utils.showToast("Failed to establish connection with server"); - // } - } catch (err) { - print(err); - // await PlatformExceptionAlertDialog( - // exception: err, - // ).show(context); - - Utils.showToast(err.toString()); - } - } - - // void changeCallStatusAPI(int sessionStatus) { - // LiveCareService service = new LiveCareService(); - // service.endCallAPI(widget.incomingCallData.sessionId, sessionStatus, context).then((res) {}).catchError((err) { - // print(err); - // }); - // } - - void backToHome() async { - // final connected = await signaling.declineCall(widget.incomingCallData.callerID, widget.incomingCallData.receiverID); - // LandingPage.isOpenCallPage = false; - // _signaling - _animationController!.dispose(); - // player.stop(); - // changeCallStatusAPI(3); - // _signaling.bye(_session, callRejected: true); - // _signaling.callDisconnected(_session, callRejected: true); - Navigator.of(context).pop(); - } - - // - // void disposeAudioResources() async { - // await player.dispose(); - // } - // - // void setAudioFile() async { - // player.stop(); - // await player.setVolume(1.0); // full volume - // try { - // await player.setAsset('assets/sounds/ring_60Sec.mp3').then((value) { - // player.setLoopMode(LoopMode.one); // loop ring sound - // player.play(); - // }).catchError((err) { - // print("Error: $err"); - // }); - // } catch (e) { - // print("Error: $e"); - // } - // } - // - // void connectSignaling({@required bool iAmCaller = false}) async { - // print("----------------- + Signaling Connection Started ---------------------------"); - // var caller = widget.incomingCallData.callerID; - // var receiver = widget.incomingCallData.receiverID; - // var host = widget.incomingCallData.server; - // - // var selfRole = iAmCaller ? "Caller" : "Receiver"; - // var selfId = iAmCaller ? caller : receiver; - // var selfUser = SocketUser(id: selfId, name: "$selfRole-$selfId", userAgent: DeviceInfo.userAgent, moreInfo: {}); - // - // var remoteRole = !iAmCaller ? "Caller" : "Receiver"; - // var remoteId = !iAmCaller ? caller : receiver; - // var remoteUser = SocketUser(id: remoteId, name: "$remoteRole-$remoteId", userAgent: DeviceInfo.userAgent, moreInfo: {}); - // - // var sessionId = "$caller-$receiver"; - // _session = SessionOneToOne(id: sessionId, local_user: selfUser, remote_user: remoteUser); - // - // _signaling = Signaling(host, session: _session); - // await _signaling.connect(); - // - // if (_signaling.state == SignalingState.Open) { - // return; - // } - // } + ) + : prov.isOutGoingCall + ? Container( + width: double.infinity, + height: double.infinity, + color: Colors.black, + child: Stack( + alignment: FractionalOffset.center, + children: [ + if (!prov.isAudioCall && prov.isVideoCall) + RTCVideoView( + prov.remoteRenderer!, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + key: const Key('remote'), + ), + if (prov.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( + prov.localVideoRenderer!, + mirror: true, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + ), + ), + ), + if (!prov.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, + ), + 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( + prov.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, + ), + ], + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + 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: const BoxConstraints(), + onPressed: () { + prov.loudOn(); + }, + elevation: 2.0, + fillColor: prov.isLoudSpeaker ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: const Icon( + Icons.volume_up, + color: MyColors.white, + size: 30.0, + ), + ), - BoxDecoration cardRadius(double radius, {required Color color, double? elevation}) { - return BoxDecoration( - shape: BoxShape.rectangle, - color: color ?? Colors.white, - borderRadius: BorderRadius.all( - Radius.circular(radius), + if (prov.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.camOff(); + }, + elevation: 2.0, + fillColor: prov.isCamOff ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + prov.isCamOff ? Icons.videocam_off : Icons.videocam, + color: MyColors.white, + size: 30.0, + ), + ), + if (prov.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.switchCamera(); + }, + elevation: 2.0, + fillColor: prov.isFrontCamera ? Colors.grey : MyColors.textMixColor, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + prov.isFrontCamera ? Icons.switch_camera_outlined : Icons.switch_camera, + color: MyColors.white, + size: 30.0, + ), + ), + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.micOff(); + }, + elevation: 2.0, + fillColor: prov.isMicOff ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + prov.isMicOff ? Icons.mic_off : Icons.mic, + color: MyColors.white, + size: 30.0, + ), + ), + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.endCall(isUserOnline: cpm.isUserOnline).then((bool 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, + ), + ), + ], + ), + ), + ), + ], + ), + ) + : const SizedBox(); + }, ), - boxShadow: [ - BoxShadow( - color: const Color( - 0xff000000, - ).withOpacity( - .05, - ), - //spreadRadius: 5, - blurRadius: elevation ?? 27, - offset: const Offset( - -2, - 3, - ), - ), - ], ); } } + +/// if (Platform.isAndroid) { +// SystemNavigator.pop(); +// } else if (Platform.isIOS) { +// exit(0); +// } diff --git a/lib/ui/chat/call/chat_outgoing_call_screen.dart b/lib/ui/chat/call/chat_outgoing_call_screen.dart index 2356e10..5be4c24 100644 --- a/lib/ui/chat/call/chat_outgoing_call_screen.dart +++ b/lib/ui/chat/call/chat_outgoing_call_screen.dart @@ -1,16 +1,14 @@ -import 'dart:convert'; import 'dart:ui'; -import 'package:camera/camera.dart'; -import 'package:flutter/cupertino.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/classes/colors.dart'; -import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; -import 'package:mohem_flutter_app/main.dart'; import 'package:mohem_flutter_app/models/chat/call.dart'; import 'package:mohem_flutter_app/provider/chat_call_provider.dart'; +import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:provider/provider.dart'; class OutGoingCall extends StatefulWidget { @@ -23,407 +21,169 @@ class OutGoingCall extends StatefulWidget { _OutGoingCallState createState() => _OutGoingCallState(); } -class _OutGoingCallState extends State with SingleTickerProviderStateMixin { - AnimationController? _animationController; - late CameraController controller; - late List _cameras; - Future? _initializeControllerFuture; - bool isCameraReady = false; - bool isMicOff = false; - bool isLoudSpeaker = false; - bool isCamOff = false; - late ChatCallProvider callProviderd; +class _OutGoingCallState extends State { + late ChatCallProvider callProvider; + late ChatProviderModel chatProvider; + bool loader = true; @override void initState() { - callProviderd = Provider.of(context, listen: false); - _animationController = AnimationController( - vsync: this, - duration: const Duration( - milliseconds: 500, - ), - ); - // _runAnimation(); - // connectSignaling(); - WidgetsBinding.instance.addPostFrameCallback( - (_) => _runAnimation(), - ); - super.initState(); } + Future init() async { + widget.isVideoCall ? callProvider.isVideoCall = true : callProvider.isVideoCall = false; + callProvider.isOutGoingCall = true; + callProvider.startTimer(); + await callProvider.initLocalCamera(chatProvmodel: chatProvider, callData: widget.outGoingCallData, context: context); + loader = false; + } + + @override + void dispose() { + super.dispose(); + } + @override Widget build(BuildContext context) { + chatProvider = Provider.of(context, listen: false); + callProvider = Provider.of(context, listen: false); + init(); return Scaffold( - body: FutureBuilder( - future: _initializeControllerFuture, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - return Stack( - alignment: FractionalOffset.center, - children: [ - if (widget.isVideoCall) - Positioned.fill( - child: CameraPreview( - controller, + body: Consumer(builder: (BuildContext context, ChatCallProvider chatcp, Widget? child) { + return loader + ? const Center( + child: CircularProgressIndicator(), + ) + : Stack( + alignment: FractionalOffset.center, + children: [ + if (chatcp.isVideoCall) + Positioned.fill( + child: RTCVideoView( + chatcp.localVideoRenderer!, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + ), ), - ), - Positioned.fill( - child: ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: Container( - decoration: BoxDecoration( - color: MyColors.grey57Color.withOpacity( - 0.3, + 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, - ), - 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( - widget.outGoingCallData.title, - style: const TextStyle( - fontSize: 21, - fontWeight: FontWeight.bold, - color: MyColors.white, - letterSpacing: -1.26, - height: 23 / 12, + 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( + widget.outGoingCallData.receiverName.toString().replaceAll(".", " "), + style: const TextStyle( + fontSize: 21, + fontWeight: FontWeight.bold, + color: MyColors.white, + letterSpacing: -1.26, + height: 23 / 12, + ), ), - ), - const Text( - "Ringing...", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Color( - 0xffC6C6C6, + const Text( + "Ringing...", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color( + 0xffC6C6C6, + ), + letterSpacing: -0.48, + height: 23 / 24, ), - letterSpacing: -0.48, - height: 23 / 24, ), - ), - const SizedBox( - height: 2, - ), - ], + const SizedBox( + height: 2, + ), + ], + ), ), ), - ), - ], - ), - // Container( - // margin: const EdgeInsets.all(21.0), - // width: MediaQuery.of(context).size.width, - // decoration: cardRadius(15.0, color: MyColors.black, elevation: null), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // mainAxisSize: MainAxisSize.min, - // children: [ - // Container( - // padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 6.0), - // child: Text( - // "TranslationBase.of(context).appoInfo", - // style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: MyColors.white, letterSpacing: -0.64, height: 23 / 12), - // ), - // ), - // Container( - // padding: const EdgeInsets.only(left: 16.0, right: 16.0), - // child: Text( - // "widget.OutGoingCallData.appointmentdate + widget.OutGoingCallData.appointmenttime", - // style: TextStyle(fontSize: 12.0, letterSpacing: -0.48, color: Color(0xff8E8E8E), fontWeight: FontWeight.w600), - // ), - // ), - // Container( - // padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 21.0), - // child: Text( - // "widget.OutGoingCallData.clinicname", - // style: TextStyle(fontSize: 12.0, letterSpacing: -0.48, color: Color(0xff8E8E8E), fontWeight: FontWeight.w600), - // ), - // ), - // ], - // ), - // ), - const Spacer(), - Container( - margin: const EdgeInsets.only( - bottom: 70.0, - left: 49, - right: 49, + ], ), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (widget.isVideoCall) - RawMaterialButton( - onPressed: () { - _camOff(); - }, - elevation: 2.0, - fillColor: isCamOff ? MyColors.green2DColor : Colors.grey, - padding: const EdgeInsets.all( - 15.0, - ), - shape: const CircleBorder(), - child: Icon( - isCamOff ? Icons.videocam_off : Icons.videocam, - color: MyColors.white, - size: 35.0, - ), - ) - else + const Spacer(), + Container( + margin: const EdgeInsets.only( + bottom: 70.0, + left: 49, + right: 49, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ RawMaterialButton( onPressed: () { - _loudOn(); + chatcp.endCall(isUserOnline: chatProvider.isUserOnline).then((bool value) { + if (value) { + Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.chatDetailed)); + } + }); }, elevation: 2.0, - fillColor: isLoudSpeaker ? MyColors.green2DColor : Colors.grey, - padding: const EdgeInsets.all( - 15.0, - ), + fillColor: MyColors.redA3Color, + padding: const EdgeInsets.all(15.0), shape: const CircleBorder(), child: const Icon( - Icons.volume_up, + Icons.call_end, color: MyColors.white, size: 35.0, ), ), - RawMaterialButton( - onPressed: () { - _micOff(); - }, - elevation: 2.0, - fillColor: isMicOff ? MyColors.green2DColor : Colors.grey, - padding: const EdgeInsets.all( - 15.0, - ), - shape: const CircleBorder(), - child: Icon( - isMicOff ? Icons.mic_off : Icons.mic, - color: MyColors.white, - size: 35.0, - ), - ), - RawMaterialButton( - onPressed: () { - backToHome(); - }, - elevation: 2.0, - fillColor: MyColors.redA3Color, - padding: const EdgeInsets.all( - 15.0, - ), - shape: const CircleBorder(), - child: const Icon( - Icons.call_end, - color: MyColors.white, - size: 35.0, - ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), ), ), - ), - ], - ); - } else { - return const Center( - child: CircularProgressIndicator(), - ); - } - }, - ), + ], + ); + }), ); } - void _runAnimation() async { - _cameras = await availableCameras(); - CameraDescription firstCamera = _cameras[1]; - controller = CameraController(firstCamera, ResolutionPreset.medium); - _initializeControllerFuture = controller.initialize(); - setState(() {}); - // setAudioFile(); - for (int i = 0; i < 100; i++) { - await _animationController!.forward(); - await _animationController!.reverse(); - } - } - - void _micOff() { - setState(() { - isMicOff = !isMicOff; - }); - } - - void _camOff() { - setState(() { - isCamOff = !isCamOff; - }); - } - - void _loudOn() { - setState(() { - isLoudSpeaker = !isLoudSpeaker; - }); - } - - Future _submit() async { - try { - // backToHome(); - // final roomModel = RoomModel(name: widget.OutGoingCallData.name, token: widget.OutGoingCallData.sessionId, identity: widget.OutGoingCallData.identity); - await controller?.dispose(); - - // changeCallStatusAPI(4); - - // if (_session != null && _signaling != null) { - // await Navigator.of(context).pushReplacement( - // MaterialPageRoute( - // // fullscreenDialog: true, - // builder: (BuildContext context) { - // // if (widget.OutGoingCallData.isWebRTC == "true") { - // return StartVideoCall(signaling: _signaling, session: _session); - // - // // else { - // // return OpenTokConnectCallPage(apiKey: OPENTOK_API_KEY, sessionId: widget.OutGoingCallData.sessionId, token: widget.OutGoingCallData.token); - // // } - // - // // return VideoCallWebPage(receiverId: widget.OutGoingCallData.receiverID, callerId: widget.OutGoingCallData.callerID); // Web WebRTC VideoCall - // - // // return CallHomePage(receiverId: widget.OutGoingCallData.receiverID, callerId: widget.OutGoingCallData.callerID); // App WebRTC VideoCall - // }, - // ), - // ); - // } else { - // // Invalid Params/Data - // Utils.showToast("Failed to establish connection with server"); - // } - } catch (err) { - print(err); - // await PlatformExceptionAlertDialog( - // exception: err, - // ).show(context); - - Utils.showToast(err.toString()); - } - } - - // void changeCallStatusAPI(int sessionStatus) { - // LiveCareService service = new LiveCareService(); - // service.endCallAPI(widget.OutGoingCallData.sessionId, sessionStatus, context).then((res) {}).catchError((err) { - // print(err); - // }); - // } - - void backToHome() async { - // final connected = await signaling.declineCall(widget.OutGoingCallData.callerID, widget.OutGoingCallData.receiverID); - // LandingPage.isOpenCallPage = false; - // _signaling - _animationController!.dispose(); - // player.stop(); - // changeCallStatusAPI(3); - // _signaling.bye(_session, callRejected: true); - // _signaling.callDisconnected(_session, callRejected: true); - Navigator.of(context).pop(); - } - - // - // void disposeAudioResources() async { - // await player.dispose(); - // } - // - // void setAudioFile() async { - // player.stop(); - // await player.setVolume(1.0); // full volume - // try { - // await player.setAsset('assets/sounds/ring_60Sec.mp3').then((value) { - // player.setLoopMode(LoopMode.one); // loop ring sound - // player.play(); - // }).catchError((err) { - // print("Error: $err"); - // }); - // } catch (e) { - // print("Error: $e"); - // } - // } - // - // void connectSignaling({@required bool iAmCaller = false}) async { - // print("----------------- + Signaling Connection Started ---------------------------"); - // var caller = widget.OutGoingCallData.callerID; - // var receiver = widget.OutGoingCallData.receiverID; - // var host = widget.OutGoingCallData.server; - // - // var selfRole = iAmCaller ? "Caller" : "Receiver"; - // var selfId = iAmCaller ? caller : receiver; - // var selfUser = SocketUser(id: selfId, name: "$selfRole-$selfId", userAgent: DeviceInfo.userAgent, moreInfo: {}); - // - // var remoteRole = !iAmCaller ? "Caller" : "Receiver"; - // var remoteId = !iAmCaller ? caller : receiver; - // var remoteUser = SocketUser(id: remoteId, name: "$remoteRole-$remoteId", userAgent: DeviceInfo.userAgent, moreInfo: {}); - // - // var sessionId = "$caller-$receiver"; - // _session = SessionOneToOne(id: sessionId, local_user: selfUser, remote_user: remoteUser); - // - // _signaling = Signaling(host, session: _session); - // await _signaling.connect(); - // - // if (_signaling.state == SignalingState.Open) { - // return; - // } - // } - BoxDecoration cardRadius(double radius, {required Color color, double? elevation}) { return BoxDecoration( shape: BoxShape.rectangle, color: color ?? Colors.white, - borderRadius: BorderRadius.all( - Radius.circular(radius), - ), - boxShadow: [ - BoxShadow( - color: const Color( - 0xff000000, - ).withOpacity( - .05, - ), - //spreadRadius: 5, - blurRadius: elevation ?? 27, - offset: const Offset( - -2, - 3, - ), - ), - ], + borderRadius: BorderRadius.all(Radius.circular(radius)), + boxShadow: [BoxShadow(color: const Color(0xff000000).withOpacity(.05), blurRadius: elevation ?? 27, offset: const Offset(-2, 3))], ); } } diff --git a/lib/ui/chat/call/draggable_cam_screen.dart b/lib/ui/chat/call/draggable_cam_screen.dart new file mode 100644 index 0000000..14f0fc8 --- /dev/null +++ b/lib/ui/chat/call/draggable_cam_screen.dart @@ -0,0 +1,171 @@ +// import 'dart:async'; +// import 'dart:io'; +// import 'package:flutter/material.dart'; +// +// class DraggableCam extends StatefulWidget { +// //final Size availableScreenSize; +// final Widget child; +// final double scaleFactor; +// // final Stream onButtonBarVisible; +// // final Stream onButtonBarHeight; +// +// const DraggableCam({ +// Key? key, +// //@required this.availableScreenSize, +// required this.child, +// // @required this.onButtonBarVisible, +// // @required this.onButtonBarHeight, +// +// /// The portion of the screen the DraggableWidget should use. +// this.scaleFactor = .25, +// }) : assert(scaleFactor != null && scaleFactor > 0 && scaleFactor <= .4), +// // assert(availableScreenSize != null), +// // assert(onButtonBarVisible != null), +// // assert(onButtonBarHeight != null), +// super(key: key); +// +// @override +// _DraggablePublisherState createState() => _DraggablePublisherState(); +// } +// +// class _DraggablePublisherState extends State { +// bool _isButtonBarVisible = true; +// double _buttonBarHeight = 0; +// late double _width; +// late double _height; +// late double _top; +// late double _left; +// late double _viewPaddingTop; +// late double _viewPaddingBottom; +// final double _padding = 8.0; +// final Duration _duration300ms = const Duration(milliseconds: 300); +// final Duration _duration0ms = const Duration(milliseconds: 0); +// late Duration _duration; +// late StreamSubscription _streamSubscription; +// late StreamSubscription _streamHeightSubscription; +// +// @override +// void initState() { +// super.initState(); +// _duration = _duration300ms; +// _width = widget.availableScreenSize.width * widget.scaleFactor; +// _height = _width * (widget.availableScreenSize.height / widget.availableScreenSize.width); +// _top = widget.availableScreenSize.height - (_buttonBarHeight + _padding) - _height; +// _left = widget.availableScreenSize.width - _padding - _width; +// +// _streamSubscription = widget.onButtonBarVisible.listen(_buttonBarVisible); +// _streamHeightSubscription = widget.onButtonBarHeight.listen(_getButtonBarHeight); +// } +// +// @override +// void didChangeDependencies() { +// var mediaQuery = MediaQuery.of(context); +// _viewPaddingTop = mediaQuery.viewPadding.top; +// _viewPaddingBottom = mediaQuery.viewPadding.bottom; +// super.didChangeDependencies(); +// } +// +// @override +// void dispose() { +// _streamSubscription.cancel(); +// _streamHeightSubscription.cancel(); +// super.dispose(); +// } +// +// void _getButtonBarHeight(double height) { +// setState(() { +// _buttonBarHeight = height; +// _positionWidget(); +// }); +// } +// +// void _buttonBarVisible(bool visible) { +// if (!mounted) { +// return; +// } +// setState(() { +// _isButtonBarVisible = visible; +// if (_duration == _duration300ms) { +// // only position the widget when we are not currently dragging it around +// _positionWidget(); +// } +// }); +// } +// +// @override +// Widget build(BuildContext context) { +// return AnimatedPositioned( +// top: _top, +// left: _left, +// width: _width, +// height: _height, +// duration: _duration, +// child: Listener( +// onPointerDown: (_) => _duration = _duration0ms, +// onPointerMove: (PointerMoveEvent event) { +// setState(() { +// _left = (_left + event.delta.dx).roundToDouble(); +// _top = (_top + event.delta.dy).roundToDouble(); +// }); +// }, +// onPointerUp: (_) => _positionWidget(), +// onPointerCancel: (_) => _positionWidget(), +// child: ClippedVideo( +// height: _height, +// width: _width, +// child: widget.child, +// ), +// ), +// ); +// } +// +// double _getCurrentStatusBarHeight() { +// if (_isButtonBarVisible) { +// return _viewPaddingTop; +// } +// final _defaultViewPaddingTop = Platform.isIOS ? 20.0 : Platform.isAndroid ? 24.0 : 0.0; +// if (_viewPaddingTop > _defaultViewPaddingTop) { +// // There must be a hardware notch in the display. +// return _viewPaddingTop; +// } +// return 0.0; +// } +// +// double _getCurrentButtonBarHeight() { +// if (_isButtonBarVisible) { +// return _buttonBarHeight + _viewPaddingBottom; +// } +// return _viewPaddingBottom; +// } +// +// void _positionWidget() { +// // Determine the center of the object being dragged so we can decide +// // in which corner the object should be placed. +// var dx = (_width / 2) + _left; +// dx = dx < 0 ? 0 : dx >= widget.availableScreenSize.width ? widget.availableScreenSize.width - 1 : dx; +// var dy = (_height / 2) + _top; +// dy = dy < 0 ? 0 : dy >= widget.availableScreenSize.height ? widget.availableScreenSize.height - 1 : dy; +// final draggableCenter = Offset(dx, dy); +// +// setState(() { +// _duration = _duration300ms; +// if (Rect.fromLTRB(0, 0, widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2).contains(draggableCenter)) { +// // Top-left +// _top = _getCurrentStatusBarHeight() + _padding; +// _left = _padding; +// } else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, 0, widget.availableScreenSize.width, widget.availableScreenSize.height / 2).contains(draggableCenter)) { +// // Top-right +// _top = _getCurrentStatusBarHeight() + _padding; +// _left = widget.availableScreenSize.width - _padding - _width; +// } else if (Rect.fromLTRB(0, widget.availableScreenSize.height / 2, widget.availableScreenSize.width / 2, widget.availableScreenSize.height).contains(draggableCenter)) { +// // Bottom-left +// _top = widget.availableScreenSize.height - (_getCurrentButtonBarHeight() + _padding) - _height; +// _left = _padding; +// } else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2, widget.availableScreenSize.width, widget.availableScreenSize.height).contains(draggableCenter)) { +// // Bottom-right +// _top = widget.availableScreenSize.height - (_getCurrentButtonBarHeight() + _padding) - _height; +// _left = widget.availableScreenSize.width - _padding - _width; +// } +// }); +// } +// } diff --git a/lib/ui/chat/chat_bubble.dart b/lib/ui/chat/chat_bubble.dart index d6483dc..223f38e 100644 --- a/lib/ui/chat/chat_bubble.dart +++ b/lib/ui/chat/chat_bubble.dart @@ -1,5 +1,4 @@ import 'dart:io'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -18,9 +17,9 @@ import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.da import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/ui/chat/chat_full_image_preview.dart'; import 'package:mohem_flutter_app/ui/chat/common.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:rxdart/rxdart.dart'; +import 'package:video_player/video_player.dart'; class ChatBubble extends StatelessWidget { ChatBubble({Key? key, required this.dateTime, required this.cItem}) : super(key: key); @@ -82,7 +81,8 @@ class ChatBubble extends StatelessWidget { } } else { Utils.showLoading(context); - Uint8List encodedString = await ChatApiClient().downloadURL(fileName: data.contant!, fileTypeDescription: provider.getFileTypeDescription(data.fileTypeResponse!.fileTypeName ?? "")); + Uint8List encodedString = + await ChatApiClient().downloadURL(fileName: data.contant!, fileTypeDescription: provider.getFileTypeDescription(data.fileTypeResponse!.fileTypeName ?? ""), fileSource: 1); // try { File sFile = await provider.downChatVoice(encodedString, data.fileTypeResponse!.fileTypeName ?? "", data); if (sFile.path.isEmpty) { @@ -195,20 +195,26 @@ class ChatBubble extends StatelessWidget { }), ), ).paddingOnly(bottom: 4), - if (fileTypeID == 13 && cItem.voiceController != null) - currentWaveBubble(context, cItem) + if (fileTypeID == 13 && cItem.voiceController != null) currentWaveBubble(context, cItem), + if (fileTypeID == 16) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + showVideoThumb(context, cItem), + Row( + children: [ + Directionality(textDirection: provider.getTextDirection(cItem.contant ?? ""), child: (cItem.contant ?? "").toText12().expanded), + ], + ), + ], + ) else Row( children: [ - if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8 - // || fileTypeID == 2 - ) + if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8) SvgPicture.asset(provider.getType(fileTypeName ?? ""), height: 30, width: 22, alignment: Alignment.center, fit: BoxFit.cover).paddingOnly(left: 0, right: 10), Directionality(textDirection: provider.getTextDirection(cItem.contant ?? ""), child: (cItem.contant ?? "").toText12().expanded), - if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8 - //|| fileTypeID == 2 - ) - const Icon(Icons.remove_red_eye, size: 16) + if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8) const Icon(Icons.remove_red_eye, size: 16) ], ), Align( @@ -300,20 +306,26 @@ class ChatBubble extends StatelessWidget { }), ), ).paddingOnly(bottom: 4), - if (fileTypeID == 13 && cItem.voiceController != null) - recipetWaveBubble(context, cItem) + if (fileTypeID == 13 && cItem.voiceController != null) recipetWaveBubble(context, cItem), + if (fileTypeID == 16) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + showVideoThumb(context, cItem), + Row( + children: [ + Directionality(textDirection: provider.getTextDirection(cItem.contant ?? ""), child: (cItem.contant ?? "").toText12(color: Colors.white).expanded), + ], + ), + ], + ) else Row( children: [ - if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8 - // || fileTypeID == 2 - ) + if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8) SvgPicture.asset(provider.getType(fileTypeName ?? ""), height: 30, width: 22, alignment: Alignment.center, fit: BoxFit.cover).paddingOnly(left: 0, right: 10), Directionality(textDirection: provider.getTextDirection(cItem.contant ?? ""), child: (cItem.contant ?? "").toText12(color: Colors.white).expanded), - if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8 - //|| fileTypeID == 2 - ) - const Icon(Icons.remove_red_eye, color: Colors.white, size: 16) + if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8) const Icon(Icons.remove_red_eye, color: Colors.white, size: 16) ], ), Align( @@ -324,11 +336,7 @@ class ChatBubble extends StatelessWidget { ), ], ), - ).paddingOnly(right: MediaQuery.of(context).size.width * 0.3); - } - - Widget voiceMsg(BuildContext context) { - return Container(); + ).paddingOnly(right: MediaQuery.of(context).size.width * 0.33); } Widget showImage({required bool isReplyPreview, required String fileName, required String fileTypeDescription}) { @@ -342,7 +350,7 @@ class ChatBubble extends StatelessWidget { ); } else { return FutureBuilder( - future: ChatApiClient().downloadURL(fileName: fileName, fileTypeDescription: fileTypeDescription), + future: ChatApiClient().downloadURL(fileName: fileName, fileTypeDescription: fileTypeDescription, fileSource: 1), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState != ConnectionState.waiting) { if (snapshot.data == null) { @@ -470,4 +478,50 @@ class ChatBubble extends StatelessWidget { }, ); } + + Widget showVideoThumb(BuildContext context, SingleUserChatModel data) { + return LoadVideo(data: data); + } +} + +class LoadVideo extends StatefulWidget { + final SingleUserChatModel data; + + const LoadVideo({Key? key, required this.data}) : super(key: key); + + @override + State createState() => _LoadVideoState(); +} + +class _LoadVideoState extends State { + late VideoPlayerController videoController; + + @override + void initState() { + videoController = VideoPlayerController.networkUrl(Uri.parse('https://apiderichat.hmg.com/attachments/${widget.data.fileTypeResponse?.fileName}'))..initialize().then((_) {}); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(5.0), + child: AspectRatio( + aspectRatio: videoController.value.aspectRatio, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + VideoPlayer(videoController), + Align( + alignment: Alignment.center, + child: Icon( + Icons.play_arrow, + color: Colors.white.withOpacity(.7), + size: 56, + ), + ) + ], + ), + )); + } } diff --git a/lib/ui/chat/chat_detailed_screen.dart b/lib/ui/chat/chat_detailed_screen.dart index a81219b..8ce68dc 100644 --- a/lib/ui/chat/chat_detailed_screen.dart +++ b/lib/ui/chat/chat_detailed_screen.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:convert'; + import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -10,22 +10,20 @@ 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/main.dart'; import 'package:mohem_flutter_app/models/chat/call.dart'; import 'package:mohem_flutter_app/models/chat/get_search_user_chat_model.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'; 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/chat/custom_auto_direction.dart'; import 'package:mohem_flutter_app/ui/chat/call/chat_outgoing_call_screen.dart'; import 'package:mohem_flutter_app/ui/chat/chat_bubble.dart'; import 'package:mohem_flutter_app/ui/chat/common.dart'; +import 'package:mohem_flutter_app/ui/chat/custom_auto_direction.dart'; import 'package:mohem_flutter_app/widgets/chat_app_bar_widge.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; -import 'package:signalr_netcore/signalr_client.dart'; import 'package:swipe_to/swipe_to.dart'; class ChatDetailedScreenParams { @@ -78,7 +76,7 @@ class _ChatDetailScreenState extends State { Widget build(BuildContext context) { params = ModalRoute.of(context)!.settings.arguments as ChatDetailedScreenParams; data = Provider.of(context, listen: false); - // callPro = Provider.of(context, listen: false); + callPro = Provider.of(context, listen: false); if (params != null) { data.getSingleUserChatHistory( senderUID: AppState().chatDetails!.response!.id!.toInt(), @@ -98,14 +96,32 @@ class _ChatDetailScreenState extends State { showTyping: true, chatUser: params!.chatUser, actions: [ - // SvgPicture.asset("assets/icons/chat/call.svg", width: 21, height: 23).onPress(() { - // makeCall(callType: "AUDIO"); - // }), - // 24.width, - // SvgPicture.asset("assets/icons/chat/video_call.svg", width: 21, height: 18).onPress(() { - // makeCall(callType: "VIDEO"); - // }), - // 21.width, + // if (Platform.isAndroid) + SvgPicture.asset("assets/icons/chat/call.svg", width: 21, height: 23).onPress(() async { + Future micPer = Permission.microphone.request(); + if (await micPer.isGranted) { + makeCall(callType: "AUDIO"); + } else { + Permission.microphone.request().isGranted.then((value) { + makeCall(callType: "AUDIO"); + }); + } + }), + // if (Platform.isAndroid) + 24.width, + // if (Platform.isAndroid) + SvgPicture.asset("assets/icons/chat/video_call.svg", width: 21, height: 18).onPress(() async { + Future camPer = Permission.camera.request(); + Future micPer = Permission.microphone.request(); + + if (await camPer.isGranted && await micPer.isGranted) { + makeCall(callType: "VIDEO"); + } else { + if (await Permission.camera.request().isGranted && await Permission.microphone.isGranted) makeCall(callType: "VIDEO"); + } + }), + // if (Platform.isAndroid) + 21.width, ], ), body: SafeArea( @@ -149,17 +165,19 @@ class _ChatDetailScreenState extends State { ); }, ).onPress(() async { - logger.w(m.userChatHistory[i].toJson()); + // logger.w(m.userChatHistory[i].toJson()); if (m.userChatHistory[i].fileTypeResponse != null && m.userChatHistory[i].fileTypeId != null) { if (m.userChatHistory[i].fileTypeId! == 1 || - m.userChatHistory[i].fileTypeId! == 5 || - m.userChatHistory[i].fileTypeId! == 7 || - m.userChatHistory[i].fileTypeId! == 6 || - m.userChatHistory[i].fileTypeId! == 8 - // || m.userChatHistory[i].fileTypeId! == 2 - ) { + m.userChatHistory[i].fileTypeId! == 5 || + m.userChatHistory[i].fileTypeId! == 7 || + m.userChatHistory[i].fileTypeId! == 6 || + m.userChatHistory[i].fileTypeId! == 8 || + m.userChatHistory[i].fileTypeId! == 16) { m.getChatMedia(context, - fileTypeName: m.userChatHistory[i].fileTypeResponse!.fileTypeName ?? "", fileTypeID: m.userChatHistory[i].fileTypeId!, fileName: m.userChatHistory[i].contant!); + fileTypeName: m.userChatHistory[i].fileTypeResponse!.fileTypeName ?? "", + fileTypeID: m.userChatHistory[i].fileTypeId!, + fileName: m.userChatHistory[i].contant!, + fileSource: 1); } } }); @@ -352,29 +370,21 @@ class _ChatDetailScreenState extends State { } void makeCall({required String callType}) async { - callPro.initCallListeners(); - print("================== Make call Triggered ============================"); Map json = { - "callerID": AppState().chatDetails!.response!.id!.toString(), - "callerDetails": AppState().chatDetails!.toJson(), - "receiverID": params!.chatUser!.id.toString(), - "receiverDetails": params!.chatUser!.toJson(), + "callerID": AppState().chatDetails!.response!.id, + "callerName": AppState().chatDetails!.response!.userName, + "callerEmail": AppState().chatDetails!.response!.email, + "callerTitle": AppState().chatDetails!.response!.title, + "callerPhone": AppState().chatDetails!.response!.phone, + "receiverID": params!.chatUser!.id, + "receiverName": params!.chatUser!.userName, + "receiverEmail": params!.chatUser!.email, + "receiverTitle": params!.chatUser!.title, + "receiverPhone": params!.chatUser!.phone, "title": params!.chatUser!.userName!.replaceAll(".", " "), - "calltype": callType == "VIDEO" ? "Video" : "Audio", + "callType": callType == "VIDEO" ? "Video" : "Audio", }; - logger.w(json); CallDataModel callData = CallDataModel.fromJson(json); - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => OutGoingCall( - isVideoCall: callType == "VIDEO" ? true : false, - outGoingCallData: callData, - ), - ), - ).then((value) { - print("then"); - callPro.stopListeners(); - }); + await Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => OutGoingCall(isVideoCall: callType == "VIDEO" ? true : false, outGoingCallData: callData))); } } diff --git a/lib/ui/chat/chat_home.dart b/lib/ui/chat/chat_home.dart index 426c9a2..19cdd24 100644 --- a/lib/ui/chat/chat_home.dart +++ b/lib/ui/chat/chat_home.dart @@ -2,17 +2,17 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/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/main.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/chat/chat_home_screen.dart'; import 'package:mohem_flutter_app/ui/chat/favorite_users_screen.dart'; import 'package:mohem_flutter_app/ui/chat/group_chat.dart'; import 'package:mohem_flutter_app/ui/chat/my_team_screen.dart'; -import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:provider/provider.dart'; import 'package:signalr_netcore/signalr_client.dart'; @@ -28,12 +28,15 @@ class _ChatHomeState extends State { int tabIndex = 0; PageController controller = PageController(); late ChatProviderModel data; + late ChatCallProvider callProvider; @override void initState() { super.initState(); data = Provider.of(context, listen: false); + callProvider = Provider.of(context, listen: false); data.registerEvents(); + // callProvider.initCallListeners(); } @override @@ -43,23 +46,16 @@ class _ChatHomeState extends State { } void fetchAgain() { - if (chatHubConnection.state != HubConnectionState.Connected) { + if (chatHubConnection!.state != HubConnectionState.Connected) { data.getUserAutoLoginToken().whenComplete(() async { - await data.buildHubConnection(); + await data.buildHubConnection(context: context, ccProvider: callProvider); data.getUserRecentChats(); - }); return; } if (data.searchedChats == null || data.searchedChats!.isEmpty) { data.isLoading = true; - data.getUserRecentChats().whenComplete(() async { - // String isAppOpendByChat = await Utils.getStringFromPrefs("isAppOpendByChat"); - // String notificationData = await Utils.getStringFromPrefs("notificationData"); - // if (isAppOpendByChat != "null" || isAppOpendByChat == "true" && notificationData != "null") { - // data.openChatByNoti(context); - // } - }); + data.getUserRecentChats().whenComplete(() async {}); } } @@ -68,7 +64,7 @@ class _ChatHomeState extends State { fetchAgain(); return Scaffold( backgroundColor: MyColors.white, - appBar: AppBarWidget(context, title: LocaleKeys.chat.tr(), showHomeButton: true), + appBar: AppBarWidget(context, title: LocaleKeys.chat.tr(), showHomeButton: true, isBackButton: false), body: Column( children: [ Container( @@ -91,7 +87,7 @@ class _ChatHomeState extends State { child: Row( children: [ myTab(LocaleKeys.mychats.tr(), 0), - // myTab(LocaleKeys.group.tr(), 1), + myTab(LocaleKeys.group.tr(), 1), myTab(LocaleKeys.favorite.tr(), 2), AppState().getempStatusIsManager ? myTab(LocaleKeys.myTeam.tr(), 3) : const SizedBox(), ], @@ -107,7 +103,7 @@ class _ChatHomeState extends State { }, children: [ ChatHomeScreen(), - // GropChatHomeScreen(), + GropChatHomeScreen(), ChatFavoriteUsersScreen(), AppState().getempStatusIsManager ? const MyTeamScreen() : const SizedBox(), ], diff --git a/lib/ui/chat/chat_home_screen.dart b/lib/ui/chat/chat_home_screen.dart index 04d7c2e..abcd9fa 100644 --- a/lib/ui/chat/chat_home_screen.dart +++ b/lib/ui/chat/chat_home_screen.dart @@ -1,7 +1,5 @@ import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; @@ -15,7 +13,6 @@ import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheets/search_employee_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; import 'package:provider/provider.dart'; -import 'package:pull_to_refresh/pull_to_refresh.dart'; class ChatHomeScreen extends StatefulWidget { const ChatHomeScreen({Key? key}) : super(key: key); @@ -154,16 +151,9 @@ class _ChatHomeScreenState extends State { alignment: Alignment.center, width: 18, height: 18, - decoration: const BoxDecoration( - color: MyColors.redColor, - borderRadius: BorderRadius.all( - Radius.circular(20), - ), - ), - child: (m.searchedChats![index].unreadMessageCount!.toString()) - .toText10( - color: MyColors.white, - ) + decoration: const BoxDecoration(color: MyColors.redColor, borderRadius: BorderRadius.all(Radius.circular(22))), + child: (m.searchedChats![index].unreadMessageCount! >= 10 ? "10+" : m.searchedChats![index].unreadMessageCount!.toString()) + .toText10(color: MyColors.white) .center, ).paddingOnly(right: 10).center, Icon( @@ -172,22 +162,14 @@ class _ChatHomeScreenState extends State { ).onPress( () { if (m.searchedChats![index].isFav == null || m.searchedChats![index].isFav == false) { - m.favoriteUser( - userID: AppState().chatDetails!.response!.id!, - targetUserID: m.searchedChats![index].id!, - fromSearch: false - ); + m.favoriteUser(userID: AppState().chatDetails!.response!.id!, targetUserID: m.searchedChats![index].id!, fromSearch: false); } else if (m.searchedChats![index].isFav == true) { m.unFavoriteUser( userID: AppState().chatDetails!.response!.id!, targetUserID: m.searchedChats![index].id!, ); } else { - m.favoriteUser( - userID: AppState().chatDetails!.response!.id!, - targetUserID: m.searchedChats![index].id!, - fromSearch: false - ); + m.favoriteUser(userID: AppState().chatDetails!.response!.id!, targetUserID: m.searchedChats![index].id!, fromSearch: false); } }, ).center @@ -217,9 +199,10 @@ class _ChatHomeScreenState extends State { child: Container( width: 60, height: 60, - decoration: const BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + //shape: BoxShape.circle, + gradient: const LinearGradient( transform: GradientRotation(.46), begin: Alignment.topRight, end: Alignment.bottomLeft, diff --git a/lib/ui/chat/common.dart b/lib/ui/chat/common.dart index e0cb4d0..17e61da 100644 --- a/lib/ui/chat/common.dart +++ b/lib/ui/chat/common.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:math'; import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:flutter/material.dart'; @@ -187,3 +188,40 @@ class WaveBubble extends StatelessWidget { ); } } + +class CallTimer extends StatefulWidget { + const CallTimer({Key? key}) : super(key: key); + + @override + State createState() => _CallTimerState(); +} + +class _CallTimerState extends State { + late Timer _timer; + int _timeExpandedBySeconds = 0; + + @override + void initState() { + // TODO: implement initState + super.initState(); + _timer = Timer.periodic(const Duration(seconds: 1), (Timer timer) { + setState(() { + _timeExpandedBySeconds += 1; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Text( + _timeExpandedBySeconds.toString(), + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, + color: MyColors.white, + letterSpacing: -1.26, + height: 23 / 12, + ), + ); + } +} diff --git a/lib/ui/chat/create_group.dart b/lib/ui/chat/create_group.dart index 43b2635..17f5fc1 100644 --- a/lib/ui/chat/create_group.dart +++ b/lib/ui/chat/create_group.dart @@ -36,7 +36,6 @@ class CreateGroupBottomSheet extends StatefulWidget { Function(ReplacementList) onSelectEmployee; bool fromChat; groups.GroupResponse groupDetails; - CreateGroupBottomSheet({ Key? key, required this.title, @@ -77,7 +76,8 @@ class _CreateGroupBottomSheetState extends State { int _selectedSearchIndex = 0; List selectedUsers = []; - + List addedUser = []; + List removedUser = []; void fetchUserByInput({bool isNeedLoading = true}) async { try { Utils.showLoading(context); @@ -397,6 +397,8 @@ class _CreateGroupBottomSheetState extends State { if (user.isNotEmpty) { user.first.isChecked = false; } + selectedUsers[index2].userAction =2; + removedUser.add(selectedUsers[index2]); selectedUsers.remove( selectedUsers[index2]); }); @@ -535,8 +537,12 @@ class _CreateGroupBottomSheetState extends State { if (provider.chatUsersList![index] .isChecked == true) { + provider + .chatUsersList![index].userAction =1; selectedUsers.add(provider .chatUsersList![index]); + + } else { selectedUsers.remove(provider .chatUsersList![index]); @@ -677,7 +683,7 @@ class _CreateGroupBottomSheetState extends State { admin.totalCount = 0; mainUsers.add(admin); CreateGroupRequest request = CreateGroupRequest( - groupUserList: [...selectedUsers, ...mainUsers].toList(), + groupUserList: [...selectedUsers, ...mainUsers, ...removedUser].toList(), canArchive: false, isMeeting: false, canShareS: isShareScreen, diff --git a/lib/ui/chat/group_chat.dart b/lib/ui/chat/group_chat.dart index 35b4c5c..86a1359 100644 --- a/lib/ui/chat/group_chat.dart +++ b/lib/ui/chat/group_chat.dart @@ -1,28 +1,20 @@ import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.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/config/routes.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/get_group_chat_history.dart'; import 'package:mohem_flutter_app/models/chat/get_user_groups_by_id.dart'; import 'package:mohem_flutter_app/models/worklist/replacement_list_model.dart'; import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; -import 'package:mohem_flutter_app/ui/chat/chat_detailed_screen.dart'; import 'package:mohem_flutter_app/ui/chat/create_group.dart'; import 'package:mohem_flutter_app/ui/chat/group_chat_detaied_screen.dart'; -import 'package:mohem_flutter_app/ui/chat/manage_group.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; -import 'package:mohem_flutter_app/widgets/bottom_sheets/search_employee_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; import 'package:provider/provider.dart'; -import 'package:pull_to_refresh/pull_to_refresh.dart'; class GropChatHomeScreen extends StatefulWidget { const GropChatHomeScreen({Key? key}) : super(key: key); @@ -59,26 +51,17 @@ class _GropChatHomeScreenState extends State { children: [ TextField( controller: m.searchGroup, - style: const TextStyle( - color: MyColors.darkTextColor, - fontWeight: FontWeight.w500, - fontSize: 12), + style: const TextStyle(color: MyColors.darkTextColor, fontWeight: FontWeight.w500, fontSize: 12), onChanged: (String val) { m.filterGroups(val); }, decoration: InputDecoration( border: fieldBorder(radius: 5, color: 0xFFE5E5E5), - focusedBorder: - fieldBorder(radius: 5, color: 0xFFE5E5E5), - enabledBorder: - fieldBorder(radius: 5, color: 0xFFE5E5E5), + focusedBorder: fieldBorder(radius: 5, color: 0xFFE5E5E5), + enabledBorder: fieldBorder(radius: 5, color: 0xFFE5E5E5), contentPadding: const EdgeInsets.all(11), hintText: LocaleKeys.searchGroup.tr(), - hintStyle: const TextStyle( - color: MyColors.lightTextColor, - fontStyle: FontStyle.italic, - fontWeight: FontWeight.w500, - fontSize: 12), + hintStyle: const TextStyle(color: MyColors.lightTextColor, fontStyle: FontStyle.italic, fontWeight: FontWeight.w500, fontSize: 12), filled: true, fillColor: MyColors.greyF7Color, suffixIconConstraints: const BoxConstraints(), @@ -112,70 +95,53 @@ class _GropChatHomeScreenState extends State { padding: const EdgeInsets.all(10.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(24.0), - border: Border.all( - width: 1, color: Colors.black), + border: Border.all(width: 1, color: Colors.black), ), child: SvgPicture.asset( "assets/images/chat-group.svg", )), - Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - (m.uGroups![index] - .groupName! - .toText14( - color: MyColors.darkTextColor) - .paddingOnly(left: 11, top: 16))!, - ]), + Column(mainAxisAlignment: MainAxisAlignment.start, children: [ + (m.uGroups![index].groupName!.toText14(color: MyColors.darkTextColor).paddingOnly(left: 11, top: 16)), + ]), Align( - alignment: Alignment.centerRight, - - child: PopupMenuButton( - onSelected: (String value){ - goToSelected(m.uGroups![index], m, value); - - }, - itemBuilder: (context) => [ - PopupMenuItem( - value: '1', - enabled: m.uGroups![index].isAdmin ?? false, - child: (LocaleKeys.edit - .tr() - .toText14(color: m.userGroups?.groupresponse![index].isAdmin == true ? MyColors.darkTextColor: MyColors.lightGreyColor) - .paddingOnly(left: 11, top: 16))), - PopupMenuItem( - value: '2', - enabled: m.uGroups![index].isAdmin ?? false, - child: (LocaleKeys.delete - .tr() - .toText14(color: m.uGroups![index].isAdmin == true ? MyColors.darkTextColor: MyColors.lightGreyColor) - .paddingOnly(left: 11, top: 16))), - PopupMenuItem( - value: '3', - enabled: m.uGroups![index].isAdmin ?? false, - onTap: () { - - }, - child: (LocaleKeys.manage - .tr() - .toText14(color: m.uGroups![index].isAdmin == true ? MyColors.darkTextColor: MyColors.lightGreyColor) - .paddingOnly(left: 11, top: 16))), - PopupMenuItem( - value: '4', - child: (LocaleKeys.members - .tr() - .toText14(color: MyColors.darkTextColor) - .paddingOnly(left: 11, top: 16))), - ], - ) - - - ) - .expanded + alignment: Alignment.centerRight, + child: PopupMenuButton( + onSelected: (String value) { + goToSelected(m.uGroups![index], m, value); + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: '1', + enabled: m.uGroups![index].isAdmin ?? false, + child: (LocaleKeys.edit + .tr() + .toText14(color: m.userGroups.groupresponse![index].isAdmin == true ? MyColors.darkTextColor : MyColors.lightGreyColor) + .paddingOnly(left: 11, top: 16))), + PopupMenuItem( + value: '2', + enabled: m.uGroups![index].isAdmin ?? false, + child: (LocaleKeys.delete + .tr() + .toText14(color: m.uGroups![index].isAdmin == true ? MyColors.darkTextColor : MyColors.lightGreyColor) + .paddingOnly(left: 11, top: 16))), + PopupMenuItem( + value: '3', + enabled: m.uGroups![index].isAdmin ?? false, + onTap: () {}, + child: (LocaleKeys.manage + .tr() + .toText14(color: m.uGroups![index].isAdmin == true ? MyColors.darkTextColor : MyColors.lightGreyColor) + .paddingOnly(left: 11, top: 16))), + PopupMenuItem(value: '4', child: (LocaleKeys.members.tr().toText14(color: MyColors.darkTextColor).paddingOnly(left: 11, top: 16))), + ], + )).expanded ], ), ).onPress(() { - chatDetails(m.uGroups![index], m,); + chatDetails( + m.uGroups![index], + m, + ); // Navigator.pushNamed( // context, // AppRoutes.chatDetailed, @@ -187,9 +153,7 @@ class _GropChatHomeScreenState extends State { // }); }); }, - separatorBuilder: (BuildContext context, int index) => - const Divider(color: MyColors.black) - .paddingOnly(left: 59), + separatorBuilder: (BuildContext context, int index) => const Divider(color: MyColors.black).paddingOnly(left: 59), ).expanded, ], ).paddingOnly(left: 21, right: 21); @@ -199,9 +163,10 @@ class _GropChatHomeScreenState extends State { child: Container( width: 60, height: 60, - decoration: const BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + // shape: BoxShape.circle, + gradient: const LinearGradient( transform: GradientRotation(.46), begin: Alignment.topRight, end: Alignment.bottomLeft, @@ -218,16 +183,15 @@ class _GropChatHomeScreenState extends State { ), ), onPressed: () async { - showMyBottomSheet( context, callBackFunc: () {}, child: CreateGroupBottomSheet( - title:LocaleKeys.addUsers.tr(), + title: LocaleKeys.addUsers.tr(), apiMode: LocaleKeys.delegate.tr(), fromChat: true, onSelectEmployee: (ReplacementList _selectedEmployee) {}, - groupDetails:GroupResponse(), + groupDetails: GroupResponse(), ), ); }, @@ -244,30 +208,32 @@ class _GropChatHomeScreenState extends State { ); } -void goToSelected(GroupResponse? groupDetails, ChatProviderModel m, String value) { - switch(value) { - case '1': + void goToSelected(GroupResponse? groupDetails, ChatProviderModel m, String value) { + switch (value) { + case '1': editGroup(groupDetails, m); break; - case '2': - deleteGroup(groupDetails, m, context); - break; - case '3': - Navigator.pushNamed(context, + case '2': + deleteGroup(groupDetails, m, context); + break; + case '3': + Navigator.pushNamed( + context, AppRoutes.manageGroup, - arguments: groupDetails , - ); - break; - case '4': - Navigator.pushNamed(context, - AppRoutes.groupMembers, - arguments: groupDetails!.groupUserList, - ); - break; - } + arguments: groupDetails, + ); + break; + case '4': + Navigator.pushNamed( + context, + AppRoutes.groupMembers, + arguments: groupDetails!.groupUserList, + ); + break; + } } - void deleteGroup( - GroupResponse? groupDetails, ChatProviderModel m, BuildContext context) { + + void deleteGroup(GroupResponse? groupDetails, ChatProviderModel m, BuildContext context) { groupDetails!.groupUserList; Utils.confirmDialog( context, @@ -280,28 +246,22 @@ void goToSelected(GroupResponse? groupDetails, ChatProviderModel m, String value } Future chatDetails(GroupResponse? groupDetails, ChatProviderModel m) async { + // await m.getGroupChatHistory(groupDetails!); - // await m.getGroupChatHistory(groupDetails!); - - Navigator.pushNamed(context, - AppRoutes.groupChatDetailed, - arguments: - GroupChatDetailedScreenParams( - groupDetails, - false)); + Navigator.pushNamed(context, AppRoutes.groupChatDetailed, arguments: GroupChatDetailedScreenParams(groupDetails, false)); } + Future editGroup(GroupResponse? groupDetails, ChatProviderModel m) async { showMyBottomSheet( context, callBackFunc: () {}, child: CreateGroupBottomSheet( - title:LocaleKeys.editGroups.tr(), + title: LocaleKeys.editGroups.tr(), apiMode: LocaleKeys.delegate.tr(), fromChat: true, onSelectEmployee: (ReplacementList _selectedEmployee) {}, groupDetails: groupDetails!, ), ); - } } diff --git a/lib/ui/chat/group_chat_bubble.dart b/lib/ui/chat/group_chat_bubble.dart index 69a50df..e16935b 100644 --- a/lib/ui/chat/group_chat_bubble.dart +++ b/lib/ui/chat/group_chat_bubble.dart @@ -1,5 +1,4 @@ import 'dart:io'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -19,13 +18,12 @@ import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.da import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/ui/chat/chat_full_image_preview.dart'; import 'package:mohem_flutter_app/ui/chat/common.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:rxdart/rxdart.dart'; +import 'package:video_player/video_player.dart'; class GroupChatBubble extends StatelessWidget { - GroupChatBubble({Key? key, required this.dateTime, required this.cItem}) - : super(key: key); + GroupChatBubble({Key? key, required this.dateTime, required this.cItem}) : super(key: key); final String dateTime; final GetGroupChatHistoryAsync cItem; @@ -52,27 +50,15 @@ class GroupChatBubble extends StatelessWidget { late Offset screenOffset; void makeAssign() { - isCurrentUser = cItem.currentUserId == AppState().chatDetails!.response!.id - ? true - : false; + isCurrentUser = cItem.currentUserId == AppState().chatDetails!.response!.id ? true : false; isSeen = cItem.isSeen == true ? true : false; isReplied = cItem.groupChatReplyResponse != null ? true : false; // isVoice = cItem.fileTypeId == 13 && cItem.voiceController != null ? true : false; fileTypeID = cItem.fileTypeId; - fileTypeName = cItem.fileTypeResponse != null - ? cItem.fileTypeResponse!.fileTypeName - : ""; - fileTypeDescription = cItem.fileTypeResponse != null - ? cItem.fileTypeResponse!.fileTypeDescription - : ""; - isDelivered = cItem.currentUserId == AppState().chatDetails!.response!.id && - cItem.isDelivered == true - ? true - : false; - userName = AppState().chatDetails!.response!.userName == - cItem.currentUserName.toString() - ? "You" - : cItem.currentUserName.toString(); + fileTypeName = cItem.fileTypeResponse != null ? cItem.fileTypeResponse!.fileTypeName : ""; + fileTypeDescription = cItem.fileTypeResponse != null ? cItem.fileTypeResponse!.fileTypeDescription : ""; + isDelivered = cItem.currentUserId == AppState().chatDetails!.response!.id && cItem.isDelivered == true ? true : false; + userName = AppState().chatDetails!.response!.userName == cItem.currentUserName.toString() ? "You" : cItem.currentUserName.toString(); } void playVoice( @@ -81,8 +67,7 @@ class GroupChatBubble extends StatelessWidget { }) async { if (data.voice != null && data.voice!.existsSync()) { if (Platform.isIOS) { - Duration? duration = await data.voiceController! - .setAudioSource(MyCustomStream(data.voice!.readAsBytesSync())); + Duration? duration = await data.voiceController!.setAudioSource(MyCustomStream(data.voice!.readAsBytesSync())); await data.voiceController!.seek(duration); await data.voiceController!.setLoopMode(LoopMode.off); await data.voiceController!.setVolume(1.0); @@ -97,13 +82,10 @@ class GroupChatBubble extends StatelessWidget { } } else { Utils.showLoading(context); - Uint8List encodedString = await ChatApiClient().downloadURL( - fileName: data.contant!, - fileTypeDescription: provider.getFileTypeDescription( - data.fileTypeResponse!.fileTypeName ?? "")); + Uint8List encodedString = + await ChatApiClient().downloadURL(fileName: data.contant!, fileTypeDescription: provider.getFileTypeDescription(data.fileTypeResponse!.fileTypeName ?? ""), fileSource: 2); // try { - File sFile = await provider.downChatVoice( - encodedString, data.fileTypeResponse!.fileTypeName ?? "", data); + File sFile = await provider.downChatVoice(encodedString, data.fileTypeResponse!.fileTypeName ?? "", data); if (sFile.path.isEmpty) { logger.d("Path Is Emptyyyyyyy"); } else { @@ -112,8 +94,7 @@ class GroupChatBubble extends StatelessWidget { data.voice = sFile; if (Platform.isIOS) { logger.d("isIOS"); - Duration? duration = await data.voiceController! - .setAudioSource(MyCustomStream(data.voice!.readAsBytesSync())); + Duration? duration = await data.voiceController!.setAudioSource(MyCustomStream(data.voice!.readAsBytesSync())); await data.voiceController!.seek(duration); await data.voiceController!.setLoopMode(LoopMode.off); await data.voiceController!.setVolume(1.0); @@ -121,8 +102,7 @@ class GroupChatBubble extends StatelessWidget { Utils.hideLoading(context); data.voiceController!.play(); } else { - Duration? duration = - await data.voiceController!.setFilePath(sFile.path); + Duration? duration = await data.voiceController!.setFilePath(sFile.path); await data.voiceController!.setLoopMode(LoopMode.off); await data.voiceController!.seek(duration); Utils.hideLoading(context); @@ -131,8 +111,7 @@ class GroupChatBubble extends StatelessWidget { } } - void pausePlaying(BuildContext context, - {required SingleUserChatModel data}) async { + void pausePlaying(BuildContext context, {required SingleUserChatModel data}) async { await data.voiceController!.pause(); } @@ -143,14 +122,8 @@ class GroupChatBubble extends StatelessWidget { } } - Stream get _positionDataStream => - Rx.combineLatest3( - cItem.voiceController!.positionStream, - cItem.voiceController!.bufferedPositionStream, - cItem.voiceController!.durationStream, - (Duration position, Duration bufferedPosition, Duration? duration) => - PositionData( - position, bufferedPosition, duration ?? Duration.zero)); + Stream get _positionDataStream => Rx.combineLatest3(cItem.voiceController!.positionStream, cItem.voiceController!.bufferedPositionStream, + cItem.voiceController!.durationStream, (Duration position, Duration bufferedPosition, Duration? duration) => PositionData(position, bufferedPosition, duration ?? Duration.zero)); @override Widget build(BuildContext context) { @@ -172,49 +145,26 @@ class GroupChatBubble extends StatelessWidget { width: double.infinity, decoration: BoxDecoration( border: Border( - left: BorderSide( - width: 6, - color: isCurrentUser - ? MyColors.gradiantStartColor - : MyColors.white), + left: BorderSide(width: 6, color: isCurrentUser ? MyColors.gradiantStartColor : MyColors.white), ), - color: isCurrentUser - ? MyColors.black.withOpacity(0.10) - : MyColors.black.withOpacity(0.30), + color: isCurrentUser ? MyColors.black.withOpacity(0.10) : MyColors.black.withOpacity(0.30), ), child: Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (userName) - .toText12( - color: MyColors.gradiantStartColor, isBold: false) - .paddingOnly(right: 5, top: 5, bottom: 0, left: 5), + (userName).toText12(color: MyColors.gradiantStartColor, isBold: false).paddingOnly(right: 5, top: 5, bottom: 0, left: 5), Directionality( - textDirection: provider.getTextDirection( - cItem.groupChatReplyResponse != null - ? cItem.groupChatReplyResponse!.contant - .toString() - : ""), - child: (cItem.groupChatReplyResponse != null - ? cItem.groupChatReplyResponse!.contant - .toString() - : "") - .toText10( - color: isCurrentUser - ? MyColors.grey71Color - : MyColors.white.withOpacity(0.5), - isBold: false, - maxlines: 4) + textDirection: provider.getTextDirection(cItem.groupChatReplyResponse != null ? cItem.groupChatReplyResponse!.contant.toString() : ""), + child: (cItem.groupChatReplyResponse != null ? cItem.groupChatReplyResponse!.contant.toString() : "") + .toText10(color: isCurrentUser ? MyColors.grey71Color : MyColors.white.withOpacity(0.5), isBold: false, maxlines: 4) .paddingOnly(right: 5, top: 5, bottom: 8, left: 5), ), ], ).expanded, if (cItem.groupChatReplyResponse != null) - if (cItem.groupChatReplyResponse!.fileTypeId == 12 || - cItem.groupChatReplyResponse!.fileTypeId == 3 || - cItem.groupChatReplyResponse!.fileTypeId == 4) + if (cItem.groupChatReplyResponse!.fileTypeId == 12 || cItem.groupChatReplyResponse!.fileTypeId == 3 || cItem.groupChatReplyResponse!.fileTypeId == 4) ClipRRect( borderRadius: BorderRadius.circular(8.0), child: SizedBox( @@ -222,13 +172,8 @@ class GroupChatBubble extends StatelessWidget { width: 32, child: showImage( isReplyPreview: false, - fileName: - cItem.groupChatReplyResponse!.contant!, - fileTypeDescription: cItem - .groupChatReplyResponse! - .fileTypeResponse! - .fileTypeDescription ?? - "image/jpg")), + fileName: cItem.groupChatReplyResponse!.contant!, + fileTypeDescription: cItem.groupChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg")), ).paddingOnly(left: 10, right: 10, bottom: 16, top: 16), ], ), @@ -246,46 +191,35 @@ class GroupChatBubble extends StatelessWidget { isReplyPreview: false, fileName: cItem.contant!, fileTypeDescription: - cItem.fileTypeResponse!.fileTypeDescription) + cItem.fileTypeResponse != null && cItem.fileTypeResponse!.fileTypeDescription != null ? cItem.fileTypeResponse!.fileTypeDescription : cItem.fileTypeResponse!.fileTypeName) .onPress(() { showDialog( context: context, anchorPoint: screenOffset, - builder: (BuildContext context) => ChatImagePreviewScreen( - imgTitle: cItem.contant!, img: cItem.image!), + builder: (BuildContext context) => ChatImagePreviewScreen(imgTitle: cItem.contant!, img: cItem.image!), ); }), ), ).paddingOnly(bottom: 4), - if (fileTypeID == 13 && cItem.voiceController != null) - currentWaveBubble(context, cItem) + if (fileTypeID == 13 && cItem.voiceController != null) currentWaveBubble(context, cItem), + if (fileTypeID == 16) + Column( + children: [ + showVideoThumb(context, cItem), + Row( + children: [ + Directionality(textDirection: provider.getTextDirection(cItem.contant ?? ""), child: (cItem.contant ?? "").toText12().expanded), + ], + ), + ], + ) else Row( children: [ - if (fileTypeID == 1 || - fileTypeID == 5 || - fileTypeID == 7 || - fileTypeID == 6 || - fileTypeID == 8 - // || fileTypeID == 2 - ) - SvgPicture.asset(provider.getType(fileTypeName ?? ""), - height: 30, - width: 22, - alignment: Alignment.center, - fit: BoxFit.cover) - .paddingOnly(left: 0, right: 10), - Directionality( - textDirection: provider.getTextDirection(cItem.contant ?? ""), - child: (cItem.contant ?? "").toText12().expanded), - if (fileTypeID == 1 || - fileTypeID == 5 || - fileTypeID == 7 || - fileTypeID == 6 || - fileTypeID == 8 - //|| fileTypeID == 2 - ) - const Icon(Icons.remove_red_eye, size: 16) + if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8) + SvgPicture.asset(provider.getType(fileTypeName ?? ""), height: 30, width: 22, alignment: Alignment.center, fit: BoxFit.cover).paddingOnly(left: 0, right: 10), + Directionality(textDirection: provider.getTextDirection(cItem.contant ?? ""), child: (cItem.contant ?? "").toText12().expanded), + if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8) const Icon(Icons.remove_red_eye, size: 16) ], ), Align( @@ -297,32 +231,24 @@ class GroupChatBubble extends StatelessWidget { color: MyColors.grey41Color.withOpacity(.5), ), 7.width, - Icon(isDelivered ? Icons.done_all : Icons.done_all, - color: isSeen ? MyColors.textMixColor : MyColors.grey9DColor, - size: 14), + Icon(isDelivered ? Icons.done_all : Icons.done_all, color: isSeen ? MyColors.textMixColor : MyColors.grey9DColor, size: 14), ], ), ), ], - ) - .paddingOnly(top: 11, left: 13, right: 13, bottom: 5) - .objectContainerView(disablePadding: true) - .paddingOnly(left: MediaQuery.of(context).size.width * 0.3); + ).paddingOnly(top: 11, left: 13, right: 13, bottom: 5).objectContainerView(disablePadding: true).paddingOnly(left: MediaQuery.of(context).size.width * 0.3); } Widget receiptUser(BuildContext context) { return Container( - padding: const EdgeInsets.only(top: 5, left: 8, right: 13, bottom: 5), + padding: const EdgeInsets.only(top: 11, left: 13, right: 13, bottom: 5), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), gradient: const LinearGradient( transform: GradientRotation(.83), begin: Alignment.topRight, end: Alignment.bottomLeft, - colors: [ - MyColors.gradiantEndColor, - MyColors.gradiantStartColor - ], + colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor], ), ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -332,51 +258,25 @@ class GroupChatBubble extends StatelessWidget { child: Container( width: double.infinity, decoration: BoxDecoration( - border: Border( - left: BorderSide( - width: 6, - color: isCurrentUser - ? MyColors.gradiantStartColor - : MyColors.white)), - color: isCurrentUser - ? MyColors.black.withOpacity(0.10) - : MyColors.black.withOpacity(0.30), + border: Border(left: BorderSide(width: 6, color: isCurrentUser ? MyColors.gradiantStartColor : MyColors.white)), + color: isCurrentUser ? MyColors.black.withOpacity(0.10) : MyColors.black.withOpacity(0.30), ), child: Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (userName) - .toText12( - color: MyColors.gradiantStartColor, - isBold: false) - .paddingOnly(right: 5, top: 5, bottom: 0, left: 5), + (userName).toText12(color: MyColors.gradiantStartColor, isBold: false).paddingOnly(right: 5, top: 5, bottom: 0, left: 5), Directionality( - textDirection: provider.getTextDirection( - cItem.groupChatReplyResponse != null - ? cItem.groupChatReplyResponse!.contant - .toString() - : ""), - child: (cItem.groupChatReplyResponse != null - ? cItem.groupChatReplyResponse!.contant - .toString() - : "") - .toText10( - color: isCurrentUser - ? MyColors.grey71Color - : MyColors.white.withOpacity(0.5), - isBold: false, - maxlines: 4) - .paddingOnly( - right: 5, top: 5, bottom: 8, left: 5), + textDirection: provider.getTextDirection(cItem.groupChatReplyResponse != null ? cItem.groupChatReplyResponse!.contant.toString() : ""), + child: (cItem.groupChatReplyResponse != null ? cItem.groupChatReplyResponse!.contant.toString() : "") + .toText10(color: isCurrentUser ? MyColors.grey71Color : MyColors.white.withOpacity(0.5), isBold: false, maxlines: 4) + .paddingOnly(right: 5, top: 5, bottom: 8, left: 5), ), ], ).expanded, if (cItem.groupChatReplyResponse != null) - if (cItem.groupChatReplyResponse!.fileTypeId == 12 || - cItem.groupChatReplyResponse!.fileTypeId == 3 || - cItem.groupChatReplyResponse!.fileTypeId == 4) + if (cItem.groupChatReplyResponse!.fileTypeId == 12 || cItem.groupChatReplyResponse!.fileTypeId == 3 || cItem.groupChatReplyResponse!.fileTypeId == 4) ClipRRect( borderRadius: BorderRadius.circular(8.0), child: SizedBox( @@ -384,13 +284,8 @@ class GroupChatBubble extends StatelessWidget { width: 32, child: showImage( isReplyPreview: true, - fileName: - cItem.groupChatReplyResponse!.contant!, - fileTypeDescription: cItem - .groupChatReplyResponse! - .fileTypeResponse! - .fileTypeDescription ?? - "image/jpg"), + fileName: cItem.groupChatReplyResponse!.contant!, + fileTypeDescription: cItem.groupChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg"), ), ).paddingOnly(left: 10, right: 10, bottom: 16, top: 16) ], @@ -405,82 +300,55 @@ class GroupChatBubble extends StatelessWidget { child: SizedBox( height: 140, width: 227, - child: showImage( - isReplyPreview: false, - fileName: cItem.contant ?? "", - fileTypeDescription: - cItem.fileTypeResponse!.fileTypeDescription ?? - "image/jpg") - .onPress(() { + child: showImage(isReplyPreview: false, fileName: cItem.contant ?? "", fileTypeDescription: cItem.fileTypeResponse!.fileTypeDescription ?? "image/jpg").onPress(() { showDialog( context: context, anchorPoint: screenOffset, - builder: (BuildContext context) => ChatImagePreviewScreen( - imgTitle: cItem.contant ?? "", img: cItem.image!), + builder: (BuildContext context) => ChatImagePreviewScreen(imgTitle: cItem.contant ?? "", img: cItem.image!), ); }), ), ).paddingOnly(bottom: 4), - if (fileTypeID == 13 && cItem.voiceController != null) - recipetWaveBubble(context, cItem) - else + if (fileTypeID == 13 && cItem.voiceController != null) recipetWaveBubble(context, cItem), + if (fileTypeID == 16) Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - cItem.currentUserName!.toText10( - color: Colors.black, - ).paddingOnly(bottom: 5), + showVideoThumb(context, cItem), Row( children: [ - if (fileTypeID == 1 || - fileTypeID == 5 || - fileTypeID == 7 || - fileTypeID == 6 || - fileTypeID == 8 + Directionality(textDirection: provider.getTextDirection(cItem.contant ?? ""), child: (cItem.contant ?? "").toText12(color: Colors.white).expanded), + ], + ), + ], + ) + else + Row( + children: [ + if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8 // || fileTypeID == 2 ) - SvgPicture.asset(provider.getType(fileTypeName ?? ""), - height: 30, - width: 22, - alignment: Alignment.center, - fit: BoxFit.cover) - .paddingOnly(left: 0, right: 10), - Directionality( - textDirection: - provider.getTextDirection(cItem.contant ?? ""), - child: (cItem.contant ?? "") - .toText12(color: Colors.white) - .expanded), - if (fileTypeID == 1 || - fileTypeID == 5 || - fileTypeID == 7 || - fileTypeID == 6 || - fileTypeID == 8 + SvgPicture.asset(provider.getType(fileTypeName ?? ""), height: 30, width: 22, alignment: Alignment.center, fit: BoxFit.cover).paddingOnly(left: 0, right: 10), + Directionality(textDirection: provider.getTextDirection(cItem.contant ?? ""), child: (cItem.contant ?? "").toText12(color: Colors.white).expanded), + if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8 //|| fileTypeID == 2 ) - const Icon(Icons.remove_red_eye, - color: Colors.white, size: 16) - ], - ), - Align( - alignment: Alignment.topRight, - child: dateTime.toText10( - color: Colors.white.withOpacity(.71), - ).paddingOnly(top:5), - ), + const Icon(Icons.remove_red_eye, color: Colors.white, size: 16) ], ), - ])).paddingOnly(right: MediaQuery.of(context).size.width * 0.3); + Align( + alignment: Alignment.centerRight, + child: dateTime.toText10( + color: Colors.white.withOpacity(.71), + ), + ), + ])).paddingOnly(right: MediaQuery.of(context).size.width * 0.33); } Widget voiceMsg(BuildContext context) { return Container(); } - Widget showImage( - {required bool isReplyPreview, - required String fileName, - required String fileTypeDescription}) { + Widget showImage({required bool isReplyPreview, required String fileName, required String fileTypeDescription}) { if (cItem.isImageLoaded != null && cItem.image != null) { return Image.memory( cItem.image!, @@ -491,8 +359,7 @@ class GroupChatBubble extends StatelessWidget { ); } else { return FutureBuilder( - future: ChatApiClient().downloadURL( - fileName: fileName, fileTypeDescription: fileTypeDescription), + future: ChatApiClient().downloadURL(fileName: fileName, fileTypeDescription: fileTypeDescription, fileSource: 2), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState != ConnectionState.waiting) { if (snapshot.data == null) { @@ -519,20 +386,14 @@ class GroupChatBubble extends StatelessWidget { } } - Widget currentWaveBubble( - BuildContext context, GetGroupChatHistoryAsync data) { + Widget currentWaveBubble(BuildContext context, GetGroupChatHistoryAsync data) { return Container( margin: const EdgeInsets.all(0), decoration: BoxDecoration( border: Border( - left: BorderSide( - width: 6, - color: - isCurrentUser ? MyColors.gradiantStartColor : MyColors.white), + left: BorderSide(width: 6, color: isCurrentUser ? MyColors.gradiantStartColor : MyColors.white), ), - color: isCurrentUser - ? MyColors.black.withOpacity(0.10) - : MyColors.black.withOpacity(0.30), + color: isCurrentUser ? MyColors.black.withOpacity(0.10) : MyColors.black.withOpacity(0.30), ), child: Row( children: [ @@ -540,14 +401,12 @@ class GroupChatBubble extends StatelessWidget { // getPlayer(player: data.voiceController!, modelData: data), StreamBuilder( stream: _positionDataStream, - builder: - (BuildContext context, AsyncSnapshot snapshot) { + builder: (BuildContext context, AsyncSnapshot snapshot) { PositionData? positionData = snapshot.data; return SeekBar( duration: positionData?.duration ?? Duration.zero, position: positionData?.position ?? Duration.zero, - bufferedPosition: - positionData?.bufferedPosition ?? Duration.zero, + bufferedPosition: positionData?.bufferedPosition ?? Duration.zero, onChangeEnd: data.voiceController!.seek, ).expanded; }, @@ -557,20 +416,18 @@ class GroupChatBubble extends StatelessWidget { ).circle(5); } - Widget recipetWaveBubble( - BuildContext context, GetGroupChatHistoryAsync data) { + Widget showVideoThumb(BuildContext context, GetGroupChatHistoryAsync data) { + return LoadVideo(data: data); + } + + Widget recipetWaveBubble(BuildContext context, GetGroupChatHistoryAsync data) { return Container( margin: const EdgeInsets.all(0), decoration: BoxDecoration( border: Border( - left: BorderSide( - width: 6, - color: - isCurrentUser ? MyColors.gradiantStartColor : MyColors.white), + left: BorderSide(width: 6, color: isCurrentUser ? MyColors.gradiantStartColor : MyColors.white), ), - color: isCurrentUser - ? MyColors.black.withOpacity(0.10) - : MyColors.black.withOpacity(0.30), + color: isCurrentUser ? MyColors.black.withOpacity(0.10) : MyColors.black.withOpacity(0.30), ), child: Row( mainAxisSize: MainAxisSize.max, @@ -579,14 +436,12 @@ class GroupChatBubble extends StatelessWidget { //getPlayer(player: data.voiceController!, modelData: data), StreamBuilder( stream: _positionDataStream, - builder: - (BuildContext context, AsyncSnapshot snapshot) { + builder: (BuildContext context, AsyncSnapshot snapshot) { PositionData? positionData = snapshot.data; return SeekBar( duration: positionData?.duration ?? Duration.zero, position: positionData?.position ?? Duration.zero, - bufferedPosition: - positionData?.bufferedPosition ?? Duration.zero, + bufferedPosition: positionData?.bufferedPosition ?? Duration.zero, onChangeEnd: data.voiceController!.seek, ).expanded; }, @@ -596,16 +451,14 @@ class GroupChatBubble extends StatelessWidget { ).circle(5); } - Widget getPlayer( - {required AudioPlayer player, required SingleUserChatModel modelData}) { + Widget getPlayer({required AudioPlayer player, required SingleUserChatModel modelData}) { return StreamBuilder( stream: player.playerStateStream, builder: (BuildContext context, AsyncSnapshot snapshot) { PlayerState? playerState = snapshot.data; ProcessingState? processingState = playerState?.processingState; bool? playing = playerState?.playing; - if (processingState == ProcessingState.loading || - processingState == ProcessingState.buffering) { + if (processingState == ProcessingState.loading || processingState == ProcessingState.buffering) { return Container( margin: const EdgeInsets.all(8.0), width: 30.0, @@ -641,3 +494,45 @@ class GroupChatBubble extends StatelessWidget { ); } } + +class LoadVideo extends StatefulWidget { + final GetGroupChatHistoryAsync data; + + const LoadVideo({Key? key, required this.data}) : super(key: key); + + @override + State createState() => _LoadVideoState(); +} + +class _LoadVideoState extends State { + late VideoPlayerController videoController; + + @override + void initState() { + videoController = VideoPlayerController.networkUrl(Uri.parse('https://apiderichat.hmg.com/groupattachments/${widget.data.fileTypeResponse?.fileName}'))..initialize().then((_) {}); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(5.0), + child: AspectRatio( + aspectRatio: videoController.value.aspectRatio, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + VideoPlayer(videoController), + Align( + alignment: Alignment.center, + child: Icon( + Icons.play_arrow, + color: Colors.white.withOpacity(.7), + size: 56, + ), + ) + ], + ), + )); + } +} diff --git a/lib/ui/chat/group_chat_detaied_screen.dart b/lib/ui/chat/group_chat_detaied_screen.dart index 84ae3bd..19992bb 100644 --- a/lib/ui/chat/group_chat_detaied_screen.dart +++ b/lib/ui/chat/group_chat_detaied_screen.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; + import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -11,24 +12,17 @@ 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/main.dart'; -import 'package:mohem_flutter_app/models/chat/call.dart'; import 'package:mohem_flutter_app/models/chat/get_group_chat_history.dart'; -import 'package:mohem_flutter_app/models/chat/get_search_user_chat_model.dart'; -import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.dart'; import 'package:mohem_flutter_app/models/chat/get_user_groups_by_id.dart'; -import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.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/chat/custom_auto_direction.dart'; -import 'package:mohem_flutter_app/ui/chat/call/chat_outgoing_call_screen.dart'; -import 'package:mohem_flutter_app/ui/chat/chat_bubble.dart'; import 'package:mohem_flutter_app/ui/chat/common.dart'; +import 'package:mohem_flutter_app/ui/chat/custom_auto_direction.dart'; import 'package:mohem_flutter_app/ui/chat/group_chat_bubble.dart'; import 'package:mohem_flutter_app/widgets/chat_app_bar_widge.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; import 'package:provider/provider.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; -import 'package:signalr_netcore/signalr_client.dart'; import 'package:swipe_to/swipe_to.dart'; class GroupChatDetailedScreenParams { @@ -58,11 +52,11 @@ class _GroupChatDetailScreenState extends State { data.paginationVal = data.paginationVal + 10; if (params != null) { data.getGroupChatHistory(params!.groupChatDetails! - // senderUID: AppState().chatDetails!.response!.id!.toInt(), - // receiverUID: params!.groupChatDetails!.groupId!, - // loadMore: true, - // isNewChat: false, - ); + // senderUID: AppState().chatDetails!.response!.id!.toInt(), + // receiverUID: params!.groupChatDetails!.groupId!, + // loadMore: true, + // isNewChat: false, + ); } } await Future.delayed( @@ -83,13 +77,12 @@ class _GroupChatDetailScreenState extends State { data = Provider.of(context, listen: false); // callPro = Provider.of(context, listen: false); if (params != null) { - data.getGroupChatHistory( - params!.groupChatDetails! - // senderUID: AppState().chatDetails!.response!.id!.toInt(), - // receiverUID: params!.groupChatHistory!.groupId!, - // loadMore: false, - // isNewChat: params!.isNewChat!, - ); + data.getGroupChatHistory(params!.groupChatDetails! + // senderUID: AppState().chatDetails!.response!.id!.toInt(), + // receiverUID: params!.groupChatHistory!.groupId!, + // loadMore: false, + // isNewChat: params!.isNewChat!, + ); data.initAudio(receiverId: params!.groupChatDetails!.groupId!); } @@ -99,8 +92,8 @@ class _GroupChatDetailScreenState extends State { context, title: params!.groupChatDetails!.groupName.toString().replaceAll(".", " ").capitalizeFirstofEach, showHomeButton: false, - // showTyping: true, - // chatUser: params!.groupChatHistory!.groupChatHistoryTargetUserList as ChatUser, + // showTyping: true, + // chatUser: params!.groupChatHistory!.groupChatHistoryTargetUserList as ChatUser, actions: [ // SvgPicture.asset("assets/icons/chat/call.svg", width: 21, height: 23).onPress(() { // makeCall(callType: "AUDIO"); @@ -117,224 +110,222 @@ class _GroupChatDetailScreenState extends State { builder: (BuildContext context, ChatProviderModel m, Widget? child) { return (m.isLoading ? ChatHomeShimmer( - isDetailedScreen: true, - ) + isDetailedScreen: true, + ) : Column( - children: [ - SmartRefresher( - enablePullDown: false, - enablePullUp: true, - onLoading: () { - getMoreChat(); - }, - header: const MaterialClassicHeader( - color: MyColors.gradiantEndColor, - ), - controller: _rc, - reverse: true, - child: ListView.separated( - controller: m.scrollController, - shrinkWrap: true, - physics: const BouncingScrollPhysics(), - reverse: true, - itemCount: m.groupChatHistory.length, - padding: const EdgeInsets.all(21), - separatorBuilder: (BuildContext cxt, int index) => 8.height, - itemBuilder: (BuildContext context, int i) { - return SwipeTo( - iconColor: MyColors.lightGreenColor, - child: GroupChatBubble( - dateTime: m.groupChatHistory[i].createdDate!, - cItem: m.groupChatHistory[i], - ), - onRightSwipe: (val) { - m.groupChatReply( - m.groupChatHistory[i], - ); + children: [ + SmartRefresher( + enablePullDown: false, + enablePullUp: true, + onLoading: () { + getMoreChat(); }, - ).onPress(() async { - logger.w(m.userChatHistory[i].toJson()); - if (m.userChatHistory[i].fileTypeResponse != null && m.userChatHistory[i].fileTypeId != null) { - if (m.userChatHistory[i].fileTypeId! == 1 || - m.userChatHistory[i].fileTypeId! == 5 || - m.userChatHistory[i].fileTypeId! == 7 || - m.userChatHistory[i].fileTypeId! == 6 || - m.userChatHistory[i].fileTypeId! == 8 - // || m.userChatHistory[i].fileTypeId! == 2 - ) { - m.getChatMedia(context, - fileTypeName: m.userChatHistory[i].fileTypeResponse!.fileTypeName ?? "", fileTypeID: m.userChatHistory[i].fileTypeId!, fileName: m.userChatHistory[i].contant!); - } - } - }); - }, - ), - ).expanded, - if (m.isReplyMsg) - SizedBox( - height: 82, - child: Row( - children: [ - Container(height: 82, color: MyColors.textMixColor, width: 6), - Container( - color: MyColors.darkTextColor.withOpacity(0.10), - padding: const EdgeInsets.only(top: 11, left: 14, bottom: 14, right: 21), + header: const MaterialClassicHeader( + color: MyColors.gradiantEndColor, + ), + controller: _rc, + reverse: true, + child: ListView.separated( + controller: m.scrollController, + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + reverse: true, + itemCount: m.groupChatHistory.length, + padding: const EdgeInsets.all(21), + separatorBuilder: (BuildContext cxt, int index) => 8.height, + itemBuilder: (BuildContext context, int i) { + return SwipeTo( + iconColor: MyColors.lightGreenColor, + child: GroupChatBubble( + dateTime: m.groupChatHistory[i].createdDate!, + cItem: m.groupChatHistory[i], + ), + onRightSwipe: (val) { + m.groupChatReply( + m.groupChatHistory[i], + ); + }, + ).onPress(() async { + logger.w(m.groupChatHistory[i].toJson()); + if (m.groupChatHistory[i].fileTypeResponse != null && m.groupChatHistory[i].fileTypeId != null) { + if (m.groupChatHistory[i].fileTypeId! == 1 || + m.groupChatHistory[i].fileTypeId! == 5 || + m.groupChatHistory[i].fileTypeId! == 7 || + m.groupChatHistory[i].fileTypeId! == 6 || + m.groupChatHistory[i].fileTypeId! == 8 || + m.groupChatHistory[i].fileTypeId! == 16) { + m.getChatMedia(context, + fileTypeName: m.groupChatHistory[i].fileTypeResponse!.fileTypeName ?? "", + fileTypeID: m.groupChatHistory[i].fileTypeId!, + fileName: m.groupChatHistory[i].contant!, + fileSource: 2); + } + } + }); + }, + ), + ).expanded, + if (m.isReplyMsg) + SizedBox( + height: 82, child: Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - (AppState().chatDetails!.response!.userName == m.groupChatReplyData.first.currentUserName.toString() - ? "You" - : m.groupChatReplyData.first.currentUserName.toString().replaceAll(".", " ")) - .toText14(color: MyColors.lightGreenColor), - (m.groupChatReplyData.isNotEmpty ? m.groupChatReplyData.first.contant! : "").toText12(color: MyColors.grey71Color, maxLine: 2) - ], + children: [ + Container(height: 82, color: MyColors.textMixColor, width: 6), + Container( + color: MyColors.darkTextColor.withOpacity(0.10), + padding: const EdgeInsets.only(top: 11, left: 14, bottom: 14, right: 21), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (AppState().chatDetails!.response!.userName == m.groupChatReplyData.first.currentUserName.toString() + ? "You" + : m.groupChatReplyData.first.currentUserName.toString().replaceAll(".", " ")) + .toText14(color: MyColors.lightGreenColor), + (m.groupChatReplyData.isNotEmpty ? m.groupChatReplyData.first.contant! : "").toText12(color: MyColors.grey71Color, maxLine: 2) + ], + ).expanded, + 12.width, + if (m.isReplyMsg && m.groupChatReplyData.isNotEmpty) showReplyImage(m.groupChatReplyData, m), + 12.width, + const Icon(Icons.cancel, size: 23, color: MyColors.grey7BColor).onPress(m.closeMe), + ], + ), ).expanded, - 12.width, - if (m.isReplyMsg && m.groupChatReplyData.isNotEmpty) showReplyImage(m.groupChatReplyData, m), - 12.width, - const Icon(Icons.cancel, size: 23, color: MyColors.grey7BColor).onPress(m.closeMe), ], ), - ).expanded, - ], - ), - ), - if (m.isAttachmentMsg && m.sFileType == ".png" || m.sFileType == ".jpeg" || m.sFileType == ".jpg") - SizedBox(height: 200, width: double.infinity, child: Image.file(m.selectedFile, fit: BoxFit.cover)).paddingOnly(left: 21, right: 21, top: 21), - const Divider(height: 1, color: MyColors.lightGreyEFColor), - if (m.isRecoding) - Column( - children: [ - Row( - children: [ - Text(m.buildTimer()).paddingAll(10), - if (m.isRecoding && m.isPlaying) - WaveBubble( - playerController: m.playerController, - isPlaying: m.playerController.playerState == PlayerState.playing, - onTap: () {}, - ).expanded - else - AudioWaveforms( - waveStyle: const WaveStyle( - waveColor: MyColors.lightGreenColor, - middleLineColor: Colors.transparent, - extendWaveform: true, - showBottom: true, - showTop: true, - waveThickness: 2, - showMiddleLine: false, - middleLineThickness: 0, - ), - padding: const EdgeInsets.all(5), - shouldCalculateScrolledPosition: false, - margin: EdgeInsets.zero, - size: const Size(double.infinity, 30.0), - recorderController: m.recorderController, - backgroundColor: Colors.white, - ).expanded, - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Icon( - Icons.delete_outlined, - size: 26, - color: MyColors.lightGreenColor, - ).paddingAll(10).onPress(() { - m.deleteRecoding(); - }), - SvgPicture.asset("assets/icons/chat/chat_send_icon.svg", height: 26, width: 26) - .onPress( - () => m.sendGroupChatMessage(context, - targetUserId: params!.groupChatDetails!.groupId!, - userStatus: 0, - userEmail: "", - targetUserName: params!.groupChatDetails!.groupName!, - userList: params!.groupChatDetails!.groupUserList! - ), - ) - .paddingOnly(right: 21), - ], - ), - ], - ).objectContainerView(disablePadding: true, radius: 0), - if (!m.isRecoding) - Row( - children: [ - CustomAutoDirection( - onDirectionChange: (bool isRTL) => m.onDirectionChange(isRTL), - text: m.msgText, - child: TextField( - // textDirection: m.textDirection, - controller: m.message, - decoration: InputDecoration( - hintTextDirection: m.textDirection, - hintText: m.isAttachmentMsg ? m.selectedFile.path.split("/").last : LocaleKeys.typeheretoreply.tr(), - hintStyle: TextStyle(color: m.isAttachmentMsg ? MyColors.darkTextColor : MyColors.grey98Color, fontSize: 14), - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - disabledBorder: InputBorder.none, - filled: true, - fillColor: MyColors.white, - contentPadding: const EdgeInsets.only( - left: 21, - top: 20, - bottom: 20, - ), - prefixIconConstraints: const BoxConstraints(), - prefixIcon: m.sFileType.isNotEmpty - ? SvgPicture.asset(m.getType(m.sFileType), height: 30, width: 22, alignment: Alignment.center, fit: BoxFit.cover).paddingOnly(left: 21, right: 15) - : null, - ), - onChanged: (String val) { - m.inputBoxDirection(val); - m.groupTypingInvoke(groupDetails: params!.groupChatDetails!, groupId: params!.groupChatDetails!.groupId!); - }, - ).expanded, - ), - if (m.sFileType.isNotEmpty) - Row( + ), + if (m.isAttachmentMsg && m.sFileType == ".png" || m.sFileType == ".jpeg" || m.sFileType == ".jpg") + SizedBox(height: 200, width: double.infinity, child: Image.file(m.selectedFile, fit: BoxFit.cover)).paddingOnly(left: 21, right: 21, top: 21), + const Divider(height: 1, color: MyColors.lightGreyEFColor), + if (m.isRecoding) + Column( children: [ - const Icon(Icons.cancel, size: 15, color: MyColors.redA3Color).paddingOnly(right: 5), - ("Clear").toText11(color: MyColors.redA3Color, isUnderLine: true).paddingOnly(left: 0), + Row( + children: [ + Text(m.buildTimer()).paddingAll(10), + if (m.isRecoding && m.isPlaying) + WaveBubble( + playerController: m.playerController, + isPlaying: m.playerController.playerState == PlayerState.playing, + onTap: () {}, + ).expanded + else + AudioWaveforms( + waveStyle: const WaveStyle( + waveColor: MyColors.lightGreenColor, + middleLineColor: Colors.transparent, + extendWaveform: true, + showBottom: true, + showTop: true, + waveThickness: 2, + showMiddleLine: false, + middleLineThickness: 0, + ), + padding: const EdgeInsets.all(5), + shouldCalculateScrolledPosition: false, + margin: EdgeInsets.zero, + size: const Size(double.infinity, 30.0), + recorderController: m.recorderController, + backgroundColor: Colors.white, + ).expanded, + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Icon( + Icons.delete_outlined, + size: 26, + color: MyColors.lightGreenColor, + ).paddingAll(10).onPress(() { + m.deleteRecoding(); + }), + SvgPicture.asset("assets/icons/chat/chat_send_icon.svg", height: 26, width: 26) + .onPress( + () => m.sendGroupChatMessage(context, + targetUserId: params!.groupChatDetails!.groupId!, + userStatus: 0, + userEmail: "", + targetUserName: params!.groupChatDetails!.groupName!, + userList: params!.groupChatDetails!.groupUserList!), + ) + .paddingOnly(right: 21), + ], + ), ], - ).onPress(() => m.removeAttachment()).paddingOnly(right: 15), - if (m.sFileType.isEmpty) - RotationTransition( - turns: const AlwaysStoppedAnimation(45 / 360), - child: const Icon(Icons.attach_file_rounded, size: 26, color: MyColors.grey3AColor).onPress( - () => { - m.selectImageToUpload(context) + ).objectContainerView(disablePadding: true, radius: 0), + if (!m.isRecoding) + Row( + children: [ + CustomAutoDirection( + onDirectionChange: (bool isRTL) => m.onDirectionChange(isRTL), + text: m.msgText, + child: TextField( + // textDirection: m.textDirection, + controller: m.message, + decoration: InputDecoration( + hintTextDirection: m.textDirection, + hintText: m.isAttachmentMsg ? m.selectedFile.path.split("/").last : LocaleKeys.typeheretoreply.tr(), + hintStyle: TextStyle(color: m.isAttachmentMsg ? MyColors.darkTextColor : MyColors.grey98Color, fontSize: 14), + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + filled: true, + fillColor: MyColors.white, + contentPadding: const EdgeInsets.only( + left: 21, + top: 20, + bottom: 20, + ), + prefixIconConstraints: const BoxConstraints(), + prefixIcon: m.sFileType.isNotEmpty + ? SvgPicture.asset(m.getType(m.sFileType), height: 30, width: 22, alignment: Alignment.center, fit: BoxFit.cover).paddingOnly(left: 21, right: 15) + : null, + ), + onChanged: (String val) { + m.inputBoxDirection(val); + m.groupTypingInvoke(groupDetails: params!.groupChatDetails!, groupId: params!.groupChatDetails!.groupId!); }, - ), - ).paddingOnly(right: 15), - const Icon( - Icons.mic, - color: MyColors.lightGreenColor, - ).paddingOnly(right: 15).onPress(() { - m.startRecoding(context); - }), - SvgPicture.asset("assets/icons/chat/chat_send_icon.svg", height: 26, width: 26) - .onPress( - () =>m.sendGroupChatMessage(context, - targetUserId: params!.groupChatDetails!.groupId!, - userStatus: 0, - userEmail: "", - targetUserName: params!.groupChatDetails!.groupName!, - userList: params!.groupChatDetails!.groupUserList! + ).expanded, ), - ) - .paddingOnly(right: 21), + if (m.sFileType.isNotEmpty) + Row( + children: [ + const Icon(Icons.cancel, size: 15, color: MyColors.redA3Color).paddingOnly(right: 5), + ("Clear").toText11(color: MyColors.redA3Color, isUnderLine: true).paddingOnly(left: 0), + ], + ).onPress(() => m.removeAttachment()).paddingOnly(right: 15), + if (m.sFileType.isEmpty) + RotationTransition( + turns: const AlwaysStoppedAnimation(45 / 360), + child: const Icon(Icons.attach_file_rounded, size: 26, color: MyColors.grey3AColor).onPress( + () => {m.selectImageToUpload(context)}, + ), + ).paddingOnly(right: 15), + const Icon( + Icons.mic, + color: MyColors.lightGreenColor, + ).paddingOnly(right: 15).onPress(() { + m.startRecoding(context); + }), + SvgPicture.asset("assets/icons/chat/chat_send_icon.svg", height: 26, width: 26) + .onPress( + () => m.sendGroupChatMessage(context, + targetUserId: params!.groupChatDetails!.groupId!, + userStatus: 0, + userEmail: "", + targetUserName: params!.groupChatDetails!.groupName!, + userList: params!.groupChatDetails!.groupUserList!), + ) + .paddingOnly(right: 21), + ], + ).objectContainerView(disablePadding: true, radius: 0), ], - ).objectContainerView(disablePadding: true, radius: 0), - ], - )); + )); }, ), ), @@ -352,17 +343,17 @@ class _GroupChatDetailScreenState extends State { } else { return data.first.fileTypeResponse != null && data.first.fileTypeResponse!.fileTypeName != null ? Container( - width: 43, - height: 43, - constraints: const BoxConstraints(), - decoration: BoxDecoration(border: Border.all(color: MyColors.darkGrey3BColor, width: 1), borderRadius: BorderRadius.circular(10.0), color: Colors.white), - child: SvgPicture.asset(m.getType(data.first.fileTypeResponse!.fileTypeName ?? ""), alignment: Alignment.center, fit: BoxFit.cover).paddingOnly(left: 5, right: 5, top: 5, bottom: 5)) + width: 43, + height: 43, + constraints: const BoxConstraints(), + decoration: BoxDecoration(border: Border.all(color: MyColors.darkGrey3BColor, width: 1), borderRadius: BorderRadius.circular(10.0), color: Colors.white), + child: SvgPicture.asset(m.getType(data.first.fileTypeResponse!.fileTypeName ?? ""), alignment: Alignment.center, fit: BoxFit.cover).paddingOnly(left: 5, right: 5, top: 5, bottom: 5)) : const SizedBox(); } } void makeCall({required String callType}) async { - callPro.initCallListeners(); + callPro.initCallListeners(context: context); print("================== Make call Triggered ============================"); // Map json = { // "callerID": AppState().chatDetails!.response!.id!.toString(), @@ -373,22 +364,22 @@ class _GroupChatDetailScreenState extends State { // "calltype": callType == "VIDEO" ? "Video" : "Audio", // }; logger.w(json); - // CallDataModel callData = CallDataModel.fromJson(json); - // await Navigator.push( - // context, - // MaterialPageRoute( - // builder: (BuildContext context) => OutGoingCall( - // isVideoCall: callType == "VIDEO" ? true : false, - // outGoingCallData: callData, - // ), - // ), - // ).then((value) { - // print("then"); - // callPro.stopListeners(); - // }); + // CallDataModel callData = CallDataModel.fromJson(json); + // await Navigator.push( + // context, + // MaterialPageRoute( + // builder: (BuildContext context) => OutGoingCall( + // isVideoCall: callType == "VIDEO" ? true : false, + // outGoingCallData: callData, + // ), + // ), + // ).then((value) { + // print("then"); + // callPro.stopListeners(); + // }); } + GroupUserList getCurrentUser(int id, GroupResponse groupChatDetails) { - return groupChatDetails.groupUserList!.firstWhere((GroupUserList item) => item.id ==id); + return groupChatDetails.groupUserList!.firstWhere((GroupUserList item) => item.id == id); } - } diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index 4116594..6139f79 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -3,8 +3,9 @@ import 'dart:io'; import 'dart:ui' as ui; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_callkit_incoming/entities/call_event.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:flutter_countdown_timer/flutter_countdown_timer.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mohem_flutter_app/api/dashboard_api_client.dart'; @@ -16,10 +17,16 @@ 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/main.dart'; +import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.dart'; +import 'package:mohem_flutter_app/models/itg/itg_main_response.dart'; +import 'package:mohem_flutter_app/models/itg/itg_response_model.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart'; import 'package:mohem_flutter_app/models/privilege_list_model.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/provider/dashboard_provider_model.dart'; +import 'package:mohem_flutter_app/ui/chat/call/chat_incoming_call_screen.dart'; import 'package:mohem_flutter_app/ui/landing/widget/app_drawer.dart'; import 'package:mohem_flutter_app/ui/landing/widget/menus_widget.dart'; import 'package:mohem_flutter_app/ui/landing/widget/services_widget.dart'; @@ -33,8 +40,6 @@ import 'package:provider/provider.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:signalr_netcore/signalr_client.dart'; -late HubConnection chatHubConnection; - class DashboardScreen extends StatefulWidget { DashboardScreen({Key? key}) : super(key: key); @@ -48,20 +53,23 @@ class _DashboardScreenState extends State with WidgetsBindingOb late DashboardProviderModel data; late MarathonProvider marathonProvider; late ChatProviderModel cProvider; + late ChatCallProvider chatCallProvider; final GlobalKey _scaffoldState = GlobalKey(); - final RefreshController _refreshController = RefreshController(initialRefresh: false); - int currentIndex = 0; @override void initState() { WidgetsBinding.instance.addObserver(this); super.initState(); + if (Platform.isAndroid) { + callListeners(); + } scheduleMicrotask(() { data = Provider.of(context, listen: false); marathonProvider = Provider.of(context, listen: false); cProvider = Provider.of(context, listen: false); + chatCallProvider = Provider.of(context, listen: false); if (checkIfPrivilegedForChat()) { _bHubCon(); } @@ -69,6 +77,66 @@ class _DashboardScreenState extends State with WidgetsBindingOb }); } + Future callListeners() async { + try { + FlutterCallkitIncoming.onEvent.listen((CallEvent? event) async { + switch (event!.event) { + case Event.actionCallIncoming: + break; + case Event.actionCallStart: + break; + case Event.actionCallAccept: + if (mounted) { + moveToCallScreen(); + } + break; + case Event.actionCallDecline: + SingleUserChatModel callerData; + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + declineCall(cuserid: event.body["extra"]["callerDetails"]["currentUserId"], ruserid: event.body["extra"]["callerDetails"]["targetUserId"]); + FlutterCallkitIncoming.endAllCalls(); + + break; + case Event.actionCallEnded: + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + FlutterCallkitIncoming.endAllCalls(); + break; + case Event.actionCallTimeout: + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + FlutterCallkitIncoming.endAllCalls(); + break; + } + }); + } on Exception {} + } + + void declineCall({required int cuserid, required int ruserid}) async { + logger.i("-----------------------Call Decline Hit---------------------------"); + if (chatHubConnection!.state == HubConnectionState.Connected) { + try { + await chatHubConnection!.invoke("CallDeclinedAsync", args: [cuserid, ruserid]); + await chatHubConnection!.invoke("UpdateUserStatusAsync", args: [cuserid, 1]); + } catch (e) { + logger.w(e); + } + } + } + + Future moveToCallScreen() async { + dynamic calls = await FlutterCallkitIncoming.activeCalls(); + if (calls is List) { + if (calls.isNotEmpty) { + Future.delayed(const Duration(seconds: 1)).whenComplete(() { + MaterialPageRoute pageRoute = MaterialPageRoute(builder: (BuildContext context) => StartCallPage()); + Navigator.push(context, pageRoute); + }); + } + } + } + @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { @@ -88,24 +156,24 @@ class _DashboardScreenState extends State with WidgetsBindingOb void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); - if (!cProvider.disbaleChatForThisUser) { - chatHubConnection.stop(); + if (!cProvider.disableChatForThisUser) { + chatHubConnection!.stop(); } } void _bHubCon() { cProvider.getUserAutoLoginToken().whenComplete(() async { - if (!cProvider.disbaleChatForThisUser) { + if (!cProvider.disableChatForThisUser) { String isAppOpendByChat = await Utils.getStringFromPrefs("isAppOpendByChat"); if (isAppOpendByChat != "null" && isAppOpendByChat == "true") { Utils.showLoading(context); - cProvider.buildHubConnection(); + await cProvider.buildHubConnection(context: context, ccProvider: chatCallProvider); Future.delayed(const Duration(seconds: 2), () async { cProvider.invokeChatCounter(userId: AppState().chatDetails!.response!.id!); gotoChat(context); }); } else { - cProvider.buildHubConnection(); + await cProvider.buildHubConnection(context: context, ccProvider: chatCallProvider); Future.delayed(const Duration(seconds: 2), () { cProvider.invokeChatCounter(userId: AppState().chatDetails!.response!.id!); }); @@ -115,16 +183,16 @@ class _DashboardScreenState extends State with WidgetsBindingOb } Future checkHubCon() async { - if (chatHubConnection.state == HubConnectionState.Connected) { - await chatHubConnection.stop(); - await chatHubConnection.start(); - } else if (chatHubConnection.state != HubConnectionState.Connected) { - await chatHubConnection.start(); + if (chatHubConnection!.state == HubConnectionState.Connected) { + await chatHubConnection!.stop(); + await chatHubConnection!.start(); + } else if (chatHubConnection!.state != HubConnectionState.Connected) { + await chatHubConnection!.start(); } } void gotoChat(BuildContext context) async { - if (chatHubConnection.state == HubConnectionState.Connected) { + if (chatHubConnection!.state == HubConnectionState.Connected) { Utils.hideLoading(context); Navigator.pushNamed(context, AppRoutes.chat); String isAppOpendByChat = await Utils.getStringFromPrefs("isAppOpendByChat"); @@ -153,17 +221,17 @@ class _DashboardScreenState extends State with WidgetsBindingOb if (isFromInit) { checkERMChannel(); } - if (!cProvider.disbaleChatForThisUser && !isFromInit) checkHubCon(); + if (!cProvider.disableChatForThisUser && !isFromInit) checkHubCon(); _refreshController.refreshCompleted(); } void checkERMChannel() { - data.getITGNotification().then((val) { + data.getITGNotification().then((MohemmItgResponseItem? val) { if (val!.result!.data != null) { print("-------------------- Survey ----------------------------"); if (val.result!.data!.notificationType == "Survey") { DashboardApiClient().getAdvertisementDetail(val.result!.data!.notificationMasterId ?? "").then( - (value) { + (ItgMainRes? value) { if (value!.mohemmItgResponseItem!.statusCode == 200) { if (value.mohemmItgResponseItem!.result!.data != null) { // Navigator.pushNamed(context, AppRoutes.survey, arguments: val.result!.data); @@ -179,13 +247,22 @@ class _DashboardScreenState extends State with WidgetsBindingOb } else { print("------------------------------------------- Ads --------------------"); DashboardApiClient().getAdvertisementDetail(val.result!.data!.notificationMasterId ?? "").then( - (value) { + (ItgMainRes? value) { if (value!.mohemmItgResponseItem!.statusCode == 200) { if (value.mohemmItgResponseItem!.result!.data != null) { Navigator.pushNamed(context, AppRoutes.advertisement, arguments: { "masterId": val.result!.data!.notificationMasterId, "advertisement": value.mohemmItgResponseItem!.result!.data!.advertisement, }); + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (BuildContext context) => ITGAdsScreen( + // addMasterId: val.result!.data!.notificationMasterId!, + // advertisement: value.mohemmItgResponseItem!.result!.data!.advertisement!, + // ), + // ), + // ); } } }, @@ -199,6 +276,46 @@ class _DashboardScreenState extends State with WidgetsBindingOb Widget build(BuildContext context) { return Scaffold( key: _scaffoldState, + // appBar: AppBar( + // actions: [ + // IconButton( + // onPressed: () { + // data.getITGNotification().then((val) { + // if (val!.result!.data != null) { + // print("-------------------- Survey ----------------------------"); + // if (val.result!.data!.notificationType == "Survey") { + // Navigator.pushNamed(context, AppRoutes.survey, arguments: val.result!.data); + // } else { + // print("------------------------------------------- Ads --------------------"); + // DashboardApiClient().getAdvertisementDetail(val.result!.data!.notificationMasterId ?? "").then( + // (value) { + // if (value!.mohemmItgResponseItem!.statusCode == 200) { + // if (value.mohemmItgResponseItem!.result!.data != null) { + // Navigator.pushNamed(context, AppRoutes.advertisement, arguments: { + // "masterId": val.result!.data!.notificationMasterId, + // "advertisement": value.mohemmItgResponseItem!.result!.data!.advertisement, + // }); + // + // // Navigator.push( + // // context, + // // MaterialPageRoute( + // // builder: (BuildContext context) => ITGAdsScreen( + // // addMasterId: val.result!.data!.notificationMasterId!, + // // advertisement: value.mohemmItgResponseItem!.result!.data!.advertisement!, + // // ), + // // ), + // // ); + // } + // } + // }, + // ); + // } + // } + // }); + // }, + // icon: Icon(Icons.add)) + // ], + // ), body: Column( children: [ Row( @@ -565,16 +682,15 @@ class _DashboardScreenState extends State with WidgetsBindingOb icon: Stack( alignment: Alignment.centerLeft, children: [ - SvgPicture.asset( - "assets/icons/chat/chat.svg", - color: !checkIfPrivilegedForChat() - ? MyColors.lightGreyE3Color - : currentIndex == 4 - ? MyColors.grey3AColor - : cProvider.disbaleChatForThisUser - ? MyColors.lightGreyE3Color - : MyColors.grey98Color, - ).paddingAll(4), + SvgPicture.asset("assets/icons/chat/chat.svg", color: !checkIfPrivilegedForChat() ? MyColors.lightGreyE3Color : MyColors.grey98Color + + // currentIndex == 4 + // ? MyColors.grey3AColor + // : cProvider.disableChatForThisUser + // ? MyColors.lightGreyE3Color + // : MyColors.grey98Color, + ) + .paddingAll(4), Consumer( builder: (BuildContext cxt, ChatProviderModel data, Widget? child) { return !checkIfPrivilegedForChat() @@ -585,7 +701,7 @@ class _DashboardScreenState extends State with WidgetsBindingOb child: Container( padding: const EdgeInsets.only(left: 4, right: 4), alignment: Alignment.center, - decoration: BoxDecoration(color: cProvider.disbaleChatForThisUser ? MyColors.pinkDarkColor : MyColors.redColor, borderRadius: BorderRadius.circular(17)), + decoration: BoxDecoration(color: data.disableChatForThisUser ? MyColors.pinkDarkColor : MyColors.redColor, borderRadius: BorderRadius.circular(17)), child: data.chatUConvCounter.toString().toText10(color: Colors.white), ), ); @@ -637,6 +753,7 @@ class _DashboardScreenState extends State with WidgetsBindingOb } } }); + Navigator.pushNamed(context, AppRoutes.offersAndDiscountsDetails, arguments: getOffersDetailList); } diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 94724fc..f8cb7be 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; @@ -8,7 +9,12 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; - +import 'package:flutter_callkit_incoming/entities/call_event.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; +import 'package:flutter_ios_voip_kit/call_state_type.dart'; +import 'package:flutter_ios_voip_kit/flutter_ios_voip_kit.dart'; +import 'package:logger/logger.dart'; +import 'package:mohem_flutter_app/api/chat/chat_api_client.dart'; // import 'package:huawei_hmsavailability/huawei_hmsavailability.dart'; import 'package:mohem_flutter_app/api/login_api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; @@ -21,15 +27,17 @@ 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/main.dart'; +import 'package:mohem_flutter_app/models/chat/call.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'; import 'package:mohem_flutter_app/models/member_login_list_model.dart'; import 'package:mohem_flutter_app/models/privilege_list_model.dart'; +import 'package:mohem_flutter_app/ui/chat/call/chat_incoming_call_screen.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:mohem_flutter_app/widgets/input_widget.dart'; - -// import 'package:safe_device/safe_device.dart'; +import 'package:signalr_netcore/hub_connection.dart'; import 'package:wifi_iot/wifi_iot.dart'; class LoginScreen extends StatefulWidget { @@ -41,7 +49,7 @@ class LoginScreen extends StatefulWidget { } } -class _LoginScreenState extends State { +class _LoginScreenState extends State with WidgetsBindingObserver { TextEditingController username = TextEditingController(); TextEditingController password = TextEditingController(); @@ -49,6 +57,7 @@ class _LoginScreenState extends State { MemberLoginListModel? _memberLoginList; late final FirebaseMessaging _firebaseMessaging; + IosCallPayload? _iosCallPayload; bool _autoLogin = false; @@ -58,9 +67,13 @@ class _LoginScreenState extends State { bool isRealDevice = false; bool isOnExternalStorage = false; bool isDevelopmentModeEnable = false; + bool isIncomingCall = false; // late HmsApiAvailability hmsApiAvailability; + final FlutterIOSVoIPKit voIPKit = FlutterIOSVoIPKit.instance; + late Timer timeOutTimer; + @override void initState() { super.initState(); @@ -69,6 +82,119 @@ class _LoginScreenState extends State { // if (kReleaseMode) { // checkDeviceSafety(); // } + WidgetsBinding.instance.addObserver(this); + if (Platform.isAndroid) { + callListeners(); + checkAndNavigationCallingPage(); + } + if (Platform.isIOS) { + setupVoIPCallBacks(); + } + } + + // IOS Voip Call + Future setupVoIPCallBacks() async { + if (Platform.isIOS) { + voIPKit.getVoIPToken().then((String? value) async { + print('๐ŸŽˆ example: getVoIPToken: $value'); + if (value != null) { + AppState().setiosVoipPlayerID = await ChatApiClient().oneSignalVoip(value); + print('๐ŸŽˆ example: OneSignal ID: ${AppState().iosVoipPlayerID}'); + } + }); + } + + voIPKit.onDidUpdatePushToken = (String token) { + print('๐ŸŽˆ example: onDidUpdatePushToken: $token'); + }; + + voIPKit.onDidReceiveIncomingPush = ( + Map payload, + ) async { + _iosCallPayload = IosCallPayload.fromJson(payload); + _timeOut(callDetails: _iosCallPayload!.incomingCallerId!); + }; + + voIPKit.onDidRejectIncomingCall = ( + String uuid, + String callerId, + ) async { + timeOutTimer.cancel(); + declineCall( + cuserid: int.parse(callerId.split("-")[1]), + ruserid: int.parse(callerId.split("-")[0]), + ); + await voIPKit.endCall(); + }; + + voIPKit.onDidAcceptIncomingCall = ( + String uuid, + String callerId, + ) async { + await connectCall(uuid: uuid, callDetails: callerId); + voIPKit.acceptIncomingCall(callerState: CallStateType.calling); + voIPKit.callConnected().then((value) => timeOutTimer.cancel()); + }; + } + + void _timeOut({required String callDetails}) async { + timeOutTimer = Timer(const Duration(seconds: 25), () async { + String? incomingCallerName = await voIPKit.getIncomingCallerName(); + voIPKit.unansweredIncomingCall( + skipLocalNotification: true, + missedCallTitle: '๐Ÿ“ž Missed call', + missedCallBody: 'There was a call from $incomingCallerName', + ); + declineCall(cuserid: int.parse(callDetails.split("-")[1]), ruserid: int.parse(callDetails.split("-")[0])); + await voIPKit.endCall(); + }); + } + + Future connectCall({required String uuid, required String callDetails}) async { + isIncomingCall = true; + if (AppState().getisUserOnline) { + _iosCallPayload = IosCallPayload( + uuid: uuid, + incomingCallerId: callDetails.split("-")[0], + incomingCallReciverId: callDetails.split("-")[1], + incomingCallerName: _iosCallPayload!.incomingCallerName, + incomingCallType: callDetails.split("-").last); + } else { + _iosCallPayload = IosCallPayload( + uuid: uuid, incomingCallerId: callDetails.split("-")[0], incomingCallReciverId: callDetails.split("-")[1], incomingCallerName: null, incomingCallType: callDetails.split("-").last); + } + if (_iosCallPayload!.incomingCallerName == null) { + if (Platform.isIOS) { + Utils.hideLoading(context); + } + + if (Platform.isIOS) { + Future.delayed(Duration(seconds: 3), () async { + MaterialPageRoute pageRoute = await MaterialPageRoute( + builder: (BuildContext context) => StartCallPage( + payload: _iosCallPayload, + ), + ); + Navigator.push(context, pageRoute); + }); + } + } else if (AppState().getisUserOnline) { + BuildContext context = AppRoutes.navigatorKey.currentContext!; + if (Platform.isIOS) { + Future.delayed(Duration(seconds: 3), () async { + MaterialPageRoute pageRoute = await MaterialPageRoute( + builder: (BuildContext context) => StartCallPage( + payload: _iosCallPayload, + ), + ); + Navigator.push(context, pageRoute); + }); + } + } else { + if (Platform.isAndroid) FlutterCallkitIncoming.endAllCalls(); + if (Platform.isIOS) await voIPKit.endCall(); + Utils.showToast("Something wen't wrong"); + } } // void checkDeviceSafety() async { @@ -87,6 +213,96 @@ class _LoginScreenState extends State { // } // } + Future callListeners() async { + try { + FlutterCallkitIncoming.onEvent.listen((CallEvent? event) async { + switch (event!.event) { + case Event.actionCallIncoming: + break; + case Event.actionCallStart: + break; + case Event.actionCallAccept: + if (mounted) { + isIncomingCall = true; + moveToCallScreen(); + } + break; + case Event.actionCallDecline: + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + declineCall(cuserid: event.body["extra"]["callerDetails"]["currentUserId"], ruserid: event.body["extra"]["callerDetails"]["targetUserId"]); + FlutterCallkitIncoming.endAllCalls(); + + break; + case Event.actionCallEnded: + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + FlutterCallkitIncoming.endAllCalls(); + break; + case Event.actionCallTimeout: + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + FlutterCallkitIncoming.endAllCalls(); + break; + } + print('${event.body}\n'); + }); + } on Exception { + logger.log(Level.info, "EXCEPTION-ON-EVENTS"); + } + } + + void declineCall({required int cuserid, required int ruserid}) async { + logger.i("-----------------------Call Decline Hit---------------------------"); + if (chatHubConnection!.state == HubConnectionState.Connected) { + try { + await chatHubConnection!.invoke("CallDeclinedAsync", args: [cuserid, ruserid]); + await chatHubConnection!.invoke("UpdateUserStatusAsync", args: [cuserid, 1]); + } catch (e) { + logger.w(e); + } + } + } + + Future moveToCallScreen() async { + dynamic calls = await FlutterCallkitIncoming.activeCalls(); + if (calls is List) { + if (calls.isNotEmpty) { + if (Platform.isAndroid) { + Utils.hideLoading(context); + } + MaterialPageRoute pageRoute = MaterialPageRoute(builder: (BuildContext context) => StartCallPage()); + Navigator.push(context, pageRoute).whenComplete(() { + checkFirebaseToken(); + }); + } else { + FlutterCallkitIncoming.endAllCalls(); + Utils.showToast("Something wen't wrong"); + } + } + } + + Future getCurrentCall() async { + //check current call from pushkit if possible + var calls = await FlutterCallkitIncoming.activeCalls(); + if (calls is List) { + if (calls.isNotEmpty) { + return calls[0]; + } else { + return null; + } + } + } + + Future checkAndNavigationCallingPage() async { + var currentCall = await getCurrentCall(); + if (currentCall != null && Platform.isAndroid) { + isIncomingCall = true; + Utils.hideLoading(context); + Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => StartCallPage())); + } + } + @override void dispose() { super.dispose(); @@ -102,8 +318,8 @@ class _LoginScreenState extends State { if (Platform.isAndroid) { try { if (!(await Utils.isGoogleServicesAvailable())) { - print("HUAWEI APPPP GALLERYYYY!!!!"); - AppNotifications().init(firebaseToken, context); + debugPrint("HUAWEI APPPP GALLERYYYY!!!!"); + // AppNotifications().init(firebaseToken, context); AppState().setIsHuawei = true; AppNotifications().initHuaweiPush(checkLoginInfo); } else { @@ -224,7 +440,15 @@ class _LoginScreenState extends State { // 13777 // Ab12345cd } - if (isAppOpenBySystem!) checkFirebaseToken(); + // if (isAppOpenBySystem!) checkFirebaseToken(); + + Utils.showLoading(context); + Future.delayed(Duration(seconds: Platform.isIOS ? 3 : 2)).whenComplete(() { + if (!isIncomingCall) { + Utils.hideLoading(context); + if (isAppOpenBySystem!) checkFirebaseToken(); + } + }); } // username.text = "15444"; @@ -299,7 +523,11 @@ class _LoginScreenState extends State { DefaultButton(LocaleKeys.login.tr(), () async { SystemChannels.textInput.invokeMethod('TextInput.hide'); performLogin(); - }).insideContainer + }).insideContainer, + // DefaultButton("Call", () async { + // SystemChannels.textInput.invokeMethod('TextInput.hide'); + // connectCall(); + // }).insideContainer ], ), ); diff --git a/lib/ui/login/verify_last_login_screen.dart b/lib/ui/login/verify_last_login_screen.dart index c48d1f3..9b8dbd0 100644 --- a/lib/ui/login/verify_last_login_screen.dart +++ b/lib/ui/login/verify_last_login_screen.dart @@ -3,8 +3,9 @@ import 'dart:io'; import 'package:easy_localization/src/public_ext.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_callkit_incoming/entities/call_event.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:local_auth/auth_strings.dart'; import 'package:local_auth/local_auth.dart'; import 'package:mohem_flutter_app/api/login_api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; @@ -20,10 +21,12 @@ import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/models/basic_member_information_model.dart'; import 'package:mohem_flutter_app/models/generic_response_model.dart'; import 'package:mohem_flutter_app/models/get_mobile_login_info_list_model.dart'; +import 'package:mohem_flutter_app/ui/chat/call/chat_incoming_call_screen.dart'; import 'package:mohem_flutter_app/ui/dialogs/id/business_card_dialog.dart'; import 'package:mohem_flutter_app/ui/dialogs/id/employee_digital_id_dialog.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:mohem_flutter_app/widgets/dialogs/dialogs.dart'; +import 'package:local_auth_ios/local_auth_ios.dart'; // WhatsApp 4 // SMS 1 @@ -52,15 +55,71 @@ class _VerifyLastLoginScreenState extends State { void initState() { _getAvailableBiometrics(); // setDefault(); + if (Platform.isAndroid) { + callListeners(); + } super.initState(); } + Future callListeners() async { + try { + FlutterCallkitIncoming.onEvent.listen((CallEvent? event) async { + switch (event!.event) { + case Event.actionCallIncoming: + // await ChatVoipCall().declineCall(payload: jsonEncode(event.body)); + break; + case Event.actionCallStart: + break; + case Event.actionCallAccept: + if (mounted) { + // isIncomingCall = true; + moveToCallScreen(); + } + break; + case Event.actionCallDecline: + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + FlutterCallkitIncoming.endAllCalls(); + + break; + case Event.actionCallEnded: + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + FlutterCallkitIncoming.endAllCalls(); + break; + case Event.actionCallTimeout: + Utils.saveStringFromPrefs("isIncomingCall", "false"); + Utils.saveStringFromPrefs("inComingCallData", "null"); + FlutterCallkitIncoming.endAllCalls(); + break; + } + }); + } on Exception {} + } + + Future moveToCallScreen() async { + dynamic calls = await FlutterCallkitIncoming.activeCalls(); + if (calls is List) { + if (calls.isNotEmpty) { + if (Platform.isAndroid) { + Utils.hideLoading(context); + } + var pageRoute = MaterialPageRoute(builder: (context) => StartCallPage()); + Navigator.push(context, pageRoute).whenComplete(() { + // checkFirebaseToken(); + }); + } else { + FlutterCallkitIncoming.endAllCalls(); + Utils.showToast("Something wen't wrong"); + } + } + } + @override Widget build(BuildContext context) { mobileLoginInfoListModel ??= ModalRoute.of(context)!.settings.arguments as GetMobileLoginInfoListModel; // String empName = AppState().isArabic(context) ? AppState().memberInformationList!.eMPLOYEEDISPLAYNAMEAr! : AppState().memberInformationList!.eMPLOYEEDISPLAYNAMEEn!; String empName = mobileLoginInfoListModel!.employeeName!; - return Scaffold( appBar: AppBar( backgroundColor: Colors.transparent, @@ -220,10 +279,18 @@ class _VerifyLastLoginScreenState extends State { Future loginWithFaceIDAndBiometrics() async { IOSAuthMessages iosStrings = - const IOSAuthMessages(cancelButton: 'cancel', goToSettingsButton: 'settings', goToSettingsDescription: 'Please set up your Touch ID.', lockOut: 'Please reenable your Touch ID'); + const IOSAuthMessages(cancelButton: 'cancel', goToSettingsButton: 'settings', goToSettingsDescription: 'Please set up your Touch ID.', lockOut: 'Please re enable your Touch ID'); bool authenticated = false; try { - authenticated = await auth.authenticate(localizedReason: 'Scan your fingerprint to authenticate', useErrorDialogs: true, stickyAuth: true, biometricOnly: true, iOSAuthStrings: iosStrings); + authenticated = await auth.authenticate( + localizedReason: 'Scan your fingerprint to authenticate', + authMessages: [iosStrings], + options: const AuthenticationOptions( + useErrorDialogs: true, + stickyAuth: true, + biometricOnly: true, + ), + ); } on PlatformException catch (e) { print(e); Utils.hideLoading(context); @@ -289,7 +356,7 @@ class _VerifyLastLoginScreenState extends State { width: 38, color: isDisable ? MyColors.darkTextColor.withOpacity(0.7) : null, ), - _title.toText16(height: 20/16) + _title.toText16(height: 20 / 16) ], ), ), @@ -404,5 +471,4 @@ class _VerifyLastLoginScreenState extends State { // // isLoading = isTrue; // }); // } - } diff --git a/lib/ui/login/verify_login_screen.dart b/lib/ui/login/verify_login_screen.dart index 0147472..628adc9 100644 --- a/lib/ui/login/verify_login_screen.dart +++ b/lib/ui/login/verify_login_screen.dart @@ -4,7 +4,6 @@ import 'package:easy_localization/src/public_ext.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:local_auth/auth_strings.dart'; import 'package:local_auth/local_auth.dart'; import 'package:mohem_flutter_app/api/login_api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; @@ -13,13 +12,13 @@ import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/dialogs/otp_dialog.dart'; -import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/models/basic_member_information_model.dart'; import 'package:mohem_flutter_app/models/generic_response_model.dart'; import 'package:mohem_flutter_app/models/member_information_list_model.dart'; import 'package:mohem_flutter_app/models/privilege_list_model.dart'; +import 'package:local_auth_ios/local_auth_ios.dart'; // WhatsApp 2 // SMS 1 @@ -516,7 +515,16 @@ class _VerifyLoginScreenState extends State { const IOSAuthMessages(cancelButton: 'cancel', goToSettingsButton: 'settings', goToSettingsDescription: 'Please set up your Touch ID.', lockOut: 'Please reenable your Touch ID'); bool authenticated = false; try { - authenticated = await auth.authenticate(localizedReason: 'Scan your fingerprint to authenticate', useErrorDialogs: true, stickyAuth: true, biometricOnly: true, iOSAuthStrings: iosStrings); + // authenticated = await auth.authenticate(localizedReason: 'Scan your fingerprint to authenticate', useErrorDialogs: true, stickyAuth: true, biometricOnly: true, iOSAuthStrings: iosStrings); + authenticated = await auth.authenticate( + localizedReason: 'Scan your fingerprint to authenticate', + authMessages: [iosStrings], + options: const AuthenticationOptions( + useErrorDialogs: true, + stickyAuth: true, + biometricOnly: true, + ) + ); } on PlatformException catch (e) { print(e); Utils.hideLoading(context); @@ -572,7 +580,7 @@ class _VerifyLoginScreenState extends State { width: 38, color: isDisable ? MyColors.darkTextColor.withOpacity(0.7) : null, ), - _title.toText16(height: 20/16) + _title.toText16(height: 20 / 16) ], ), ), @@ -679,5 +687,4 @@ class _VerifyLoginScreenState extends State { // // isLoading = isTrue; // }); // } - } diff --git a/lib/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart b/lib/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart index 20da2c8..6828228 100644 --- a/lib/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart +++ b/lib/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart @@ -96,7 +96,8 @@ class _DynamicInputScreenState extends State { SubmitEITTransactionList submitEITTransactionList = await MyAttendanceApiClient().submitEitTransaction(dESCFLEXCONTEXTCODE, dynamicParams!.dynamicId, values, empID: dynamicParams!.selectedEmp); Utils.hideLoading(context); await Navigator.pushNamed(context, AppRoutes.requestSubmitScreen, - arguments: RequestSubmitScreenParams(LocaleKeys.submit.tr(), submitEITTransactionList.pTRANSACTIONID!, submitEITTransactionList.pITEMKEY!, 'eit',isAttachmentMandatory: dynamicParams!.isAttachmentMandatory)); + arguments: RequestSubmitScreenParams(LocaleKeys.submit.tr(), submitEITTransactionList.pTRANSACTIONID!, submitEITTransactionList.pITEMKEY!, 'eit', + isAttachmentMandatory: dynamicParams!.isAttachmentMandatory)); if (!AppState().cancelRequestTrancsection) { return; } @@ -360,6 +361,10 @@ class _DynamicInputScreenState extends State { idColName = idColName.parseMonth(); } } + idColName = Utils.formatDateDefault(idColName!); + // commenting to test + // DateTime date = DateFormat('yyyy-MM-dd').parse(idColName!); + // idColName = DateFormat('yyyy-MM-dd HH:mm:ss').format(date); } } diff --git a/lib/ui/my_team/view_attendance.dart b/lib/ui/my_team/view_attendance.dart index a45f35c..7cb9dde 100644 --- a/lib/ui/my_team/view_attendance.dart +++ b/lib/ui/my_team/view_attendance.dart @@ -15,7 +15,7 @@ import 'package:mohem_flutter_app/models/get_time_card_summary_list_model.dart'; import 'package:mohem_flutter_app/models/my_team/get_employee_subordinates_list.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/circular_step_progress_bar.dart'; -import 'package:month_picker_dialog_2/month_picker_dialog_2.dart'; +import 'package:month_picker_dialog/month_picker_dialog.dart'; import 'package:pie_chart/pie_chart.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; @@ -169,8 +169,10 @@ class _ViewAttendanceState extends State { initialDate: formattedDate, firstDate: DateTime(searchYear - 2), lastDate: DateTime.now(), - confirmText: Text(LocaleKeys.confirm.tr()), - cancelText: Text(LocaleKeys.cancel.tr()), + confirmWidget: Text(LocaleKeys.confirm.tr()), + cancelWidget: Text(LocaleKeys.cancel.tr()), + // confirmText: Text(LocaleKeys.confirm.tr()), + // cancelText: Text(LocaleKeys.cancel.tr()), ).then( (selectedDate) { if (selectedDate != null) { diff --git a/lib/ui/profile/delete_family_member.dart b/lib/ui/profile/delete_family_member.dart index 3b73512..8ecdcb9 100644 --- a/lib/ui/profile/delete_family_member.dart +++ b/lib/ui/profile/delete_family_member.dart @@ -115,8 +115,8 @@ class _DeleteFamilyMemberState extends State { padding: EdgeInsets.only(left: 50, right: 50), child: TextButton( style: TextButton.styleFrom( - primary: MyColors.white, - onSurface: MyColors.white, + foregroundColor: MyColors.white, + disabledForegroundColor: MyColors.white, backgroundColor: MyColors.gradiantEndColor, ), onPressed: () { diff --git a/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart b/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart index b4cb242..eb5df20 100644 --- a/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart +++ b/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart @@ -1,13 +1,12 @@ import 'dart:io'; -import 'dart:typed_data'; import 'dart:ui' as ui; -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; @@ -16,7 +15,7 @@ import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:share/share.dart'; +import 'package:share_plus/share_plus.dart'; import 'package:url_launcher/url_launcher.dart'; class OffersAndDiscountsDetails extends StatefulWidget { @@ -76,7 +75,7 @@ class _OffersAndDiscountsDetailsState extends State { : getOffersList[0].titleEn!.toText22(isBold: true, color: const Color(0xff2B353E)).center, Html( data: AppState().isArabic(context) ? getOffersList[0].descriptionAr! : getOffersList[0].descriptionEn ?? "", - onLinkTap: (String? url, RenderContext context, Map attributes, _) { + onLinkTap: (String? url, Map attributes, _) { launchUrl(Uri.parse(url!)); }, ), @@ -125,9 +124,10 @@ class _OffersAndDiscountsDetailsState extends State { Uint8List pngBytes = byteData!.buffer.asUint8List(); Directory tempDir = await getTemporaryDirectory(); - File file = await File('${tempDir.path}/${DateTime.now().toString()}.png').create(); + String fileName = "${DateTime.now().toString()}.png"; + File file = await File('${tempDir.path}/$fileName').create(); await file.writeAsBytes(pngBytes); - await Share.shareFiles([(file.path)], text: AppState().isArabic(context) ? getOffersList[0].titleAr : getOffersList[0].titleEn); + await Share.shareXFiles([XFile(file.path)], text: AppState().isArabic(context) ? getOffersList[0].titleAr : getOffersList[0].titleEn); } catch (ex) { debugPrint(ex.toString()); } diff --git a/lib/widgets/app_bar_widget.dart b/lib/widgets/app_bar_widget.dart index 6f9898e..1039a9d 100644 --- a/lib/widgets/app_bar_widget.dart +++ b/lib/widgets/app_bar_widget.dart @@ -10,11 +10,14 @@ AppBar AppBarWidget(BuildContext context, bool showHomeButton = true, bool showWorkListSettingButton = false, bool showMemberButton = false, + bool isBackButton =true, List? actions, void Function()? onHomeTapped, void Function()? onBackTapped}) { return AppBar( + automaticallyImplyLeading:false, leadingWidth: 0, + leading:null, title: Row( children: [ GestureDetector( @@ -22,8 +25,8 @@ AppBar AppBarWidget(BuildContext context, onTap: Feedback.wrapForTap(() { (onBackTapped == null ? Navigator.maybePop(context) : onBackTapped()); }, context), - child: const Icon(Icons.arrow_back_ios, color: MyColors.darkIconColor), - ), + child: const Icon(Icons.arrow_back_ios_new, color: MyColors.darkIconColor), + ) , 4.width, title.toText24(color: MyColors.darkTextColor, isBold: true).expanded, ], diff --git a/lib/widgets/bottom_sheets/attachment_options.dart b/lib/widgets/bottom_sheets/attachment_options.dart index 30189a0..d0f4f49 100644 --- a/lib/widgets/bottom_sheets/attachment_options.dart +++ b/lib/widgets/bottom_sheets/attachment_options.dart @@ -9,7 +9,7 @@ class AttachmentOptions extends StatelessWidget { VoidCallback onGalleryTap; VoidCallback onFilesTap; bool showFilesOption; - + String pickSelection =""; AttachmentOptions({Key? key, required this.onCameraTap, required this.onGalleryTap, required this.onFilesTap, this.showFilesOption = true}) : super(key: key); @override @@ -21,6 +21,7 @@ class AttachmentOptions extends StatelessWidget { children: [ "Upload Attachment".toSectionHeading(), "Select from gallery or open camera".toText11(weight: FontWeight.w500), + GridView( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 105 / 105, crossAxisSpacing: 9, mainAxisSpacing: 9), physics: const NeverScrollableScrollPhysics(), diff --git a/lib/widgets/chat_app_bar_widge.dart b/lib/widgets/chat_app_bar_widge.dart index cc252ec..c7630bd 100644 --- a/lib/widgets/chat_app_bar_widge.dart +++ b/lib/widgets/chat_app_bar_widge.dart @@ -13,6 +13,7 @@ AppBar ChatAppBarWidget(BuildContext context, {required String title, bool showHomeButton = true, ChatUser? chatUser, bool showTyping = false, List? actions, void Function()? onHomeTapped, void Function()? onBackTapped}) { return AppBar( leadingWidth: 0, + automaticallyImplyLeading:false, title: Consumer(builder: (BuildContext cxt, ChatProviderModel data, Widget? child) { return Row( children: [ diff --git a/lib/widgets/image_picker.dart b/lib/widgets/image_picker.dart index 19b1e00..cc8d18c 100644 --- a/lib/widgets/image_picker.dart +++ b/lib/widgets/image_picker.dart @@ -11,6 +11,8 @@ import 'package:mohem_flutter_app/widgets/bottom_sheets/attachment_options.dart' import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; import 'package:permission_handler/permission_handler.dart'; +final ImagePicker picker = ImagePicker(); + class ImageOptions { static void showImageOptionsNew(BuildContext context, bool showFilesOption, Function(String, File) image) { showMyBottomSheet( @@ -23,8 +25,9 @@ class ImageOptions { cameraImageAndroid(image); } else { File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.camera, imageQuality: 20))?.path ?? ""); - String fileName = _image.path; - var bytes = File(fileName).readAsBytesSync(); + // XFile? media = await picker.pickMedia(); + String? fileName = _image?.path; + var bytes = File(fileName!).readAsBytesSync(); String base64Encode = base64.encode(bytes); if (base64Encode != null) { image(base64Encode, _image); @@ -35,7 +38,7 @@ class ImageOptions { if (Platform.isAndroid) { galleryImageAndroid(image); } else { - File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.gallery, imageQuality: 20))?.path ?? ""); + File _image = File((await picker.pickMedia())?.path ?? ""); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); @@ -146,7 +149,7 @@ class ImageOptions { } void galleryImageAndroid(Function(String, File) image) async { - File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.gallery, imageQuality: 20))?.path ?? ""); + File _image = File((await picker.pickMedia())?.path ?? ""); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); @@ -157,7 +160,7 @@ void galleryImageAndroid(Function(String, File) image) async { } void cameraImageAndroid(Function(String, File) image) async { - File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.camera, imageQuality: 20))?.path ?? ""); + File _image = File((await picker.pickMedia())?.path ?? ""); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); diff --git a/pubspec.yaml b/pubspec.yaml index 82ae41b..6c7b417 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html #version: 3.3.01+300040 -version: 3.3.6+300046 +version: 3.7.7+1 environment: sdk: ">=2.16.0 <3.0.0" @@ -44,10 +44,10 @@ dependencies: permission_handler: ^10.2.0 flutter_svg: any sizer: ^2.0.15 - local_auth: ^1.1.9 + local_auth: ^2.1.6 fluttertoast: ^8.0.8 - syncfusion_flutter_calendar: ^19.4.48 - # flutter_calendar_carousel: ^2.1.0 + syncfusion_flutter_calendar: ^20.1.58 +# flutter_calendar_carousel: ^2.1.0 pie_chart: ^5.1.0 shared_preferences: ^2.0.12 firebase_messaging: ^13.0.4 @@ -62,8 +62,8 @@ dependencies: image_picker: ^0.8.5+3 file_picker: 5.2.5 geolocator: ^9.0.2 - month_year_picker: ^0.2.0+1 - month_picker_dialog_2: 0.5.5 + month_year_picker: any + month_picker_dialog: ^2.0.2 open_file: ^3.2.1 wifi_iot: ^0.3.18 flutter_html: ^3.0.0-alpha.6 @@ -71,7 +71,7 @@ dependencies: qr_code_scanner: ^1.0.1 # qr_flutter: ^4.0.0 url_launcher: ^6.0.15 - share: 2.0.4 + share_plus: ^4.5.3 flutter_rating_bar: ^4.0.1 auto_size_text: ^3.0.0 pull_to_refresh: ^2.0.0 @@ -85,14 +85,18 @@ dependencies: cached_network_image: ^3.2.2 #Chat - signalr_netcore: ^1.3.3 + signalr_netcore: ^1.3.6 logging: ^1.0.1 - swipe_to: ^1.0.2 - flutter_webrtc: ^0.9.17 - camera: ^0.10.3 + swipe_to: ^1.0.5 + #flutter_webrtc: ^0.9.34 + flutter_webrtc: ^0.9.47 + draggable_widget: ^2.0.0 + flutter_callkit_incoming: ^2.0.0+1 + camera: ^0.10.5+9 flutter_local_notifications: ^10.0.0 #firebase_analytics: any + #Chat Voice Message Recoding & Play audio_waveforms: ^0.1.5+1 rxdart: ^0.27.7 @@ -119,11 +123,12 @@ dependencies: # store_checker: ^1.1.0 google_api_availability: ^3.0.1 - in_app_update: 3.0.0 - #todo its for temporary purpose, later will remove this. dotted_border: ^2.0.0+3 + in_app_update: 3.0.0 + flutter_ios_voip_kit: ^0.1.0 + # saf: ^1.0.3+4