diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index 1fea99ca8..87d0917df 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -96,7 +96,6 @@ 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 */ @@ -193,7 +192,6 @@ 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 */ @@ -516,7 +514,6 @@ isa = PBXGroup; children = ( D7B99E982B29B39000BE7BF2 /* CallViewModel.swift */, - D7F4D9CC2B5FD83A00CDCD76 /* CallsListViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -715,7 +712,6 @@ 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/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 3064c318a..add41db0d 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -140,6 +140,9 @@ }, "Accept all" : { + }, + "Active" : { + }, "Add a picture" : { @@ -477,6 +480,9 @@ }, "Remove picture" : { + }, + "Resuming" : { + }, "Say %@ and click on the letters given by your correspondent:" : { diff --git a/Linphone/TelecomManager/TelecomManager.swift b/Linphone/TelecomManager/TelecomManager.swift index edb5c263e..80260c035 100644 --- a/Linphone/TelecomManager/TelecomManager.swift +++ b/Linphone/TelecomManager/TelecomManager.swift @@ -113,6 +113,16 @@ class TelecomManager: ObservableObject { } } + func setHeldOtherCallsWithCore(exceptCallid: String) { + CoreContext.shared.doOnCoreQueue { core in + for call in core.calls { + if (call.callLog?.callId != exceptCallid && call.state != .Paused && call.state != .Pausing && call.state != .PausedByRemote) { + self.setHeld(call: call, hold: true) + } + } + } + } + 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) { diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index 5bf4f3347..17ade1bfe 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -48,6 +48,7 @@ struct CallView: View { @State var showingDialer = false + @State var isShowCallsListFragment = false @Binding var isShowStartCallFragment: Bool var body: some View { @@ -104,6 +105,25 @@ struct CallView: View { ) } onDismiss: {} } + + if isShowCallsListFragment { + CallsListFragment(callViewModel: callViewModel, isShowCallsListFragment: $isShowCallsListFragment) + .zIndex(4) + .transition(.move(edge: .bottom)) + /* + .sheet(isPresented: $showingDialer) { + DialerBottomSheet( + startCallViewModel: startCallViewModel, + showingDialer: $showingDialer, + currentCall: nil + ) + .presentationDetents([.medium]) + // .interactiveDismissDisabled() + .presentationBackgroundInteraction(.enabled(upThrough: .medium)) + } + */ + } + if callViewModel.zrtpPopupDisplayed == true { ZRTPPopup(callViewModel: callViewModel) .background(.black.opacity(0.65)) @@ -116,6 +136,7 @@ struct CallView: View { HStack {} .onAppear { callViewModel.resetCallView() + callViewModel.getCallsList() } } } @@ -763,17 +784,20 @@ struct CallView: View { VStack { Button { + callViewModel.getCallsList() + withAnimation { + isShowCallsListFragment.toggle() + } } label: { Image("phone-list") .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("Call list") .foregroundStyle(.white) diff --git a/Linphone/UI/Call/Fragments/CallsListFragment.swift b/Linphone/UI/Call/Fragments/CallsListFragment.swift index 3acd5cfea..bee565a4d 100644 --- a/Linphone/UI/Call/Fragments/CallsListFragment.swift +++ b/Linphone/UI/Call/Fragments/CallsListFragment.swift @@ -21,7 +21,10 @@ import SwiftUI struct CallsListFragment: View { - @ObservedObject var callsListViewModel: CallsListViewModel + @ObservedObject private var coreContext = CoreContext.shared + @ObservedObject private var contactsManager = ContactsManager.shared + + @ObservedObject var callViewModel: CallViewModel @State private var delayedColor = Color.white @@ -66,7 +69,7 @@ struct CallsListFragment: View { .padding(.bottom, 4) .background(.white) - //callsList + callsList } .background(.white) } @@ -85,174 +88,145 @@ struct CallsListFragment: View { } } - /* var callsList: some View { VStack { List { - ForEach(0.. 1 + ? callViewModel.calls[index].callLog!.remoteAddress!.displayName!.components(separatedBy: " ")[1] + : "")) .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] + firstName: callViewModel.calls[index].callLog!.remoteAddress!.username ?? "Username Error", + lastName: callViewModel.calls[index].callLog!.remoteAddress!.username!.components(separatedBy: " ").count > 1 + ? callViewModel.calls[index].callLog!.remoteAddress!.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) + .default_text_style(styleSize: 16) + .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() + Text(callViewModel.calls[index].callLog!.remoteAddress!.displayName != nil + ? callViewModel.calls[index].callLog!.remoteAddress!.displayName! + : callViewModel.calls[index].callLog!.remoteAddress!.username!) + .default_text_style(styleSize: 16) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) } Spacer() + + HStack { + if callViewModel.calls[index].state == .PausedByRemote + || callViewModel.calls[index].state == .Pausing + || callViewModel.calls[index].state == .Paused + || callViewModel.calls[index].state == .Resuming { + Text(callViewModel.calls[index].state == .Resuming ? "Resuming" : "Paused") + .default_text_style_300(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .trailing) + .lineLimit(1) + .padding(.horizontal, 4) + + Image("pause") + .resizable() + .frame(width: 25, height: 25) + } else { + Text("Active") + .default_text_style_300(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .trailing) + .lineLimit(1) + .padding(.horizontal, 4) + + Image("phone-call") + .resizable() + .frame(width: 25, height: 25) + } + } } - - 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)) + .listRowInsets(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)) .listRowSeparator(.hidden) .background(.white) .onTapGesture { - withAnimation { - historyViewModel.displayedCall = historyListViewModel.callLogs[index] + if callViewModel.currentCall != nil && callViewModel.calls[index].callLog!.callId == callViewModel.currentCall!.callLog!.callId { + if callViewModel.currentCall!.state == .StreamsRunning { + do { + try callViewModel.currentCall!.pause() + + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + callViewModel.isPaused = true + } + } catch { + + } + } else { + do { + try callViewModel.currentCall!.resume() + + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + callViewModel.isPaused = false + } + } catch { + + } + } + } else { + TelecomManager.shared.setHeldOtherCallsWithCore(exceptCallid: "") + TelecomManager.shared.setHeld(call: callViewModel.calls[index], hold: callViewModel.calls[index].state == .StreamsRunning) + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + callViewModel.resetCallView() + } } + } .onLongPressGesture(minimumDuration: 0.2) { - historyViewModel.selectedCall = historyListViewModel.callLogs[index] - showingSheet.toggle() } } } .listStyle(.plain) .overlay( VStack { - if historyListViewModel.callLogs.isEmpty { + if callViewModel.calls.isEmpty { Spacer() Image("illus-belledonne") .resizable() @@ -271,9 +245,8 @@ struct CallsListFragment: View { .navigationTitle("") .navigationBarHidden(true) } - */ } #Preview { - CallsListFragment(callsListViewModel: CallsListViewModel(), isShowCallsListFragment: .constant(true)) + CallsListFragment(callViewModel: CallViewModel(), isShowCallsListFragment: .constant(true)) } diff --git a/Linphone/UI/Call/ViewModel/CallViewModel.swift b/Linphone/UI/Call/ViewModel/CallViewModel.swift index 7bc797ad1..794fb5213 100644 --- a/Linphone/UI/Call/ViewModel/CallViewModel.swift +++ b/Linphone/UI/Call/ViewModel/CallViewModel.swift @@ -44,6 +44,8 @@ class CallViewModel: ObservableObject { @Published var isZrtpPq: Bool = false @Published var isRemoteDeviceTrusted: Bool = false + var calls: [Call] = [] + let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() var currentCall: Call? @@ -113,6 +115,12 @@ class CallViewModel: ObservableObject { } } + func getCallsList() { + coreContext.doOnCoreQueue { core in + self.calls = core.calls + } + } + func terminateCall() { coreContext.doOnCoreQueue { core in if self.currentCall != nil { diff --git a/Linphone/UI/Call/ViewModel/CallsListViewModel.swift b/Linphone/UI/Call/ViewModel/CallsListViewModel.swift deleted file mode 100644 index edd847b2e..000000000 --- a/Linphone/UI/Call/ViewModel/CallsListViewModel.swift +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 - } - } -}