mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-17 11:08:06 +00:00
Start a new call when another call is in progress
This commit is contained in:
parent
fbd578ea37
commit
55631bf4f4
12 changed files with 473 additions and 71 deletions
|
|
@ -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 */,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)"]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
279
Linphone/UI/Call/Fragments/CallsListFragment.swift
Normal file
279
Linphone/UI/Call/Fragments/CallsListFragment.swift
Normal 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))
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
37
Linphone/UI/Call/ViewModel/CallsListViewModel.swift
Normal file
37
Linphone/UI/Call/ViewModel/CallsListViewModel.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)!)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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: {})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue