Conference Active Speaker view landscape mode

This commit is contained in:
Christophe Deschamps 2022-08-16 19:17:27 +02:00
parent 12f9de3384
commit 8ba4230c19
14 changed files with 152 additions and 50 deletions

View file

@ -48,10 +48,10 @@ extension UIDevice {
}
static func notchHeight() -> CGFloat {
guard #available(iOS 11.0, *), let topPadding = UIApplication.shared.keyWindow?.safeAreaInsets.top else {
guard #available(iOS 11.0, *), let topPadding = UIApplication.shared.keyWindow?.safeAreaInsets.top, let sidePadding = UIApplication.shared.keyWindow?.safeAreaInsets.left else {
return 0
}
return topPadding
return [.landscapeRight,.landscapeLeft].contains(UIDevice.current.orientation) ? sidePadding : topPadding
}
}

View file

@ -39,6 +39,14 @@ extension UIView {
return self
}
func squareMax(_ size:Int) -> UIView {
snp.makeConstraints { (make) in
make.height.lessThanOrEqualTo(size).priority(.high)
make.width.equalTo(snp.height).priority(.high)
}
return self
}
func makeHeightMatchWidth() -> UIView {
snp.makeConstraints { (make) in
@ -47,6 +55,13 @@ extension UIView {
return self
}
func makeWidthMatchHeight() -> UIView {
snp.makeConstraints { (make) in
make.width.equalTo(snp.height)
}
return self
}
func size(w:CGFloat,h:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.width.equalTo(w)
@ -122,6 +137,16 @@ extension UIView {
return self
}
func matchParentDimmensions(insetedBy:UIEdgeInsets) -> UIView {
snp.makeConstraints { (make) in
make.left.equalToSuperview().offset(insetedBy.left)
make.top.equalToSuperview().offset(insetedBy.top)
make.right.equalToSuperview().offset(-insetedBy.right)
make.bottom.equalToSuperview().offset(-insetedBy.bottom)
}
return self
}
func matchDimensionsWith(view:UIView, insetedByDx:CGFloat = 0) -> UIView {
snp.makeConstraints { (make) in
make.left.top.equalTo(view).offset(insetedByDx)
@ -258,12 +283,26 @@ extension UIView {
return self
}
func updateAlignParentLeft(withMargin:CGFloat = 0.0) -> UIView {
snp.updateConstraints { (make) in
make.left.equalToSuperview().offset(withMargin)
}
return self
}
func alignParentLeft(withMargin:Int) -> UIView {
return alignParentLeft(withMargin:CGFloat(withMargin))
}
func alignParentRight(withMargin:Int = 0) -> UIView {
snp.makeConstraints { (make) in
make.right.equalToSuperview().offset(-withMargin).priorityRequired()
}
return self
}
func updateAlignParentRight(withMargin:CGFloat = 0) -> UIView {
snp.updateConstraints { (make) in
make.right.equalToSuperview().offset(-withMargin)
}
return self

View file

@ -223,9 +223,7 @@ class ControlsViewModel {
}
func toggleFullScreen() {
if (isVideoEnabled.value == true) {
fullScreenMode.value = fullScreenMode.value != true
}
fullScreenMode.value = fullScreenMode.value != true
}
func toggleMuteMicrophone() {

View file

@ -25,7 +25,7 @@ import linphonesw
@objc class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // Replaces CallView
// Layout constants
let content_inset = 12.0
static let content_inset = 12.0
var callPausedByRemoteView : PausedCallOrConferenceView? = nil
var callPausedByLocalView : PausedCallOrConferenceView? = nil
@ -49,7 +49,8 @@ import linphonesw
@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 }
@ -69,16 +70,13 @@ import linphonesw
// Controls
let controlsView = ControlsView(showVideo: true, controlsViewModel: ControlsViewModel.shared)
view.addSubview(controlsView)
controlsView.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done()
// Container view
let fullScreenMutableContainerView = UIView()
fullScreenMutableContainerView.backgroundColor = .clear
self.view.addSubview(fullScreenMutableContainerView)
fullScreenMutableContainerView.matchParentSideBorders(insetedByDx: content_inset).matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
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()
@ -159,7 +157,7 @@ import linphonesw
ConferenceViewModel.shared.conferenceCreationPending.readCurrentAndObserve { isCreationPending in
if (ConferenceViewModel.shared.conferenceExists.value == true && isCreationPending == true) {
fullScreenMutableContainerView.addSubview(self.conferenceJoinSpinner)
self.fullScreenMutableContainerView.addSubview(self.conferenceJoinSpinner)
self.conferenceJoinSpinner.square(IncomingOutgoingCommonView.spinner_size).center().done()
self.conferenceJoinSpinner.startRotation()
} else {
@ -239,7 +237,7 @@ import linphonesw
boucingCounter.dataSource = CallsViewModel.shared.chatAndCallsCount
view.addSubview(extraButtonsView)
extraButtonsView.matchParentSideBorders(insetedByDx: content_inset).alignParentBottom(withMargin:SharedLayoutConstants.bottom_margin_notch_clearance).done()
extraButtonsView.matchParentSideBorders(insetedByDx: ActiveCallOrConferenceView.content_inset).alignParentBottom(withMargin:SharedLayoutConstants.bottom_margin_notch_clearance).done()
ControlsViewModel.shared.hideExtraButtons.readCurrentAndObserve { (_) in
self.hideModalSubview(view: self.extraButtonsView)
}
@ -321,8 +319,8 @@ import linphonesw
VoipDialog.toast(message: VoipTexts.conference_first_to_join)
}
}
}
} // viewDidLoad
func displaySelectedConferenceLayout() {
let conferenceMode = ConferenceViewModel.shared.conferenceDisplayMode.value
@ -355,8 +353,8 @@ import linphonesw
participantsListView?.removeFromSuperview()
participantsListView = nil
ControlsViewModel.shared.numpadVisible.value = false
ControlsViewModel.shared.callStatsVisible.value = false
ControlsViewModel.shared.numpadVisible.value = false
ControlsViewModel.shared.callStatsVisible.value = false
ControlsViewModel.shared.fullScreenMode.value = false
super.viewWillDisappear(animated)
}
@ -402,4 +400,18 @@ import linphonesw
}
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()
}
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
super.didRotate(from: fromInterfaceOrientation)
self.layoutRotatableElements()
self.conferenceActiveSpeakerView?.layoutRotatableElements()
self.currentCallView?.layoutRotatableElements()
}
}

View file

@ -48,7 +48,7 @@ class ActiveCallView: UIView { // = currentCall
let remotelyRecordedIndicator = RemotelyRecordingView(height: ActiveCallView.remote_recording_height,text: VoipTexts.call_remote_recording)
let centerSection = UIView()
let avatar = Avatar(diameter: CGFloat(Avatar.diameter_for_call_views), color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large)
let avatar = Avatar(color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large)
let displayNameBottom = StyledLabel(VoipTheme.call_remote_name)
var recordCallButtons : [CallControlButton] = []
var pauseCallButtons : [CallControlButton] = []
@ -178,7 +178,6 @@ class ActiveCallView: UIView { // = currentCall
// Avatar
centerSection.addSubview(avatar)
avatar.square(Avatar.diameter_for_call_views).center().done()
// Remote Video Display
centerSection.addSubview(remoteVideo)
@ -227,7 +226,18 @@ class ActiveCallView: UIView { // = currentCall
addSubview(stack)
stack.matchParentDimmensions().done()
layoutRotatableElements()
}
func layoutRotatableElements() {
avatar.removeConstraints().done()
if ([.landscapeLeft, .landscapeRight].contains( UIDevice.current.orientation)) {
avatar.square(Avatar.diameter_for_call_views_land).center().done()
} else {
avatar.square(Avatar.diameter_for_call_views).center().done()
}
}
required init?(coder: NSCoder) {

View file

@ -36,7 +36,7 @@ class VoipCallCell: UITableViewCell {
var onMenuClickAction : (()->Void) = {}
let callStatusIcon = UIImageView()
let avatar = Avatar(diameter:VoipCallCell.avatar_size,color:LightDarkColor(VoipTheme.voip_contact_avatar_calls_list,VoipTheme.voip_contact_avatar_calls_list), textStyle: VoipTheme.call_generated_avatar_small)
let avatar = Avatar(color:LightDarkColor(VoipTheme.voip_contact_avatar_calls_list,VoipTheme.voip_contact_avatar_calls_list), textStyle: VoipTheme.call_generated_avatar_small)
let displayName = StyledLabel(VoipTheme.call_list_active_name_font)
let sipAddress = StyledLabel(VoipTheme.call_list_active_sip_uri_font)
var menuButton : CallControlButton? = nil

View file

@ -35,7 +35,7 @@ class VoipActiveSpeakerParticipantCell: UICollectionViewCell {
let videoView = UIView()
let avatar = Avatar(diameter:VoipActiveSpeakerParticipantCell.avatar_size,color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_medium)
let avatar = Avatar(color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_medium)
let pause = UIImageView(image: UIImage(named: "voip_pause")?.tinted(with: .white))
let switchCamera = UIImageView(image: UIImage(named:"voip_change_camera")?.tinted(with:.white))
let displayName = StyledLabel(VoipTheme.conference_participant_name_font_as)

View file

@ -33,7 +33,7 @@ class VoipAudioOnlyParticipantCell: UICollectionViewCell {
let common_margin = 10.0
let avatar = Avatar(diameter:VoipCallCell.avatar_size,color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_small)
let avatar = Avatar(color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_small)
let paused = UIImageView(image: UIImage(named: "voip_pause")?.tinted(with: .white))
let muted = MicMuted(VoipAudioOnlyParticipantCell.mute_size)
let joining = RotatingSpinner()

View file

@ -44,11 +44,12 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol
let activeSpeakerView = UIView()
let activeSpeakerVideoView = UIView()
let activeSpeakerAvatar = Avatar(diameter: CGFloat(Avatar.diameter_for_call_views), color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large)
let activeSpeakerAvatar = Avatar(color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large)
let activeSpeakerDisplayName = StyledLabel(VoipTheme.call_remote_name)
var grid : UICollectionView
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
var fullScreenOpaqueMasqForNotchedDevices = UIView()
var conferenceViewModel: ConferenceViewModel? = nil {
didSet {
@ -98,7 +99,6 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol
init() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.scrollDirection = .horizontal
@ -160,22 +160,20 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol
remotelyRecording.matchParentSideBorders().alignUnder(view:upperSection, withMargin:ActiveCallView.remote_recording_margin_top).height(CGFloat(ActiveCallView.remote_recording_height)).done()
// Container view that can toggle full screen by ckick
// Container view that can toggle full screen by single tap
let fullScreenMutableView = UIView()
addSubview(fullScreenMutableView)
fullScreenMutableView.backgroundColor = VoipTheme.voipBackgroundColor.get()
fullScreenMutableView.matchParentSideBorders().alignUnder(view:headerView,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
fullScreenOpaqueMasqForNotchedDevices.backgroundColor = fullScreenMutableView.backgroundColor
// Active speaker
fullScreenMutableView.addSubview(activeSpeakerView)
activeSpeakerView.layer.cornerRadius = ActiveCallView.center_view_corner_radius
activeSpeakerView.clipsToBounds = true
activeSpeakerView.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get()
activeSpeakerView.matchParentSideBorders().alignParentTop().done()
activeSpeakerView.addSubview(activeSpeakerAvatar)
activeSpeakerAvatar.square(Avatar.diameter_for_call_views).center().done()
activeSpeakerView.addSubview(activeSpeakerVideoView)
activeSpeakerVideoView.matchParentDimmensions().done()
@ -190,9 +188,7 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol
grid.backgroundColor = .clear
grid.isScrollEnabled = true
fullScreenMutableView.addSubview(grid)
grid.matchParentSideBorders().height(grid_height).alignParentBottom().alignUnder(view: activeSpeakerView, withMargin:ActiveCallView.center_view_margin_top).done()
// Full screen video togggle
activeSpeakerView.onClick {
ControlsViewModel.shared.toggleFullScreen()
@ -203,12 +199,18 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol
return
}
fullScreenMutableView.removeConstraints().done()
fullScreenMutableView.removeFromSuperview()
self.fullScreenOpaqueMasqForNotchedDevices.removeFromSuperview()
if (fullScreen == true) {
fullScreenMutableView.removeFromSuperview()
PhoneMainView.instance().mainViewController.view?.addSubview(fullScreenMutableView)
fullScreenMutableView.matchParentDimmensions().done()
self.fullScreenOpaqueMasqForNotchedDevices.addSubview(fullScreenMutableView)
PhoneMainView.instance().mainViewController.view?.addSubview(self.fullScreenOpaqueMasqForNotchedDevices)
self.fullScreenOpaqueMasqForNotchedDevices.matchParentDimmensions().done()
if (UIDevice.hasNotch()) {
fullScreenMutableView.matchParentDimmensions(insetedBy:UIApplication.shared.keyWindow!.safeAreaInsets).done()
} else {
fullScreenMutableView.matchParentDimmensions().done()
}
} else {
fullScreenMutableView.removeFromSuperview()
self.addSubview(fullScreenMutableView)
fullScreenMutableView.matchParentSideBorders().alignUnder(view:headerView,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
}
@ -217,12 +219,37 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol
}
}
//Rotation
layoutRotatableElements()
}
// Rotations
func layoutRotatableElements() {
grid.removeConstraints().done()
activeSpeakerView.removeConstraints().done()
activeSpeakerAvatar.removeConstraints().done()
if ([.landscapeLeft, .landscapeRight].contains( UIDevice.current.orientation)) {
activeSpeakerView.alignParentTop().alignParentBottom().alignParentLeft().toLeftOf(grid,withRightMargin: ActiveCallOrConferenceView.content_inset).done()
if (UIDevice.current.orientation == .landscapeLeft) { // work around some constraints issues with Notch on the left.
let superView = grid.superview
grid.removeFromSuperview()
superView?.addSubview(grid)
}
grid.width(grid_height).toRightOf(activeSpeakerView,withLeftMargin: ActiveCallOrConferenceView.content_inset).alignParentTop().alignParentBottom().alignParentRight().done()
layout.scrollDirection = .vertical
activeSpeakerAvatar.square(Avatar.diameter_for_call_views_land).center().done()
} else {
activeSpeakerAvatar.square(Avatar.diameter_for_call_views).center().done()
activeSpeakerView.matchParentSideBorders().alignParentTop().done()
grid.matchParentSideBorders().height(grid_height).alignParentBottom().alignUnder(view: activeSpeakerView, withMargin:ActiveCallView.center_view_margin_top).done()
layout.scrollDirection = .horizontal
}
}
// UICollectionView related delegates
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return inter_cell
}

View file

@ -36,7 +36,7 @@ class VoipGridParticipantCell: UICollectionViewCell {
let videoView = UIView()
let avatar = Avatar(diameter:VoipGridParticipantCell.avatar_size,color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_medium)
let avatar = Avatar(color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_medium)
let pause = UIImageView(image: UIImage(named: "voip_pause")?.tinted(with: .white))
let switchCamera = UIImageView(image: UIImage(named:"voip_change_camera")?.tinted(with:.white))
let displayName = StyledLabel(VoipTheme.conference_participant_name_font_grid)

View file

@ -37,7 +37,7 @@ import linphonesw
let spinner = RotatingSpinner()
let duration = CallTimer(nil, VoipTheme.call_header_subtitle)
let avatar = Avatar(diameter: CGFloat(Avatar.diameter_for_call_views),color:VoipTheme.voipParticipantBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large)
let avatar = Avatar(color:VoipTheme.voipParticipantBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large)
let displayName = StyledLabel(VoipTheme.call_header_title)
let sipAddress = StyledLabel(VoipTheme.call_header_subtitle)
@ -70,15 +70,28 @@ import linphonesw
// Center : Avatar + Display name + SIP Address
let centerSection = UIView()
centerSection.addSubview(avatar)
avatar.square(Avatar.diameter_for_call_views).center().done()
centerSection.addSubview(displayName)
displayName.height(IncomingOutgoingCommonView.display_name_height).matchParentSideBorders().alignUnder(view:avatar,withMargin:IncomingOutgoingCommonView.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()
self.view.addSubview(centerSection)
centerSection.matchParentSideBorders().center().done()
layoutRotatableElements()
}
func layoutRotatableElements() {
avatar.removeConstraints().done()
if ([.landscapeLeft, .landscapeRight].contains( UIDevice.current.orientation)) {
avatar.square(Avatar.diameter_for_call_views_land).center().done()
} else {
avatar.square(Avatar.diameter_for_call_views).center().done()
}
}
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
super.didRotate(from: fromInterfaceOrientation)
self.layoutRotatableElements()
}
override func viewWillAppear(_ animated: Bool) {

View file

@ -37,7 +37,7 @@ class VoipParticipantCell: UITableViewCell {
let lime_badge_offset = -10.0
let avatar = Avatar(diameter:VoipCallCell.avatar_size,color:VoipTheme.primaryTextColor, textStyle: VoipTheme.call_generated_avatar_small)
let avatar = Avatar(color:VoipTheme.primaryTextColor, textStyle: VoipTheme.call_generated_avatar_small)
let limeBadge = UIImageView(image: UIImage(named: "security_toggle_icon_green"))
let displayName = StyledLabel(VoipTheme.conference_participant_name_font)
let sipAddress = StyledLabel(VoipTheme.conference_participant_sip_uri_font)
@ -122,8 +122,6 @@ class VoipParticipantCell: UITableViewCell {
contentView.addSubview(isAdminView)
isAdminView.height(check_box_size).toLeftOf(removePart!).centerY().done()
}

View file

@ -19,10 +19,13 @@
import Foundation
import linphonesw
import SnapKit
class Avatar : UIImageView {
static let diameter_for_call_views = 191
static let diameter_for_call_views_land = 130
required init?(coder: NSCoder) {
initialsLabel = StyledLabel(VoipTheme.call_generated_avatar_large)
@ -31,10 +34,9 @@ class Avatar : UIImageView {
let initialsLabel: StyledLabel
init (diameter: CGFloat, color:LightDarkColor,textStyle:TextStyle) {
init (color:LightDarkColor,textStyle:TextStyle) {
initialsLabel = StyledLabel(textStyle)
super.init(frame: .zero)
layer.cornerRadius = diameter/2.0
clipsToBounds = true
self.backgroundColor = color.get()
addSubview(initialsLabel)
@ -56,7 +58,10 @@ class Avatar : UIImageView {
}
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = self.frame.width / 2.0
}
}

View file

@ -26,7 +26,7 @@ target 'linphone' do
# Pods for linphone
pod 'SVProgressHUD'
pod 'SnapKit'
pod 'SnapKit', '~> 5.6.0'
pod 'DropDown'
pod 'IQKeyboardManager'
all_pods