diff --git a/Linphone/Assets.xcassets/phone-list.imageset/Contents.json b/Linphone/Assets.xcassets/phone-list.imageset/Contents.json
new file mode 100644
index 000000000..93d7f6f6b
--- /dev/null
+++ b/Linphone/Assets.xcassets/phone-list.imageset/Contents.json
@@ -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
+ }
+}
diff --git a/Linphone/Assets.xcassets/phone-list.imageset/phone-list.svg b/Linphone/Assets.xcassets/phone-list.imageset/phone-list.svg
new file mode 100644
index 000000000..d070e2710
--- /dev/null
+++ b/Linphone/Assets.xcassets/phone-list.imageset/phone-list.svg
@@ -0,0 +1,6 @@
+
diff --git a/Linphone/Assets.xcassets/phone-transfer.imageset/Contents.json b/Linphone/Assets.xcassets/phone-transfer.imageset/Contents.json
new file mode 100644
index 000000000..702f535c8
--- /dev/null
+++ b/Linphone/Assets.xcassets/phone-transfer.imageset/Contents.json
@@ -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
+ }
+}
diff --git a/Linphone/Assets.xcassets/phone-transfer.imageset/phone-transfer.svg b/Linphone/Assets.xcassets/phone-transfer.imageset/phone-transfer.svg
new file mode 100644
index 000000000..c63342fd6
--- /dev/null
+++ b/Linphone/Assets.xcassets/phone-transfer.imageset/phone-transfer.svg
@@ -0,0 +1,4 @@
+
diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift
index 736500641..a07c6b926 100644
--- a/Linphone/Core/CoreContext.swift
+++ b/Linphone/Core/CoreContext.swift
@@ -105,10 +105,6 @@ final class CoreContext: ObservableObject {
self.mCore.videoCaptureEnabled = true
self.mCore.videoDisplayEnabled = true
- let videoActivationPolicy = self.mCore.videoActivationPolicy!
- videoActivationPolicy.automaticallyAccept = true
- self.mCore.videoActivationPolicy! = videoActivationPolicy
-
try? self.mCore.start()
// Create a Core listener to listen for the callback we need
diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings
index 467b2feee..8dc0dee51 100644
--- a/Linphone/Localizable.xcstrings
+++ b/Linphone/Localizable.xcstrings
@@ -107,6 +107,9 @@
},
"+" : {
+ },
+ "|" : {
+
},
"0" : {
@@ -268,6 +271,9 @@
},
"Deny all" : {
+ },
+ "Dialer" : {
+
},
"Display Name" : {
@@ -415,9 +421,6 @@
},
"Outgoing Call" : {
- },
- "Participants" : {
-
},
"password" : {
"extractionState" : "manual",
@@ -438,6 +441,12 @@
},
"Pause" : {
+ },
+ "Paused" : {
+
+ },
+ "Paused by remote" : {
+
},
"Personnalize your profil mode" : {
@@ -474,9 +483,6 @@
},
"Scan QR code" : {
- },
- "Screen share" : {
-
},
"Search contact or history call" : {
@@ -540,6 +546,9 @@
},
"to Linphone" : {
+ },
+ "Transfer" : {
+
},
"Transport" : {
diff --git a/Linphone/Ressources/linphonerc-factory b/Linphone/Ressources/linphonerc-factory
index 4074322fe..0abf8269d 100644
--- a/Linphone/Ressources/linphonerc-factory
+++ b/Linphone/Ressources/linphonerc-factory
@@ -31,6 +31,8 @@ ec_calibrator_cool_tones=1
[video]
auto_resize_preview_to_keep_ratio=1
max_conference_size=vga
+automatically_accept=1
+automatically_initiate=0
[misc]
enable_basic_to_client_group_chat_room_migration=0
diff --git a/Linphone/TelecomManager/ProviderDelegate.swift b/Linphone/TelecomManager/ProviderDelegate.swift
index 55ff1cf40..44ef6095e 100644
--- a/Linphone/TelecomManager/ProviderDelegate.swift
+++ b/Linphone/TelecomManager/ProviderDelegate.swift
@@ -1,21 +1,21 @@
/*
-* Copyright (c) 2010-2020 Belledonne Communications SARL.
-*
-* This file is part of linphone-iphone
-*
-* 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
-* the Free Software Foundation, either version 3 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with this program. If not, see .
-*/
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
// swiftlint:disable line_length
import Foundation
@@ -56,19 +56,19 @@ class CallInfo {
}
/*
-* A delegate to support callkit.
-*/
+ * A delegate to support callkit.
+ */
class ProviderDelegate: NSObject {
let provider: CXProvider
var uuids: [String: UUID] = [:]
var callInfos: [UUID: CallInfo] = [:]
-
+
override init() {
provider = CXProvider(configuration: ProviderDelegate.providerConfiguration)
super.init()
provider.setDelegate(self, queue: nil)
}
-
+
static var providerConfiguration: CXProviderConfiguration {
get {
let providerConfiguration = CXProviderConfiguration()
@@ -97,18 +97,18 @@ class ProviderDelegate: NSObject {
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 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)]")
- decline(uuid: uuid)
-
- CoreContext.shared.doOnCoreQueue(synchronous: true) { core in
- try? call?.decline(reason: .Busy)
- }
- return
- }
- }
- */
+ 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 {
+ 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)
+
+ CoreContext.shared.doOnCoreQueue(synchronous: true) { core in
+ try? call?.decline(reason: .Busy)
+ }
+ return
+ }
+ }
+ */
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
@@ -140,7 +140,7 @@ class ProviderDelegate: NSObject {
}
}
}
-
+
func updateCall(uuid: UUID, handle: String, hasVideo: Bool = false, displayName: String) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: handle)
@@ -148,11 +148,11 @@ class ProviderDelegate: NSObject {
update.hasVideo = hasVideo
provider.reportCall(with: uuid, updated: update)
}
-
+
func reportOutgoingCallStartedConnecting(uuid: UUID) {
provider.reportOutgoingCall(with: uuid, startedConnectingAt: nil)
}
-
+
func reportOutgoingCallConnected(uuid: UUID) {
provider.reportOutgoingCall(with: uuid, connectedAt: nil)
}
@@ -164,7 +164,7 @@ class ProviderDelegate: NSObject {
func decline(uuid: UUID) {
provider.reportCall(with: uuid, endedAt: .init(), reason: .unanswered)
}
-
+
func endCallNotExist(uuid: UUID, timeout: DispatchTime) {
DispatchQueue.main.asyncAfter(deadline: timeout) {
CoreContext.shared.doOnCoreQueue(synchronous: true) { core in
@@ -188,7 +188,7 @@ extension ProviderDelegate: CXProviderDelegate {
let uuid = action.callUUID
let callId = callInfos[uuid]?.callId
-
+
// remove call infos first, otherwise CXEndCallAction will be called more than onece
if callId != nil {
uuids.removeValue(forKey: callId!)
@@ -203,7 +203,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
}
}
-
+
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
let uuid = action.callUUID
let callInfo = callInfos[uuid]
@@ -221,15 +221,17 @@ extension ProviderDelegate: CXProviderDelegate {
let call = core.getCallByCallid(callId: callId)
- if UIApplication.shared.applicationState != .active {
- TelecomManager.shared.backgroundContextCall = call
- TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true
- if #available(iOS 16.0, *) {
- if call?.cameraEnabled == true {
- call?.cameraEnabled = AVCaptureSession().isMultitaskingCameraAccessSupported
+ DispatchQueue.main.async() {
+ if UIApplication.shared.applicationState != .active {
+ TelecomManager.shared.backgroundContextCall = call
+ TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true
+ if #available(iOS 16.0, *) {
+ 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
@@ -242,7 +244,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
}
}
-
+
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
let uuid = action.callUUID
let callId = callInfos[uuid]?.callId ?? ""
@@ -275,29 +277,29 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
} else {
if call?.conference != nil && core.callsNb > 1 {/*
- try TelecomManager.shared.lc?.enterConference()
- action.fulfill()
- NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
- */} else {
- try call!.resume()
- // We'll notify callkit that the action is fulfilled when receiving the 200Ok, which is the point
- // where we actually start the media streams.
- TelecomManager.shared.actionToFulFill = action
- // HORRIBLE HACK HERE - PLEASE APPLE FIX THIS !!
- // When resuming a SIP call after a native call has ended remotely, didActivate: audioSession
- // is never called.
- // It looks like in this case, it is implicit.
- // As a result we have to notify the Core that the AudioSession is active.
- // The SpeakerBox demo application written by Apple exhibits this behavior.
- // 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
- // handler, while it is called from didActivate: audioSession otherwise.
- // 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.")
- core.activateAudioSession(actived: true)
- TelecomManager.shared.callkitAudioSessionActivated = true
- }
+ try TelecomManager.shared.lc?.enterConference()
+ action.fulfill()
+ NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
+ */} else {
+ try call!.resume()
+ // We'll notify callkit that the action is fulfilled when receiving the 200Ok, which is the point
+ // where we actually start the media streams.
+ TelecomManager.shared.actionToFulFill = action
+ // HORRIBLE HACK HERE - PLEASE APPLE FIX THIS !!
+ // When resuming a SIP call after a native call has ended remotely, didActivate: audioSession
+ // is never called.
+ // It looks like in this case, it is implicit.
+ // As a result we have to notify the Core that the AudioSession is active.
+ // The SpeakerBox demo application written by Apple exhibits this behavior.
+ // 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
+ // handler, while it is called from didActivate: audioSession otherwise.
+ // 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.")
+ core.activateAudioSession(actived: true)
+ TelecomManager.shared.callkitAudioSessionActivated = true
+ }
}
}
} catch {
@@ -332,7 +334,7 @@ extension ProviderDelegate: CXProviderDelegate {
}
}
}
-
+
func provider(_ provider: CXProvider, perform action: CXSetGroupCallAction) {
CoreContext.shared.doOnCoreQueue { core in
Log.info("CallKit: Call grouped callUUid : \(action.callUUID) with callUUID: \(String(describing: action.callUUIDToGroupWith)).")
@@ -340,7 +342,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
}
}
-
+
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
let uuid = action.callUUID
let callId = callInfos[uuid]?.callId
@@ -350,7 +352,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
}
}
-
+
func provider(_ provider: CXProvider, perform action: CXPlayDTMFCallAction) {
let uuid = action.callUUID
let callId = callInfos[uuid]?.callId ?? ""
@@ -368,18 +370,18 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
}
}
-
+
func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
let uuid = action.uuid
let callId = callInfos[uuid]?.callId
Log.error("CallKit: Call time out with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
action.fulfill()
}
-
+
func providerDidReset(_ provider: CXProvider) {
Log.info("CallKit: did reset.")
}
-
+
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
CoreContext.shared.doOnCoreQueue { core in
Log.info("CallKit: audio session activated.")
@@ -387,7 +389,7 @@ extension ProviderDelegate: CXProviderDelegate {
TelecomManager.shared.callkitAudioSessionActivated = true
}
}
-
+
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
CoreContext.shared.doOnCoreQueue { core in
Log.info("CallKit: audio session deactivated.")
diff --git a/Linphone/TelecomManager/TelecomManager.swift b/Linphone/TelecomManager/TelecomManager.swift
index 22372bc1a..506ba21e4 100644
--- a/Linphone/TelecomManager/TelecomManager.swift
+++ b/Linphone/TelecomManager/TelecomManager.swift
@@ -41,9 +41,11 @@ class TelecomManager: ObservableObject {
let callController: CXCallController // to support callkit
@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 isRemoteRecording: Bool = false
+ @Published var isRecordingByRemote: Bool = false
+ @Published var isPausedByRemote: Bool = false
var actionToFulFill: CXCallAction?
var callkitAudioSessionActivated: Bool?
@@ -118,15 +120,15 @@ class TelecomManager: ObservableObject {
}
}
- func doCallWithCore(addr: Address) {
- CoreContext.shared.doOnCoreQueue { core in
+ func doCallWithCore(addr: Address) {
+ CoreContext.shared.doOnCoreQueue { core in
do {
try self.startCallCallKit(core: core, addr: addr, isSas: false, isVideo: false, isConference: false)
} catch {
Log.error("[TelecomManager] unable to create address for a new outgoing call : \(addr) \(error) ")
}
- }
- }
+ }
+ }
private func makeRecordFilePath() -> String{
var filePath = "recording_"
@@ -140,25 +142,25 @@ class TelecomManager: ObservableObject {
let writablePath = paths[0]
return writablePath.appending("/\(filePath)")
}
-
+
func doCall(core: Core, addr: Address, isSas: Bool, isVideo: Bool, isConference: Bool = false) throws {
// let displayName = FastAddressBook.displayName(for: addr.getCobject)
let lcallParams = try core.createCallParams(call: nil)
/*
- if ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference") && AppManager.network() == .network_2g {
- Log.directLog(BCTBX_LOG_MESSAGE, text: "Enabling low bandwidth mode")
- lcallParams.lowBandwidthEnabled = true
- }
-
- if (displayName != nil) {
- try addr.setDisplayname(newValue: displayName!)
- }
-
- if(ConfigManager.instance().lpConfigBoolForKey(key: "override_domain_with_default_one")) {
- try addr.setDomain(newValue: ConfigManager.instance().lpConfigStringForKey(key: "domain", section: "assistant"))
- }
- */
+ if ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference") && AppManager.network() == .network_2g {
+ Log.directLog(BCTBX_LOG_MESSAGE, text: "Enabling low bandwidth mode")
+ lcallParams.lowBandwidthEnabled = true
+ }
+
+ if (displayName != nil) {
+ try addr.setDisplayname(newValue: displayName!)
+ }
+
+ if(ConfigManager.instance().lpConfigBoolForKey(key: "override_domain_with_default_one")) {
+ try addr.setDomain(newValue: ConfigManager.instance().lpConfigStringForKey(key: "domain", section: "assistant"))
+ }
+ */
if nextCallIsTransfer {
let call = core.currentCall
@@ -176,13 +178,13 @@ class TelecomManager: ObservableObject {
lcallParams.mediaEncryption = .ZRTP
}
if isConference {
- /* if (ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! != .AudioOnly) {
- lcallParams.videoEnabled = true
- lcallParams.videoDirection = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true ? .SendRecv : .RecvOnly
- lcallParams.conferenceVideoLayout = ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! == .Grid ? .Grid : .ActiveSpeaker
- } else {
- lcallParams.videoEnabled = false
- }*/
+ /* if (ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! != .AudioOnly) {
+ lcallParams.videoEnabled = true
+ lcallParams.videoDirection = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true ? .SendRecv : .RecvOnly
+ lcallParams.conferenceVideoLayout = ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! == .Grid ? .Grid : .ActiveSpeaker
+ } else {
+ lcallParams.videoEnabled = false
+ }*/
} else {
lcallParams.videoEnabled = isVideo
}
@@ -202,7 +204,7 @@ class TelecomManager: ObservableObject {
}
DispatchQueue.main.async {
-
+ self.outgoingCallStarted = true
self.callStarted = true
if self.callInProgress == false {
withAnimation {
@@ -220,12 +222,12 @@ class TelecomManager: ObservableObject {
callParams.recordFile = makeRecordFilePath()
callParams.videoEnabled = hasVideo
/*if (ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference")) {
- let low_bandwidth = (AppManager.network() == .network_2g)
- if (low_bandwidth) {
- Log.directLog(BCTBX_LOG_MESSAGE, text: "Low bandwidth mode")
- }
- callParams.lowBandwidthEnabled = low_bandwidth
- }*/
+ let low_bandwidth = (AppManager.network() == .network_2g)
+ if (low_bandwidth) {
+ Log.directLog(BCTBX_LOG_MESSAGE, text: "Low bandwidth mode")
+ }
+ callParams.lowBandwidthEnabled = low_bandwidth
+ }*/
// We set the record file name here because we can't do it after the call is started.
// let address = call.callLog?.fromAddress
@@ -234,10 +236,10 @@ class TelecomManager: ObservableObject {
// callParams.recordFile = writablePath
/*
- 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.")
- chatView.stopVoiceRecording()
- }*/
+ 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.")
+ chatView.stopVoiceRecording()
+ }*/
if call.callLog?.wasConference() == true {
// Prevent incoming group call to start in audio only layout
@@ -249,9 +251,9 @@ class TelecomManager: ObservableObject {
try call.acceptWithParams(params: callParams)
- DispatchQueue.main.async {
- self.callStarted = true
- }
+ DispatchQueue.main.async {
+ self.callStarted = true
+ }
} catch {
Log.error("accept call failed \(error)")
}
@@ -334,17 +336,18 @@ class TelecomManager: ObservableObject {
if cstate == .PushIncomingReceived {
displayIncomingCall(call: call, handle: "Calling", hasVideo: false, callId: callId, displayName: "Calling")
} else {
- remoteVideo = (core.videoActivationPolicy?.automaticallyAccept ?? false) && (call.remoteParams?.videoEnabled ?? false)
- if remoteVideo {
- Log.info("[Call] Remote video is activated")
- }
-
- isRemoteRecording = call.remoteParams?.isRecording ?? false
-
- if isRemoteRecording && ToastViewModel.shared.toastMessage.isEmpty {
+ DispatchQueue.main.async {
+ self.remoteVideo = (core.videoActivationPolicy?.automaticallyAccept ?? false) && (call.remoteParams?.videoEnabled ?? false)
- 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 = ""
let friend = ContactsManager.shared.getFriendWithAddress(address: call.remoteAddress!)
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.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 !isRemoteRecording && ToastViewModel.shared.toastMessage.contains("is recording") {
-
- DispatchQueue.main.async {
+ if !self.isRecordingByRemote && ToastViewModel.shared.toastMessage.contains("is recording") {
+
withAnimation {
ToastViewModel.shared.toastMessage = ""
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 {
@@ -381,24 +389,28 @@ class TelecomManager: ObservableObject {
TelecomManager.setAppData(sCall: call, appData: appData)
}
/*
- 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")
- ConferenceViewModel.shared.initConference(conference)
- ConferenceViewModel.shared.configureConference(conference)
- }
- */
+ 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")
+ ConferenceViewModel.shared.initConference(conference)
+ ConferenceViewModel.shared.configureConference(conference)
+ }
+ */
switch cstate {
case .IncomingReceived:
let addr = call.remoteAddress
let displayName = incomingDisplayName(call: call)
- #if targetEnvironment(simulator)
+#if targetEnvironment(simulator)
DispatchQueue.main.async {
- withAnimation {
- TelecomManager.shared.callInProgress = true
+ self.outgoingCallStarted = false
+ self.callStarted = true
+ if self.callInProgress == false {
+ withAnimation {
+ self.callInProgress = true
+ }
}
}
- #endif
+#endif
if call.replacedCall != nil {
endCallKitReplacedCall = false
@@ -415,17 +427,17 @@ class TelecomManager: ObservableObject {
} else if TelecomManager.callKitEnabled(core: core) {
/*
let isConference = isConferenceCall(call: call)
- let isEarlyConference = isConference && CallsViewModel.shared.currentCallData.value??.isConferenceCall.value != true // Conference info not be received yet.
- if (isEarlyConference) {
- CallsViewModel.shared.currentCallData.readCurrentAndObserve { _ in
- let uuid = providerDelegate.uuids["\(callId)"]
- if (uuid != nil) {
- 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)
- }
- }
- }
- */
+ let isEarlyConference = isConference && CallsViewModel.shared.currentCallData.value??.isConferenceCall.value != true // Conference info not be received yet.
+ if (isEarlyConference) {
+ CallsViewModel.shared.currentCallData.readCurrentAndObserve { _ in
+ let uuid = providerDelegate.uuids["\(callId)"]
+ if (uuid != nil) {
+ 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)
+ }
+ }
+ }
+ */
let uuid = providerDelegate.uuids["\(callId)"]
if call.replacedCall == nil {
TelecomManager.uuidReplacedCall = callId
@@ -438,18 +450,23 @@ class TelecomManager: ObservableObject {
displayIncomingCall(call: call, handle: addr!.asStringUriOnly(), hasVideo: remoteVideo, callId: callId, displayName: displayName)
}
} /* else if UIApplication.shared.applicationState != .active {
- // not support callkit , use notif
- let content = UNMutableNotificationContent()
- content.title = NSLocalizedString("Incoming call", comment: "")
- content.body = displayName
- content.sound = UNNotificationSound.init(named: UNNotificationSoundName.init("notes_of_the_optimistic.caf"))
- content.categoryIdentifier = "call_cat"
- content.userInfo = ["CallId": callId]
- let req = UNNotificationRequest.init(identifier: "call_request", content: content, trigger: nil)
- UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
- } */
+ // not support callkit , use notif
+ let content = UNMutableNotificationContent()
+ content.title = NSLocalizedString("Incoming call", comment: "")
+ content.body = displayName
+ content.sound = UNNotificationSound.init(named: UNNotificationSoundName.init("notes_of_the_optimistic.caf"))
+ content.categoryIdentifier = "call_cat"
+ content.userInfo = ["CallId": callId]
+ let req = UNNotificationRequest.init(identifier: "call_request", content: content, trigger: nil)
+ UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
+ } */
case .StreamsRunning:
if TelecomManager.callKitEnabled(core: core) {
+
+ DispatchQueue.main.async {
+ self.outgoingCallStarted = false
+ }
+
let uuid = providerDelegate.uuids["\(callId)"]
if uuid != nil {
let callInfo = providerDelegate.callInfos[uuid!]
@@ -463,10 +480,10 @@ class TelecomManager: ObservableObject {
}
/*
- if speakerBeforePause {
- speakerBeforePause = false
- AudioRouteUtils.routeAudioToSpeaker(core: core)
- }
+ if speakerBeforePause {
+ speakerBeforePause = false
+ AudioRouteUtils.routeAudioToSpeaker(core: core)
+ }
*/
actionToFulFill?.fulfill()
@@ -491,12 +508,12 @@ class TelecomManager: ObservableObject {
providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid!)
} else {
if false { /* isConferenceCall(call: call) {
- 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)
- providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
- providerDelegate.uuids.updateValue(uuid, forKey: "")
- providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid)
- Core.get().activateAudioSession(actived: true) */
+ 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)
+ providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
+ providerDelegate.uuids.updateValue(uuid, forKey: "")
+ providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid)
+ Core.get().activateAudioSession(actived: true) */
} else {
referedToCall = callId
}
@@ -505,19 +522,6 @@ class TelecomManager: ObservableObject {
case .End,
.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
if core.callsNb == 0 {
core.outputAudioDevice = core.defaultOutputAudioDevice
@@ -527,18 +531,34 @@ class TelecomManager: ObservableObject {
// bluetoothEnabled = false
}
- 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)
+ DispatchQueue.main.async {
+ withAnimation {
+ self.outgoingCallStarted = false
+ self.callInProgress = false
+ self.callStarted = false
+ }
- // 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)")
+ var displayName = "Unknown"
+ if call.dir == .Incoming {
+ displayName = self.incomingDisplayName(call: call)
+ } else { // if let addr = call.remoteAddress, let contactName = FastAddressBook.displayName(for: addr.getCobject) {
+ displayName = "TODOContactName"
+ }
+
+
+ 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:
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
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self, userInfo: [
diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift
index dc681036d..9f1d99f5a 100644
--- a/Linphone/UI/Call/CallView.swift
+++ b/Linphone/UI/Call/CallView.swift
@@ -24,10 +24,10 @@ import AVFAudio
import linphonesw
struct CallView: View {
-
- @ObservedObject private var coreContext = CoreContext.shared
- @ObservedObject private var telecomManager = TelecomManager.shared
- @ObservedObject private var contactsManager = ContactsManager.shared
+
+ @ObservedObject private var coreContext = CoreContext.shared
+ @ObservedObject private var telecomManager = TelecomManager.shared
+ @ObservedObject private var contactsManager = ContactsManager.shared
@ObservedObject var callViewModel: CallViewModel
@@ -35,8 +35,8 @@ struct CallView: View {
@State private var orientation = UIDevice.current.orientation
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 hideButtonsSheet: Bool = false
@State var options: Int = 1
@@ -45,10 +45,10 @@ struct CallView: View {
@State var angleDegree = 0.0
@State var fullscreenVideo = false
-
- var body: some View {
- GeometryReader { geo in
- if #available(iOS 16.4, *) {
+
+ var body: some View {
+ GeometryReader { geo in
+ if #available(iOS 16.4, *) {
innerView(geometry: geo)
.sheet(isPresented:
.constant(
@@ -60,54 +60,55 @@ struct CallView: View {
)
) {
GeometryReader { _ in
- VStack(spacing: 0) {
- HStack(spacing: 12) {
- Button {
+ VStack(spacing: 0) {
+ HStack(spacing: 12) {
+ Button {
callViewModel.terminateCall()
- } label: {
- Image("phone-disconnect")
- .renderingMode(.template)
- .resizable()
- .foregroundStyle(.white)
- .frame(width: 32, height: 32)
-
- }
- .frame(width: 90, height: 60)
- .background(Color.redDanger500)
- .cornerRadius(40)
-
- Spacer()
-
- Button {
+ } label: {
+ Image("phone-disconnect")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(.white)
+ .frame(width: 32, height: 32)
+
+ }
+ .frame(width: 90, height: 60)
+ .background(Color.redDanger500)
+ .cornerRadius(40)
+
+ Spacer()
+
+ Button {
callViewModel.toggleVideo()
- } label: {
+ } label: {
Image(callViewModel.cameraDisplayed ? "video-camera" : "video-camera-slash")
- .renderingMode(.template)
- .resizable()
- .foregroundStyle(.white)
- .frame(width: 32, height: 32)
-
- }
- .frame(width: 60, height: 60)
- .background(Color.gray500)
- .cornerRadius(40)
-
- Button {
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white)
+ .frame(width: 32, height: 32)
+
+ }
+ .frame(width: 60, height: 60)
+ .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500)
+ .cornerRadius(40)
+ .disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
+
+ Button {
callViewModel.toggleMuteMicrophone()
- } label: {
+ } label: {
Image(callViewModel.micMutted ? "microphone-slash" : "microphone")
- .renderingMode(.template)
- .resizable()
+ .renderingMode(.template)
+ .resizable()
.foregroundStyle(callViewModel.micMutted ? .black : .white)
- .frame(width: 32, height: 32)
-
- }
- .frame(width: 60, height: 60)
+ .frame(width: 32, height: 32)
+
+ }
+ .frame(width: 60, height: 60)
.background(callViewModel.micMutted ? .white : Color.gray500)
- .cornerRadius(40)
-
- Button {
- if AVAudioSession.sharedInstance().availableInputs != nil
+ .cornerRadius(40)
+
+ Button {
+ if AVAudioSession.sharedInstance().availableInputs != nil
&& !AVAudioSession.sharedInstance().availableInputs!.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty {
hideButtonsSheet = true
@@ -123,200 +124,206 @@ struct CallView: View {
}
}
- } label: {
+ } label: {
Image(imageAudioRoute)
- .renderingMode(.template)
- .resizable()
- .foregroundStyle(.white)
- .frame(width: 32, height: 32)
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(.white)
+ .frame(width: 32, height: 32)
.onAppear(perform: getAudioRouteImage)
.onReceive(pub) { (output) in
self.getAudioRouteImage()
}
-
- }
- .frame(width: 60, height: 60)
- .background(Color.gray500)
- .cornerRadius(40)
- }
- .frame(height: geo.size.height * 0.15)
- .padding(.horizontal, 20)
+
+ }
+ .frame(width: 60, height: 60)
+ .background(Color.gray500)
+ .cornerRadius(40)
+ }
+ .frame(height: geo.size.height * 0.15)
+ .padding(.horizontal, 20)
.padding(.top, -6)
-
- HStack(spacing: 0) {
- VStack {
- Button {
- } label: {
- Image("screencast")
- .renderingMode(.template)
- .resizable()
- .foregroundStyle(.white)
- .frame(width: 32, height: 32)
- }
- .frame(width: 60, height: 60)
- .background(Color.gray500)
- .cornerRadius(40)
-
- Text("Screen share")
- .foregroundStyle(.white)
- .default_text_style(styleSize: 15)
- }
- .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
-
- VStack {
- Button {
- } label: {
- Image("users")
- .renderingMode(.template)
- .resizable()
- .foregroundStyle(.white)
- .frame(width: 32, height: 32)
- }
- .frame(width: 60, height: 60)
- .background(Color.gray500)
- .cornerRadius(40)
-
- Text("Participants")
- .foregroundStyle(.white)
- .default_text_style(styleSize: 15)
- }
- .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
-
- VStack {
- Button {
- } label: {
- Image("chat-teardrop-text")
- .renderingMode(.template)
- .resizable()
- .foregroundStyle(.white)
- .frame(width: 32, height: 32)
- }
- .frame(width: 60, height: 60)
- .background(Color.gray500)
- .cornerRadius(40)
-
- Text("Messages")
- .foregroundStyle(.white)
- .default_text_style(styleSize: 15)
- }
- .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
-
- VStack {
- Button {
- } label: {
- Image("notebook")
- .renderingMode(.template)
- .resizable()
- .foregroundStyle(.white)
- .frame(width: 32, height: 32)
- }
- .frame(width: 60, height: 60)
- .background(Color.gray500)
- .cornerRadius(40)
-
- Text("Disposition")
- .foregroundStyle(.white)
- .default_text_style(styleSize: 15)
- }
- .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
- }
- .frame(height: geo.size.height * 0.15)
-
- HStack(spacing: 0) {
- VStack {
- Button {
- } label: {
- Image("phone-call")
- .renderingMode(.template)
- .resizable()
- .foregroundStyle(.white)
- .frame(width: 32, height: 32)
- }
- .frame(width: 60, height: 60)
- .background(Color.gray500)
- .cornerRadius(40)
-
- Text("Call list")
- .foregroundStyle(.white)
- .default_text_style(styleSize: 15)
- }
- .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
-
- VStack {
- Button {
+
+ HStack(spacing: 0) {
+ VStack {
+ Button {
+ } label: {
+ Image("phone-transfer")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(.white)
+ .frame(width: 32, height: 32)
+ }
+ .frame(width: 60, height: 60)
+ .background(Color.gray500)
+ .cornerRadius(40)
+
+ Text("Transfer")
+ .foregroundStyle(.white)
+ .default_text_style(styleSize: 15)
+ }
+ .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
+
+ VStack {
+ Button {
+ } label: {
+ Image("phone-plus")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(.white)
+ .frame(width: 32, height: 32)
+ }
+ .frame(width: 60, height: 60)
+ .background(Color.gray500)
+ .cornerRadius(40)
+
+ Text("New call")
+ .foregroundStyle(.white)
+ .default_text_style(styleSize: 15)
+ }
+ .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
+
+ VStack {
+ Button {
+ } label: {
+ Image("phone-list")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(.white)
+ .frame(width: 32, height: 32)
+ }
+ .frame(width: 60, height: 60)
+ .background(Color.gray500)
+ .cornerRadius(40)
+
+ Text("Call list")
+ .foregroundStyle(.white)
+ .default_text_style(styleSize: 15)
+ }
+ .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
+
+ VStack {
+ Button {
+ } label: {
+ Image("dialer")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(.white)
+ .frame(width: 32, height: 32)
+ }
+ .frame(width: 60, height: 60)
+ .background(Color.gray500)
+ .cornerRadius(40)
+
+ Text("Dialer")
+ .foregroundStyle(.white)
+ .default_text_style(styleSize: 15)
+ }
+ .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
+ }
+ .frame(height: geo.size.height * 0.15)
+
+ HStack(spacing: 0) {
+ VStack {
+ Button {
+ } label: {
+ Image("chat-teardrop-text")
+ .renderingMode(.template)
+ .resizable()
+ //.foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white)
+ .foregroundStyle(Color.gray500)
+ .frame(width: 32, height: 32)
+ }
+ .frame(width: 60, height: 60)
+ //.background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500)
+ .background(Color.gray600)
+ .cornerRadius(40)
+ //.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
+ .disabled(true)
+
+ Text("Messages")
+ .foregroundStyle(.white)
+ .default_text_style(styleSize: 15)
+ }
+ .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
+
+ VStack {
+ Button {
callViewModel.togglePause()
- } label: {
- Image("pause")
- .renderingMode(.template)
- .resizable()
- .foregroundStyle(.white)
- .frame(width: 32, height: 32)
- }
- .frame(width: 60, height: 60)
- .background(Color.gray500)
- .cornerRadius(40)
-
- Text("Pause")
- .foregroundStyle(.white)
- .default_text_style(styleSize: 15)
- }
- .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
-
- VStack {
- Button {
+ } label: {
+ Image(callViewModel.isPaused ? "play" : "pause")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(telecomManager.isPausedByRemote ? Color.gray500 : .white)
+ .frame(width: 32, height: 32)
+ }
+ .frame(width: 60, height: 60)
+ .background(telecomManager.isPausedByRemote ? Color.gray600 : (callViewModel.isPaused ? Color.greenSuccess500 : Color.gray500))
+ .cornerRadius(40)
+ .disabled(telecomManager.isPausedByRemote)
+
+ Text("Pause")
+ .foregroundStyle(.white)
+ .default_text_style(styleSize: 15)
+ }
+ .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
+
+ VStack {
+ Button {
callViewModel.toggleRecording()
- } label: {
- Image("record-fill")
- .renderingMode(.template)
- .resizable()
- .foregroundStyle(.white)
- .frame(width: 32, height: 32)
- }
- .frame(width: 60, height: 60)
- .background(callViewModel.isRecording ? Color.redDanger500 : Color.gray500)
- .cornerRadius(40)
-
- Text("Record")
- .foregroundStyle(.white)
- .default_text_style(styleSize: 15)
- }
- .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
-
- VStack {
- Button {
- } label: {
- Image("video-camera")
- .renderingMode(.template)
- .resizable()
- .foregroundStyle(.white)
- .frame(width: 32, height: 32)
- }
- .frame(width: 60, height: 60)
- .background(Color.gray500)
- .cornerRadius(40)
-
- Text("Disposition")
- .foregroundStyle(.white)
- .default_text_style(styleSize: 15)
- }
- .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
- .hidden()
- }
- .frame(height: geo.size.height * 0.15)
-
- Spacer()
- }
- .frame(maxHeight: .infinity, alignment: .top)
+ } label: {
+ Image("record-fill")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white)
+ .frame(width: 32, height: 32)
+ }
+ .frame(width: 60, height: 60)
+ .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : (callViewModel.isRecording ? Color.redDanger500 : Color.gray500))
+ .cornerRadius(40)
+ .disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
+
+ Text("Record")
+ .foregroundStyle(.white)
+ .default_text_style(styleSize: 15)
+ }
+ .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
+
+ VStack {
+ Button {
+ } label: {
+ Image("video-camera")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(.white)
+ .frame(width: 32, height: 32)
+ }
+ .frame(width: 60, height: 60)
+ .background(Color.gray500)
+ .cornerRadius(40)
+
+ Text("Disposition")
+ .foregroundStyle(.white)
+ .default_text_style(styleSize: 15)
+ }
+ .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25)
+ .hidden()
+ }
+ .frame(height: geo.size.height * 0.15)
+
+ Spacer()
+ }
+ .frame(maxHeight: .infinity, alignment: .top)
.presentationBackground(.black)
- .presentationDetents([.fraction(0.1), .medium])
- .interactiveDismissDisabled()
- .presentationBackgroundInteraction(.enabled)
- }
- }
- .sheet(isPresented: $audioRouteSheet, onDismiss: {
+ .presentationDetents([.fraction(0.1), .fraction(0.45)])
+ .interactiveDismissDisabled()
+ .presentationBackgroundInteraction(.enabled)
+ }
+ }
+ .sheet(isPresented: $audioRouteSheet, onDismiss: {
audioRouteSheet = false
hideButtonsSheet = false
- }) {
+ }) {
VStack(spacing: 0) {
Button(action: {
options = 1
@@ -346,9 +353,9 @@ struct CallView: View {
Image(!callViewModel.isHeadPhoneAvailable() ? "ear" : "headset")
.renderingMode(.template)
- .resizable()
+ .resizable()
.foregroundStyle(.white)
- .frame(width: 25, height: 25, alignment: .leading)
+ .frame(width: 25, height: 25, alignment: .leading)
}
})
.frame(maxHeight: .infinity)
@@ -416,16 +423,16 @@ struct CallView: View {
}
.padding(.horizontal, 20)
.presentationBackground(Color.gray600)
- .presentationDetents([.fraction(0.3)])
+ .presentationDetents([.fraction(0.3)])
.frame(maxHeight: .infinity)
}
- }
- }
- }
-
- @ViewBuilder
+ }
+ }
+ }
+
+ @ViewBuilder
func innerView(geometry: GeometryProxy) -> some View {
- VStack {
+ VStack {
if !fullscreenVideo {
Rectangle()
.foregroundColor(Color.orangeMain500)
@@ -451,6 +458,35 @@ struct CallView: View {
.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()
if callViewModel.cameraDisplayed {
@@ -469,65 +505,65 @@ struct CallView: View {
.frame(height: 40)
.zIndex(1)
}
-
- ZStack {
- VStack {
- Spacer()
-
- if callViewModel.remoteAddress != nil {
- let addressFriend = contactsManager.getFriendWithAddress(address: callViewModel.remoteAddress!)
-
- let contactAvatarModel = addressFriend != nil
- ? ContactsManager.shared.avatarListModel.first(where: {
- ($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy)
- && $0.friend!.name == addressFriend!.name
- && $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly()
- })
- : ContactAvatarModel(friend: nil, withPresence: false)
-
- if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty {
- if contactAvatarModel != nil {
- Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 100, hidePresence: true)
- }
- } else {
- if callViewModel.remoteAddress!.displayName != nil {
- Image(uiImage: contactsManager.textToImage(
- firstName: callViewModel.remoteAddress!.displayName!,
- lastName: callViewModel.remoteAddress!.displayName!.components(separatedBy: " ").count > 1
- ? callViewModel.remoteAddress!.displayName!.components(separatedBy: " ")[1]
- : ""))
- .resizable()
- .frame(width: 100, height: 100)
- .clipShape(Circle())
-
- } else {
- Image(uiImage: contactsManager.textToImage(
- firstName: callViewModel.remoteAddress!.username ?? "Username Error",
- lastName: callViewModel.remoteAddress!.username!.components(separatedBy: " ").count > 1
- ? callViewModel.remoteAddress!.username!.components(separatedBy: " ")[1]
- : ""))
- .resizable()
- .frame(width: 100, height: 100)
- .clipShape(Circle())
- }
-
- }
- } else {
- Image("profil-picture-default")
- .resizable()
- .frame(width: 100, height: 100)
- .clipShape(Circle())
- }
-
- Text(callViewModel.displayName)
- .padding(.top)
- .foregroundStyle(.white)
-
- Text(callViewModel.remoteAddressString)
- .foregroundStyle(.white)
-
- Spacer()
- }
+
+ ZStack {
+ VStack {
+ Spacer()
+
+ if callViewModel.remoteAddress != nil {
+ let addressFriend = contactsManager.getFriendWithAddress(address: callViewModel.remoteAddress!)
+
+ let contactAvatarModel = addressFriend != nil
+ ? ContactsManager.shared.avatarListModel.first(where: {
+ ($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy)
+ && $0.friend!.name == addressFriend!.name
+ && $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly()
+ })
+ : ContactAvatarModel(friend: nil, withPresence: false)
+
+ if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty {
+ if contactAvatarModel != nil {
+ Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 100, hidePresence: true)
+ }
+ } else {
+ if callViewModel.remoteAddress!.displayName != nil {
+ Image(uiImage: contactsManager.textToImage(
+ firstName: callViewModel.remoteAddress!.displayName!,
+ lastName: callViewModel.remoteAddress!.displayName!.components(separatedBy: " ").count > 1
+ ? callViewModel.remoteAddress!.displayName!.components(separatedBy: " ")[1]
+ : ""))
+ .resizable()
+ .frame(width: 100, height: 100)
+ .clipShape(Circle())
+
+ } else {
+ Image(uiImage: contactsManager.textToImage(
+ firstName: callViewModel.remoteAddress!.username ?? "Username Error",
+ lastName: callViewModel.remoteAddress!.username!.components(separatedBy: " ").count > 1
+ ? callViewModel.remoteAddress!.username!.components(separatedBy: " ")[1]
+ : ""))
+ .resizable()
+ .frame(width: 100, height: 100)
+ .clipShape(Circle())
+ }
+
+ }
+ } else {
+ Image("profil-picture-default")
+ .resizable()
+ .frame(width: 100, height: 100)
+ .clipShape(Circle())
+ }
+
+ Text(callViewModel.displayName)
+ .padding(.top)
+ .foregroundStyle(.white)
+
+ Text(callViewModel.remoteAddressString)
+ .foregroundStyle(.white)
+
+ Spacer()
+ }
LinphoneVideoViewHolder { view in
coreContext.doOnCoreQueue { core in
@@ -535,7 +571,7 @@ struct CallView: View {
}
}
.frame(
- width:
+ width:
angleDegree == 0
? 120 * ((geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) / 160)
: 120 * ((geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) / 120),
@@ -577,8 +613,8 @@ struct CallView: View {
VStack {
Image("record-fill")
.renderingMode(.template)
- .resizable()
- .foregroundStyle(Color.redDanger500)
+ .resizable()
+ .foregroundStyle(Color.redDanger500)
.frame(width: 32, height: 32)
.padding(10)
.if(fullscreenVideo) { view in
@@ -594,31 +630,39 @@ struct CallView: View {
)
}
- if !telecomManager.callStarted && !fullscreenVideo {
- VStack {
- ActivityIndicator()
- .frame(width: 20, height: 20)
- .padding(.top, 100)
-
+ if telecomManager.outgoingCallStarted {
+ VStack {
+ ActivityIndicator()
+ .frame(width: 20, height: 20)
+ .padding(.top, 100)
+
Text(callViewModel.counterToMinutes())
+ .onAppear {
+ callViewModel.timeElapsed = 0
+ startDate = Date.now
+ }
.onReceive(callViewModel.timer) { firedDate in
callViewModel.timeElapsed = Int(firedDate.timeIntervalSince(startDate))
-
- }
- .padding(.top)
- .foregroundStyle(.white)
-
- Spacer()
- }
- .background(.clear)
- }
- }
+
+ }
+ .padding(.top)
+ .foregroundStyle(.white)
+
+ Spacer()
+ }
+ .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(
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(Color.gray600)
- .cornerRadius(20)
+ .background(Color.gray600)
+ .cornerRadius(20)
.padding(.horizontal, fullscreenVideo ? 0 : 4)
.onRotate { newOrientation in
orientation = newOrientation
@@ -647,7 +691,7 @@ struct CallView: View {
callViewModel.orientationUpdate(orientation: orientation)
}
-
+
if !fullscreenVideo {
if telecomManager.callStarted {
if telecomManager.callStarted && idiom != .pad && !(orientation == .landscapeLeft || orientation == .landscapeRight
@@ -684,13 +728,14 @@ struct CallView: View {
Image("video-camera")
.renderingMode(.template)
.resizable()
- .foregroundStyle(.white)
+ .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white)
.frame(width: 32, height: 32)
}
.frame(width: 60, height: 60)
- .background(Color.gray500)
+ .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500)
.cornerRadius(40)
+ .disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
Button {
callViewModel.toggleMuteMicrophone()
@@ -764,13 +809,13 @@ struct CallView: View {
.padding(.top, 20)
}
}
- }
- .frame(maxWidth: .infinity, maxHeight: .infinity)
- .background(Color.gray900)
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .background(Color.gray900)
.if(fullscreenVideo) { view in
view.ignoresSafeArea(.all)
}
- }
+ }
func getAudioRouteImage() {
imageAudioRoute = AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue == "Speaker" }).isEmpty
diff --git a/Linphone/UI/Call/ViewModel/CallViewModel.swift b/Linphone/UI/Call/ViewModel/CallViewModel.swift
index 382e41e5e..26f9dd2ed 100644
--- a/Linphone/UI/Call/ViewModel/CallViewModel.swift
+++ b/Linphone/UI/Call/ViewModel/CallViewModel.swift
@@ -35,7 +35,8 @@ class CallViewModel: ObservableObject {
@Published var cameraDisplayed: Bool = false
@Published var isRecording: 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()
@@ -76,6 +77,8 @@ class CallViewModel: ObservableObject {
self.micMutted = self.currentCall!.microphoneMuted
self.cameraDisplayed = self.currentCall!.cameraEnabled == true
self.isRecording = self.currentCall!.params!.isRecording
+ self.isPaused = self.isCallPaused()
+ self.timeElapsed = 0
}
}
}
@@ -83,8 +86,9 @@ class CallViewModel: ObservableObject {
func terminateCall() {
withAnimation {
- telecomManager.callInProgress = false
+ telecomManager.outgoingCallStarted = false
telecomManager.callStarted = false
+ telecomManager.callInProgress = false
}
coreContext.doOnCoreQueue { _ in
@@ -98,6 +102,7 @@ class CallViewModel: ObservableObject {
func acceptCall() {
withAnimation {
+ telecomManager.outgoingCallStarted = false
telecomManager.callInProgress = true
telecomManager.callStarted = true
}
@@ -184,9 +189,11 @@ class CallViewModel: ObservableObject {
if self.isCallPaused() {
Log.info("[CallViewModel] Resuming call \(self.currentCall!.remoteAddress!.asStringUriOnly())")
try self.currentCall!.resume()
+ self.isPaused = false
} else {
Log.info("[CallViewModel] Pausing call \(self.currentCall!.remoteAddress!.asStringUriOnly())")
try self.currentCall!.pause()
+ self.isPaused = true
}
} catch _ {
@@ -195,7 +202,7 @@ class CallViewModel: ObservableObject {
}
}
- private func isCallPaused() -> Bool {
+ func isCallPaused() -> Bool {
var result = false
if self.currentCall != nil {
switch self.currentCall!.state {