Refresh view when call is paused

This commit is contained in:
Benoit Martins 2024-01-09 16:42:11 +01:00 committed by QuentinArguillere
parent 8469e8583e
commit d97f07942f
11 changed files with 677 additions and 560 deletions

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "phone-list.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.1334 15.1013L15.8935 13.2014L15.8818 13.196C15.6617 13.1018 15.4216 13.0641 15.1832 13.0861C14.9448 13.1081 14.7157 13.1892 14.5165 13.322C14.493 13.3375 14.4705 13.3543 14.449 13.3724L12.2584 15.2399C10.8706 14.5658 9.43775 13.1438 8.76365 11.774L10.6339 9.55006C10.6519 9.52756 10.669 9.50506 10.6852 9.48076C10.8151 9.28213 10.894 9.05445 10.9147 8.81798C10.9355 8.5815 10.8974 8.34357 10.804 8.12535V8.11455L8.89865 3.86742C8.77512 3.58236 8.5627 3.34489 8.29311 3.19047C8.02353 3.03605 7.71123 2.97296 7.40284 3.01062C6.1833 3.1711 5.06388 3.77001 4.25366 4.69552C3.44343 5.62102 2.9978 6.80981 3.00001 8.03985C3.00001 15.1859 8.81405 20.9999 15.9601 20.9999C17.1901 21.0021 18.3789 20.5565 19.3044 19.7463C20.2299 18.9361 20.8289 17.8166 20.9893 16.5971C21.0271 16.2888 20.9641 15.9766 20.8098 15.707C20.6556 15.4375 20.4183 15.225 20.1334 15.1013ZM15.9601 19.5599C12.9058 19.5566 9.97757 18.3418 7.81786 16.1821C5.65814 14.0224 4.44335 11.0941 4.44002 8.03985C4.43663 7.16099 4.75327 6.31094 5.33079 5.64845C5.90831 4.98597 6.70723 4.55635 7.57834 4.43983C7.57799 4.44342 7.57799 4.44704 7.57834 4.45063L9.46835 8.68066L7.60804 10.9073C7.58916 10.929 7.57201 10.9522 7.55674 10.9766C7.4213 11.1844 7.34185 11.4237 7.32608 11.6713C7.31031 11.9188 7.35876 12.1663 7.46674 12.3896C8.28215 14.0573 9.96246 15.725 11.6482 16.5395C11.8731 16.6465 12.122 16.6933 12.3705 16.6753C12.6189 16.6573 12.8585 16.5752 13.0657 16.4369C13.0888 16.4213 13.111 16.4045 13.1323 16.3865L15.3202 14.5199L19.5502 16.4144C19.5502 16.4144 19.5574 16.4144 19.5601 16.4144C19.445 17.2868 19.016 18.0873 18.3534 18.6662C17.6908 19.2452 16.84 19.5629 15.9601 19.5599Z" fill="white"/>
<path d="M12.7 8.90039H20.5" stroke="white" stroke-linecap="round"/>
<path d="M12.7 6.19922H20.5" stroke="white" stroke-linecap="round"/>
<path d="M12.7 3.5H20.5" stroke="white" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "phone-transfer.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.9888 17.1636C20.8216 18.4339 20.1977 19.6 19.2337 20.444C18.2696 21.288 17.0313 21.7522 15.75 21.7499C8.30626 21.7499 2.25001 15.6936 2.25001 8.24986C2.24771 6.96857 2.7119 5.73025 3.55588 4.7662C4.39986 3.80214 5.56592 3.17827 6.83626 3.01111C7.15739 2.97181 7.48261 3.03739 7.76342 3.19807C8.04422 3.35875 8.26555 3.60591 8.39438 3.90267L10.3744 8.32392V8.33517C10.4729 8.56247 10.5136 8.81063 10.4928 9.05749C10.472 9.30435 10.3904 9.54222 10.2553 9.74986C10.2384 9.77517 10.2206 9.79861 10.2019 9.82205L8.25001 12.1358C8.9522 13.5627 10.4447 15.042 11.8903 15.7461L14.1722 13.8045C14.1946 13.7857 14.2181 13.7682 14.2425 13.752C14.4497 13.6131 14.6885 13.5283 14.9369 13.5054C15.1853 13.4825 15.4355 13.5221 15.6647 13.6208L15.6769 13.6264L20.0934 15.6055C20.3909 15.7337 20.6389 15.9548 20.8003 16.2356C20.9616 16.5165 21.0278 16.842 20.9888 17.1636ZM19.5 16.9761C19.5 16.9761 19.4934 16.9761 19.4897 16.9761L15.0834 15.0017L12.8006 16.9442C12.7785 16.963 12.7553 16.9805 12.7313 16.9967C12.5154 17.1407 12.2659 17.2263 12.0071 17.245C11.7483 17.2638 11.489 17.215 11.2547 17.1036C9.49876 16.2552 7.74845 14.518 6.89907 12.7808C6.7866 12.5482 6.73613 12.2904 6.75255 12.0325C6.76898 11.7747 6.85174 11.5254 6.99282 11.3089C7.00872 11.2835 7.02659 11.2594 7.04626 11.2367L9.00001 8.92017L7.03126 4.51392C7.03089 4.51018 7.03089 4.50641 7.03126 4.50267C6.12212 4.62126 5.28739 5.0672 4.68339 5.75697C4.0794 6.44673 3.74755 7.33302 3.75001 8.24986C3.75348 11.4314 5.01888 14.4816 7.26856 16.7313C9.51825 18.981 12.5685 20.2464 15.75 20.2499C16.6663 20.253 17.5523 19.9223 18.2425 19.3196C18.9327 18.7169 19.3797 17.8835 19.5 16.9752V16.9761Z" fill="white"/>
<path d="M11.5076 5.75317C11.5075 5.6718 11.5235 5.59121 11.5546 5.51602C11.5858 5.44083 11.6314 5.37252 11.6889 5.31498C11.7465 5.25744 11.8148 5.2118 11.89 5.18068C11.9652 5.14956 12.0458 5.13357 12.1271 5.13361L15.8866 5.13416L14.7543 4.00176C14.6381 3.88563 14.5729 3.72813 14.5729 3.5639C14.5729 3.39968 14.6381 3.24218 14.7543 3.12605C14.8704 3.00993 15.0279 2.94469 15.1921 2.94469C15.3563 2.94469 15.5138 3.00993 15.63 3.12605L17.8192 5.31532C17.9354 5.43145 18.0006 5.58895 18.0006 5.75317C18.0006 5.9174 17.9354 6.0749 17.8192 6.19103L15.63 8.3803C15.5138 8.49642 15.3563 8.56166 15.1921 8.56166C15.0279 8.56166 14.8704 8.49642 14.7543 8.3803C14.6381 8.26417 14.5729 8.10667 14.5729 7.94244C14.5729 7.77822 14.6381 7.62071 14.7543 7.50459L15.8866 6.37219L12.1271 6.37274C12.0458 6.37278 11.9652 6.35679 11.89 6.32567C11.8148 6.29455 11.7465 6.24891 11.6889 6.19137C11.6314 6.13383 11.5858 6.06551 11.5546 5.99032C11.5235 5.91513 11.5075 5.83455 11.5076 5.75317Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -105,10 +105,6 @@ final class CoreContext: ObservableObject {
self.mCore.videoCaptureEnabled = true self.mCore.videoCaptureEnabled = true
self.mCore.videoDisplayEnabled = true self.mCore.videoDisplayEnabled = true
let videoActivationPolicy = self.mCore.videoActivationPolicy!
videoActivationPolicy.automaticallyAccept = true
self.mCore.videoActivationPolicy! = videoActivationPolicy
try? self.mCore.start() try? self.mCore.start()
// Create a Core listener to listen for the callback we need // Create a Core listener to listen for the callback we need

View file

@ -107,6 +107,9 @@
}, },
"+" : { "+" : {
},
"|" : {
}, },
"0" : { "0" : {
@ -268,6 +271,9 @@
}, },
"Deny all" : { "Deny all" : {
},
"Dialer" : {
}, },
"Display Name" : { "Display Name" : {
@ -415,9 +421,6 @@
}, },
"Outgoing Call" : { "Outgoing Call" : {
},
"Participants" : {
}, },
"password" : { "password" : {
"extractionState" : "manual", "extractionState" : "manual",
@ -438,6 +441,12 @@
}, },
"Pause" : { "Pause" : {
},
"Paused" : {
},
"Paused by remote" : {
}, },
"Personnalize your profil mode" : { "Personnalize your profil mode" : {
@ -474,9 +483,6 @@
}, },
"Scan QR code" : { "Scan QR code" : {
},
"Screen share" : {
}, },
"Search contact or history call" : { "Search contact or history call" : {
@ -540,6 +546,9 @@
}, },
"to Linphone" : { "to Linphone" : {
},
"Transfer" : {
}, },
"Transport" : { "Transport" : {

View file

@ -31,6 +31,8 @@ ec_calibrator_cool_tones=1
[video] [video]
auto_resize_preview_to_keep_ratio=1 auto_resize_preview_to_keep_ratio=1
max_conference_size=vga max_conference_size=vga
automatically_accept=1
automatically_initiate=0
[misc] [misc]
enable_basic_to_client_group_chat_room_migration=0 enable_basic_to_client_group_chat_room_migration=0

View file

@ -1,21 +1,21 @@
/* /*
* Copyright (c) 2010-2020 Belledonne Communications SARL. * Copyright (c) 2010-2020 Belledonne Communications SARL.
* *
* This file is part of linphone-iphone * This file is part of linphone-iphone
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
// swiftlint:disable line_length // swiftlint:disable line_length
import Foundation import Foundation
@ -56,19 +56,19 @@ class CallInfo {
} }
/* /*
* A delegate to support callkit. * A delegate to support callkit.
*/ */
class ProviderDelegate: NSObject { class ProviderDelegate: NSObject {
let provider: CXProvider let provider: CXProvider
var uuids: [String: UUID] = [:] var uuids: [String: UUID] = [:]
var callInfos: [UUID: CallInfo] = [:] var callInfos: [UUID: CallInfo] = [:]
override init() { override init() {
provider = CXProvider(configuration: ProviderDelegate.providerConfiguration) provider = CXProvider(configuration: ProviderDelegate.providerConfiguration)
super.init() super.init()
provider.setDelegate(self, queue: nil) provider.setDelegate(self, queue: nil)
} }
static var providerConfiguration: CXProviderConfiguration { static var providerConfiguration: CXProviderConfiguration {
get { get {
let providerConfiguration = CXProviderConfiguration() let providerConfiguration = CXProviderConfiguration()
@ -97,18 +97,18 @@ class ProviderDelegate: NSObject {
let callId = callInfo?.callId ?? "" let callId = callInfo?.callId ?? ""
/* /*
if (ConfigManager.instance().config?.hasEntry(section: "app", key: "max_calls") == 1) { // moved from misc to app section intentionally upon app start or remote configuration if (ConfigManager.instance().config?.hasEntry(section: "app", key: "max_calls") == 1) { // moved from misc to app section intentionally upon app start or remote configuration
if let maxCalls = ConfigManager.instance().config?.getInt(section: "app",key: "max_calls",defaultValue: 10), Core.get().callsNb > maxCalls { if let maxCalls = ConfigManager.instance().config?.getInt(section: "app",key: "max_calls",defaultValue: 10), Core.get().callsNb > maxCalls {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: declining call, as max calls (\(maxCalls)) reached call-id: [\(String(describing: callId))] and UUID: [\(uuid.description)]") Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: declining call, as max calls (\(maxCalls)) reached call-id: [\(String(describing: callId))] and UUID: [\(uuid.description)]")
decline(uuid: uuid) decline(uuid: uuid)
CoreContext.shared.doOnCoreQueue(synchronous: true) { core in CoreContext.shared.doOnCoreQueue(synchronous: true) { core in
try? call?.decline(reason: .Busy) try? call?.decline(reason: .Busy)
} }
return return
} }
} }
*/ */
Log.info("CallKit: report new incoming call with call-id: [\(callId)] and UUID: [\(uuid.description)]") Log.info("CallKit: report new incoming call with call-id: [\(callId)] and UUID: [\(uuid.description)]")
// TelecomManager.instance().setHeldOtherCalls(exceptCallid: callId ?? "") // ALREADY COMMENTED ON LINPHONE-IPHONE 5.2 // TelecomManager.instance().setHeldOtherCalls(exceptCallid: callId ?? "") // ALREADY COMMENTED ON LINPHONE-IPHONE 5.2
@ -140,7 +140,7 @@ class ProviderDelegate: NSObject {
} }
} }
} }
func updateCall(uuid: UUID, handle: String, hasVideo: Bool = false, displayName: String) { func updateCall(uuid: UUID, handle: String, hasVideo: Bool = false, displayName: String) {
let update = CXCallUpdate() let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: handle) update.remoteHandle = CXHandle(type: .generic, value: handle)
@ -148,11 +148,11 @@ class ProviderDelegate: NSObject {
update.hasVideo = hasVideo update.hasVideo = hasVideo
provider.reportCall(with: uuid, updated: update) provider.reportCall(with: uuid, updated: update)
} }
func reportOutgoingCallStartedConnecting(uuid: UUID) { func reportOutgoingCallStartedConnecting(uuid: UUID) {
provider.reportOutgoingCall(with: uuid, startedConnectingAt: nil) provider.reportOutgoingCall(with: uuid, startedConnectingAt: nil)
} }
func reportOutgoingCallConnected(uuid: UUID) { func reportOutgoingCallConnected(uuid: UUID) {
provider.reportOutgoingCall(with: uuid, connectedAt: nil) provider.reportOutgoingCall(with: uuid, connectedAt: nil)
} }
@ -164,7 +164,7 @@ class ProviderDelegate: NSObject {
func decline(uuid: UUID) { func decline(uuid: UUID) {
provider.reportCall(with: uuid, endedAt: .init(), reason: .unanswered) provider.reportCall(with: uuid, endedAt: .init(), reason: .unanswered)
} }
func endCallNotExist(uuid: UUID, timeout: DispatchTime) { func endCallNotExist(uuid: UUID, timeout: DispatchTime) {
DispatchQueue.main.asyncAfter(deadline: timeout) { DispatchQueue.main.asyncAfter(deadline: timeout) {
CoreContext.shared.doOnCoreQueue(synchronous: true) { core in CoreContext.shared.doOnCoreQueue(synchronous: true) { core in
@ -188,7 +188,7 @@ extension ProviderDelegate: CXProviderDelegate {
let uuid = action.callUUID let uuid = action.callUUID
let callId = callInfos[uuid]?.callId let callId = callInfos[uuid]?.callId
// remove call infos first, otherwise CXEndCallAction will be called more than onece // remove call infos first, otherwise CXEndCallAction will be called more than onece
if callId != nil { if callId != nil {
uuids.removeValue(forKey: callId!) uuids.removeValue(forKey: callId!)
@ -203,7 +203,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill() action.fulfill()
} }
} }
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
let uuid = action.callUUID let uuid = action.callUUID
let callInfo = callInfos[uuid] let callInfo = callInfos[uuid]
@ -221,15 +221,17 @@ extension ProviderDelegate: CXProviderDelegate {
let call = core.getCallByCallid(callId: callId) let call = core.getCallByCallid(callId: callId)
if UIApplication.shared.applicationState != .active { DispatchQueue.main.async() {
TelecomManager.shared.backgroundContextCall = call if UIApplication.shared.applicationState != .active {
TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true TelecomManager.shared.backgroundContextCall = call
if #available(iOS 16.0, *) { TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true
if call?.cameraEnabled == true { if #available(iOS 16.0, *) {
call?.cameraEnabled = AVCaptureSession().isMultitaskingCameraAccessSupported if call?.cameraEnabled == true {
call?.cameraEnabled = AVCaptureSession().isMultitaskingCameraAccessSupported
}
} else {
call?.cameraEnabled = false // Disable camera while app is not on foreground
} }
} else {
call?.cameraEnabled = false // Disable camera while app is not on foreground
} }
} }
TelecomManager.shared.callkitAudioSessionActivated = false TelecomManager.shared.callkitAudioSessionActivated = false
@ -242,7 +244,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill() action.fulfill()
} }
} }
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) { func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
let uuid = action.callUUID let uuid = action.callUUID
let callId = callInfos[uuid]?.callId ?? "" let callId = callInfos[uuid]?.callId ?? ""
@ -275,29 +277,29 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill() action.fulfill()
} else { } else {
if call?.conference != nil && core.callsNb > 1 {/* if call?.conference != nil && core.callsNb > 1 {/*
try TelecomManager.shared.lc?.enterConference() try TelecomManager.shared.lc?.enterConference()
action.fulfill() action.fulfill()
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self) NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
*/} else { */} else {
try call!.resume() try call!.resume()
// We'll notify callkit that the action is fulfilled when receiving the 200Ok, which is the point // We'll notify callkit that the action is fulfilled when receiving the 200Ok, which is the point
// where we actually start the media streams. // where we actually start the media streams.
TelecomManager.shared.actionToFulFill = action TelecomManager.shared.actionToFulFill = action
// HORRIBLE HACK HERE - PLEASE APPLE FIX THIS !! // HORRIBLE HACK HERE - PLEASE APPLE FIX THIS !!
// When resuming a SIP call after a native call has ended remotely, didActivate: audioSession // When resuming a SIP call after a native call has ended remotely, didActivate: audioSession
// is never called. // is never called.
// It looks like in this case, it is implicit. // It looks like in this case, it is implicit.
// As a result we have to notify the Core that the AudioSession is active. // As a result we have to notify the Core that the AudioSession is active.
// The SpeakerBox demo application written by Apple exhibits this behavior. // The SpeakerBox demo application written by Apple exhibits this behavior.
// https://developer.apple.com/documentation/callkit/making_and_receiving_voip_calls_with_callkit // https://developer.apple.com/documentation/callkit/making_and_receiving_voip_calls_with_callkit
// We can clearly see there that startAudio() is called immediately in the CXSetHeldCallAction // We can clearly see there that startAudio() is called immediately in the CXSetHeldCallAction
// handler, while it is called from didActivate: audioSession otherwise. // handler, while it is called from didActivate: audioSession otherwise.
// Callkit's design is not consistent, or its documentation imcomplete, wich is somewhat disapointing. // Callkit's design is not consistent, or its documentation imcomplete, wich is somewhat disapointing.
// //
Log.info("Assuming AudioSession is active when executing a CXSetHeldCallAction with isOnHold=false.") Log.info("Assuming AudioSession is active when executing a CXSetHeldCallAction with isOnHold=false.")
core.activateAudioSession(actived: true) core.activateAudioSession(actived: true)
TelecomManager.shared.callkitAudioSessionActivated = true TelecomManager.shared.callkitAudioSessionActivated = true
} }
} }
} }
} catch { } catch {
@ -332,7 +334,7 @@ extension ProviderDelegate: CXProviderDelegate {
} }
} }
} }
func provider(_ provider: CXProvider, perform action: CXSetGroupCallAction) { func provider(_ provider: CXProvider, perform action: CXSetGroupCallAction) {
CoreContext.shared.doOnCoreQueue { core in CoreContext.shared.doOnCoreQueue { core in
Log.info("CallKit: Call grouped callUUid : \(action.callUUID) with callUUID: \(String(describing: action.callUUIDToGroupWith)).") Log.info("CallKit: Call grouped callUUid : \(action.callUUID) with callUUID: \(String(describing: action.callUUIDToGroupWith)).")
@ -340,7 +342,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill() action.fulfill()
} }
} }
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
let uuid = action.callUUID let uuid = action.callUUID
let callId = callInfos[uuid]?.callId let callId = callInfos[uuid]?.callId
@ -350,7 +352,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill() action.fulfill()
} }
} }
func provider(_ provider: CXProvider, perform action: CXPlayDTMFCallAction) { func provider(_ provider: CXProvider, perform action: CXPlayDTMFCallAction) {
let uuid = action.callUUID let uuid = action.callUUID
let callId = callInfos[uuid]?.callId ?? "" let callId = callInfos[uuid]?.callId ?? ""
@ -368,18 +370,18 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill() action.fulfill()
} }
} }
func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) { func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
let uuid = action.uuid let uuid = action.uuid
let callId = callInfos[uuid]?.callId let callId = callInfos[uuid]?.callId
Log.error("CallKit: Call time out with call-id: \(String(describing: callId)) an UUID: \(uuid.description).") Log.error("CallKit: Call time out with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
action.fulfill() action.fulfill()
} }
func providerDidReset(_ provider: CXProvider) { func providerDidReset(_ provider: CXProvider) {
Log.info("CallKit: did reset.") Log.info("CallKit: did reset.")
} }
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
CoreContext.shared.doOnCoreQueue { core in CoreContext.shared.doOnCoreQueue { core in
Log.info("CallKit: audio session activated.") Log.info("CallKit: audio session activated.")
@ -387,7 +389,7 @@ extension ProviderDelegate: CXProviderDelegate {
TelecomManager.shared.callkitAudioSessionActivated = true TelecomManager.shared.callkitAudioSessionActivated = true
} }
} }
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
CoreContext.shared.doOnCoreQueue { core in CoreContext.shared.doOnCoreQueue { core in
Log.info("CallKit: audio session deactivated.") Log.info("CallKit: audio session deactivated.")

View file

@ -41,9 +41,11 @@ class TelecomManager: ObservableObject {
let callController: CXCallController // to support callkit let callController: CXCallController // to support callkit
@Published var callInProgress: Bool = false @Published var callInProgress: Bool = false
@Published var callStarted: Bool = false @Published var callStarted: Bool = false
@Published var outgoingCallStarted: Bool = false
@Published var remoteVideo: Bool = false @Published var remoteVideo: Bool = false
@Published var isRemoteRecording: Bool = false @Published var isRecordingByRemote: Bool = false
@Published var isPausedByRemote: Bool = false
var actionToFulFill: CXCallAction? var actionToFulFill: CXCallAction?
var callkitAudioSessionActivated: Bool? var callkitAudioSessionActivated: Bool?
@ -118,15 +120,15 @@ class TelecomManager: ObservableObject {
} }
} }
func doCallWithCore(addr: Address) { func doCallWithCore(addr: Address) {
CoreContext.shared.doOnCoreQueue { core in CoreContext.shared.doOnCoreQueue { core in
do { do {
try self.startCallCallKit(core: core, addr: addr, isSas: false, isVideo: false, isConference: false) try self.startCallCallKit(core: core, addr: addr, isSas: false, isVideo: false, isConference: false)
} catch { } catch {
Log.error("[TelecomManager] unable to create address for a new outgoing call : \(addr) \(error) ") Log.error("[TelecomManager] unable to create address for a new outgoing call : \(addr) \(error) ")
} }
} }
} }
private func makeRecordFilePath() -> String{ private func makeRecordFilePath() -> String{
var filePath = "recording_" var filePath = "recording_"
@ -140,25 +142,25 @@ class TelecomManager: ObservableObject {
let writablePath = paths[0] let writablePath = paths[0]
return writablePath.appending("/\(filePath)") return writablePath.appending("/\(filePath)")
} }
func doCall(core: Core, addr: Address, isSas: Bool, isVideo: Bool, isConference: Bool = false) throws { func doCall(core: Core, addr: Address, isSas: Bool, isVideo: Bool, isConference: Bool = false) throws {
// let displayName = FastAddressBook.displayName(for: addr.getCobject) // let displayName = FastAddressBook.displayName(for: addr.getCobject)
let lcallParams = try core.createCallParams(call: nil) let lcallParams = try core.createCallParams(call: nil)
/* /*
if ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference") && AppManager.network() == .network_2g { if ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference") && AppManager.network() == .network_2g {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Enabling low bandwidth mode") Log.directLog(BCTBX_LOG_MESSAGE, text: "Enabling low bandwidth mode")
lcallParams.lowBandwidthEnabled = true lcallParams.lowBandwidthEnabled = true
} }
if (displayName != nil) { if (displayName != nil) {
try addr.setDisplayname(newValue: displayName!) try addr.setDisplayname(newValue: displayName!)
} }
if(ConfigManager.instance().lpConfigBoolForKey(key: "override_domain_with_default_one")) { if(ConfigManager.instance().lpConfigBoolForKey(key: "override_domain_with_default_one")) {
try addr.setDomain(newValue: ConfigManager.instance().lpConfigStringForKey(key: "domain", section: "assistant")) try addr.setDomain(newValue: ConfigManager.instance().lpConfigStringForKey(key: "domain", section: "assistant"))
} }
*/ */
if nextCallIsTransfer { if nextCallIsTransfer {
let call = core.currentCall let call = core.currentCall
@ -176,13 +178,13 @@ class TelecomManager: ObservableObject {
lcallParams.mediaEncryption = .ZRTP lcallParams.mediaEncryption = .ZRTP
} }
if isConference { if isConference {
/* if (ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! != .AudioOnly) { /* if (ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! != .AudioOnly) {
lcallParams.videoEnabled = true lcallParams.videoEnabled = true
lcallParams.videoDirection = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true ? .SendRecv : .RecvOnly lcallParams.videoDirection = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true ? .SendRecv : .RecvOnly
lcallParams.conferenceVideoLayout = ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! == .Grid ? .Grid : .ActiveSpeaker lcallParams.conferenceVideoLayout = ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! == .Grid ? .Grid : .ActiveSpeaker
} else { } else {
lcallParams.videoEnabled = false lcallParams.videoEnabled = false
}*/ }*/
} else { } else {
lcallParams.videoEnabled = isVideo lcallParams.videoEnabled = isVideo
} }
@ -202,7 +204,7 @@ class TelecomManager: ObservableObject {
} }
DispatchQueue.main.async { DispatchQueue.main.async {
self.outgoingCallStarted = true
self.callStarted = true self.callStarted = true
if self.callInProgress == false { if self.callInProgress == false {
withAnimation { withAnimation {
@ -220,12 +222,12 @@ class TelecomManager: ObservableObject {
callParams.recordFile = makeRecordFilePath() callParams.recordFile = makeRecordFilePath()
callParams.videoEnabled = hasVideo callParams.videoEnabled = hasVideo
/*if (ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference")) { /*if (ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference")) {
let low_bandwidth = (AppManager.network() == .network_2g) let low_bandwidth = (AppManager.network() == .network_2g)
if (low_bandwidth) { if (low_bandwidth) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Low bandwidth mode") Log.directLog(BCTBX_LOG_MESSAGE, text: "Low bandwidth mode")
} }
callParams.lowBandwidthEnabled = low_bandwidth callParams.lowBandwidthEnabled = low_bandwidth
}*/ }*/
// We set the record file name here because we can't do it after the call is started. // We set the record file name here because we can't do it after the call is started.
// let address = call.callLog?.fromAddress // let address = call.callLog?.fromAddress
@ -234,10 +236,10 @@ class TelecomManager: ObservableObject {
// callParams.recordFile = writablePath // callParams.recordFile = writablePath
/* /*
if let chatView : ChatConversationView = PhoneMainView.instance().VIEW(ChatConversationView.compositeViewDescription()), chatView.isVoiceRecording { if let chatView : ChatConversationView = PhoneMainView.instance().VIEW(ChatConversationView.compositeViewDescription()), chatView.isVoiceRecording {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Voice recording in progress, stopping it befoce accepting the call.") Log.directLog(BCTBX_LOG_MESSAGE, text: "Voice recording in progress, stopping it befoce accepting the call.")
chatView.stopVoiceRecording() chatView.stopVoiceRecording()
}*/ }*/
if call.callLog?.wasConference() == true { if call.callLog?.wasConference() == true {
// Prevent incoming group call to start in audio only layout // Prevent incoming group call to start in audio only layout
@ -249,9 +251,9 @@ class TelecomManager: ObservableObject {
try call.acceptWithParams(params: callParams) try call.acceptWithParams(params: callParams)
DispatchQueue.main.async { DispatchQueue.main.async {
self.callStarted = true self.callStarted = true
} }
} catch { } catch {
Log.error("accept call failed \(error)") Log.error("accept call failed \(error)")
} }
@ -334,17 +336,18 @@ class TelecomManager: ObservableObject {
if cstate == .PushIncomingReceived { if cstate == .PushIncomingReceived {
displayIncomingCall(call: call, handle: "Calling", hasVideo: false, callId: callId, displayName: "Calling") displayIncomingCall(call: call, handle: "Calling", hasVideo: false, callId: callId, displayName: "Calling")
} else { } else {
remoteVideo = (core.videoActivationPolicy?.automaticallyAccept ?? false) && (call.remoteParams?.videoEnabled ?? false)
if remoteVideo { DispatchQueue.main.async {
Log.info("[Call] Remote video is activated") self.remoteVideo = (core.videoActivationPolicy?.automaticallyAccept ?? false) && (call.remoteParams?.videoEnabled ?? false)
}
isRemoteRecording = call.remoteParams?.isRecording ?? false
if isRemoteRecording && ToastViewModel.shared.toastMessage.isEmpty {
DispatchQueue.main.async { if self.remoteVideo {
Log.info("[Call] Remote video is activated")
}
self.isRecordingByRemote = call.remoteParams?.isRecording ?? false
if self.isRecordingByRemote && ToastViewModel.shared.toastMessage.isEmpty {
var displayName = "" var displayName = ""
let friend = ContactsManager.shared.getFriendWithAddress(address: call.remoteAddress!) let friend = ContactsManager.shared.getFriendWithAddress(address: call.remoteAddress!)
if friend != nil && friend!.address != nil && friend!.address!.displayName != nil { if friend != nil && friend!.address != nil && friend!.address!.displayName != nil {
@ -358,22 +361,27 @@ class TelecomManager: ObservableObject {
} }
ToastViewModel.shared.toastMessage = "\(displayName) is recording" ToastViewModel.shared.toastMessage = "\(displayName) is recording"
ToastViewModel.shared.displayToast = true ToastViewModel.shared.displayToast = true
Log.info("[Call] Call is recording by \(call.remoteAddress!.asStringUriOnly())")
} }
Log.info("[Call] Call is recording by \(call.remoteAddress!.asStringUriOnly())") if !self.isRecordingByRemote && ToastViewModel.shared.toastMessage.contains("is recording") {
}
if !isRemoteRecording && ToastViewModel.shared.toastMessage.contains("is recording") {
DispatchQueue.main.async {
withAnimation { withAnimation {
ToastViewModel.shared.toastMessage = "" ToastViewModel.shared.toastMessage = ""
ToastViewModel.shared.displayToast = false ToastViewModel.shared.displayToast = false
} }
Log.info("[Call] Recording is stopped by \(call.remoteAddress!.asStringUriOnly())")
} }
Log.info("[Call] Call is recording Stop recording by \(call.remoteAddress!.asStringUriOnly())") switch call.state {
case Call.State.PausedByRemote:
self.isPausedByRemote = true
default:
self.isPausedByRemote = false
}
} }
if call.userData == nil { if call.userData == nil {
@ -381,24 +389,28 @@ class TelecomManager: ObservableObject {
TelecomManager.setAppData(sCall: call, appData: appData) TelecomManager.setAppData(sCall: call, appData: appData)
} }
/* /*
if let conference = call.conference, ConferenceViewModel.shared.conference.value == nil { if let conference = call.conference, ConferenceViewModel.shared.conference.value == nil {
Log.info("[Call] Found conference attached to call and no conference in dedicated view model, init & configure it") Log.info("[Call] Found conference attached to call and no conference in dedicated view model, init & configure it")
ConferenceViewModel.shared.initConference(conference) ConferenceViewModel.shared.initConference(conference)
ConferenceViewModel.shared.configureConference(conference) ConferenceViewModel.shared.configureConference(conference)
} }
*/ */
switch cstate { switch cstate {
case .IncomingReceived: case .IncomingReceived:
let addr = call.remoteAddress let addr = call.remoteAddress
let displayName = incomingDisplayName(call: call) let displayName = incomingDisplayName(call: call)
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
DispatchQueue.main.async { DispatchQueue.main.async {
withAnimation { self.outgoingCallStarted = false
TelecomManager.shared.callInProgress = true self.callStarted = true
if self.callInProgress == false {
withAnimation {
self.callInProgress = true
}
} }
} }
#endif #endif
if call.replacedCall != nil { if call.replacedCall != nil {
endCallKitReplacedCall = false endCallKitReplacedCall = false
@ -415,17 +427,17 @@ class TelecomManager: ObservableObject {
} else if TelecomManager.callKitEnabled(core: core) { } else if TelecomManager.callKitEnabled(core: core) {
/* /*
let isConference = isConferenceCall(call: call) let isConference = isConferenceCall(call: call)
let isEarlyConference = isConference && CallsViewModel.shared.currentCallData.value??.isConferenceCall.value != true // Conference info not be received yet. let isEarlyConference = isConference && CallsViewModel.shared.currentCallData.value??.isConferenceCall.value != true // Conference info not be received yet.
if (isEarlyConference) { if (isEarlyConference) {
CallsViewModel.shared.currentCallData.readCurrentAndObserve { _ in CallsViewModel.shared.currentCallData.readCurrentAndObserve { _ in
let uuid = providerDelegate.uuids["\(callId)"] let uuid = providerDelegate.uuids["\(callId)"]
if (uuid != nil) { if (uuid != nil) {
displayName = "\(VoipTexts.conference_incoming_title): \(CallsViewModel.shared.currentCallData.value??.remoteConferenceSubject.value ?? "") (\(CallsViewModel.shared.currentCallData.value??.conferenceParticipantsCountLabel.value ?? ""))" displayName = "\(VoipTexts.conference_incoming_title): \(CallsViewModel.shared.currentCallData.value??.remoteConferenceSubject.value ?? "") (\(CallsViewModel.shared.currentCallData.value??.conferenceParticipantsCountLabel.value ?? ""))"
providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName) providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName)
} }
} }
} }
*/ */
let uuid = providerDelegate.uuids["\(callId)"] let uuid = providerDelegate.uuids["\(callId)"]
if call.replacedCall == nil { if call.replacedCall == nil {
TelecomManager.uuidReplacedCall = callId TelecomManager.uuidReplacedCall = callId
@ -438,18 +450,23 @@ class TelecomManager: ObservableObject {
displayIncomingCall(call: call, handle: addr!.asStringUriOnly(), hasVideo: remoteVideo, callId: callId, displayName: displayName) displayIncomingCall(call: call, handle: addr!.asStringUriOnly(), hasVideo: remoteVideo, callId: callId, displayName: displayName)
} }
} /* else if UIApplication.shared.applicationState != .active { } /* else if UIApplication.shared.applicationState != .active {
// not support callkit , use notif // not support callkit , use notif
let content = UNMutableNotificationContent() let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Incoming call", comment: "") content.title = NSLocalizedString("Incoming call", comment: "")
content.body = displayName content.body = displayName
content.sound = UNNotificationSound.init(named: UNNotificationSoundName.init("notes_of_the_optimistic.caf")) content.sound = UNNotificationSound.init(named: UNNotificationSoundName.init("notes_of_the_optimistic.caf"))
content.categoryIdentifier = "call_cat" content.categoryIdentifier = "call_cat"
content.userInfo = ["CallId": callId] content.userInfo = ["CallId": callId]
let req = UNNotificationRequest.init(identifier: "call_request", content: content, trigger: nil) let req = UNNotificationRequest.init(identifier: "call_request", content: content, trigger: nil)
UNUserNotificationCenter.current().add(req, withCompletionHandler: nil) UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
} */ } */
case .StreamsRunning: case .StreamsRunning:
if TelecomManager.callKitEnabled(core: core) { if TelecomManager.callKitEnabled(core: core) {
DispatchQueue.main.async {
self.outgoingCallStarted = false
}
let uuid = providerDelegate.uuids["\(callId)"] let uuid = providerDelegate.uuids["\(callId)"]
if uuid != nil { if uuid != nil {
let callInfo = providerDelegate.callInfos[uuid!] let callInfo = providerDelegate.callInfos[uuid!]
@ -463,10 +480,10 @@ class TelecomManager: ObservableObject {
} }
/* /*
if speakerBeforePause { if speakerBeforePause {
speakerBeforePause = false speakerBeforePause = false
AudioRouteUtils.routeAudioToSpeaker(core: core) AudioRouteUtils.routeAudioToSpeaker(core: core)
} }
*/ */
actionToFulFill?.fulfill() actionToFulFill?.fulfill()
@ -491,12 +508,12 @@ class TelecomManager: ObservableObject {
providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid!) providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid!)
} else { } else {
if false { /* isConferenceCall(call: call) { if false { /* isConferenceCall(call: call) {
let uuid = UUID() let uuid = UUID()
let callInfo = CallInfo.newOutgoingCallInfo(addr: call.remoteAddress!, isSas: call.params?.mediaEncryption == .ZRTP, displayName: VoipTexts.conference_default_title, isVideo: call.params?.videoEnabled == true, isConference:true) let callInfo = CallInfo.newOutgoingCallInfo(addr: call.remoteAddress!, isSas: call.params?.mediaEncryption == .ZRTP, displayName: VoipTexts.conference_default_title, isVideo: call.params?.videoEnabled == true, isConference:true)
providerDelegate.callInfos.updateValue(callInfo, forKey: uuid) providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
providerDelegate.uuids.updateValue(uuid, forKey: "") providerDelegate.uuids.updateValue(uuid, forKey: "")
providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid) providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid)
Core.get().activateAudioSession(actived: true) */ Core.get().activateAudioSession(actived: true) */
} else { } else {
referedToCall = callId referedToCall = callId
} }
@ -505,19 +522,6 @@ class TelecomManager: ObservableObject {
case .End, case .End,
.Error: .Error:
DispatchQueue.main.async {
withAnimation {
self.callInProgress = false
self.callStarted = false
}
}
var displayName = "Unknown"
if call.dir == .Incoming {
displayName = incomingDisplayName(call: call)
} else { // if let addr = call.remoteAddress, let contactName = FastAddressBook.displayName(for: addr.getCobject) {
displayName = "TODOContactName"
}
UIDevice.current.isProximityMonitoringEnabled = false UIDevice.current.isProximityMonitoringEnabled = false
if core.callsNb == 0 { if core.callsNb == 0 {
core.outputAudioDevice = core.defaultOutputAudioDevice core.outputAudioDevice = core.defaultOutputAudioDevice
@ -527,18 +531,34 @@ class TelecomManager: ObservableObject {
// bluetoothEnabled = false // bluetoothEnabled = false
} }
if UIApplication.shared.applicationState != .active && (callLog == nil || callLog?.status == .Missed || callLog?.status == .Aborted || callLog?.status == .EarlyAborted) { DispatchQueue.main.async {
// Configure the notification's payload. withAnimation {
let content = UNMutableNotificationContent() self.outgoingCallStarted = false
content.title = NSString.localizedUserNotificationString(forKey: NSLocalizedString("Missed call", comment: ""), arguments: nil) self.callInProgress = false
content.body = NSString.localizedUserNotificationString(forKey: displayName, arguments: nil) self.callStarted = false
}
// Deliver the notification. var displayName = "Unknown"
let request = UNNotificationRequest(identifier: "call_request", content: content, trigger: nil) // Schedule the notification. if call.dir == .Incoming {
let center = UNUserNotificationCenter.current() displayName = self.incomingDisplayName(call: call)
center.add(request) { (error: Error?) in } else { // if let addr = call.remoteAddress, let contactName = FastAddressBook.displayName(for: addr.getCobject) {
if error != nil { displayName = "TODOContactName"
Log.info("Error while adding notification request : \(error!.localizedDescription)") }
if UIApplication.shared.applicationState != .active && (callLog == nil || callLog?.status == .Missed || callLog?.status == .Aborted || callLog?.status == .EarlyAborted) {
// Configure the notification's payload.
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: NSLocalizedString("Missed call", comment: ""), arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: displayName, arguments: nil)
// Deliver the notification.
let request = UNNotificationRequest(identifier: "call_request", content: content, trigger: nil) // Schedule the notification.
let center = UNUserNotificationCenter.current()
center.add(request) { (error: Error?) in
if error != nil {
Log.info("Error while adding notification request : \(error!.localizedDescription)")
}
} }
} }
} }
@ -583,22 +603,6 @@ class TelecomManager: ObservableObject {
default: default:
break break
} }
// AudioRouteUtils.isBluetoothAvailable(core: core)
// AudioRouteUtils.isHeadsetAudioRouteAvailable(core: core)
// AudioRouteUtils.isBluetoothAudioRouteAvailable(core: core)
/*
let readyForRoutechange = callkitAudioSessionActivated == nil || (callkitAudioSessionActivated == true)
if readyForRoutechange && (cstate == .IncomingReceived || cstate == .OutgoingInit || cstate == .Connected || cstate == .StreamsRunning) {
if (call.currentParams?.videoEnabled ?? false) && AudioRouteUtils.isReceiverEnabled(core: core) && call.conference == nil {
AudioRouteUtils.routeAudioToSpeaker(core: core, call: call)
} else if AudioRouteUtils.isBluetoothAvailable(core: core) {
// Use bluetooth device by default if one is available
AudioRouteUtils.routeAudioToBluetooth(core: core, call: call)
}
}
*/
} }
// post Notification kLinphoneCallUpdate // post Notification kLinphoneCallUpdate
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self, userInfo: [ NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self, userInfo: [

View file

@ -24,10 +24,10 @@ import AVFAudio
import linphonesw import linphonesw
struct CallView: View { struct CallView: View {
@ObservedObject private var coreContext = CoreContext.shared @ObservedObject private var coreContext = CoreContext.shared
@ObservedObject private var telecomManager = TelecomManager.shared @ObservedObject private var telecomManager = TelecomManager.shared
@ObservedObject private var contactsManager = ContactsManager.shared @ObservedObject private var contactsManager = ContactsManager.shared
@ObservedObject var callViewModel: CallViewModel @ObservedObject var callViewModel: CallViewModel
@ -35,8 +35,8 @@ struct CallView: View {
@State private var orientation = UIDevice.current.orientation @State private var orientation = UIDevice.current.orientation
let pub = NotificationCenter.default.publisher(for: AVAudioSession.routeChangeNotification) let pub = NotificationCenter.default.publisher(for: AVAudioSession.routeChangeNotification)
@State var startDate = Date.now @State var startDate = Date.now
@State var audioRouteSheet: Bool = false @State var audioRouteSheet: Bool = false
@State var hideButtonsSheet: Bool = false @State var hideButtonsSheet: Bool = false
@State var options: Int = 1 @State var options: Int = 1
@ -45,10 +45,10 @@ struct CallView: View {
@State var angleDegree = 0.0 @State var angleDegree = 0.0
@State var fullscreenVideo = false @State var fullscreenVideo = false
var body: some View { var body: some View {
GeometryReader { geo in GeometryReader { geo in
if #available(iOS 16.4, *) { if #available(iOS 16.4, *) {
innerView(geometry: geo) innerView(geometry: geo)
.sheet(isPresented: .sheet(isPresented:
.constant( .constant(
@ -60,54 +60,55 @@ struct CallView: View {
) )
) { ) {
GeometryReader { _ in GeometryReader { _ in
VStack(spacing: 0) { VStack(spacing: 0) {
HStack(spacing: 12) { HStack(spacing: 12) {
Button { Button {
callViewModel.terminateCall() callViewModel.terminateCall()
} label: { } label: {
Image("phone-disconnect") Image("phone-disconnect")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
} }
.frame(width: 90, height: 60) .frame(width: 90, height: 60)
.background(Color.redDanger500) .background(Color.redDanger500)
.cornerRadius(40) .cornerRadius(40)
Spacer() Spacer()
Button { Button {
callViewModel.toggleVideo() callViewModel.toggleVideo()
} label: { } label: {
Image(callViewModel.cameraDisplayed ? "video-camera" : "video-camera-slash") Image(callViewModel.cameraDisplayed ? "video-camera" : "video-camera-slash")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
} }
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.background(Color.gray500) .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500)
.cornerRadius(40) .cornerRadius(40)
.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
Button {
Button {
callViewModel.toggleMuteMicrophone() callViewModel.toggleMuteMicrophone()
} label: { } label: {
Image(callViewModel.micMutted ? "microphone-slash" : "microphone") Image(callViewModel.micMutted ? "microphone-slash" : "microphone")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(callViewModel.micMutted ? .black : .white) .foregroundStyle(callViewModel.micMutted ? .black : .white)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
} }
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.background(callViewModel.micMutted ? .white : Color.gray500) .background(callViewModel.micMutted ? .white : Color.gray500)
.cornerRadius(40) .cornerRadius(40)
Button { Button {
if AVAudioSession.sharedInstance().availableInputs != nil if AVAudioSession.sharedInstance().availableInputs != nil
&& !AVAudioSession.sharedInstance().availableInputs!.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty { && !AVAudioSession.sharedInstance().availableInputs!.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty {
hideButtonsSheet = true hideButtonsSheet = true
@ -123,200 +124,206 @@ struct CallView: View {
} }
} }
} label: { } label: {
Image(imageAudioRoute) Image(imageAudioRoute)
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
.onAppear(perform: getAudioRouteImage) .onAppear(perform: getAudioRouteImage)
.onReceive(pub) { (output) in .onReceive(pub) { (output) in
self.getAudioRouteImage() self.getAudioRouteImage()
} }
} }
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.background(Color.gray500) .background(Color.gray500)
.cornerRadius(40) .cornerRadius(40)
} }
.frame(height: geo.size.height * 0.15) .frame(height: geo.size.height * 0.15)
.padding(.horizontal, 20) .padding(.horizontal, 20)
.padding(.top, -6) .padding(.top, -6)
HStack(spacing: 0) { HStack(spacing: 0) {
VStack { VStack {
Button { Button {
} label: { } label: {
Image("screencast") Image("phone-transfer")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
} }
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.background(Color.gray500) .background(Color.gray500)
.cornerRadius(40) .cornerRadius(40)
Text("Screen share") Text("Transfer")
.foregroundStyle(.white) .foregroundStyle(.white)
.default_text_style(styleSize: 15) .default_text_style(styleSize: 15)
} }
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
VStack { VStack {
Button { Button {
} label: { } label: {
Image("users") Image("phone-plus")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
} }
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.background(Color.gray500) .background(Color.gray500)
.cornerRadius(40) .cornerRadius(40)
Text("Participants") Text("New call")
.foregroundStyle(.white) .foregroundStyle(.white)
.default_text_style(styleSize: 15) .default_text_style(styleSize: 15)
} }
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
VStack { VStack {
Button { Button {
} label: { } label: {
Image("chat-teardrop-text") Image("phone-list")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
} }
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.background(Color.gray500) .background(Color.gray500)
.cornerRadius(40) .cornerRadius(40)
Text("Messages") Text("Call list")
.foregroundStyle(.white) .foregroundStyle(.white)
.default_text_style(styleSize: 15) .default_text_style(styleSize: 15)
} }
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
VStack { VStack {
Button { Button {
} label: { } label: {
Image("notebook") Image("dialer")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
} }
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.background(Color.gray500) .background(Color.gray500)
.cornerRadius(40) .cornerRadius(40)
Text("Disposition") Text("Dialer")
.foregroundStyle(.white) .foregroundStyle(.white)
.default_text_style(styleSize: 15) .default_text_style(styleSize: 15)
} }
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
} }
.frame(height: geo.size.height * 0.15) .frame(height: geo.size.height * 0.15)
HStack(spacing: 0) { HStack(spacing: 0) {
VStack { VStack {
Button { Button {
} label: { } label: {
Image("phone-call") Image("chat-teardrop-text")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) //.foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white)
.frame(width: 32, height: 32) .foregroundStyle(Color.gray500)
} .frame(width: 32, height: 32)
.frame(width: 60, height: 60) }
.background(Color.gray500) .frame(width: 60, height: 60)
.cornerRadius(40) //.background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500)
.background(Color.gray600)
Text("Call list") .cornerRadius(40)
.foregroundStyle(.white) //.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
.default_text_style(styleSize: 15) .disabled(true)
}
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) Text("Messages")
.foregroundStyle(.white)
VStack { .default_text_style(styleSize: 15)
Button { }
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
VStack {
Button {
callViewModel.togglePause() callViewModel.togglePause()
} label: { } label: {
Image("pause") Image(callViewModel.isPaused ? "play" : "pause")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) .foregroundStyle(telecomManager.isPausedByRemote ? Color.gray500 : .white)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
} }
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.background(Color.gray500) .background(telecomManager.isPausedByRemote ? Color.gray600 : (callViewModel.isPaused ? Color.greenSuccess500 : Color.gray500))
.cornerRadius(40) .cornerRadius(40)
.disabled(telecomManager.isPausedByRemote)
Text("Pause")
.foregroundStyle(.white) Text("Pause")
.default_text_style(styleSize: 15) .foregroundStyle(.white)
} .default_text_style(styleSize: 15)
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) }
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
VStack {
Button { VStack {
Button {
callViewModel.toggleRecording() callViewModel.toggleRecording()
} label: { } label: {
Image("record-fill") Image("record-fill")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
} }
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.background(callViewModel.isRecording ? Color.redDanger500 : Color.gray500) .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : (callViewModel.isRecording ? Color.redDanger500 : Color.gray500))
.cornerRadius(40) .cornerRadius(40)
.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
Text("Record")
.foregroundStyle(.white) Text("Record")
.default_text_style(styleSize: 15) .foregroundStyle(.white)
} .default_text_style(styleSize: 15)
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) }
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
VStack {
Button { VStack {
} label: { Button {
Image("video-camera") } label: {
.renderingMode(.template) Image("video-camera")
.resizable() .renderingMode(.template)
.foregroundStyle(.white) .resizable()
.frame(width: 32, height: 32) .foregroundStyle(.white)
} .frame(width: 32, height: 32)
.frame(width: 60, height: 60) }
.background(Color.gray500) .frame(width: 60, height: 60)
.cornerRadius(40) .background(Color.gray500)
.cornerRadius(40)
Text("Disposition")
.foregroundStyle(.white) Text("Disposition")
.default_text_style(styleSize: 15) .foregroundStyle(.white)
} .default_text_style(styleSize: 15)
.frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) }
.hidden() .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
} .hidden()
.frame(height: geo.size.height * 0.15) }
.frame(height: geo.size.height * 0.15)
Spacer()
} Spacer()
.frame(maxHeight: .infinity, alignment: .top) }
.frame(maxHeight: .infinity, alignment: .top)
.presentationBackground(.black) .presentationBackground(.black)
.presentationDetents([.fraction(0.1), .medium]) .presentationDetents([.fraction(0.1), .fraction(0.45)])
.interactiveDismissDisabled() .interactiveDismissDisabled()
.presentationBackgroundInteraction(.enabled) .presentationBackgroundInteraction(.enabled)
} }
} }
.sheet(isPresented: $audioRouteSheet, onDismiss: { .sheet(isPresented: $audioRouteSheet, onDismiss: {
audioRouteSheet = false audioRouteSheet = false
hideButtonsSheet = false hideButtonsSheet = false
}) { }) {
VStack(spacing: 0) { VStack(spacing: 0) {
Button(action: { Button(action: {
options = 1 options = 1
@ -346,9 +353,9 @@ struct CallView: View {
Image(!callViewModel.isHeadPhoneAvailable() ? "ear" : "headset") Image(!callViewModel.isHeadPhoneAvailable() ? "ear" : "headset")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(width: 25, height: 25, alignment: .leading) .frame(width: 25, height: 25, alignment: .leading)
} }
}) })
.frame(maxHeight: .infinity) .frame(maxHeight: .infinity)
@ -416,16 +423,16 @@ struct CallView: View {
} }
.padding(.horizontal, 20) .padding(.horizontal, 20)
.presentationBackground(Color.gray600) .presentationBackground(Color.gray600)
.presentationDetents([.fraction(0.3)]) .presentationDetents([.fraction(0.3)])
.frame(maxHeight: .infinity) .frame(maxHeight: .infinity)
} }
} }
} }
} }
@ViewBuilder @ViewBuilder
func innerView(geometry: GeometryProxy) -> some View { func innerView(geometry: GeometryProxy) -> some View {
VStack { VStack {
if !fullscreenVideo { if !fullscreenVideo {
Rectangle() Rectangle()
.foregroundColor(Color.orangeMain500) .foregroundColor(Color.orangeMain500)
@ -451,6 +458,35 @@ struct CallView: View {
.foregroundStyle(.white) .foregroundStyle(.white)
} }
if !telecomManager.outgoingCallStarted && telecomManager.callInProgress {
Text("|")
.foregroundStyle(.white)
ZStack {
Text(callViewModel.timeElapsed.convertDurationToString())
.onAppear {
callViewModel.timeElapsed = 0
startDate = Date.now
}
.onReceive(callViewModel.timer) { firedDate in
callViewModel.timeElapsed = Int(firedDate.timeIntervalSince(startDate))
}
.foregroundStyle(.white)
.if(callViewModel.isPaused || telecomManager.isPausedByRemote) { view in
view.hidden()
}
if callViewModel.isPaused {
Text("Paused")
.foregroundStyle(.white)
} else if telecomManager.isPausedByRemote {
Text("Paused by remote")
.foregroundStyle(.white)
}
}
}
Spacer() Spacer()
if callViewModel.cameraDisplayed { if callViewModel.cameraDisplayed {
@ -469,65 +505,65 @@ struct CallView: View {
.frame(height: 40) .frame(height: 40)
.zIndex(1) .zIndex(1)
} }
ZStack { ZStack {
VStack { VStack {
Spacer() Spacer()
if callViewModel.remoteAddress != nil { if callViewModel.remoteAddress != nil {
let addressFriend = contactsManager.getFriendWithAddress(address: callViewModel.remoteAddress!) let addressFriend = contactsManager.getFriendWithAddress(address: callViewModel.remoteAddress!)
let contactAvatarModel = addressFriend != nil let contactAvatarModel = addressFriend != nil
? ContactsManager.shared.avatarListModel.first(where: { ? ContactsManager.shared.avatarListModel.first(where: {
($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy) ($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy)
&& $0.friend!.name == addressFriend!.name && $0.friend!.name == addressFriend!.name
&& $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly() && $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly()
}) })
: ContactAvatarModel(friend: nil, withPresence: false) : ContactAvatarModel(friend: nil, withPresence: false)
if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty { if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty {
if contactAvatarModel != nil { if contactAvatarModel != nil {
Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 100, hidePresence: true) Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 100, hidePresence: true)
} }
} else { } else {
if callViewModel.remoteAddress!.displayName != nil { if callViewModel.remoteAddress!.displayName != nil {
Image(uiImage: contactsManager.textToImage( Image(uiImage: contactsManager.textToImage(
firstName: callViewModel.remoteAddress!.displayName!, firstName: callViewModel.remoteAddress!.displayName!,
lastName: callViewModel.remoteAddress!.displayName!.components(separatedBy: " ").count > 1 lastName: callViewModel.remoteAddress!.displayName!.components(separatedBy: " ").count > 1
? callViewModel.remoteAddress!.displayName!.components(separatedBy: " ")[1] ? callViewModel.remoteAddress!.displayName!.components(separatedBy: " ")[1]
: "")) : ""))
.resizable() .resizable()
.frame(width: 100, height: 100) .frame(width: 100, height: 100)
.clipShape(Circle()) .clipShape(Circle())
} else { } else {
Image(uiImage: contactsManager.textToImage( Image(uiImage: contactsManager.textToImage(
firstName: callViewModel.remoteAddress!.username ?? "Username Error", firstName: callViewModel.remoteAddress!.username ?? "Username Error",
lastName: callViewModel.remoteAddress!.username!.components(separatedBy: " ").count > 1 lastName: callViewModel.remoteAddress!.username!.components(separatedBy: " ").count > 1
? callViewModel.remoteAddress!.username!.components(separatedBy: " ")[1] ? callViewModel.remoteAddress!.username!.components(separatedBy: " ")[1]
: "")) : ""))
.resizable() .resizable()
.frame(width: 100, height: 100) .frame(width: 100, height: 100)
.clipShape(Circle()) .clipShape(Circle())
} }
} }
} else { } else {
Image("profil-picture-default") Image("profil-picture-default")
.resizable() .resizable()
.frame(width: 100, height: 100) .frame(width: 100, height: 100)
.clipShape(Circle()) .clipShape(Circle())
} }
Text(callViewModel.displayName) Text(callViewModel.displayName)
.padding(.top) .padding(.top)
.foregroundStyle(.white) .foregroundStyle(.white)
Text(callViewModel.remoteAddressString) Text(callViewModel.remoteAddressString)
.foregroundStyle(.white) .foregroundStyle(.white)
Spacer() Spacer()
} }
LinphoneVideoViewHolder { view in LinphoneVideoViewHolder { view in
coreContext.doOnCoreQueue { core in coreContext.doOnCoreQueue { core in
@ -535,7 +571,7 @@ struct CallView: View {
} }
} }
.frame( .frame(
width: width:
angleDegree == 0 angleDegree == 0
? 120 * ((geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) / 160) ? 120 * ((geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) / 160)
: 120 * ((geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) / 120), : 120 * ((geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) / 120),
@ -577,8 +613,8 @@ struct CallView: View {
VStack { VStack {
Image("record-fill") Image("record-fill")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(Color.redDanger500) .foregroundStyle(Color.redDanger500)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
.padding(10) .padding(10)
.if(fullscreenVideo) { view in .if(fullscreenVideo) { view in
@ -594,31 +630,39 @@ struct CallView: View {
) )
} }
if !telecomManager.callStarted && !fullscreenVideo { if telecomManager.outgoingCallStarted {
VStack { VStack {
ActivityIndicator() ActivityIndicator()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.padding(.top, 100) .padding(.top, 100)
Text(callViewModel.counterToMinutes()) Text(callViewModel.counterToMinutes())
.onAppear {
callViewModel.timeElapsed = 0
startDate = Date.now
}
.onReceive(callViewModel.timer) { firedDate in .onReceive(callViewModel.timer) { firedDate in
callViewModel.timeElapsed = Int(firedDate.timeIntervalSince(startDate)) callViewModel.timeElapsed = Int(firedDate.timeIntervalSince(startDate))
} }
.padding(.top) .padding(.top)
.foregroundStyle(.white) .foregroundStyle(.white)
Spacer() Spacer()
} }
.background(.clear) .frame(
} maxWidth: fullscreenVideo ? geometry.size.width : geometry.size.width - 8,
} maxHeight: fullscreenVideo ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - 140
)
.background(.clear)
}
}
.frame( .frame(
maxWidth: fullscreenVideo ? geometry.size.width : geometry.size.width - 8, maxWidth: fullscreenVideo ? geometry.size.width : geometry.size.width - 8,
maxHeight: fullscreenVideo ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - 140 maxHeight: fullscreenVideo ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - 140
) )
.background(Color.gray600) .background(Color.gray600)
.cornerRadius(20) .cornerRadius(20)
.padding(.horizontal, fullscreenVideo ? 0 : 4) .padding(.horizontal, fullscreenVideo ? 0 : 4)
.onRotate { newOrientation in .onRotate { newOrientation in
orientation = newOrientation orientation = newOrientation
@ -647,7 +691,7 @@ struct CallView: View {
callViewModel.orientationUpdate(orientation: orientation) callViewModel.orientationUpdate(orientation: orientation)
} }
if !fullscreenVideo { if !fullscreenVideo {
if telecomManager.callStarted { if telecomManager.callStarted {
if telecomManager.callStarted && idiom != .pad && !(orientation == .landscapeLeft || orientation == .landscapeRight if telecomManager.callStarted && idiom != .pad && !(orientation == .landscapeLeft || orientation == .landscapeRight
@ -684,13 +728,14 @@ struct CallView: View {
Image("video-camera") Image("video-camera")
.renderingMode(.template) .renderingMode(.template)
.resizable() .resizable()
.foregroundStyle(.white) .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
} }
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.background(Color.gray500) .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500)
.cornerRadius(40) .cornerRadius(40)
.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
Button { Button {
callViewModel.toggleMuteMicrophone() callViewModel.toggleMuteMicrophone()
@ -764,13 +809,13 @@ struct CallView: View {
.padding(.top, 20) .padding(.top, 20)
} }
} }
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray900) .background(Color.gray900)
.if(fullscreenVideo) { view in .if(fullscreenVideo) { view in
view.ignoresSafeArea(.all) view.ignoresSafeArea(.all)
} }
} }
func getAudioRouteImage() { func getAudioRouteImage() {
imageAudioRoute = AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue == "Speaker" }).isEmpty imageAudioRoute = AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue == "Speaker" }).isEmpty

View file

@ -35,7 +35,8 @@ class CallViewModel: ObservableObject {
@Published var cameraDisplayed: Bool = false @Published var cameraDisplayed: Bool = false
@Published var isRecording: Bool = false @Published var isRecording: Bool = false
@Published var isRemoteRecording: Bool = false @Published var isRemoteRecording: Bool = false
@State var timeElapsed: Int = 0 @Published var isPaused: Bool = false
@Published var timeElapsed: Int = 0
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@ -76,6 +77,8 @@ class CallViewModel: ObservableObject {
self.micMutted = self.currentCall!.microphoneMuted self.micMutted = self.currentCall!.microphoneMuted
self.cameraDisplayed = self.currentCall!.cameraEnabled == true self.cameraDisplayed = self.currentCall!.cameraEnabled == true
self.isRecording = self.currentCall!.params!.isRecording self.isRecording = self.currentCall!.params!.isRecording
self.isPaused = self.isCallPaused()
self.timeElapsed = 0
} }
} }
} }
@ -83,8 +86,9 @@ class CallViewModel: ObservableObject {
func terminateCall() { func terminateCall() {
withAnimation { withAnimation {
telecomManager.callInProgress = false telecomManager.outgoingCallStarted = false
telecomManager.callStarted = false telecomManager.callStarted = false
telecomManager.callInProgress = false
} }
coreContext.doOnCoreQueue { _ in coreContext.doOnCoreQueue { _ in
@ -98,6 +102,7 @@ class CallViewModel: ObservableObject {
func acceptCall() { func acceptCall() {
withAnimation { withAnimation {
telecomManager.outgoingCallStarted = false
telecomManager.callInProgress = true telecomManager.callInProgress = true
telecomManager.callStarted = true telecomManager.callStarted = true
} }
@ -184,9 +189,11 @@ class CallViewModel: ObservableObject {
if self.isCallPaused() { if self.isCallPaused() {
Log.info("[CallViewModel] Resuming call \(self.currentCall!.remoteAddress!.asStringUriOnly())") Log.info("[CallViewModel] Resuming call \(self.currentCall!.remoteAddress!.asStringUriOnly())")
try self.currentCall!.resume() try self.currentCall!.resume()
self.isPaused = false
} else { } else {
Log.info("[CallViewModel] Pausing call \(self.currentCall!.remoteAddress!.asStringUriOnly())") Log.info("[CallViewModel] Pausing call \(self.currentCall!.remoteAddress!.asStringUriOnly())")
try self.currentCall!.pause() try self.currentCall!.pause()
self.isPaused = true
} }
} catch _ { } catch _ {
@ -195,7 +202,7 @@ class CallViewModel: ObservableObject {
} }
} }
private func isCallPaused() -> Bool { func isCallPaused() -> Bool {
var result = false var result = false
if self.currentCall != nil { if self.currentCall != nil {
switch self.currentCall!.state { switch self.currentCall!.state {