Start a new call when another call is in progress

This commit is contained in:
Benoit Martins 2024-01-19 16:49:05 +01:00
parent fbd578ea37
commit 55631bf4f4
12 changed files with 473 additions and 71 deletions

View file

@ -95,6 +95,8 @@
D7E6D0512AEBDBD500A57AAF /* ContactsListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D0502AEBDBD500A57AAF /* ContactsListBottomSheet.swift */; };
D7E6D0552AEBFCCE00A57AAF /* ContactsInnerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */; };
D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */; };
D7F4D9CB2B5FD27200CDCD76 /* CallsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F4D9CA2B5FD27200CDCD76 /* CallsListFragment.swift */; };
D7F4D9CD2B5FD83A00CDCD76 /* CallsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F4D9CC2B5FD83A00CDCD76 /* CallsListViewModel.swift */; };
D7FB55112AD447FD00A5AB15 /* RegisterFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */; };
/* End PBXBuildFile section */
@ -190,6 +192,8 @@
D7E6D0502AEBDBD500A57AAF /* ContactsListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsListBottomSheet.swift; sourceTree = "<group>"; };
D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsInnerFragment.swift; sourceTree = "<group>"; };
D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsFragment.swift; sourceTree = "<group>"; };
D7F4D9CA2B5FD27200CDCD76 /* CallsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallsListFragment.swift; sourceTree = "<group>"; };
D7F4D9CC2B5FD83A00CDCD76 /* CallsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallsListViewModel.swift; sourceTree = "<group>"; };
D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterFragment.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -407,6 +411,7 @@
isa = PBXGroup;
children = (
D75759312B56D40900E7AC10 /* ZRTPPopup.swift */,
D7F4D9CA2B5FD27200CDCD76 /* CallsListFragment.swift */,
);
path = Fragments;
sourceTree = "<group>";
@ -511,6 +516,7 @@
isa = PBXGroup;
children = (
D7B99E982B29B39000BE7BF2 /* CallViewModel.swift */,
D7F4D9CC2B5FD83A00CDCD76 /* CallsListViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
@ -696,6 +702,7 @@
D732A90F2B04C3B400DB42BA /* HistoryFragment.swift in Sources */,
D79622342B1DFE600037EACD /* DialerBottomSheet.swift in Sources */,
D78290BB2ADD40B2004AA85C /* ContactViewModel.swift in Sources */,
D7F4D9CB2B5FD27200CDCD76 /* CallsListFragment.swift in Sources */,
D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */,
D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */,
D7E6D04D2AEBD77600A57AAF /* CustomBottomSheet.swift in Sources */,
@ -708,6 +715,7 @@
D71FCA7F2AE1397200D2E43E /* ContactsListViewModel.swift in Sources */,
D71FCA812AE14CFC00D2E43E /* ContactsListFragment.swift in Sources */,
D719ABB72ABC67BF00B41C10 /* LinphoneApp.swift in Sources */,
D7F4D9CD2B5FD83A00CDCD76 /* CallsListViewModel.swift in Sources */,
D732A91B2B061BD900DB42BA /* HistoryListBottomSheet.swift in Sources */,
D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */,
D726E4392B16440C0083C415 /* ContactAvatarModel.swift in Sources */,

View file

@ -221,7 +221,7 @@ extension ProviderDelegate: CXProviderDelegate {
let call = core.getCallByCallid(callId: callId)
DispatchQueue.main.async() {
DispatchQueue.main.async {
if UIApplication.shared.applicationState != .active {
TelecomManager.shared.backgroundContextCall = call
TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true
@ -276,30 +276,33 @@ extension ProviderDelegate: CXProviderDelegate {
// attempt to resume another one.
action.fulfill()
} else {
if call?.conference != nil && core.callsNb > 1 {/*
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
}
*/
} 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 {

View file

@ -46,6 +46,8 @@ class TelecomManager: ObservableObject {
@Published var remoteVideo: Bool = false
@Published var isRecordingByRemote: Bool = false
@Published var isPausedByRemote: Bool = false
@Published var refreshCallViewModel: Bool = false
@Published var remainingCall: Bool = false
var actionToFulFill: CXCallAction?
var callkitAudioSessionActivated: Bool?
@ -95,7 +97,7 @@ class TelecomManager: ObservableObject {
if TelecomManager.callKitEnabled(core: core) {// && !nextCallIsTransfer != true {
let uuid = UUID()
let name = "outgoingTODO" // FastAddressBook.displayName(for: addr) ?? "unknow"
let name = addr?.asStringUriOnly() ?? "unknow" // FastAddressBook.displayName(for: addr) ?? "unknow"
let handle = CXHandle(type: .generic, value: addr?.asStringUriOnly() ?? "")
let startCallAction = CXStartCallAction(call: uuid, handle: handle)
let transaction = CXTransaction(action: startCallAction)
@ -104,13 +106,42 @@ class TelecomManager: ObservableObject {
providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
providerDelegate.uuids.updateValue(uuid, forKey: "")
// setHeldOtherCalls(core: core, exceptCallid: "")
setHeldOtherCalls(core: core, exceptCallid: "")
requestTransaction(transaction, action: "startCall")
} else {
try doCall(core: core, addr: addr!, isSas: isSas, isVideo: isVideo, isConference: isConference)
}
}
func setHeldOtherCalls(core: Core, exceptCallid: String) {
for call in core.calls {
if (call.callLog?.callId != exceptCallid && call.state != .Paused && call.state != .Pausing && call.state != .PausedByRemote) {
setHeld(call: call, hold: true)
}
}
}
func setHeld(call: Call, hold: Bool) {
#if targetEnvironment(simulator)
if (hold) {
try?call.pause()
} else {
try?call.resume()
}
#else
let callid = call.callLog?.callId ?? ""
let uuid = providerDelegate.uuids["\(callid)"]
if (uuid == nil) {
Log.error("Can not find correspondant call to set held.")
return
}
let setHeldAction = CXSetHeldCallAction(call: uuid!, onHold: hold)
let transaction = CXTransaction(action: setHeldAction)
requestTransaction(transaction, action: "setHeld")
#endif
}
func startCall(core: Core, addr: String, isSas: Bool = false, isVideo: Bool, isConference: Bool = false) {
do {
let address = try Factory.Instance.createAddress(addr: addr)
@ -419,7 +450,6 @@ class TelecomManager: ObservableObject {
}
}
#endif
if call.replacedCall != nil {
endCallKitReplacedCall = false
@ -503,9 +533,12 @@ class TelecomManager: ObservableObject {
.OutgoingProgress,
.OutgoingRinging,
.OutgoingEarlyMedia:
print("OutgoingInitOutgoingInit \(core.maxCalls)")
if TelecomManager.callKitEnabled(core: core) {
let uuid = providerDelegate.uuids[""]
if uuid != nil && callId.isEmpty {
if uuid != nil {
let callInfo = providerDelegate.callInfos[uuid!]
callInfo!.callId = callId
providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!)
@ -539,11 +572,25 @@ class TelecomManager: ObservableObject {
// bluetoothEnabled = false
}
//if core.callsNb == 0 {
DispatchQueue.main.async {
withAnimation {
self.outgoingCallStarted = false
self.callInProgress = false
self.callStarted = false
if core.callsNb == 0 {
withAnimation {
self.outgoingCallStarted = false
self.callInProgress = false
self.callStarted = false
}
} else {
if core.calls.last != nil {
self.setHeld(call: core.calls.last!, hold: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.remainingCall = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.remainingCall = false
}
}
}
}
var displayName = "Unknown"
@ -553,7 +600,6 @@ class TelecomManager: ObservableObject {
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()
@ -570,6 +616,7 @@ class TelecomManager: ObservableObject {
}
}
}
//}
if TelecomManager.callKitEnabled(core: core) {
var uuid = providerDelegate.uuids["\(callId)"]

View file

@ -37,7 +37,6 @@ struct CallView: View {
let pub = NotificationCenter.default.publisher(for: AVAudioSession.routeChangeNotification)
@State var startDate = Date.now
@State var audioRouteSheet: Bool = false
@State var hideButtonsSheet: Bool = false
@State var options: Int = 1
@ -49,6 +48,8 @@ struct CallView: View {
@State var showingDialer = false
@Binding var isShowStartCallFragment: Bool
var body: some View {
GeometryReader { geo in
ZStack {
@ -110,6 +111,13 @@ struct CallView: View {
callViewModel.zrtpPopupDisplayed = false
}
}
if telecomManager.remainingCall {
HStack {}
.onAppear {
callViewModel.resetCallView()
}
}
}
.onAppear {
callViewModel.enableAVAudioSession()
@ -273,8 +281,8 @@ struct CallView: View {
ZStack {
Text(callViewModel.timeElapsed.convertDurationToString())
.onReceive(callViewModel.timer) { firedDate in
callViewModel.timeElapsed = Int(firedDate.timeIntervalSince(startDate))
.onReceive(callViewModel.timer) { _ in
callViewModel.timeElapsed = callViewModel.currentCall?.duration ?? 0
}
.foregroundStyle(.white)
.if(callViewModel.isPaused || telecomManager.isPausedByRemote) { view in
@ -477,15 +485,13 @@ struct CallView: View {
Text(callViewModel.counterToMinutes())
.onAppear {
callViewModel.timeElapsed = 0
startDate = Date.now
}
.onReceive(callViewModel.timer) { firedDate in
callViewModel.timeElapsed = Int(firedDate.timeIntervalSince(startDate))
.onReceive(callViewModel.timer) { _ in
callViewModel.timeElapsed = callViewModel.currentCall?.duration ?? 0
}
.onDisappear {
callViewModel.timeElapsed = 0
startDate = Date.now
}
.padding(.top)
.foregroundStyle(.white)
@ -734,17 +740,20 @@ struct CallView: View {
VStack {
Button {
withAnimation {
MagicSearchSingleton.shared.searchForSuggestions()
isShowStartCallFragment.toggle()
}
} label: {
Image("phone-plus")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.gray500)
.foregroundStyle(.white)
.frame(width: 32, height: 32)
}
.frame(width: 60, height: 60)
.background(Color.gray600)
.background(Color.gray500)
.cornerRadius(40)
.disabled(true)
Text("New call")
.foregroundStyle(.white)
@ -907,17 +916,20 @@ struct CallView: View {
VStack {
Button {
withAnimation {
MagicSearchSingleton.shared.searchForSuggestions()
isShowStartCallFragment.toggle()
}
} label: {
Image("phone-plus")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.gray500)
.foregroundStyle(.white)
.frame(width: 32, height: 32)
}
.frame(width: 60, height: 60)
.background(Color.gray600)
.background(Color.gray500)
.cornerRadius(40)
.disabled(true)
Text("New call")
.foregroundStyle(.white)
@ -1112,7 +1124,7 @@ struct BottomSheetView<Content: View>: View {
}
#Preview {
CallView(callViewModel: CallViewModel())
CallView(callViewModel: CallViewModel(), isShowStartCallFragment: .constant(false))
}
// swiftlint:enable type_body_length
// swiftlint:enable line_length

View file

@ -0,0 +1,279 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
import SwiftUI
struct CallsListFragment: View {
@ObservedObject var callsListViewModel: CallsListViewModel
@State private var delayedColor = Color.white
@Binding var isShowCallsListFragment: Bool
var body: some View {
ZStack {
VStack(spacing: 1) {
Rectangle()
.foregroundColor(delayedColor)
.edgesIgnoringSafeArea(.top)
.frame(height: 0)
.task(delayColor)
HStack {
Image("caret-left")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
.padding(.top, 2)
.padding(.leading, -10)
.onTapGesture {
delayColorDismiss()
withAnimation {
isShowCallsListFragment.toggle()
}
}
Text("Call list")
.multilineTextAlignment(.leading)
.default_text_style_orange_800(styleSize: 16)
Spacer()
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.padding(.horizontal)
.padding(.bottom, 4)
.background(.white)
//callsList
}
.background(.white)
}
.navigationBarHidden(true)
}
@Sendable private func delayColor() async {
try? await Task.sleep(nanoseconds: 250_000_000)
delayedColor = Color.orangeMain500
}
func delayColorDismiss() {
Task {
try? await Task.sleep(nanoseconds: 80_000_000)
delayedColor = .white
}
}
/*
var callsList: some View {
VStack {
List {
ForEach(0..<historyListViewModel.callLogs.count, id: \.self) { index in
HStack {
HStack {
let fromAddressFriend = contactsManager.getFriendWithAddress(address: historyListViewModel.callLogs[index].fromAddress!)
let toAddressFriend = contactsManager.getFriendWithAddress(address: historyListViewModel.callLogs[index].toAddress!)
let addressFriend = historyListViewModel.callLogs[index].dir == .Incoming ? fromAddressFriend : toAddressFriend
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: 45)
} else {
Image("profil-picture-default")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
}
} else {
if historyListViewModel.callLogs[index].dir == .Outgoing && historyListViewModel.callLogs[index].toAddress != nil {
if historyListViewModel.callLogs[index].toAddress!.displayName != nil {
Image(uiImage: contactsManager.textToImage(
firstName: historyListViewModel.callLogs[index].toAddress!.displayName!,
lastName: historyListViewModel.callLogs[index].toAddress!.displayName!.components(separatedBy: " ").count > 1
? historyListViewModel.callLogs[index].toAddress!.displayName!.components(separatedBy: " ")[1]
: ""))
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
} else {
Image(uiImage: contactsManager.textToImage(
firstName: historyListViewModel.callLogs[index].toAddress!.username ?? "Username Error",
lastName: historyListViewModel.callLogs[index].toAddress!.username!.components(separatedBy: " ").count > 1
? historyListViewModel.callLogs[index].toAddress!.username!.components(separatedBy: " ")[1]
: ""))
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
}
} else if historyListViewModel.callLogs[index].fromAddress != nil {
if historyListViewModel.callLogs[index].fromAddress!.displayName != nil {
Image(uiImage: contactsManager.textToImage(
firstName: historyListViewModel.callLogs[index].fromAddress!.displayName!,
lastName: historyListViewModel.callLogs[index].fromAddress!.displayName!.components(separatedBy: " ").count > 1
? historyListViewModel.callLogs[index].fromAddress!.displayName!.components(separatedBy: " ")[1]
: ""))
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
} else {
Image(uiImage: contactsManager.textToImage(
firstName: historyListViewModel.callLogs[index].fromAddress!.username ?? "Username Error",
lastName: historyListViewModel.callLogs[index].fromAddress!.username!.components(separatedBy: " ").count > 1
? historyListViewModel.callLogs[index].fromAddress!.username!.components(separatedBy: " ")[1]
: ""))
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
}
} else {
Image("profil-picture-default")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
}
}
VStack(spacing: 0) {
Spacer()
let fromAddressFriend = contactsManager.getFriendWithAddress(address: historyListViewModel.callLogs[index].fromAddress!)
let toAddressFriend = contactsManager.getFriendWithAddress(address: historyListViewModel.callLogs[index].toAddress!)
let addressFriend = historyListViewModel.callLogs[index].dir == .Incoming ? fromAddressFriend : toAddressFriend
if addressFriend != nil {
Text(addressFriend!.name!)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
} else {
if historyListViewModel.callLogs[index].dir == .Outgoing && historyListViewModel.callLogs[index].toAddress != nil {
Text(historyListViewModel.callLogs[index].toAddress!.displayName != nil
? historyListViewModel.callLogs[index].toAddress!.displayName!
: historyListViewModel.callLogs[index].toAddress!.username!)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
} else if historyListViewModel.callLogs[index].fromAddress != nil {
Text(historyListViewModel.callLogs[index].fromAddress!.displayName != nil
? historyListViewModel.callLogs[index].fromAddress!.displayName!
: historyListViewModel.callLogs[index].fromAddress!.username!)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
}
}
HStack {
Image(historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, callDir: historyListViewModel.callLogs[index].dir))
.resizable()
.frame(
width: historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, callDir: historyListViewModel.callLogs[index].dir).contains("rejected") ? 12 : 8,
height: historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, callDir: historyListViewModel.callLogs[index].dir).contains("rejected") ? 6 : 8)
Text(historyListViewModel.getCallTime(startDate: historyListViewModel.callLogs[index].startDate))
.default_text_style_300(styleSize: 12)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
}
Spacer()
}
Image("phone")
.resizable()
.frame(width: 25, height: 25)
.padding(.all, 10)
.padding(.trailing, 5)
.highPriorityGesture(
TapGesture()
.onEnded { _ in
withAnimation {
if historyListViewModel.callLogs[index].dir == .Outgoing && historyListViewModel.callLogs[index].toAddress != nil {
telecomManager.doCallWithCore(
addr: historyListViewModel.callLogs[index].toAddress!, isVideo: false
)
} else if historyListViewModel.callLogs[index].fromAddress != nil {
telecomManager.doCallWithCore(
addr: historyListViewModel.callLogs[index].fromAddress!, isVideo: false
)
}
historyViewModel.displayedCall = nil
}
}
)
}
}
.buttonStyle(.borderless)
.listRowInsets(EdgeInsets(top: 5, leading: 20, bottom: 5, trailing: 20))
.listRowSeparator(.hidden)
.background(.white)
.onTapGesture {
withAnimation {
historyViewModel.displayedCall = historyListViewModel.callLogs[index]
}
}
.onLongPressGesture(minimumDuration: 0.2) {
historyViewModel.selectedCall = historyListViewModel.callLogs[index]
showingSheet.toggle()
}
}
}
.listStyle(.plain)
.overlay(
VStack {
if historyListViewModel.callLogs.isEmpty {
Spacer()
Image("illus-belledonne")
.resizable()
.scaledToFit()
.clipped()
.padding(.all)
Text("No call for the moment...")
.default_text_style_800(styleSize: 16)
Spacer()
Spacer()
}
}
.padding(.all)
)
}
.navigationTitle("")
.navigationBarHidden(true)
}
*/
}
#Preview {
CallsListFragment(callsListViewModel: CallsListViewModel(), isShowCallsListFragment: .constant(true))
}

View file

@ -59,7 +59,7 @@ class CallViewModel: ObservableObject {
resetCallView()
}
func enableAVAudioSession(){
func enableAVAudioSession() {
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ {
@ -99,7 +99,11 @@ class CallViewModel: ObservableObject {
self.micMutted = self.currentCall!.microphoneMuted
self.isRecording = self.currentCall!.params!.isRecording
self.isPaused = self.isCallPaused()
self.timeElapsed = 0
self.timeElapsed = self.currentCall?.duration ?? 0
let authToken = self.currentCall!.authenticationToken
let isDeviceTrusted = self.currentCall!.authenticationTokenVerified && authToken != nil
self.isRemoteDeviceTrusted = self.telecomManager.callInProgress ? isDeviceTrusted : false
}
self.callSuscriptions.insert(self.currentCall!.publisher?.onEncryptionChanged?.postOnMainQueue {(cbVal: (call: Call, on: Bool, authenticationToken: String?)) in
@ -110,19 +114,15 @@ class CallViewModel: ObservableObject {
}
func terminateCall() {
withAnimation {
telecomManager.outgoingCallStarted = false
telecomManager.callStarted = false
telecomManager.callInProgress = false
}
coreContext.doOnCoreQueue { _ in
coreContext.doOnCoreQueue { core in
if self.currentCall != nil {
self.telecomManager.terminateCall(call: self.currentCall!)
}
if core.callsNb == 0 {
self.timer.upstream.connect().cancel()
}
}
timer.upstream.connect().cancel()
}
func acceptCall() {

View file

@ -0,0 +1,37 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
import Foundation
class CallsListViewModel: ObservableObject {
var coreContext = CoreContext.shared
//let nbCalls : Int
init() {
//self.getCallsList()
}
func getCallsList() {
coreContext.doOnCoreQueue { core in
core.callsNb
}
}
}

View file

@ -62,7 +62,9 @@ struct ContactInnerActionsFragment: View {
if informationIsOpen {
VStack(spacing: 0) {
if contactViewModel.indexDisplayedFriend != nil && contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
if contactViewModel.indexDisplayedFriend != nil
&& contactsManager.lastSearch.count > contactViewModel.indexDisplayedFriend!
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
ForEach(0..<contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses.count, id: \.self) { index in
HStack {
HStack {
@ -159,6 +161,7 @@ struct ContactInnerActionsFragment: View {
}
if contactViewModel.indexDisplayedFriend != nil
&& contactsManager.lastSearch.count > contactViewModel.indexDisplayedFriend!
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
&& ((contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization != nil
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.organization!.isEmpty)
@ -211,7 +214,8 @@ struct ContactInnerActionsFragment: View {
.background(Color.gray100)
VStack(spacing: 0) {
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count
if contactViewModel.indexDisplayedFriend != nil
&& contactsManager.lastSearch.count > contactViewModel.indexDisplayedFriend!
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri != nil
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.nativeUri!.isEmpty {
@ -282,15 +286,20 @@ struct ContactInnerActionsFragment: View {
}
} label: {
HStack {
Image(contactViewModel.indexDisplayedFriend != nil && contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
Image(contactViewModel.indexDisplayedFriend != nil
&& contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true ? "heart-fill" : "heart")
.renderingMode(.template)
.resizable()
.foregroundStyle(contactViewModel.indexDisplayedFriend != nil && contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
.foregroundStyle(contactViewModel.indexDisplayedFriend != nil
&& contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true ? Color.redDanger500 : Color.grayMain2c500)
.frame(width: 25, height: 25)
.padding(.all, 10)
Text(contactViewModel.indexDisplayedFriend != nil
&& contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred == true
? "Remove from favourites"

View file

@ -120,13 +120,16 @@ struct ContactInnerFragment: View {
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.photo != nil
&& !contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.photo!.isEmpty {
Avatar(contactAvatarModel: contactAvatarModel, avatarSize: 100)
} else if contactViewModel.indexDisplayedFriend != nil && contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
} else if contactViewModel.indexDisplayedFriend != nil
&& contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
Image("profil-picture-default")
.resizable()
.frame(width: 100, height: 100)
.clipShape(Circle())
}
if contactViewModel.indexDisplayedFriend != nil
&& contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil
&& contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend?.name != nil {
Text((contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend?.name)!)

View file

@ -35,7 +35,6 @@ struct EditContactFragment: View {
@Binding var isShowEditContactFragment: Bool
@Binding var isShowDismissPopup: Bool
@State private var hasTimeElapsed = false
@State private var delayedColor = Color.white
@FocusState var isFirstNameFocused: Bool

View file

@ -522,14 +522,14 @@ struct ContentView: View {
}
if isShowStartCallFragment {
if #available(iOS 16.4, *), idiom != .pad {
StartCallFragment(
startCallViewModel: startCallViewModel,
isShowStartCallFragment: $isShowStartCallFragment,
showingDialer: $showingDialer
showingDialer: $showingDialer,
resetCallView: {callViewModel.resetCallView()}
)
.zIndex(3)
.zIndex(4)
.transition(.move(edge: .bottom))
.sheet(isPresented: $showingDialer) {
DialerBottomSheet(
@ -545,9 +545,10 @@ struct ContentView: View {
StartCallFragment(
startCallViewModel: startCallViewModel,
isShowStartCallFragment: $isShowStartCallFragment,
showingDialer: $showingDialer
showingDialer: $showingDialer,
resetCallView: {callViewModel.resetCallView()}
)
.zIndex(3)
.zIndex(4)
.transition(.move(edge: .bottom))
.halfSheet(showSheet: $showingDialer) {
DialerBottomSheet(
@ -657,7 +658,7 @@ struct ContentView: View {
}
if telecomManager.callInProgress {
CallView(callViewModel: callViewModel)
CallView(callViewModel: callViewModel, isShowStartCallFragment: $isShowStartCallFragment)
.zIndex(3)
.transition(.scale.combined(with: .move(edge: .top)))
.onAppear {

View file

@ -32,9 +32,10 @@ struct StartCallFragment: View {
@Binding var showingDialer: Bool
@FocusState var isSearchFieldFocused: Bool
@State private var hasTimeElapsed = false
@State private var delayedColor = Color.white
var resetCallView: () -> Void
var body: some View {
ZStack {
VStack(spacing: 1) {
@ -141,7 +142,6 @@ struct StartCallFragment: View {
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25)
.padding(.all, 10)
})
}
}
@ -175,6 +175,8 @@ struct StartCallFragment: View {
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
resetCallView()
}
startCallViewModel.searchField = ""
@ -227,6 +229,8 @@ struct StartCallFragment: View {
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
resetCallView()
}
startCallViewModel.searchField = ""
@ -279,5 +283,5 @@ struct StartCallFragment: View {
}
#Preview {
StartCallFragment(startCallViewModel: StartCallViewModel(), isShowStartCallFragment: .constant(true), showingDialer: .constant(false))
StartCallFragment(startCallViewModel: StartCallViewModel(), isShowStartCallFragment: .constant(true), showingDialer: .constant(false), resetCallView: {})
}