diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index 6e6a82566..1fea99ca8 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -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 = ""; }; D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsInnerFragment.swift; sourceTree = ""; }; D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsFragment.swift; sourceTree = ""; }; + D7F4D9CA2B5FD27200CDCD76 /* CallsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallsListFragment.swift; sourceTree = ""; }; + D7F4D9CC2B5FD83A00CDCD76 /* CallsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallsListViewModel.swift; sourceTree = ""; }; D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterFragment.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -407,6 +411,7 @@ isa = PBXGroup; children = ( D75759312B56D40900E7AC10 /* ZRTPPopup.swift */, + D7F4D9CA2B5FD27200CDCD76 /* CallsListFragment.swift */, ); path = Fragments; sourceTree = ""; @@ -511,6 +516,7 @@ isa = PBXGroup; children = ( D7B99E982B29B39000BE7BF2 /* CallViewModel.swift */, + D7F4D9CC2B5FD83A00CDCD76 /* CallsListViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -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 */, diff --git a/Linphone/TelecomManager/ProviderDelegate.swift b/Linphone/TelecomManager/ProviderDelegate.swift index 44ef6095e..284aef309 100644 --- a/Linphone/TelecomManager/ProviderDelegate.swift +++ b/Linphone/TelecomManager/ProviderDelegate.swift @@ -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 { diff --git a/Linphone/TelecomManager/TelecomManager.swift b/Linphone/TelecomManager/TelecomManager.swift index a80beb43c..edb5c263e 100644 --- a/Linphone/TelecomManager/TelecomManager.swift +++ b/Linphone/TelecomManager/TelecomManager.swift @@ -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)"] diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index 53bd8a595..5bf4f3347 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -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: View { } #Preview { - CallView(callViewModel: CallViewModel()) + CallView(callViewModel: CallViewModel(), isShowStartCallFragment: .constant(false)) } // swiftlint:enable type_body_length // swiftlint:enable line_length diff --git a/Linphone/UI/Call/Fragments/CallsListFragment.swift b/Linphone/UI/Call/Fragments/CallsListFragment.swift new file mode 100644 index 000000000..3acd5cfea --- /dev/null +++ b/Linphone/UI/Call/Fragments/CallsListFragment.swift @@ -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 . + */ + +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.. 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)) +} diff --git a/Linphone/UI/Call/ViewModel/CallViewModel.swift b/Linphone/UI/Call/ViewModel/CallViewModel.swift index 8e98b4914..7bc797ad1 100644 --- a/Linphone/UI/Call/ViewModel/CallViewModel.swift +++ b/Linphone/UI/Call/ViewModel/CallViewModel.swift @@ -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() { diff --git a/Linphone/UI/Call/ViewModel/CallsListViewModel.swift b/Linphone/UI/Call/ViewModel/CallsListViewModel.swift new file mode 100644 index 000000000..edd847b2e --- /dev/null +++ b/Linphone/UI/Call/ViewModel/CallsListViewModel.swift @@ -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 . + */ + +import Foundation + +class CallsListViewModel: ObservableObject { + + var coreContext = CoreContext.shared + + //let nbCalls : Int + + init() { + //self.getCallsList() + } + + func getCallsList() { + coreContext.doOnCoreQueue { core in + core.callsNb + } + } +} diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift index 76f69898d..be78afc16 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift @@ -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.. 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" diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift index 0103f233a..aa8ac1e27 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift @@ -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)!) diff --git a/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift b/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift index 01ae1fcfe..92c4ed7ff 100644 --- a/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift @@ -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 diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index c966069af..c830d265f 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -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 { diff --git a/Linphone/UI/Main/History/Fragments/StartCallFragment.swift b/Linphone/UI/Main/History/Fragments/StartCallFragment.swift index 29d24fc6b..c113de297 100644 --- a/Linphone/UI/Main/History/Fragments/StartCallFragment.swift +++ b/Linphone/UI/Main/History/Fragments/StartCallFragment.swift @@ -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: {}) }