From 987ea21e057b80caf3dc1db8cca7454b74705592 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Fri, 18 Nov 2022 17:38:17 +0100 Subject: [PATCH 01/12] Multi call various fixes --- Classes/PhoneMainView.m | 1 - Classes/Swift/CallManager.swift | 2 +- .../LinphoneCore/CallExtensions.swift | 5 +-- .../Voip/ViewModels/CallsViewModel.swift | 42 +++++++++++++------ .../ActiveCallOrConferenceView.swift | 27 +++++++----- .../OutgoingCallView.swift | 3 ++ 6 files changed, 52 insertions(+), 28 deletions(-) diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m index e146e01b0..ce35f4432 100644 --- a/Classes/PhoneMainView.m +++ b/Classes/PhoneMainView.m @@ -374,7 +374,6 @@ static RootViewManager *rootViewManagerInstance = nil; } break; } - case LinphoneCallOutgoingInit: case LinphoneCallOutgoingEarlyMedia: case LinphoneCallOutgoingProgress: case LinphoneCallOutgoingRinging: { diff --git a/Classes/Swift/CallManager.swift b/Classes/Swift/CallManager.swift index c85e86ca5..efcadf96e 100644 --- a/Classes/Swift/CallManager.swift +++ b/Classes/Swift/CallManager.swift @@ -27,7 +27,7 @@ import AVFoundation @objc class CallAppData: NSObject { @objc var batteryWarningShown = false @objc var videoRequested = false /*set when user has requested for video*/ - @objc var isConference = true + @objc var isConference = false } diff --git a/Classes/Swift/Extensions/LinphoneCore/CallExtensions.swift b/Classes/Swift/Extensions/LinphoneCore/CallExtensions.swift index 197f48fbc..ed0fcdecf 100644 --- a/Classes/Swift/Extensions/LinphoneCore/CallExtensions.swift +++ b/Classes/Swift/Extensions/LinphoneCore/CallExtensions.swift @@ -39,10 +39,7 @@ extension Call { extension Call : CustomStringConvertible { public var description: String { - if let callId = callLog?.callId { - return "" - } - return "" + return "" } } diff --git a/Classes/Swift/Voip/ViewModels/CallsViewModel.swift b/Classes/Swift/Voip/ViewModels/CallsViewModel.swift index 284868eb0..734b4fd93 100644 --- a/Classes/Swift/Voip/ViewModels/CallsViewModel.swift +++ b/Classes/Swift/Voip/ViewModels/CallsViewModel.swift @@ -33,9 +33,9 @@ class CallsViewModel { let callConnectedEvent = MutableLiveData() let callUpdateEvent = MutableLiveData() let noMoreCallEvent = MutableLiveData(false) - + var core : Core { get { Core.get() } } - + static let shared = CallsViewModel() private var coreDelegate : CoreDelegateStub? @@ -44,7 +44,11 @@ class CallsViewModel { coreDelegate = CoreDelegateStub( onCallStateChanged : { (core: Core, call: Call, state: Call.State, message:String) -> Void in Log.i("[Calls] Call state changed: \(call) : \(state)") + if (state == .IncomingEarlyMedia || state == .IncomingReceived || state == .OutgoingInit) { + self.addCallToList(call:call) + } let currentCall = core.currentCall + Log.i("[Calls] Current call is \(currentCall)") if (currentCall != nil && self.currentCallData.value??.call.getCobject != currentCall?.getCobject) { self.updateCurrentCallData(currentCall: currentCall) } else if (currentCall == nil && core.callsNb > 0) { @@ -120,13 +124,17 @@ class CallsViewModel { if let removeCandidate = callsData.value?.filter{$0.call.getCobject == call.getCobject}.first { removeCandidate.destroy() } - + callsData.value = callsData.value?.filter(){$0.call.getCobject != call.getCobject} callsData.notifyValue() } private func addCallToList(call: Call) { Log.i("[Calls] Adding call \(call) to calls list") + guard self.callsData.value?.filter({$0.call.getCobject == call.getCobject}).first == nil else { + Log.i("[Calls] \(call) already present in the list") + return + } callsData.value?.append(CallData(call: call)) callsData.notifyValue() } @@ -162,29 +170,39 @@ class CallsViewModel { var callToUse = currentCall if (currentCall == nil) { Log.w("[Calls] Current call is now null") + + if (Core.get().callsNb == 1) { + let firstData = callsData.value?.first + if (firstData != nil && currentCallData.value??.call.getCobject != firstData?.call.getCobject) { + Log.i("[Calls] Only one call in Core and the current call data doesn't match it, updating it") + currentCallData.value = firstData + } + return + } - let firstCall = core.calls.first + let firstCall = core.calls.filter {$0.state != .Error && $0.state != .End && $0.state != .Released}.first if (firstCall != nil && currentCallData.value??.call.getCobject != firstCall?.getCobject) { - Log.i("[Calls] Using first call as \"current\" call") + Log.i("[Calls] Using \(firstCall?.callLog?.callId) as \"current\" call") callToUse = firstCall } } - + guard let callToUse = callToUse else { Log.w("[Calls] No call found to be used as \"current\"") return } - - let firstToUse = callsData.value?.filter{$0.call.getCobject != callToUse.getCobject}.first + + let firstToUse = callsData.value?.filter{$0.call.getCobject == callToUse.getCobject}.first if (firstToUse != nil) { + Log.i("[Calls] Updating current call to : \(firstToUse?.call)") currentCallData.value = firstToUse } else { - Log.w("[Calls] Call not found in calls data list, shouldn't happen!") + Log.w("[Calls] Call not found in calls data list, shouldn't happen! currentCallData is \(callToUse)") currentCallData.value = CallData(call: callToUse) } - - updateUnreadChatCount() + ControlsViewModel.shared.updateMicState() + //updateUnreadChatCount() } - + } diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift index c68d6b705..cd37da76e 100644 --- a/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift @@ -86,13 +86,16 @@ import linphonesw fullScreenMutableContainerView.addSubview(currentCallView!) CallsViewModel.shared.currentCallData.readCurrentAndObserve { (currentCallData) in self.updateNavigation() - self.currentCallView!.isHidden = currentCallData == nil || ConferenceViewModel.shared.conferenceExists.value == true - self.currentCallView!.callData = currentCallData != nil ? currentCallData! : nil + let isConferenceCall = currentCallData??.call.conference != nil + self.currentCallView!.isHidden = isConferenceCall + if (!isConferenceCall) { + self.currentCallView!.callData = currentCallData != nil ? currentCallData! : nil + } currentCallData??.isRemotelyPaused.readCurrentAndObserve { remotelyPaused in - self.callPausedByRemoteView?.isHidden = remotelyPaused != true || ConferenceViewModel.shared.conferenceExists.value == true + self.callPausedByRemoteView?.isHidden = remotelyPaused != true || isConferenceCall } currentCallData??.isPaused.readCurrentAndObserve { locallyPaused in - self.callPausedByLocalView?.isHidden = locallyPaused != true || ConferenceViewModel.shared.conferenceExists.value == true + self.callPausedByLocalView?.isHidden = locallyPaused != true || isConferenceCall } if (currentCallData == nil) { self.callPausedByRemoteView?.isHidden = true @@ -103,8 +106,9 @@ import linphonesw currentCallData??.isOutgoing.readCurrentAndObserve { _ in self.updateNavigation() } } self.extraButtonsView.isHidden = true - self.conferencePausedView?.isHidden = true - if (ConferenceViewModel.shared.conferenceExists.value != true) { + self.conferencePausedView?.isHidden = currentCallData??.call.conference == nil || ConferenceViewModel.shared.isConferenceLocallyPaused.value != true + + if (isConferenceCall) { self.conferenceGridView?.isHidden = true self.conferenceActiveSpeakerView?.isHidden = true self.conferenceAudioOnlyView?.isHidden = true @@ -144,20 +148,22 @@ import linphonesw conferenceGridView?.isHidden = true ConferenceViewModel.shared.conferenceExists.readCurrentAndObserve { (exists) in self.updateNavigation() - if (exists == true) { + let activeCallIsConference = CallsViewModel.shared.currentCallData.value??.call.conference != nil + if (activeCallIsConference) { self.currentCallView!.isHidden = true self.extraButtonsView.isHidden = true - self.conferencePausedView?.isHidden = true + self.conferencePausedView?.isHidden = ConferenceViewModel.shared.isConferenceLocallyPaused.value != true self.displaySelectedConferenceLayout() } else { self.conferenceGridView?.isHidden = true self.conferenceActiveSpeakerView?.isHidden = true self.conferenceActiveSpeakerView?.isHidden = true + self.conferencePausedView?.isHidden = true } } ConferenceViewModel.shared.conferenceCreationPending.readCurrentAndObserve { isCreationPending in - if (ConferenceViewModel.shared.conferenceExists.value == true && isCreationPending == true) { + if (isCreationPending == true) { self.fullScreenMutableContainerView.addSubview(self.conferenceJoinSpinner) self.conferenceJoinSpinner.square(IncomingOutgoingCommonView.spinner_size).center().done() self.conferenceJoinSpinner.startRotation() @@ -186,7 +192,7 @@ import linphonesw } } ConferenceViewModel.shared.isConferenceLocallyPaused.readCurrentAndObserve { (paused) in - self.conferencePausedView?.isHidden = paused != true || ConferenceViewModel.shared.conferenceExists.value != true + self.conferencePausedView?.isHidden = paused != true } @@ -340,6 +346,7 @@ import linphonesw extraButtonsView.refresh() ControlsViewModel.shared.callStatsVisible.notifyValue() CallsViewModel.shared.currentCallData.notifyValue() + ConferenceViewModel.shared.conferenceExists.notifyValue() ControlsViewModel.shared.audioRoutesSelected.value = false ControlsViewModel.shared.fullScreenMode.value = true } diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift index 9692d692a..7823d6643 100644 --- a/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift @@ -84,6 +84,9 @@ import linphonesw override func viewWillAppear(_ animated: Bool) { ControlsViewModel.shared.audioRoutesSelected.value = false super.viewWillAppear(animated) + if (Core.get().callsNb == 0) { + PhoneMainView.instance().popView(self.compositeViewDescription()) + } } @objc override func setCall(call:OpaquePointer) { From 8607469f6843f4f3896498ba4e64880c80723f4b Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Tue, 22 Nov 2022 16:59:01 +0100 Subject: [PATCH 02/12] Refactorisation of call views + various fixes --- Classes/ChatConversationCreateView.m | 4 +- Classes/DialerView.m | 2 +- Classes/LinphoneAppDelegate.m | 7 +- Classes/LinphoneUI/UIBackToCallButton.m | 2 +- Classes/LinphoneUI/UICompositeView.m | 2 +- Classes/PhoneMainView.m | 3 +- .../Views/ConferenceWaitingRoomFragment.swift | 2 +- .../Voip/ViewModels/CallsViewModel.swift | 33 +- .../AbstractCallView.swift | 195 ++++++++ .../AbstractIncomingOutgoingCallView.swift} | 12 +- .../ActiveCallOrConferenceView.swift | 423 ------------------ .../ConferenceCallView.swift | 159 +++++++ .../IncomingCallView.swift | 6 +- .../OutgoingCallView.swift | 4 +- .../SingleCallView.swift | 105 +++++ .../VoipConferenceActiveSpeakerView.swift | 10 +- .../Voip/Views/SharedLayoutConstants.swift | 2 +- linphone.xcodeproj/project.pbxproj | 24 +- 18 files changed, 533 insertions(+), 462 deletions(-) create mode 100644 Classes/Swift/Voip/Views/CompositeViewControllers/AbstractCallView.swift rename Classes/Swift/Voip/Views/{Fragments/IncomingOuntgoingCommonView.swift => CompositeViewControllers/AbstractIncomingOutgoingCallView.swift} (79%) delete mode 100644 Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift create mode 100644 Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift create mode 100644 Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift diff --git a/Classes/ChatConversationCreateView.m b/Classes/ChatConversationCreateView.m index 0398feaeb..4a9ea2617 100644 --- a/Classes/ChatConversationCreateView.m +++ b/Classes/ChatConversationCreateView.m @@ -209,7 +209,7 @@ static UICompositeViewDescription *compositeDescription = nil; [_tableController.contactsGroup removeAllObjects]; if (_isForVoipConference) { if (_isForOngoingVoipConference) { - [PhoneMainView.instance changeCurrentView:VIEW(ActiveCallOrConferenceView).compositeViewDescription]; + [PhoneMainView.instance changeCurrentView:VIEW(ConferenceCallView).compositeViewDescription]; [ControlsViewModelBridge showParticipants]; } else { [PhoneMainView.instance popToView:ConferenceSchedulingView.compositeViewDescription]; @@ -225,7 +225,7 @@ static UICompositeViewDescription *compositeDescription = nil; - (IBAction)onNextClick:(id)sender { if (_isForVoipConference) { if (_isForOngoingVoipConference) { - [PhoneMainView.instance changeCurrentView:VIEW(ActiveCallOrConferenceView).compositeViewDescription]; + [PhoneMainView.instance changeCurrentView:VIEW(ConferenceCallView).compositeViewDescription]; [ConferenceViewModelBridge updateParticipantsListWithAddresses:_tableController.contactsGroup]; } else { [PhoneMainView.instance changeCurrentView:VIEW(ConferenceSchedulingSummaryView).compositeViewDescription]; diff --git a/Classes/DialerView.m b/Classes/DialerView.m index 88593f6b7..500c00ead 100644 --- a/Classes/DialerView.m +++ b/Classes/DialerView.m @@ -399,7 +399,7 @@ static UICompositeViewDescription *compositeDescription = nil; } - (IBAction)onBackClick:(id)event { - [PhoneMainView.instance popToView:ActiveCallOrConferenceView.compositeViewDescription]; + [PhoneMainView.instance popToView:[CallsViewModelBridge callViewToDisplay]]; } - (IBAction)onAddressChange:(id)sender { diff --git a/Classes/LinphoneAppDelegate.m b/Classes/LinphoneAppDelegate.m index 44e5e5ce6..f594ea452 100644 --- a/Classes/LinphoneAppDelegate.m +++ b/Classes/LinphoneAppDelegate.m @@ -138,7 +138,6 @@ if ((floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) { if ([LinphoneManager.instance lpConfigBoolForKey:@"autoanswer_notif_preference"]) { linphone_call_accept(call); - [PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription]; } else { [PhoneMainView.instance displayIncomingCall:call]; } @@ -344,8 +343,9 @@ return NO; } - [PhoneMainView.instance.mainViewController getCachedController:ActiveCallOrConferenceView.compositeViewDescription.name]; // This will create the single instance of the ActiveCallOrConferenceView including listeneres - + [PhoneMainView.instance.mainViewController getCachedController:SingleCallView.compositeViewDescription.name]; // This will create the single instance of the SingleCallView including listeneres + [PhoneMainView.instance.mainViewController getCachedController:ConferenceCallView.compositeViewDescription.name]; // This will create the single instance of the ConferenceCallView including listeneres + [CallsViewModelBridge setupCallsViewNavigation]; return YES; } @@ -630,7 +630,6 @@ } } else if ([response.notification.request.content.categoryIdentifier isEqual:@"video_request"]) { if (!call) return; - [PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription]; NSTimer *videoDismissTimer = nil; UIConfirmationDialog *sheet = [UIConfirmationDialog ShowWithMessage:response.notification.request.content.body cancelMessage:nil diff --git a/Classes/LinphoneUI/UIBackToCallButton.m b/Classes/LinphoneUI/UIBackToCallButton.m index df44ff54f..9e45b2aa9 100644 --- a/Classes/LinphoneUI/UIBackToCallButton.m +++ b/Classes/LinphoneUI/UIBackToCallButton.m @@ -48,7 +48,7 @@ } - (IBAction)onBackToCallClick:(id)sender { - [PhoneMainView.instance popToView:ActiveCallOrConferenceView.compositeViewDescription]; + [PhoneMainView.instance popToView:[CallsViewModelBridge callViewToDisplay]]; } @end diff --git a/Classes/LinphoneUI/UICompositeView.m b/Classes/LinphoneUI/UICompositeView.m index 62010fb00..e370ed159 100644 --- a/Classes/LinphoneUI/UICompositeView.m +++ b/Classes/LinphoneUI/UICompositeView.m @@ -316,7 +316,7 @@ bool remove = true; /*ImagePickerView can be used as popover and we do NOT want to free it*/; - if ([key isEqualToString:ImagePickerView.compositeViewDescription.name] || [key isEqualToString:ActiveCallOrConferenceView.compositeViewDescription.name]) { + if ([key isEqualToString:ImagePickerView.compositeViewDescription.name] || [key isEqualToString:SingleCallView.compositeViewDescription.name] || [key isEqualToString:ConferenceCallView.compositeViewDescription.name]) { remove = false; } else if (exclude != nil) { for (UICompositeViewDescription *description in exclude) { diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m index ce35f4432..854d72c36 100644 --- a/Classes/PhoneMainView.m +++ b/Classes/PhoneMainView.m @@ -385,7 +385,7 @@ static RootViewManager *rootViewManagerInstance = nil; } break; } - case LinphoneCallPausedByRemote: + case LinphoneCallPausedByRemote:break; case LinphoneCallConnected: { if (![LinphoneManager.instance isCTCallCenterExist]) { /*only register CT call center CB for connected call*/ @@ -417,6 +417,7 @@ static RootViewManager *rootViewManagerInstance = nil; } case LinphoneCallUpdating: break; + } if (state == LinphoneCallEnd || state == LinphoneCallError || floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) [self updateApplicationBadgeNumber]; diff --git a/Classes/Swift/Conference/Views/ConferenceWaitingRoomFragment.swift b/Classes/Swift/Conference/Views/ConferenceWaitingRoomFragment.swift index dfb537d00..fd1a8f10c 100644 --- a/Classes/Swift/Conference/Views/ConferenceWaitingRoomFragment.swift +++ b/Classes/Swift/Conference/Views/ConferenceWaitingRoomFragment.swift @@ -142,7 +142,7 @@ import linphonesw self.layoutPicker?.isHidden = joining == true if (joining == true) { self.view.addSubview(self.conferenceJoinSpinner) - self.conferenceJoinSpinner.square(IncomingOutgoingCommonView.spinner_size).center().done() + self.conferenceJoinSpinner.square(AbstractIncomingOutgoingCallView.spinner_size).center().done() self.conferenceJoinSpinner.startRotation() self.controlsView.isHidden = true } else { diff --git a/Classes/Swift/Voip/ViewModels/CallsViewModel.swift b/Classes/Swift/Voip/ViewModels/CallsViewModel.swift index 734b4fd93..85cb96a13 100644 --- a/Classes/Swift/Voip/ViewModels/CallsViewModel.swift +++ b/Classes/Swift/Voip/ViewModels/CallsViewModel.swift @@ -72,13 +72,14 @@ class CallsViewModel { call.answerVideoUpdateRequest(accept: false) } } - }else if (state == Call.State.Connected) { + } else if (state == Call.State.Connected) { self.callConnectedEvent.value = call } else if (state == Call.State.StreamsRunning) { self.callUpdateEvent.value = call } self.updateInactiveCallsCount() self.callsData.notifyValue() + self.currentCallData.notifyValue() }, onMessageReceived : { (core: Core, room: ChatRoom, message: ChatMessage) -> Void in @@ -203,6 +204,32 @@ class CallsViewModel { ControlsViewModel.shared.updateMicState() //updateUnreadChatCount() } - - +} + +@objc class CallsViewModelBridge : NSObject { + @objc static func callViewToDisplay() -> UICompositeViewDescription? { + if let call = CallsViewModel.shared.currentCallData.value??.call { + if (call.conference != nil) { + return ConferenceCallView.compositeDescription + } else { + return SingleCallView.compositeDescription + } + } else { + return DialerView.compositeViewDescription() + } + } + @objc static func setupCallsViewNavigation() { + CallsViewModel.shared.currentCallData.readCurrentAndObserve { currentCallData in + guard currentCallData != nil && currentCallData??.call != nil && currentCallData??.isOutgoing.value != true && currentCallData??.isIncoming.value != true else { + PhoneMainView.instance().popView(SingleCallView.compositeDescription) + PhoneMainView.instance().popView(ConferenceCallView.compositeDescription) + return + } + if (currentCallData??.call.conference != nil) { + PhoneMainView.instance().changeCurrentView(ConferenceCallView.compositeDescription) + } else { + PhoneMainView.instance().changeCurrentView(SingleCallView.compositeDescription) + } + } + } } diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/AbstractCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/AbstractCallView.swift new file mode 100644 index 000000000..48d1368bd --- /dev/null +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/AbstractCallView.swift @@ -0,0 +1,195 @@ +/* + * 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 UIKit +import linphonesw + + +@objc class AbstractCallView: UIViewController { + + let extraButtonsView = VoipExtraButtonsView() + var numpadView : NumpadView? = nil + var currentCallStatsVew : CallStatsView? = nil + var shadingMask = UIView() + var videoAcceptDialog : VoipDialog? = nil + var dismissableView : DismissableView? = nil + + var audioRoutesView : AudioRoutesView? = nil + let fullScreenMutableContainerView = UIView() + let controlsView = ControlsView(showVideo: true, controlsViewModel: ControlsViewModel.shared) + + override func viewDidLoad() { + super.viewDidLoad() + + UIDeviceBridge.displayModeSwitched.readCurrentAndObserve { _ in + self.view.backgroundColor = VoipTheme.voipBackgroundColor.get() + } + + // Hangup + let hangup = CallControlButton(width: 65, imageInset:AbstractIncomingOutgoingCallView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: { + ControlsViewModel.shared.hangUp() + }) + view.addSubview(hangup) + hangup.alignParentLeft(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done() + + + // Controls + view.addSubview(controlsView) + controlsView.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done() + + // Container view + fullScreenMutableContainerView.backgroundColor = .clear + self.view.addSubview(fullScreenMutableContainerView) + fullScreenMutableContainerView.alignParentLeft(withMargin: SharedLayoutConstants.content_inset).alignParentRight(withMargin: SharedLayoutConstants.content_inset).matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done() + + + // Calls List + ControlsViewModel.shared.goToCallsListEvent.observe { (_) in + self.dismissableView = CallsListView() + self.view.addSubview(self.dismissableView!) + self.dismissableView?.matchParentDimmensions().done() + } + + // Goto chat + ControlsViewModel.shared.goToChatEvent.observe { (_) in + self.goToChat() + } + + + // Shading mask, everything before will be shaded upon displaying of the mask + shadingMask.backgroundColor = VoipTheme.voip_translucent_popup_background + shadingMask.isHidden = true + self.view.addSubview(shadingMask) + shadingMask.matchParentDimmensions().done() + + // Extra Buttons + let showextraButtons = CallControlButton(imageInset:AbstractIncomingOutgoingCallView.answer_decline_inset, buttonTheme: VoipTheme.call_more, onClickAction: { + self.showModalSubview(view: self.extraButtonsView) + ControlsViewModel.shared.audioRoutesSelected.value = false + }) + view.addSubview(showextraButtons) + showextraButtons.alignParentRight(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done() + + let boucingCounter = BouncingCounter(inButton:showextraButtons) + view.addSubview(boucingCounter) + boucingCounter.dataSource = CallsViewModel.shared.chatAndCallsCount + + view.addSubview(extraButtonsView) + extraButtonsView.matchParentSideBorders(insetedByDx: SharedLayoutConstants.content_inset).alignParentBottom(withMargin:SharedLayoutConstants.bottom_margin_notch_clearance).done() + ControlsViewModel.shared.hideExtraButtons.readCurrentAndObserve { (_) in + self.hideModalSubview(view: self.extraButtonsView) + } + shadingMask.onClick { + if (!self.extraButtonsView.isHidden) { + self.hideModalSubview(view: self.extraButtonsView) + } + ControlsViewModel.shared.audioRoutesSelected.value = false + } + + // Numpad + ControlsViewModel.shared.numpadVisible.readCurrentAndObserve { (visible) in + if (visible == true && CallsViewModel.shared.currentCallData.value != nil ) { + self.numpadView?.removeFromSuperview() + self.shadingMask.isHidden = false + self.numpadView = NumpadView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!,marginTop: 0.0, above:self.controlsView, onDismissAction: { + ControlsViewModel.shared.numpadVisible.value = false + }) + } else { + self.numpadView?.removeFromSuperview() + self.shadingMask.isHidden = true + } + + } + + // Call stats + ControlsViewModel.shared.callStatsVisible.readCurrentAndObserve { (visible) in + if (visible == true && CallsViewModel.shared.currentCallData.value != nil ) { + self.currentCallStatsVew?.removeFromSuperview() + self.shadingMask.isHidden = false + self.currentCallStatsVew = CallStatsView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!,marginTop:0.0, above:self.controlsView, onDismissAction: { + ControlsViewModel.shared.callStatsVisible.value = false + }) + } else { + self.currentCallStatsVew?.removeFromSuperview() + self.shadingMask.isHidden = true + } + } + + // Audio Routes + audioRoutesView = AudioRoutesView() + view.addSubview(audioRoutesView!) + audioRoutesView!.alignBottomWith(otherView: controlsView).done() + ControlsViewModel.shared.audioRoutesSelected.readCurrentAndObserve { (audioRoutesSelected) in + self.audioRoutesView!.isHidden = audioRoutesSelected != true + } + audioRoutesView!.alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(true) + extraButtonsView.refresh() + ControlsViewModel.shared.callStatsVisible.notifyValue() + ControlsViewModel.shared.audioRoutesSelected.value = false + } + + override func viewWillDisappear(_ animated: Bool) { + dismissableView?.removeFromSuperview() + dismissableView = nil + + ControlsViewModel.shared.numpadVisible.value = false + ControlsViewModel.shared.callStatsVisible.value = false + ControlsViewModel.shared.fullScreenMode.value = false + super.viewWillDisappear(animated) + } + + func showModalSubview(view:UIView) { + view.isHidden = false + shadingMask.isHidden = false + } + func hideModalSubview(view:UIView) { + view.isHidden = true + shadingMask.isHidden = true + } + + func goToChat() { + /*guard + let chatRoom = CallsViewModel.shared.currentCallData.value??.chatRoom + else { + Log.w("[Call] Failed to find existing chat room associated to call") + return + }*/ + PhoneMainView.instance().changeCurrentView(ChatsListView.compositeViewDescription()) + + } + + func layoutRotatableElements() { + let leftMargin = UIDevice.current.orientation == .landscapeLeft && UIDevice.hasNotch() ? UIApplication.shared.keyWindow!.safeAreaInsets.left : SharedLayoutConstants.content_inset + let rightMargin = UIDevice.current.orientation == .landscapeRight && UIDevice.hasNotch() ? UIApplication.shared.keyWindow!.safeAreaInsets.right : SharedLayoutConstants.content_inset + fullScreenMutableContainerView.updateAlignParentLeft(withMargin: leftMargin).updateAlignParentRight(withMargin: rightMargin).done() + controlsView.updateAlignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done() + } + + override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) { + super.didRotate(from: fromInterfaceOrientation) + self.layoutRotatableElements() + } + + +} diff --git a/Classes/Swift/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/AbstractIncomingOutgoingCallView.swift similarity index 79% rename from Classes/Swift/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift rename to Classes/Swift/Voip/Views/CompositeViewControllers/AbstractIncomingOutgoingCallView.swift index 4ceb0020f..ccf5f120c 100644 --- a/Classes/Swift/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/AbstractIncomingOutgoingCallView.swift @@ -22,7 +22,7 @@ import UIKit import Foundation import linphonesw -@objc class IncomingOutgoingCommonView: UIViewController { // Shared between IncomingCallView and OutgoingCallVIew (all upper part except control buttons) +@objc class AbstractIncomingOutgoingCallView: UIViewController { // Shared between IncomingCallView and OutgoingCallVIew (all upper part except control buttons) // Layout constants static let spinner_size = 50 @@ -58,22 +58,22 @@ import linphonesw view.backgroundColor = VoipTheme.voipBackgroundColor.get() view.addSubview(spinner) - spinner.square(IncomingOutgoingCommonView.spinner_size).matchParentSideBorders().alignParentTop(withMargin:IncomingOutgoingCommonView.spinner_margin_top + UIDevice.notchHeight()).done() + spinner.square(AbstractIncomingOutgoingCallView.spinner_size).matchParentSideBorders().alignParentTop(withMargin:AbstractIncomingOutgoingCallView.spinner_margin_top + UIDevice.notchHeight()).done() let callType = StyledLabel(VoipTheme.call_header_title,forCallType) view.addSubview(callType) - callType.matchParentSideBorders().alignUnder(view:spinner,withMargin:IncomingOutgoingCommonView.call_type_margin_top).done() + callType.matchParentSideBorders().alignUnder(view:spinner,withMargin:AbstractIncomingOutgoingCallView.call_type_margin_top).done() self.view.addSubview(duration) - duration.matchParentSideBorders().alignUnder(view:callType,withMargin:IncomingOutgoingCommonView.duration_margin_top).done() + duration.matchParentSideBorders().alignUnder(view:callType,withMargin:AbstractIncomingOutgoingCallView.duration_margin_top).done() // Center : Avatar + Display name + SIP Address let centerSection = UIView() centerSection.addSubview(avatar) centerSection.addSubview(displayName) - displayName.height(IncomingOutgoingCommonView.display_name_height).matchParentSideBorders().alignUnder(view:avatar,withMargin:IncomingOutgoingCommonView.display_name_margin_top).done() + displayName.height(AbstractIncomingOutgoingCallView.display_name_height).matchParentSideBorders().alignUnder(view:avatar,withMargin:AbstractIncomingOutgoingCallView.display_name_margin_top).done() centerSection.addSubview(sipAddress) - sipAddress.height(IncomingOutgoingCommonView.sip_address_height).matchParentSideBorders().alignUnder(view:displayName,withMargin:IncomingOutgoingCommonView.sip_address_margin_top).done() + sipAddress.height(AbstractIncomingOutgoingCallView.sip_address_height).matchParentSideBorders().alignUnder(view:displayName,withMargin:AbstractIncomingOutgoingCallView.sip_address_margin_top).done() self.view.addSubview(centerSection) centerSection.matchParentSideBorders().center().done() diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift deleted file mode 100644 index cd37da76e..000000000 --- a/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift +++ /dev/null @@ -1,423 +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 UIKit -import linphonesw - - -@objc class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // Replaces CallView - - // Layout constants - static let content_inset = 12.0 - - var callPausedByRemoteView : PausedCallOrConferenceView? = nil - var callPausedByLocalView : PausedCallOrConferenceView? = nil - - var conferencePausedView : PausedCallOrConferenceView? = nil - - var currentCallView : ActiveCallView? = nil - var conferenceGridView: VoipConferenceGridView? = nil - var conferenceActiveSpeakerView: VoipConferenceActiveSpeakerView? = nil - var conferenceAudioOnlyView: VoipConferenceAudioOnlyView? = nil - - let conferenceJoinSpinner = RotatingSpinner() - - - let extraButtonsView = VoipExtraButtonsView() - var numpadView : NumpadView? = nil - var currentCallStatsVew : CallStatsView? = nil - var shadingMask = UIView() - var videoAcceptDialog : VoipDialog? = nil - var dismissableView : DismissableView? = nil - @objc var participantsListView : ParticipantsListView? = nil - - var audioRoutesView : AudioRoutesView? = nil - let fullScreenMutableContainerView = UIView() - let controlsView = ControlsView(showVideo: true, controlsViewModel: ControlsViewModel.shared) - - static let compositeDescription = UICompositeViewDescription(ActiveCallOrConferenceView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: nil, fullscreen: false, isLeftFragment: false,fragmentWith: nil) - static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription } - func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription } - - override func viewDidLoad() { - super.viewDidLoad() - - UIDeviceBridge.displayModeSwitched.readCurrentAndObserve { _ in - self.view.backgroundColor = VoipTheme.voipBackgroundColor.get() - } - - // Hangup - let hangup = CallControlButton(width: 65, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: { - ControlsViewModel.shared.hangUp() - }) - view.addSubview(hangup) - hangup.alignParentLeft(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done() - - - // Controls - view.addSubview(controlsView) - controlsView.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done() - - // Container view - fullScreenMutableContainerView.backgroundColor = .clear - self.view.addSubview(fullScreenMutableContainerView) - fullScreenMutableContainerView.alignParentLeft(withMargin: ActiveCallOrConferenceView.content_inset).alignParentRight(withMargin: ActiveCallOrConferenceView.content_inset).matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done() - - // Current (Single) Call (VoipCallView) - currentCallView = ActiveCallView() - currentCallView!.isHidden = true - fullScreenMutableContainerView.addSubview(currentCallView!) - CallsViewModel.shared.currentCallData.readCurrentAndObserve { (currentCallData) in - self.updateNavigation() - let isConferenceCall = currentCallData??.call.conference != nil - self.currentCallView!.isHidden = isConferenceCall - if (!isConferenceCall) { - self.currentCallView!.callData = currentCallData != nil ? currentCallData! : nil - } - currentCallData??.isRemotelyPaused.readCurrentAndObserve { remotelyPaused in - self.callPausedByRemoteView?.isHidden = remotelyPaused != true || isConferenceCall - } - currentCallData??.isPaused.readCurrentAndObserve { locallyPaused in - self.callPausedByLocalView?.isHidden = locallyPaused != true || isConferenceCall - } - if (currentCallData == nil) { - self.callPausedByRemoteView?.isHidden = true - self.callPausedByLocalView?.isHidden = true - - } else { - currentCallData??.isIncoming.readCurrentAndObserve { _ in self.updateNavigation() } - currentCallData??.isOutgoing.readCurrentAndObserve { _ in self.updateNavigation() } - } - self.extraButtonsView.isHidden = true - self.conferencePausedView?.isHidden = currentCallData??.call.conference == nil || ConferenceViewModel.shared.isConferenceLocallyPaused.value != true - - if (isConferenceCall) { - self.conferenceGridView?.isHidden = true - self.conferenceActiveSpeakerView?.isHidden = true - self.conferenceAudioOnlyView?.isHidden = true - } - - } - - currentCallView!.matchParentDimmensions().done() - - // Paused by remote (Call) - callPausedByRemoteView = PausedCallOrConferenceView(iconName: "voip_conference_paused_big",titleText: VoipTexts.call_remotely_paused_title,subTitleText: nil) - view.addSubview(callPausedByRemoteView!) - callPausedByRemoteView?.matchParentSideBorders().matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done() - callPausedByRemoteView?.isHidden = true - - // Paused by local (Call) - callPausedByLocalView = PausedCallOrConferenceView(iconName: "voip_conference_play_big",titleText: VoipTexts.call_locally_paused_title,subTitleText: VoipTexts.call_locally_paused_subtitle, onClickAction: { - CallsViewModel.shared.currentCallData.value??.togglePause() - }) - view.addSubview(callPausedByLocalView!) - callPausedByLocalView?.matchParentSideBorders().matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done() - callPausedByLocalView?.isHidden = true - - - // Conference paused - conferencePausedView = PausedCallOrConferenceView(iconName: "voip_conference_play_big",titleText: VoipTexts.conference_paused_title,subTitleText: VoipTexts.conference_paused_subtitle, onClickAction: { - ConferenceViewModel.shared.togglePlayPause() - }) - view.addSubview(conferencePausedView!) - conferencePausedView?.matchParentSideBorders().matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done() - conferencePausedView?.isHidden = true - - // Conference grid - conferenceGridView = VoipConferenceGridView() - fullScreenMutableContainerView.addSubview(conferenceGridView!) - conferenceGridView?.matchParentDimmensions().done() - conferenceGridView?.isHidden = true - ConferenceViewModel.shared.conferenceExists.readCurrentAndObserve { (exists) in - self.updateNavigation() - let activeCallIsConference = CallsViewModel.shared.currentCallData.value??.call.conference != nil - if (activeCallIsConference) { - self.currentCallView!.isHidden = true - self.extraButtonsView.isHidden = true - self.conferencePausedView?.isHidden = ConferenceViewModel.shared.isConferenceLocallyPaused.value != true - self.displaySelectedConferenceLayout() - } else { - self.conferenceGridView?.isHidden = true - self.conferenceActiveSpeakerView?.isHidden = true - self.conferenceActiveSpeakerView?.isHidden = true - self.conferencePausedView?.isHidden = true - } - } - - ConferenceViewModel.shared.conferenceCreationPending.readCurrentAndObserve { isCreationPending in - if (isCreationPending == true) { - self.fullScreenMutableContainerView.addSubview(self.conferenceJoinSpinner) - self.conferenceJoinSpinner.square(IncomingOutgoingCommonView.spinner_size).center().done() - self.conferenceJoinSpinner.startRotation() - } else { - self.conferenceJoinSpinner.removeFromSuperview() - self.conferenceJoinSpinner.stopRotation() - } - } - - // Conference active speaker - conferenceActiveSpeakerView = VoipConferenceActiveSpeakerView() - fullScreenMutableContainerView.addSubview(conferenceActiveSpeakerView!) - conferenceActiveSpeakerView?.matchParentDimmensions().done() - conferenceActiveSpeakerView?.isHidden = true - - - // Conference audio only - conferenceAudioOnlyView = VoipConferenceAudioOnlyView() - fullScreenMutableContainerView.addSubview(conferenceAudioOnlyView!) - conferenceAudioOnlyView?.matchParentDimmensions().done() - conferenceAudioOnlyView?.isHidden = true - - ConferenceViewModel.shared.conferenceDisplayMode.readCurrentAndObserve { (conferenceMode) in - if (ConferenceViewModel.shared.conferenceExists.value == true) { - self.displaySelectedConferenceLayout() - } - } - ConferenceViewModel.shared.isConferenceLocallyPaused.readCurrentAndObserve { (paused) in - self.conferencePausedView?.isHidden = paused != true - } - - - // Calls List - ControlsViewModel.shared.goToCallsListEvent.observe { (_) in - self.dismissableView = CallsListView() - self.view.addSubview(self.dismissableView!) - self.dismissableView?.matchParentDimmensions().done() - } - - // Conference Participants List - ControlsViewModel.shared.goToConferenceParticipantsListEvent.observe { (_) in - self.participantsListView = ParticipantsListView() - self.view.addSubview(self.participantsListView!) - self.participantsListView?.matchParentDimmensions().done() - } - - // Goto chat - ControlsViewModel.shared.goToChatEvent.observe { (_) in - self.goToChat() - } - - // Conference mode selection - ControlsViewModel.shared.goToConferenceLayoutSettings.observe { (_) in - self.dismissableView = VoipConferenceDisplayModeSelectionView() - self.view.addSubview(self.dismissableView!) - self.dismissableView?.matchParentDimmensions().done() - } - - // Shading mask, everything before will be shaded upon displaying of the mask - shadingMask.backgroundColor = VoipTheme.voip_translucent_popup_background - shadingMask.isHidden = true - self.view.addSubview(shadingMask) - shadingMask.matchParentDimmensions().done() - - // Extra Buttons - let showextraButtons = CallControlButton(imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_more, onClickAction: { - self.showModalSubview(view: self.extraButtonsView) - ControlsViewModel.shared.audioRoutesSelected.value = false - }) - view.addSubview(showextraButtons) - showextraButtons.alignParentRight(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done() - - let boucingCounter = BouncingCounter(inButton:showextraButtons) - view.addSubview(boucingCounter) - boucingCounter.dataSource = CallsViewModel.shared.chatAndCallsCount - - view.addSubview(extraButtonsView) - extraButtonsView.matchParentSideBorders(insetedByDx: ActiveCallOrConferenceView.content_inset).alignParentBottom(withMargin:SharedLayoutConstants.bottom_margin_notch_clearance).done() - ControlsViewModel.shared.hideExtraButtons.readCurrentAndObserve { (_) in - self.hideModalSubview(view: self.extraButtonsView) - } - shadingMask.onClick { - if (!self.extraButtonsView.isHidden) { - self.hideModalSubview(view: self.extraButtonsView) - } - ControlsViewModel.shared.audioRoutesSelected.value = false - } - - // Numpad - ControlsViewModel.shared.numpadVisible.readCurrentAndObserve { (visible) in - if (visible == true && CallsViewModel.shared.currentCallData.value != nil ) { - self.numpadView?.removeFromSuperview() - self.shadingMask.isHidden = false - self.numpadView = NumpadView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!,marginTop:self.currentCallView?.centerSection.frame.origin.y ?? 0.0, above:self.controlsView, onDismissAction: { - ControlsViewModel.shared.numpadVisible.value = false - }) - } else { - self.numpadView?.removeFromSuperview() - self.shadingMask.isHidden = true - } - - } - - // Call stats - ControlsViewModel.shared.callStatsVisible.readCurrentAndObserve { (visible) in - if (visible == true && CallsViewModel.shared.currentCallData.value != nil ) { - self.currentCallStatsVew?.removeFromSuperview() - self.shadingMask.isHidden = false - self.currentCallStatsVew = CallStatsView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!,marginTop:self.currentCallView?.centerSection.frame.origin.y ?? 0.0, above:self.controlsView, onDismissAction: { - ControlsViewModel.shared.callStatsVisible.value = false - }) - } else { - self.currentCallStatsVew?.removeFromSuperview() - self.shadingMask.isHidden = true - } - } - - // Video activation dialog request - CallsViewModel.shared.callUpdateEvent.observe { (call) in - let core = Core.get() - if (call?.state == .StreamsRunning) { - self.videoAcceptDialog?.removeFromSuperview() - self.videoAcceptDialog = nil - } else if (call?.state == .UpdatedByRemote) { - if (core.videoCaptureEnabled || core.videoDisplayEnabled) { - if (call?.currentParams?.videoEnabled != call?.remoteParams?.videoEnabled) { - let accept = ButtonAttributes(text:VoipTexts.dialog_accept, action: {call?.answerVideoUpdateRequest(accept: true)}, isDestructive:false) - let cancel = ButtonAttributes(text:VoipTexts.dialog_decline, action: {call?.answerVideoUpdateRequest(accept: false)}, isDestructive:true) - self.videoAcceptDialog = VoipDialog(message:VoipTexts.call_video_update_requested_dialog, givenButtons: [cancel,accept]) - self.videoAcceptDialog?.show() - } - } else { - Log.w("[Call] Video display & capture are disabled, don't show video dialog") - } - } - } - - // Audio Routes - audioRoutesView = AudioRoutesView() - view.addSubview(audioRoutesView!) - audioRoutesView!.alignBottomWith(otherView: controlsView).done() - ControlsViewModel.shared.audioRoutesSelected.readCurrentAndObserve { (audioRoutesSelected) in - self.audioRoutesView!.isHidden = audioRoutesSelected != true - } - audioRoutesView!.alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done() - - // First/Last to join conference : - - ConferenceViewModel.shared.allParticipantsLeftEvent.observe { (allLeft) in - if (allLeft == true) { - VoipDialog.toast(message: VoipTexts.conference_last_user) - } - } - ConferenceViewModel.shared.firstToJoinEvent.observe { (first) in - if (first == true) { - VoipDialog.toast(message: VoipTexts.conference_first_to_join) - } - } - - } // viewDidLoad - - func displaySelectedConferenceLayout() { - let conferenceMode = ConferenceViewModel.shared.conferenceDisplayMode.value - self.conferenceGridView!.isHidden = conferenceMode != .Grid - self.conferenceActiveSpeakerView!.isHidden = conferenceMode != .ActiveSpeaker - self.conferenceAudioOnlyView!.isHidden = conferenceMode != .AudioOnly - if (conferenceMode == .Grid) { - self.conferenceGridView?.conferenceViewModel = ConferenceViewModel.shared - } - if (conferenceMode == .AudioOnly) { - self.conferenceAudioOnlyView?.conferenceViewModel = ConferenceViewModel.shared - } - if (conferenceMode == .ActiveSpeaker) { - self.conferenceActiveSpeakerView?.conferenceViewModel = ConferenceViewModel.shared - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(true) - extraButtonsView.refresh() - ControlsViewModel.shared.callStatsVisible.notifyValue() - CallsViewModel.shared.currentCallData.notifyValue() - ConferenceViewModel.shared.conferenceExists.notifyValue() - ControlsViewModel.shared.audioRoutesSelected.value = false - ControlsViewModel.shared.fullScreenMode.value = true - } - - override func viewWillDisappear(_ animated: Bool) { - dismissableView?.removeFromSuperview() - dismissableView = nil - - participantsListView?.removeFromSuperview() - participantsListView = nil - - ControlsViewModel.shared.numpadVisible.value = false - ControlsViewModel.shared.callStatsVisible.value = false - ControlsViewModel.shared.fullScreenMode.value = false - super.viewWillDisappear(animated) - } - - func showModalSubview(view:UIView) { - view.isHidden = false - shadingMask.isHidden = false - } - func hideModalSubview(view:UIView) { - view.isHidden = true - shadingMask.isHidden = true - } - - func updateNavigation() { - if (Core.get().callsNb == 0) { - PhoneMainView.instance().popView(self.compositeViewDescription()) - } else { - if let data = CallsViewModel.shared.currentCallData.value { - if (data?.isOutgoing.value == true || data?.isIncoming.value == true) { - PhoneMainView.instance().popView(self.compositeViewDescription()) - } else { - if (data!.isInRemoteConference.value == true) { - PhoneMainView.instance().pop(toView: self.compositeViewDescription()) - } else { - PhoneMainView.instance().changeCurrentView(self.compositeViewDescription()) - } - } - } else { - PhoneMainView.instance().changeCurrentView(self.compositeViewDescription()) - } - } - } - - func goToChat() { - /*guard - let chatRoom = CallsViewModel.shared.currentCallData.value??.chatRoom - else { - Log.w("[Call] Failed to find existing chat room associated to call") - return - }*/ - PhoneMainView.instance().changeCurrentView(ChatsListView.compositeViewDescription()) - - } - - - func layoutRotatableElements() { - let leftMargin = UIDevice.current.orientation == .landscapeLeft && UIDevice.hasNotch() ? UIApplication.shared.keyWindow!.safeAreaInsets.left : ActiveCallOrConferenceView.content_inset - let rightMargin = UIDevice.current.orientation == .landscapeRight && UIDevice.hasNotch() ? UIApplication.shared.keyWindow!.safeAreaInsets.right : ActiveCallOrConferenceView.content_inset - fullScreenMutableContainerView.updateAlignParentLeft(withMargin: leftMargin).updateAlignParentRight(withMargin: rightMargin).done() - controlsView.updateAlignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done() - } - - override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) { - super.didRotate(from: fromInterfaceOrientation) - self.layoutRotatableElements() - self.conferenceActiveSpeakerView?.layoutRotatableElements() - self.currentCallView?.layoutRotatableElements() - } - - -} diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift new file mode 100644 index 000000000..4b39128cb --- /dev/null +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift @@ -0,0 +1,159 @@ +/* + * 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 UIKit +import linphonesw + + +@objc class ConferenceCallView: AbstractCallView, UICompositeViewDelegate { + + var conferencePausedView : PausedCallOrConferenceView? = nil + var conferenceGridView: VoipConferenceGridView? = nil + var conferenceActiveSpeakerView: VoipConferenceActiveSpeakerView? = nil + var conferenceAudioOnlyView: VoipConferenceAudioOnlyView? = nil + let conferenceJoinSpinner = RotatingSpinner() + @objc var participantsListView : ParticipantsListView? = nil + + static let compositeDescription = UICompositeViewDescription(ConferenceCallView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: nil, fullscreen: false, isLeftFragment: false,fragmentWith: nil) + static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription } + func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription } + + override func viewDidLoad() { + super.viewDidLoad() + + // Conference paused + conferencePausedView = PausedCallOrConferenceView(iconName: "voip_conference_play_big",titleText: VoipTexts.conference_paused_title,subTitleText: VoipTexts.conference_paused_subtitle, onClickAction: { + ConferenceViewModel.shared.togglePlayPause() + }) + view.addSubview(conferencePausedView!) + conferencePausedView?.matchParentSideBorders().matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done() + conferencePausedView?.isHidden = true + + // Conference grid + conferenceGridView = VoipConferenceGridView() + fullScreenMutableContainerView.addSubview(conferenceGridView!) + conferenceGridView?.matchParentDimmensions().done() + conferenceGridView?.isHidden = true + ConferenceViewModel.shared.conferenceExists.readCurrentAndObserve { (exists) in + if (exists == true) { + self.extraButtonsView.isHidden = true + self.conferencePausedView?.isHidden = ConferenceViewModel.shared.isConferenceLocallyPaused.value != true + self.displaySelectedConferenceLayout() + } else { + self.conferenceGridView?.isHidden = true + self.conferenceActiveSpeakerView?.isHidden = true + self.conferenceActiveSpeakerView?.isHidden = true + self.conferencePausedView?.isHidden = true + } + } + + ConferenceViewModel.shared.conferenceCreationPending.readCurrentAndObserve { isCreationPending in + if (isCreationPending == true) { + self.fullScreenMutableContainerView.addSubview(self.conferenceJoinSpinner) + self.conferenceJoinSpinner.square(AbstractIncomingOutgoingCallView.spinner_size).center().done() + self.conferenceJoinSpinner.startRotation() + } else { + self.conferenceJoinSpinner.removeFromSuperview() + self.conferenceJoinSpinner.stopRotation() + } + } + + // Conference active speaker + conferenceActiveSpeakerView = VoipConferenceActiveSpeakerView() + fullScreenMutableContainerView.addSubview(conferenceActiveSpeakerView!) + conferenceActiveSpeakerView?.matchParentDimmensions().done() + conferenceActiveSpeakerView?.isHidden = true + + // Conference audio only + conferenceAudioOnlyView = VoipConferenceAudioOnlyView() + fullScreenMutableContainerView.addSubview(conferenceAudioOnlyView!) + conferenceAudioOnlyView?.matchParentDimmensions().done() + conferenceAudioOnlyView?.isHidden = true + + ConferenceViewModel.shared.conferenceDisplayMode.readCurrentAndObserve { (conferenceMode) in + if (ConferenceViewModel.shared.conferenceExists.value == true) { + self.displaySelectedConferenceLayout() + } + } + ConferenceViewModel.shared.isConferenceLocallyPaused.readCurrentAndObserve { (paused) in + self.conferencePausedView?.isHidden = paused != true + } + + // Conference Participants List + ControlsViewModel.shared.goToConferenceParticipantsListEvent.observe { (_) in + self.participantsListView = ParticipantsListView() + self.view.addSubview(self.participantsListView!) + self.participantsListView?.matchParentDimmensions().done() + } + + // Conference mode selection + ControlsViewModel.shared.goToConferenceLayoutSettings.observe { (_) in + self.dismissableView = VoipConferenceDisplayModeSelectionView() + self.view.addSubview(self.dismissableView!) + self.dismissableView?.matchParentDimmensions().done() + } + + // First/Last to join conference : + ConferenceViewModel.shared.allParticipantsLeftEvent.observe { (allLeft) in + if (allLeft == true) { + VoipDialog.toast(message: VoipTexts.conference_last_user) + } + } + ConferenceViewModel.shared.firstToJoinEvent.observe { (first) in + if (first == true) { + VoipDialog.toast(message: VoipTexts.conference_first_to_join) + } + } + } + + func displaySelectedConferenceLayout() { + let conferenceMode = ConferenceViewModel.shared.conferenceDisplayMode.value + self.conferenceGridView!.isHidden = conferenceMode != .Grid + self.conferenceActiveSpeakerView!.isHidden = conferenceMode != .ActiveSpeaker + self.conferenceAudioOnlyView!.isHidden = conferenceMode != .AudioOnly + if (conferenceMode == .Grid) { + self.conferenceGridView?.conferenceViewModel = ConferenceViewModel.shared + } + if (conferenceMode == .AudioOnly) { + self.conferenceAudioOnlyView?.conferenceViewModel = ConferenceViewModel.shared + } + if (conferenceMode == .ActiveSpeaker) { + self.conferenceActiveSpeakerView?.conferenceViewModel = ConferenceViewModel.shared + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(true) + ConferenceViewModel.shared.conferenceExists.notifyValue() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + participantsListView?.removeFromSuperview() + participantsListView = nil + } + + override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) { + super.didRotate(from: fromInterfaceOrientation) + self.conferenceActiveSpeakerView?.layoutRotatableElements() + } + + +} diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/IncomingCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/IncomingCallView.swift index e1a7693bc..ef9b4af3a 100644 --- a/Classes/Swift/Voip/Views/CompositeViewControllers/IncomingCallView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/IncomingCallView.swift @@ -22,7 +22,7 @@ import UIKit import Foundation import linphonesw -@objc class IncomingCallView: IncomingOutgoingCommonView, UICompositeViewDelegate { +@objc class IncomingCallView: AbstractIncomingOutgoingCallView, UICompositeViewDelegate { // Layout constants let buttons_distance_from_center_x = 38 @@ -38,14 +38,14 @@ import linphonesw super.viewDidLoad(forCallType: VoipTexts.call_incoming_title) // Accept - let accept = CallControlButton(width: CallControlButton.hungup_width, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_accept, onClickAction: { + let accept = CallControlButton(width: CallControlButton.hungup_width, imageInset:AbstractIncomingOutgoingCallView.answer_decline_inset, buttonTheme: VoipTheme.call_accept, onClickAction: { self.callData.map { CallManager.instance().acceptCall(call: $0.call.getCobject, hasVideo: false)} }) view.addSubview(accept) accept.centerX(withDx: buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done() // Decline - let decline = CallControlButton(width: CallControlButton.hungup_width, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: { + let decline = CallControlButton(width: CallControlButton.hungup_width, imageInset:AbstractIncomingOutgoingCallView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: { self.callData.map { CallManager.instance().terminateCall(call: $0.call.getCobject)} }) view.addSubview(decline) diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift index 7823d6643..f900e58b4 100644 --- a/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift @@ -22,7 +22,7 @@ import UIKit import Foundation import linphonesw -@objc class OutgoingCallView: IncomingOutgoingCommonView, UICompositeViewDelegate { +@objc class OutgoingCallView: AbstractIncomingOutgoingCallView, UICompositeViewDelegate { // Layout constants let numpad_icon_padding = 10.0 @@ -40,7 +40,7 @@ import linphonesw super.viewDidLoad(forCallType: VoipTexts.call_outgoing_title) // Cancel - let cancelCall = CallControlButton(width: CallControlButton.hungup_width, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: { + let cancelCall = CallControlButton(width: CallControlButton.hungup_width, imageInset:AbstractIncomingOutgoingCallView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: { self.callData.map { CallManager.instance().terminateCall(call: $0.call.getCobject)} }) view.addSubview(cancelCall) diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift new file mode 100644 index 000000000..bfa606c9a --- /dev/null +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift @@ -0,0 +1,105 @@ +/* + * 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 UIKit +import linphonesw + + +@objc class SingleCallView: AbstractCallView, UICompositeViewDelegate { + + var callPausedByRemoteView : PausedCallOrConferenceView? = nil + var callPausedByLocalView : PausedCallOrConferenceView? = nil + var currentCallView : ActiveCallView? = nil + + static let compositeDescription = UICompositeViewDescription(SingleCallView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: nil, fullscreen: false, isLeftFragment: false,fragmentWith: nil) + static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription } + func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription } + + override func viewDidLoad() { + super.viewDidLoad() + + // Current (Single) Call (VoipCallView) + currentCallView = ActiveCallView() + fullScreenMutableContainerView.addSubview(currentCallView!) + CallsViewModel.shared.currentCallData.readCurrentAndObserve { (currentCallData) in + self.currentCallView!.callData = currentCallData != nil ? currentCallData! : nil + currentCallData??.isRemotelyPaused.readCurrentAndObserve { remotelyPaused in + self.callPausedByRemoteView?.isHidden = remotelyPaused != true + } + currentCallData??.isPaused.readCurrentAndObserve { locallyPaused in + self.callPausedByLocalView?.isHidden = locallyPaused != true + } + if (currentCallData == nil) { + self.callPausedByRemoteView?.isHidden = true + self.callPausedByLocalView?.isHidden = true + + } + self.extraButtonsView.isHidden = true + } + + currentCallView!.matchParentDimmensions().done() + + // Paused by remote (Call) + callPausedByRemoteView = PausedCallOrConferenceView(iconName: "voip_conference_paused_big",titleText: VoipTexts.call_remotely_paused_title,subTitleText: nil) + view.addSubview(callPausedByRemoteView!) + callPausedByRemoteView?.matchParentSideBorders().matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done() + callPausedByRemoteView?.isHidden = true + + // Paused by local (Call) + callPausedByLocalView = PausedCallOrConferenceView(iconName: "voip_conference_play_big",titleText: VoipTexts.call_locally_paused_title,subTitleText: VoipTexts.call_locally_paused_subtitle, onClickAction: { + CallsViewModel.shared.currentCallData.value??.togglePause() + }) + view.addSubview(callPausedByLocalView!) + callPausedByLocalView?.matchParentSideBorders().matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done() + callPausedByLocalView?.isHidden = true + + // Video activation dialog request + CallsViewModel.shared.callUpdateEvent.observe { (call) in + let core = Core.get() + if (call?.state == .StreamsRunning) { + self.videoAcceptDialog?.removeFromSuperview() + self.videoAcceptDialog = nil + } else if (call?.state == .UpdatedByRemote) { + if (core.videoCaptureEnabled || core.videoDisplayEnabled) { + if (call?.currentParams?.videoEnabled != call?.remoteParams?.videoEnabled) { + let accept = ButtonAttributes(text:VoipTexts.dialog_accept, action: {call?.answerVideoUpdateRequest(accept: true)}, isDestructive:false) + let cancel = ButtonAttributes(text:VoipTexts.dialog_decline, action: {call?.answerVideoUpdateRequest(accept: false)}, isDestructive:true) + self.videoAcceptDialog = VoipDialog(message:VoipTexts.call_video_update_requested_dialog, givenButtons: [cancel,accept]) + self.videoAcceptDialog?.show() + } + } else { + Log.w("[Call] Video display & capture are disabled, don't show video dialog") + } + } + } + } + + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(true) + CallsViewModel.shared.currentCallData.notifyValue() + } + + override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) { + super.didRotate(from: fromInterfaceOrientation) + self.currentCallView?.layoutRotatableElements() + } + +} diff --git a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift index 21119b6bf..6ecf4bdf9 100644 --- a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift +++ b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift @@ -301,7 +301,7 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol muted.alignParentLeft(withMargin: switch_camera_button_margins).alignParentTop(withMargin:switch_camera_button_margins).done() activeSpeakerView.addSubview(conferenceJoinSpinner) - conferenceJoinSpinner.square(IncomingOutgoingCommonView.spinner_size).center().done() + conferenceJoinSpinner.square(AbstractIncomingOutgoingCallView.spinner_size).center().done() switchCamera.alignParentTop(withMargin: switch_camera_button_margins).alignParentRight(withMargin: switch_camera_button_margins).square(switch_camera_button_size).done() @@ -411,12 +411,12 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol activeSpeakerAvatar.square(Avatar.diameter_for_call_views_land).center().done() meGrid.alignParentRight(withMargin: ActiveCallView.center_view_margin_top).height(grid_height).width(grid_height).alignParentBottom(withMargin: ActiveCallView.center_view_margin_top).done() } else { - activeSpeakerView.alignParentTop().alignParentBottom().alignParentLeft().toLeftOf(grid,withRightMargin: ActiveCallOrConferenceView.content_inset).done() + activeSpeakerView.alignParentTop().alignParentBottom().alignParentLeft().toLeftOf(grid,withRightMargin: SharedLayoutConstants.content_inset).done() if (UIDevice.current.orientation == .landscapeLeft) { // work around some constraints issues with Notch on the left. bounceGrids() } - meGrid.width(grid_height).height(grid_height).toRightOf(activeSpeakerView,withLeftMargin: ActiveCallOrConferenceView.content_inset).alignParentBottom().alignParentRight().done() - grid.width(grid_height).toRightOf(activeSpeakerView,withLeftMargin: ActiveCallOrConferenceView.content_inset).alignParentTop().alignAbove(view: meGrid, withMargin: ActiveCallOrConferenceView.content_inset).alignParentRight().done() + meGrid.width(grid_height).height(grid_height).toRightOf(activeSpeakerView,withLeftMargin: SharedLayoutConstants.content_inset).alignParentBottom().alignParentRight().done() + grid.width(grid_height).toRightOf(activeSpeakerView,withLeftMargin: SharedLayoutConstants.content_inset).alignParentTop().alignAbove(view: meGrid, withMargin: SharedLayoutConstants.content_inset).alignParentRight().done() layout.scrollDirection = .vertical activeSpeakerAvatar.square(Avatar.diameter_for_call_views_land).center().done() } @@ -432,7 +432,7 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol activeSpeakerAvatar.square(Avatar.diameter_for_call_views).center().done() activeSpeakerView.matchParentSideBorders().alignParentTop().done() meGrid.alignParentLeft().height(grid_height).width(grid_height).alignParentBottom().alignUnder(view: activeSpeakerView, withMargin:ActiveCallView.center_view_margin_top).done() - grid.toRightOf(meGrid,withLeftMargin: ActiveCallOrConferenceView.content_inset).height(grid_height).alignParentRight().alignParentBottom().alignUnder(view: activeSpeakerView, withMargin:ActiveCallView.center_view_margin_top).done() + grid.toRightOf(meGrid,withLeftMargin: SharedLayoutConstants.content_inset).height(grid_height).alignParentRight().alignParentBottom().alignUnder(view: activeSpeakerView, withMargin:ActiveCallView.center_view_margin_top).done() layout.scrollDirection = .horizontal } } diff --git a/Classes/Swift/Voip/Views/SharedLayoutConstants.swift b/Classes/Swift/Voip/Views/SharedLayoutConstants.swift index 5eb92c3e8..ccb45a9e0 100644 --- a/Classes/Swift/Voip/Views/SharedLayoutConstants.swift +++ b/Classes/Swift/Voip/Views/SharedLayoutConstants.swift @@ -26,5 +26,5 @@ class SharedLayoutConstants { } static let margin_call_view_side_controls_buttons = 12 static let bottom_margin_notch_clearance = UIDevice.hasNotch() ? 30.0 : 0.0 - + static let content_inset = 12.0 } diff --git a/linphone.xcodeproj/project.pbxproj b/linphone.xcodeproj/project.pbxproj index 1b2529cb3..e18506a07 100644 --- a/linphone.xcodeproj/project.pbxproj +++ b/linphone.xcodeproj/project.pbxproj @@ -728,7 +728,7 @@ C63F724A285A24B10066163B /* VoipConferenceActiveSpeakerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71E4285A24B10066163B /* VoipConferenceActiveSpeakerView.swift */; }; C63F724B285A24B10066163B /* VoipConferenceDisplayModeSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71E5285A24B10066163B /* VoipConferenceDisplayModeSelectionView.swift */; }; C63F724C285A24B10066163B /* ActiveCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71E7285A24B10066163B /* ActiveCallView.swift */; }; - C63F724D285A24B10066163B /* IncomingOuntgoingCommonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71E8285A24B10066163B /* IncomingOuntgoingCommonView.swift */; }; + C63F724D285A24B10066163B /* AbstractIncomingOutgoingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71E8285A24B10066163B /* AbstractIncomingOutgoingCallView.swift */; }; C63F724E285A24B10066163B /* PausedCallOrConferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71E9285A24B10066163B /* PausedCallOrConferenceView.swift */; }; C63F724F285A24B10066163B /* LocalVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71EA285A24B10066163B /* LocalVideoView.swift */; }; C63F7250285A24B10066163B /* CallStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71EB285A24B10066163B /* CallStatsView.swift */; }; @@ -742,7 +742,6 @@ C63F7258285A24B10066163B /* ControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71F4285A24B10066163B /* ControlsView.swift */; }; C63F7259285A24B10066163B /* RemotelyRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71F5285A24B10066163B /* RemotelyRecording.swift */; }; C63F725A285A24B10066163B /* OutgoingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71F7285A24B10066163B /* OutgoingCallView.swift */; }; - C63F725B285A24B10066163B /* ActiveCallOrConferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71F8285A24B10066163B /* ActiveCallOrConferenceView.swift */; }; C63F725C285A24B10066163B /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71F9285A24B10066163B /* IncomingCallView.swift */; }; C63F725D285A24B10066163B /* SharedLayoutConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71FA285A24B10066163B /* SharedLayoutConstants.swift */; }; C63F725E285A24B10066163B /* VoipDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71FB285A24B10066163B /* VoipDialog.swift */; }; @@ -838,6 +837,9 @@ C64A854E2667B67200252AD2 /* EphemeralSettingsView.m in Sources */ = {isa = PBXBuildFile; fileRef = C64A854D2667B67200252AD2 /* EphemeralSettingsView.m */; }; C64A85502667B67A00252AD2 /* EphemeralSettingsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C64A854F2667B67A00252AD2 /* EphemeralSettingsView.xib */; }; C64A85522667B74100252AD2 /* ephemeral_messages_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C64A85512667B74100252AD2 /* ephemeral_messages_default.png */; }; + C6548820292D32FA00BF646B /* SingleCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C654881E292D32FA00BF646B /* SingleCallView.swift */; }; + C6548821292D32FA00BF646B /* ConferenceCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C654881F292D32FA00BF646B /* ConferenceCallView.swift */; }; + C6548823292D369500BF646B /* AbstractCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6548822292D369500BF646B /* AbstractCallView.swift */; }; C66B03BB26E8EB1A009B5EDC /* UIChatReplyBubbleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C66B03BD26E8EB1A009B5EDC /* UIChatReplyBubbleView.xib */; }; C66B040A26EFDA55009B5EDC /* reply_cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = C66B040926EFDA54009B5EDC /* reply_cancel.png */; }; C66B040E26F095D1009B5EDC /* cancel_forward.png in Resources */ = {isa = PBXBuildFile; fileRef = C66B040D26F095CE009B5EDC /* cancel_forward.png */; }; @@ -1900,7 +1902,7 @@ C63F71E4285A24B10066163B /* VoipConferenceActiveSpeakerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoipConferenceActiveSpeakerView.swift; sourceTree = ""; }; C63F71E5285A24B10066163B /* VoipConferenceDisplayModeSelectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoipConferenceDisplayModeSelectionView.swift; sourceTree = ""; }; C63F71E7285A24B10066163B /* ActiveCallView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveCallView.swift; sourceTree = ""; }; - C63F71E8285A24B10066163B /* IncomingOuntgoingCommonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IncomingOuntgoingCommonView.swift; sourceTree = ""; }; + C63F71E8285A24B10066163B /* AbstractIncomingOutgoingCallView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AbstractIncomingOutgoingCallView.swift; sourceTree = ""; }; C63F71E9285A24B10066163B /* PausedCallOrConferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PausedCallOrConferenceView.swift; sourceTree = ""; }; C63F71EA285A24B10066163B /* LocalVideoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalVideoView.swift; sourceTree = ""; }; C63F71EB285A24B10066163B /* CallStatsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallStatsView.swift; sourceTree = ""; }; @@ -1914,7 +1916,6 @@ C63F71F4285A24B10066163B /* ControlsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlsView.swift; sourceTree = ""; }; C63F71F5285A24B10066163B /* RemotelyRecording.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemotelyRecording.swift; sourceTree = ""; }; C63F71F7285A24B10066163B /* OutgoingCallView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingCallView.swift; sourceTree = ""; }; - C63F71F8285A24B10066163B /* ActiveCallOrConferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveCallOrConferenceView.swift; sourceTree = ""; }; C63F71F9285A24B10066163B /* IncomingCallView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = ""; }; C63F71FA285A24B10066163B /* SharedLayoutConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedLayoutConstants.swift; sourceTree = ""; }; C63F71FB285A24B10066163B /* VoipDialog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoipDialog.swift; sourceTree = ""; }; @@ -2009,6 +2010,9 @@ C64A854D2667B67200252AD2 /* EphemeralSettingsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EphemeralSettingsView.m; sourceTree = ""; }; C64A854F2667B67A00252AD2 /* EphemeralSettingsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EphemeralSettingsView.xib; sourceTree = ""; }; C64A85512667B74100252AD2 /* ephemeral_messages_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ephemeral_messages_default.png; sourceTree = ""; }; + C654881E292D32FA00BF646B /* SingleCallView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleCallView.swift; sourceTree = ""; }; + C654881F292D32FA00BF646B /* ConferenceCallView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConferenceCallView.swift; sourceTree = ""; }; + C6548822292D369500BF646B /* AbstractCallView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AbstractCallView.swift; sourceTree = ""; }; C66B03BC26E8EB1A009B5EDC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UIChatReplyBubbleView.xib; sourceTree = ""; }; C66B03C126E8EB82009B5EDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/UIChatReplyBubbleView.strings; sourceTree = ""; }; C66B03C326E8EB87009B5EDC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/UIChatReplyBubbleView.strings; sourceTree = ""; }; @@ -3530,7 +3534,6 @@ C63F71DC285A24B10066163B /* AudioRoutesView.swift */, C63F71DD285A24B10066163B /* Conference */, C63F71E6285A24B10066163B /* ActiveCall */, - C63F71E8285A24B10066163B /* IncomingOuntgoingCommonView.swift */, C63F71E9285A24B10066163B /* PausedCallOrConferenceView.swift */, C63F71EA285A24B10066163B /* LocalVideoView.swift */, C63F71EB285A24B10066163B /* CallStatsView.swift */, @@ -3590,8 +3593,11 @@ C63F71F6285A24B10066163B /* CompositeViewControllers */ = { isa = PBXGroup; children = ( + C63F71E8285A24B10066163B /* AbstractIncomingOutgoingCallView.swift */, + C6548822292D369500BF646B /* AbstractCallView.swift */, + C654881F292D32FA00BF646B /* ConferenceCallView.swift */, + C654881E292D32FA00BF646B /* SingleCallView.swift */, C63F71F7285A24B10066163B /* OutgoingCallView.swift */, - C63F71F8285A24B10066163B /* ActiveCallOrConferenceView.swift */, C63F71F9285A24B10066163B /* IncomingCallView.swift */, ); path = CompositeViewControllers; @@ -4932,8 +4938,8 @@ 63B8D6A21BCBF43100C12B09 /* UIChatCreateCell.m in Sources */, 636BC9971B5F921B00C754CE /* UIIconButton.m in Sources */, C63F7263285A24B10066163B /* FormButton.swift in Sources */, - C63F725B285A24B10066163B /* ActiveCallOrConferenceView.swift in Sources */, C63F7215285A24B10066163B /* ConferenceWaitingRoomFragment.swift in Sources */, + C6548820292D32FA00BF646B /* SingleCallView.swift in Sources */, 63423C0A1C4501D000D9A050 /* Contact.m in Sources */, C63F7262285A24B10066163B /* RotatingSpinner.swift in Sources */, 340751E7150F38FD00B89C47 /* UIVideoButton.m in Sources */, @@ -4994,6 +5000,7 @@ 63B81A0D1B57DA33009604A6 /* TPKeyboardAvoidingCollectionView.m in Sources */, C63F726D285A24B10066163B /* ProviderDelegate.swift in Sources */, C63F7266285A24B10066163B /* UICallTimer.swift in Sources */, + C6548821292D32FA00BF646B /* ConferenceCallView.swift in Sources */, C63F726C285A24B10066163B /* StyledTextView.swift in Sources */, 570742611D5A09B8004B9C84 /* ShopView.m in Sources */, D37DC6C11594AE1800B2A5EB /* LinphoneCoreSettingsStore.m in Sources */, @@ -5053,6 +5060,7 @@ C63F721F285A24B10066163B /* BackNextNavigationView.swift in Sources */, D3807FC315C28940005BE9BC /* DCRoundSwitchOutlineLayer.m in Sources */, C63F723D285A24B10066163B /* TextStyle.swift in Sources */, + C6548823292D369500BF646B /* AbstractCallView.swift in Sources */, C63F7229285A24B10066163B /* UIButtonExtensions.swift in Sources */, C63F722B285A24B10066163B /* UIDeviceExtensions.swift in Sources */, C63F724B285A24B10066163B /* VoipConferenceDisplayModeSelectionView.swift in Sources */, @@ -5105,7 +5113,7 @@ 637157A11B283FE200C91677 /* FileTransferDelegate.m in Sources */, D378AB2A15DCDB4A0098505D /* ImagePickerView.m in Sources */, 22405F001601C19200B92522 /* ImageView.m in Sources */, - C63F724D285A24B10066163B /* IncomingOuntgoingCommonView.swift in Sources */, + C63F724D285A24B10066163B /* AbstractIncomingOutgoingCallView.swift in Sources */, D37EE162160377D7003608A6 /* DTActionSheet.m in Sources */, D306459E1611EC2A00BB571E /* UILoadingImageView.m in Sources */, C63F7246285A24B10066163B /* VoipGridParticipantCell.swift in Sources */, From 3a3c04bb239f592bcfcf925cb27e819e5fec8c78 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Tue, 22 Nov 2022 21:31:20 +0100 Subject: [PATCH 03/12] Fix resuming call putting other calls on hold --- .../Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/Swift/Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift b/Classes/Swift/Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift index 79cdaa063..6e2927b2c 100644 --- a/Classes/Swift/Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift +++ b/Classes/Swift/Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift @@ -106,7 +106,7 @@ class VoipCallContextMenu: UIStackView { self.isHidden = true guard let call = self.callData?.call else { return } if (CallManager.callKitEnabled()) { - CallManager.instance().setHeld(call:call,hold:false); + CallManager.instance().setHeld(call:call.getCobject!,hold:false); } else { try?call.resume() } @@ -138,7 +138,7 @@ class VoipCallContextMenu: UIStackView { terminate.onClick { self.isHidden = true guard let call = self.callData?.call else { return } - try?call.terminate() + CallManager.instance().terminateCall(call: call.getCobject) } } From 3800aafd1fa12ccebe2489f5a355d8ae398a1172 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Tue, 22 Nov 2022 21:53:30 +0100 Subject: [PATCH 04/12] Full screen toggle --- .../CompositeViewControllers/AbstractCallView.swift | 10 ++++++---- .../Views/Fragments/ActiveCall/ActiveCallView.swift | 2 +- .../Conference/VoipConferenceActiveSpeakerView.swift | 2 +- .../Fragments/Conference/VoipConferenceGridView.swift | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/AbstractCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/AbstractCallView.swift index 48d1368bd..1178c9883 100644 --- a/Classes/Swift/Voip/Views/CompositeViewControllers/AbstractCallView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/AbstractCallView.swift @@ -61,10 +61,12 @@ import linphonesw // Calls List - ControlsViewModel.shared.goToCallsListEvent.observe { (_) in - self.dismissableView = CallsListView() - self.view.addSubview(self.dismissableView!) - self.dismissableView?.matchParentDimmensions().done() + ControlsViewModel.shared.goToCallsListEvent.observe { _ in + if (self.view.superview != nil) { + self.dismissableView = CallsListView() + self.view.addSubview(self.dismissableView!) + self.dismissableView?.matchParentDimmensions().done() + } } // Goto chat diff --git a/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift b/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift index 8d1361523..93c8ccb41 100644 --- a/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift +++ b/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift @@ -197,7 +197,7 @@ class ActiveCallView: UIView { // = currentCall ControlsViewModel.shared.toggleFullScreen() } ControlsViewModel.shared.fullScreenMode.observe { (fullScreen) in - if (self.isHidden) { + if (self.superview?.superview?.superview == nil) { return } self.remoteVideo.removeConstraints().done() diff --git a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift index 6ecf4bdf9..e57a0708d 100644 --- a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift +++ b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift @@ -336,7 +336,7 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol } ControlsViewModel.shared.fullScreenMode.observe { (fullScreen) in - if (self.isHidden) { + if (self.superview?.superview?.superview == nil || self.conferenceViewModel?.conference.value?.call?.params?.conferenceVideoLayout != .ActiveSpeaker) { return } fullScreenMutableView.removeConstraints().done() diff --git a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift index c5c34e5bc..c68c81c68 100644 --- a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift +++ b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift @@ -163,7 +163,7 @@ class VoipConferenceGridView: UIView, UICollectionViewDataSource, UICollectionVi } ControlsViewModel.shared.fullScreenMode.observe { (fullScreen) in - if (self.isHidden || self.conferenceViewModel?.conference.value?.call?.params?.conferenceVideoLayout != .Grid) { + if (self.superview?.superview?.superview == nil || self.conferenceViewModel?.conference.value?.call?.params?.conferenceVideoLayout != .Grid) { return } self.gridContainer.removeConstraints().done() From fc3f48363f1675621f510042523ebf3e5db55f6d Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Tue, 22 Nov 2022 22:23:12 +0100 Subject: [PATCH 05/12] Avoid getting back to waiting room after conf ends --- Classes/Swift/Voip/ViewModels/CallsViewModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/Swift/Voip/ViewModels/CallsViewModel.swift b/Classes/Swift/Voip/ViewModels/CallsViewModel.swift index 85cb96a13..f7590b58a 100644 --- a/Classes/Swift/Voip/ViewModels/CallsViewModel.swift +++ b/Classes/Swift/Voip/ViewModels/CallsViewModel.swift @@ -226,9 +226,9 @@ class CallsViewModel { return } if (currentCallData??.call.conference != nil) { - PhoneMainView.instance().changeCurrentView(ConferenceCallView.compositeDescription) + PhoneMainView.instance().pop(toView:ConferenceCallView.compositeDescription) } else { - PhoneMainView.instance().changeCurrentView(SingleCallView.compositeDescription) + PhoneMainView.instance().pop(toView:SingleCallView.compositeDescription) } } } From 4cb99a62a06a959d6aa77687b37ba02c375296a7 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Tue, 22 Nov 2022 22:36:54 +0100 Subject: [PATCH 06/12] Added local group call subject when merging calls --- Classes/Swift/CallManager.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Classes/Swift/CallManager.swift b/Classes/Swift/CallManager.swift index efcadf96e..0cad25e11 100644 --- a/Classes/Swift/CallManager.swift +++ b/Classes/Swift/CallManager.swift @@ -791,6 +791,7 @@ import AVFoundation do { if let core = lc, let params = try? core.createConferenceParams(conference: nil) { params.videoEnabled = false // We disable video for local conferencing (cf Android) + params.subject = VoipTexts.conference_local_title let conference = core.conference != nil ? core.conference : try core.createConferenceWithParams(params: params) try conference?.addParticipants(calls: core.calls) } From 0202c7aa6b1a7de4e8b567389596a66ca335e89a Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Tue, 22 Nov 2022 22:39:01 +0100 Subject: [PATCH 07/12] UTF8 handling in conference names in history --- Classes/LinphoneUI/UIHistoryCell.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/LinphoneUI/UIHistoryCell.m b/Classes/LinphoneUI/UIHistoryCell.m index 346c029c6..eb930d867 100644 --- a/Classes/LinphoneUI/UIHistoryCell.m +++ b/Classes/LinphoneUI/UIHistoryCell.m @@ -91,7 +91,7 @@ // Set up the cell... if (linphone_call_log_was_conference(callLog)) { const char *subject = linphone_conference_info_get_subject(linphone_call_log_get_conference_info(callLog)); - displayNameLabel.text = [NSString stringWithFormat:@"%s",subject]; + displayNameLabel.text = [NSString stringWithUTF8String:subject]; [_avatarImage setImage:[UIImage imageNamed:@"voip_multiple_contacts_avatar"]]; _stateImage.hidden = true; } else { From eff9964b126565724654824f8fb536c5fee54039 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Tue, 22 Nov 2022 22:42:04 +0100 Subject: [PATCH 08/12] Refactored to ConferenceWaitingRoomView --- Classes/HistoryListTableView.m | 4 ++-- ...RoomFragment.swift => ConferenceWaitingRoomView.swift} | 4 ++-- Classes/Swift/Conference/Views/ICSBubbleView.swift | 2 +- .../Swift/Conference/Views/ScheduledConferencesCell.swift | 2 +- linphone.xcodeproj/project.pbxproj | 8 ++++---- 5 files changed, 10 insertions(+), 10 deletions(-) rename Classes/Swift/Conference/Views/{ConferenceWaitingRoomFragment.swift => ConferenceWaitingRoomView.swift} (97%) diff --git a/Classes/HistoryListTableView.m b/Classes/HistoryListTableView.m index b572b55a6..f6b34c503 100644 --- a/Classes/HistoryListTableView.m +++ b/Classes/HistoryListTableView.m @@ -272,9 +272,9 @@ [ConferenceViewModelBridge showCancelledMeetingWithCConferenceInfo:confInfo]; return; } - ConferenceWaitingRoomFragment *view = VIEW(ConferenceWaitingRoomFragment); + ConferenceWaitingRoomView *view = VIEW(ConferenceWaitingRoomView); [view setDetailsWithSubject:[NSString stringWithUTF8String:linphone_conference_info_get_subject(confInfo)] url:[NSString stringWithUTF8String:linphone_address_as_string(linphone_conference_info_get_uri(confInfo))]]; - [PhoneMainView.instance changeCurrentView:ConferenceWaitingRoomFragment.compositeViewDescription]; + [PhoneMainView.instance changeCurrentView:ConferenceWaitingRoomView.compositeViewDescription]; } else { const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog); [LinphoneManager.instance call:addr]; diff --git a/Classes/Swift/Conference/Views/ConferenceWaitingRoomFragment.swift b/Classes/Swift/Conference/Views/ConferenceWaitingRoomView.swift similarity index 97% rename from Classes/Swift/Conference/Views/ConferenceWaitingRoomFragment.swift rename to Classes/Swift/Conference/Views/ConferenceWaitingRoomView.swift index fd1a8f10c..20fae72b9 100644 --- a/Classes/Swift/Conference/Views/ConferenceWaitingRoomFragment.swift +++ b/Classes/Swift/Conference/Views/ConferenceWaitingRoomView.swift @@ -22,7 +22,7 @@ import UIKit import linphonesw -@objc class ConferenceWaitingRoomFragment: UIViewController, UICompositeViewDelegate { // Replaces CallView +@objc class ConferenceWaitingRoomView: UIViewController, UICompositeViewDelegate { // Replaces CallView // Layout constants let common_margin = 17.0 @@ -54,7 +54,7 @@ import linphonesw let layoutPickerView = ConferenceLayoutPickerView(orientation: UIDevice.current.orientation) - static let compositeDescription = UICompositeViewDescription(ConferenceWaitingRoomFragment.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: SideMenuView.self, fullscreen: false, isLeftFragment: false,fragmentWith: nil) + static let compositeDescription = UICompositeViewDescription(ConferenceWaitingRoomView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: SideMenuView.self, fullscreen: false, isLeftFragment: false,fragmentWith: nil) static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription } func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription } diff --git a/Classes/Swift/Conference/Views/ICSBubbleView.swift b/Classes/Swift/Conference/Views/ICSBubbleView.swift index 27637e979..45b46a579 100644 --- a/Classes/Swift/Conference/Views/ICSBubbleView.swift +++ b/Classes/Swift/Conference/Views/ICSBubbleView.swift @@ -108,7 +108,7 @@ import EventKitUI joinShare.alignParentBottom(withMargin: inner_padding).width(join_share_width).alignParentRight(withMargin: inner_padding).done() join.onClick { - let view : ConferenceWaitingRoomFragment = self.VIEW(ConferenceWaitingRoomFragment.compositeViewDescription()) + let view : ConferenceWaitingRoomView = self.VIEW(ConferenceWaitingRoomView.compositeViewDescription()) PhoneMainView.instance().changeCurrentView(view.compositeViewDescription()) view.setDetails(subject: (self.conferenceData?.subject.value)!, url: (self.conferenceData?.address.value)!) } diff --git a/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift b/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift index 09914b5c5..f72bef7df 100644 --- a/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift +++ b/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift @@ -177,7 +177,7 @@ class ScheduledConferencesCell: UITableViewCell { joinEditDelete.addArrangedSubview(joinConf) joinConf.width(150).done() joinConf.onClick { - let view : ConferenceWaitingRoomFragment = self.VIEW(ConferenceWaitingRoomFragment.compositeViewDescription()) + let view : ConferenceWaitingRoomView = self.VIEW(ConferenceWaitingRoomView.compositeViewDescription()) PhoneMainView.instance().changeCurrentView(view.compositeViewDescription()) view.setDetails(subject: (self.conferenceData?.subject.value)!, url: (self.conferenceData?.address.value)!) } diff --git a/linphone.xcodeproj/project.pbxproj b/linphone.xcodeproj/project.pbxproj index e18506a07..812fc787e 100644 --- a/linphone.xcodeproj/project.pbxproj +++ b/linphone.xcodeproj/project.pbxproj @@ -673,7 +673,7 @@ C63F7212285A24B10066163B /* ScheduledConferenceData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F719F285A24B10066163B /* ScheduledConferenceData.swift */; }; C63F7213285A24B10066163B /* TimeZoneData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71A0285A24B10066163B /* TimeZoneData.swift */; }; C63F7214285A24B10066163B /* Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71A1285A24B10066163B /* Duration.swift */; }; - C63F7215285A24B10066163B /* ConferenceWaitingRoomFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71A3285A24B10066163B /* ConferenceWaitingRoomFragment.swift */; }; + C63F7215285A24B10066163B /* ConferenceWaitingRoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71A3285A24B10066163B /* ConferenceWaitingRoomView.swift */; }; C63F7216285A24B10066163B /* ScheduledConferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71A4285A24B10066163B /* ScheduledConferencesView.swift */; }; C63F7217285A24B10066163B /* ICSBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71A5285A24B10066163B /* ICSBubbleView.swift */; }; C63F7218285A24B10066163B /* ScheduledConferencesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63F71A6285A24B10066163B /* ScheduledConferencesCell.swift */; }; @@ -1847,7 +1847,7 @@ C63F719F285A24B10066163B /* ScheduledConferenceData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduledConferenceData.swift; sourceTree = ""; }; C63F71A0285A24B10066163B /* TimeZoneData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeZoneData.swift; sourceTree = ""; }; C63F71A1285A24B10066163B /* Duration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Duration.swift; sourceTree = ""; }; - C63F71A3285A24B10066163B /* ConferenceWaitingRoomFragment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConferenceWaitingRoomFragment.swift; sourceTree = ""; }; + C63F71A3285A24B10066163B /* ConferenceWaitingRoomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConferenceWaitingRoomView.swift; sourceTree = ""; }; C63F71A4285A24B10066163B /* ScheduledConferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduledConferencesView.swift; sourceTree = ""; }; C63F71A5285A24B10066163B /* ICSBubbleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ICSBubbleView.swift; sourceTree = ""; }; C63F71A6285A24B10066163B /* ScheduledConferencesCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduledConferencesCell.swift; sourceTree = ""; }; @@ -3406,7 +3406,7 @@ C63F71A2285A24B10066163B /* Views */ = { isa = PBXGroup; children = ( - C63F71A3285A24B10066163B /* ConferenceWaitingRoomFragment.swift */, + C63F71A3285A24B10066163B /* ConferenceWaitingRoomView.swift */, C63F71A4285A24B10066163B /* ScheduledConferencesView.swift */, C63F71A6285A24B10066163B /* ScheduledConferencesCell.swift */, C63F71A5285A24B10066163B /* ICSBubbleView.swift */, @@ -4938,7 +4938,7 @@ 63B8D6A21BCBF43100C12B09 /* UIChatCreateCell.m in Sources */, 636BC9971B5F921B00C754CE /* UIIconButton.m in Sources */, C63F7263285A24B10066163B /* FormButton.swift in Sources */, - C63F7215285A24B10066163B /* ConferenceWaitingRoomFragment.swift in Sources */, + C63F7215285A24B10066163B /* ConferenceWaitingRoomView.swift in Sources */, C6548820292D32FA00BF646B /* SingleCallView.swift in Sources */, 63423C0A1C4501D000D9A050 /* Contact.m in Sources */, C63F7262285A24B10066163B /* RotatingSpinner.swift in Sources */, From 9ab5a0f106498ddc7f28b715a15b19d11a080f47 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Tue, 22 Nov 2022 23:41:48 +0100 Subject: [PATCH 09/12] Enable full screen joining video conference with video --- .../ConferenceCallView.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift index 4b39128cb..73ec3c827 100644 --- a/Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift @@ -72,6 +72,7 @@ import linphonesw } else { self.conferenceJoinSpinner.removeFromSuperview() self.conferenceJoinSpinner.stopRotation() + self.switchToFullScreenIfPossible(conference: ConferenceViewModel.shared.conference.value) } } @@ -90,6 +91,9 @@ import linphonesw ConferenceViewModel.shared.conferenceDisplayMode.readCurrentAndObserve { (conferenceMode) in if (ConferenceViewModel.shared.conferenceExists.value == true) { self.displaySelectedConferenceLayout() + if (conferenceMode != .AudioOnly) { + self.switchToFullScreenIfPossible(conference: ConferenceViewModel.shared.conference.value) + } } } ConferenceViewModel.shared.isConferenceLocallyPaused.readCurrentAndObserve { (paused) in @@ -155,5 +159,20 @@ import linphonesw self.conferenceActiveSpeakerView?.layoutRotatableElements() } + private func switchToFullScreenIfPossible(conference: Conference?) { + if (ConfigManager.instance().lpConfigBoolForKey(key: "enter_video_conference_enable_full_screen_mode", defaultValue: true)) { + if (conference?.currentParams?.isVideoEnabled == true) { + if (conference?.me?.devices.count == 0) { + Log.i("[Conference Call] Conference has video enabled but our device hasn't joined yet") + } else if (conference?.me?.devices.filter { $0.isInConference && $0.getStreamAvailability(streamType: StreamType.Video) }.first != nil) { + Log.i("[Conference Call] Conference has video enabled & our device has video enabled, enabling full screen mode") + ControlsViewModel.shared.fullScreenMode.value = true + } else { + Log.i("[Conference Call] Conference has video enabled but our device video is disabled") + } + } + } + } + } From c09519bcc1dd899a802987029ad9d7abfd5ea4d7 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Tue, 22 Nov 2022 23:50:43 +0100 Subject: [PATCH 10/12] Fixed dual spinner joining conference + set color to make it visible --- .../Views/CompositeViewControllers/ConferenceCallView.swift | 2 +- .../Conference/VoipConferenceActiveSpeakerView.swift | 4 ++-- Classes/Swift/Voip/Widgets/RotatingSpinner.swift | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift index 73ec3c827..d6b4be4d1 100644 --- a/Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/ConferenceCallView.swift @@ -28,7 +28,7 @@ import linphonesw var conferenceGridView: VoipConferenceGridView? = nil var conferenceActiveSpeakerView: VoipConferenceActiveSpeakerView? = nil var conferenceAudioOnlyView: VoipConferenceAudioOnlyView? = nil - let conferenceJoinSpinner = RotatingSpinner() + let conferenceJoinSpinner = RotatingSpinner(color:VoipTheme.dark_grey_color) @objc var participantsListView : ParticipantsListView? = nil static let compositeDescription = UICompositeViewDescription(ConferenceCallView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: nil, fullscreen: false, isLeftFragment: false,fragmentWith: nil) diff --git a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift index e57a0708d..88d82def8 100644 --- a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift +++ b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift @@ -57,13 +57,13 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() var fullScreenOpaqueMasqForNotchedDevices = UIView() - let conferenceJoinSpinner = RotatingSpinner() + let conferenceJoinSpinner = RotatingSpinner(color:VoipTheme.dark_grey_color) var conferenceViewModel: ConferenceViewModel? = nil { didSet { if let model = conferenceViewModel { - self.setJoininngSpeakerState(enabled: true) + self.setJoininngSpeakerState(enabled: false) self.activeSpeakerAvatar.showAsAvatarIcon() model.subject.readCurrentAndObserve { (subject) in self.subjectLabel.text = subject diff --git a/Classes/Swift/Voip/Widgets/RotatingSpinner.swift b/Classes/Swift/Voip/Widgets/RotatingSpinner.swift index a03f12671..72c262d8b 100644 --- a/Classes/Swift/Voip/Widgets/RotatingSpinner.swift +++ b/Classes/Swift/Voip/Widgets/RotatingSpinner.swift @@ -21,10 +21,10 @@ import Foundation class RotatingSpinner : UIImageView { - init () { + init (color:UIColor = .white) { super.init(frame: .zero) self.image = UIImage(named: "voip_spinner") - self.tint(UIColor.white) + self.tint(color) self.contentMode = .scaleAspectFit } From 6a4ef41ab564a38adb9ec3c94b791e5f9f4c5781 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Wed, 23 Nov 2022 08:59:42 +0100 Subject: [PATCH 11/12] Various fixes in view model update propagation --- Classes/Swift/Voip/ViewModels/CallData.swift | 8 ++++++++ .../SingleCallView.swift | 3 +++ .../Fragments/ActiveCall/ActiveCallView.swift | 19 ++++++++++--------- .../VoipConferenceActiveSpeakerView.swift | 2 ++ 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Classes/Swift/Voip/ViewModels/CallData.swift b/Classes/Swift/Voip/ViewModels/CallData.swift index 05cd032d3..10e70c286 100644 --- a/Classes/Swift/Voip/ViewModels/CallData.swift +++ b/Classes/Swift/Voip/ViewModels/CallData.swift @@ -199,5 +199,13 @@ class CallData { isPaused.value = isCallPaused() } + func isOngoingSingleCall() -> Bool { + return !isOutGoing() && !isInComing() && call.conference == nil && call.callLog?.wasConference() != true + } + + func isOngoingConference() -> Bool { + return !isOutGoing() && !isInComing() && (call.conference != nil || call.callLog?.wasConference() == true) + } + } diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift index bfa606c9a..f525012b7 100644 --- a/Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift @@ -39,6 +39,9 @@ import linphonesw currentCallView = ActiveCallView() fullScreenMutableContainerView.addSubview(currentCallView!) CallsViewModel.shared.currentCallData.readCurrentAndObserve { (currentCallData) in + guard currentCallData??.isOngoingSingleCall() == true else { + return + } self.currentCallView!.callData = currentCallData != nil ? currentCallData! : nil currentCallData??.isRemotelyPaused.readCurrentAndObserve { remotelyPaused in self.callPausedByRemoteView?.isHidden = remotelyPaused != true diff --git a/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift b/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift index 93c8ccb41..8aae7bb6f 100644 --- a/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift +++ b/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift @@ -57,8 +57,11 @@ class ActiveCallView: UIView { // = currentCall var callData: CallData? = nil { didSet { - duration.call = callData?.call - callData?.call.remoteAddress.map { + guard let callData = callData else { + return + } + duration.call = callData.call + callData.call.remoteAddress.map { avatar.fillFromAddress(address: $0) if let displayName = $0.addressBookEnhancedDisplayName() { displayNameTop.text = displayName+" - " @@ -66,13 +69,13 @@ class ActiveCallView: UIView { // = currentCall } sipAddress.text = $0.asStringUriOnly() } - self.remotelyRecordedIndicator.isRemotelyRecorded = callData?.isRemotelyRecorded - callData?.isRecording.readCurrentAndObserve { (selected) in + self.remotelyRecordedIndicator.isRemotelyRecorded = callData.isRemotelyRecorded + callData.isRecording.readCurrentAndObserve { (selected) in self.recordCallButtons.forEach { $0.isSelected = selected == true } } - callData?.isPaused.readCurrentAndObserve { (paused) in + callData.isPaused.readCurrentAndObserve { (paused) in self.pauseCallButtons.forEach { $0.isSelected = paused == true } @@ -80,13 +83,11 @@ class ActiveCallView: UIView { // = currentCall self.localVideo.isHidden = true } } - callData?.isRemotelyRecorded.readCurrentAndObserve { (remotelyRecorded) in + callData.isRemotelyRecorded.readCurrentAndObserve { (remotelyRecorded) in self.centerSection.removeConstraints().matchParentSideBorders().alignUnder(view:remotelyRecorded == true ? self.remotelyRecordedIndicator : self.upperSection ,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done() self.setNeedsLayout() } - - - + Core.get().nativeVideoWindow = remoteVideo Core.get().nativePreviewWindowId = UnsafeMutableRawPointer(Unmanaged.passRetained(localVideo).toOpaque()) diff --git a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift index 88d82def8..8873dc9e2 100644 --- a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift +++ b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift @@ -63,6 +63,8 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol var conferenceViewModel: ConferenceViewModel? = nil { didSet { if let model = conferenceViewModel { + self.activeSpeakerVideoView.isHidden = true + self.activeSpeakerVideoViewAlone.isHidden = true self.setJoininngSpeakerState(enabled: false) self.activeSpeakerAvatar.showAsAvatarIcon() model.subject.readCurrentAndObserve { (subject) in From 781a9b07d17ccc86778825d6ba024a85a40dd8d8 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Wed, 23 Nov 2022 09:21:21 +0100 Subject: [PATCH 12/12] Fix video freeze when alone in conference A/S mode --- .../Fragments/Conference/VoipConferenceActiveSpeakerView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift index 8873dc9e2..d902c4cbb 100644 --- a/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift +++ b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift @@ -80,7 +80,6 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol let otherSpeakersCount = model.activeSpeakerConferenceParticipantDevices.value!.count self.switchCamera.isHidden = true if (otherSpeakersCount == 0) { - Core.get().nativePreviewWindow = self.activeSpeakerVideoViewAlone self.layoutRotatableElements() self.meGrid.isHidden = true self.grid.isHidden = true