forked from mirrors/linphone-iphone
Audio only layout & assorted fixes
This commit is contained in:
parent
ecad9c86b3
commit
876f22da36
13 changed files with 418 additions and 44 deletions
|
|
@ -286,9 +286,13 @@ import AVFoundation
|
|||
lcallParams.mediaEncryption = .ZRTP
|
||||
}
|
||||
if (isConference) {
|
||||
lcallParams.videoEnabled = true
|
||||
lcallParams.videoDirection = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true ? .SendRecv : .RecvOnly
|
||||
lcallParams.conferenceVideoLayout = ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value!
|
||||
if (ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! != .AudioOnly) {
|
||||
lcallParams.videoEnabled = true
|
||||
lcallParams.videoDirection = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true ? .SendRecv : .RecvOnly
|
||||
lcallParams.conferenceVideoLayout = ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! == .Grid ? .Grid : .ActiveSpeaker
|
||||
} else {
|
||||
lcallParams.videoEnabled = false
|
||||
}
|
||||
} else {
|
||||
lcallParams.videoEnabled = isVideo
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class ConferenceWaitingRoomViewModel: ControlsViewModel {
|
|||
static let sharedModel = ConferenceWaitingRoomViewModel()
|
||||
|
||||
|
||||
let joinLayout = MutableLiveData<ConferenceLayout>()
|
||||
let joinLayout = MutableLiveData<ConferenceDisplayMode>()
|
||||
let joinInProgress = MutableLiveData<Bool>(false)
|
||||
let showLayoutPicker = MutableLiveData<Bool>()
|
||||
|
||||
|
|
@ -39,12 +39,12 @@ class ConferenceWaitingRoomViewModel: ControlsViewModel {
|
|||
}
|
||||
|
||||
func reset() {
|
||||
joinLayout.value = .ActiveSpeaker // TODO add setting
|
||||
joinLayout.value = Core.get().defaultConferenceLayout == .Grid ? .Grid : .ActiveSpeaker
|
||||
joinInProgress.value = false
|
||||
isMicrophoneMuted.value = !micAuthorized()
|
||||
isMuteMicrophoneEnabled.value = true
|
||||
isSpeakerSelected.value = true
|
||||
isVideoEnabled.value = true
|
||||
isVideoEnabled.value = false
|
||||
isVideoAvailable.value = core.videoCaptureEnabled
|
||||
showLayoutPicker.value = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,10 @@ import linphonesw
|
|||
switch (layout!) {
|
||||
case .Grid: icon = "voip_conference_mosaic"; break
|
||||
case .ActiveSpeaker: icon = "voip_conference_active_speaker"; break
|
||||
// Todo audio only case .Legacy: icon = "voip_conference_audio_only"; break
|
||||
case .AudioOnly:
|
||||
icon = "voip_conference_audio_only"
|
||||
ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value = false
|
||||
break
|
||||
}
|
||||
layoutPicker.applyTintedIcons(tintedIcons: [UIButton.State.normal.rawValue : TintableIcon(name: icon ,tintColor: LightDarkColor(.white,.white))])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,9 +39,7 @@ extension Address {
|
|||
}
|
||||
|
||||
func addressBookEnhancedDisplayName() -> String? {
|
||||
if (username == Core.get().defaultAccount?.contactAddress?.username) {
|
||||
return VoipTexts.me
|
||||
} else if let contact = FastAddressBook.getContactWith(getCobject) {
|
||||
if let contact = FastAddressBook.getContactWith(getCobject) {
|
||||
return contact.displayName
|
||||
} else if (!displayName.isEmpty) {
|
||||
return displayName
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import UIKit
|
|||
@objc class VoipTexts : NSObject { // From android key names. Added intentionnally with NSLocalizedString calls for each key, so it can be picked up by translation system (Weblate or Xcode).
|
||||
|
||||
static let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as! String
|
||||
static let me = NSLocalizedString("Me",comment:"")
|
||||
|
||||
// Calls
|
||||
static let call_incoming_title = NSLocalizedString("Incoming Call",comment:"")
|
||||
|
|
@ -95,6 +94,7 @@ import UIKit
|
|||
static let conference_invite_participants_count = NSLocalizedString("%d participants",comment:"")
|
||||
static let conference_display_mode_mosaic = NSLocalizedString("Mosaic mode",comment:"")
|
||||
static let conference_display_mode_active_speaker = NSLocalizedString("Active speaker mode",comment:"")
|
||||
static let conference_display_mode_audio_only = NSLocalizedString("Audio only mode",comment:"")
|
||||
static let conference_display_no_active_speaker = NSLocalizedString("No active speaker",comment:"")
|
||||
static let conference_waiting_room_start_call = NSLocalizedString("Start",comment:"")
|
||||
static let conference_waiting_room_cancel_call = NSLocalizedString("Cancel",comment:"")
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class VoipTheme { // Names & values replicated from Android
|
|||
static let voip_light_gray = UIColor(hex:"#D0D8DE")
|
||||
static let voip_dark_gray = UIColor(hex:"#4B5964")
|
||||
static let voip_gray = UIColor(hex:"#96A5B1")
|
||||
static let voip_gray_background = UIColor(hex:"#D8D8D8")
|
||||
static let voip_gray_background = UIColor(hex:"#AFAFAF")
|
||||
static let voip_call_record_background = UIColor(hex:"#EBEBEB")
|
||||
static let voip_calls_list_inactive_background = UIColor(hex:"#F0F1F2")
|
||||
static let voip_translucent_popup_background = UIColor(hex:"#A64B5964")
|
||||
|
|
@ -115,6 +115,8 @@ class VoipTheme { // Names & values replicated from Android
|
|||
static let conference_participant_sip_uri_font = TextStyle(fgColor: LightDarkColor(primary_color,primary_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0)
|
||||
static let conference_participant_name_font_grid = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 15.0)
|
||||
static let conference_participant_name_font_as = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 12.0)
|
||||
static let conference_participant_name_font_audio_only = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName, size: 14.0)
|
||||
|
||||
static let conference_mode_title = TextStyle(fgColor: LightDarkColor(dark_grey_color,dark_grey_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 17.0)
|
||||
static let conference_mode_title_selected = conference_mode_title.boldEd()
|
||||
static let conference_scheduling_font = TextStyle(fgColor: voipTextColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 17.0)
|
||||
|
|
|
|||
|
|
@ -27,10 +27,12 @@ class ConferenceParticipantDeviceData {
|
|||
|
||||
let videoEnabled = MutableLiveData<Bool>()
|
||||
let activeSpeaker = MutableLiveData<Bool>()
|
||||
let micMuted = MutableLiveData<Bool>()
|
||||
|
||||
let isInConference = MutableLiveData<Bool>()
|
||||
|
||||
|
||||
var core : Core { get { Core.get() } }
|
||||
|
||||
|
||||
private var participantDeviceDelegate : ParticipantDeviceDelegate?
|
||||
|
||||
init (participantDevice:ParticipantDevice, isMe:Bool) {
|
||||
|
|
@ -40,30 +42,38 @@ class ConferenceParticipantDeviceData {
|
|||
onIsSpeakingChanged: { (participantDevice, isSpeaking) in
|
||||
Log.i("[Conference Participant Device] Participant \(participantDevice.address?.asStringUriOnly()) isspeaking = \(isSpeaking)")
|
||||
self.activeSpeaker.value = isSpeaking
|
||||
}, onConferenceJoined: { (participantDevice) in
|
||||
},
|
||||
onIsMuted: { (participantDevice, isMuted) in
|
||||
Log.i("[Conference Participant Device] Participant \(participantDevice.address?.asStringUriOnly()) muted = \(isMuted)")
|
||||
self.micMuted.value = isMuted
|
||||
},
|
||||
onConferenceJoined: { (participantDevice) in
|
||||
Log.i("[Conference Participant Device] Participant \(participantDevice.address?.asStringUriOnly()) has joined the conference")
|
||||
self.isInConference.value = true
|
||||
}, onConferenceLeft: { (participantDevice) in
|
||||
},
|
||||
onConferenceLeft: { (participantDevice) in
|
||||
Log.i("[Conference Participant Device] Participant \(participantDevice.address?.asStringUriOnly()) has left the conference")
|
||||
self.isInConference.value = false
|
||||
}, onStreamCapabilityChanged: { (participantDevice, direction, streamType) in
|
||||
},
|
||||
onStreamCapabilityChanged: { (participantDevice, direction, streamType) in
|
||||
Log.i("[Conference Participant Device] Participant \(participantDevice.address?.asStringUriOnly()) video stream direction changed: \(direction)")
|
||||
self.videoEnabled.value = direction == MediaDirection.SendOnly || direction == MediaDirection.SendRecv
|
||||
if (streamType == StreamType.Video) {
|
||||
Log.i("[Conference Participant Device] Participant [\(participantDevice.address?.asStringUriOnly())] video capability changed to \(direction)")
|
||||
}
|
||||
}, onStreamAvailabilityChanged: { (participantDevice, available, streamType) in
|
||||
},
|
||||
onStreamAvailabilityChanged: { (participantDevice, available, streamType) in
|
||||
if (streamType == StreamType.Video) {
|
||||
Log.i("[Conference Participant Device] Participant [\(participantDevice.address?.asStringUriOnly())] video availability changed to \(available)")
|
||||
self.videoEnabled.value = available
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
)
|
||||
|
||||
participantDevice.addDelegate(delegate: participantDeviceDelegate!)
|
||||
activeSpeaker.value = false
|
||||
|
||||
micMuted.value = participantDevice.isMuted
|
||||
|
||||
videoEnabled.value = participantDevice.getStreamAvailability(streamType: .Video)
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import linphonesw
|
|||
var currentCallView : ActiveCallView? = nil
|
||||
var conferenceGridView: VoipConferenceGridView? = nil
|
||||
var conferenceActiveSpeakerView: VoipConferenceActiveSpeakerView? = nil
|
||||
var conferenceAudioOnlyView: VoipConferenceAudioOnlyView? = nil
|
||||
|
||||
let conferenceJoinSpinner = RotatingSpinner()
|
||||
|
||||
|
||||
|
|
@ -116,18 +118,17 @@ import linphonesw
|
|||
fullScreenMutableContainerView.addSubview(conferenceGridView!)
|
||||
conferenceGridView?.matchParentDimmensions().done()
|
||||
conferenceGridView?.isHidden = true
|
||||
ConferenceViewModel.shared.conferenceExists.readCurrentAndObserve { (isInConference) in
|
||||
ConferenceViewModel.shared.conferenceExists.readCurrentAndObserve { (exists) in
|
||||
self.updateNavigation()
|
||||
if (isInConference == true) {
|
||||
if (exists == true) {
|
||||
self.currentCallView!.isHidden = true
|
||||
self.extraButtonsView.isHidden = true
|
||||
self.conferencePausedView?.isHidden = true
|
||||
let conferenceMode = ConferenceViewModel.shared.conferenceDisplayMode.value
|
||||
self.conferenceGridView!.isHidden = conferenceMode != .Grid
|
||||
self.conferenceActiveSpeakerView?.isHidden = conferenceMode != .ActiveSpeaker
|
||||
self.conferenceGridView?.conferenceViewModel = ConferenceViewModel.shared
|
||||
self.displaySelectedConferenceLayout()
|
||||
} else {
|
||||
self.conferenceGridView?.isHidden = true
|
||||
self.conferenceActiveSpeakerView?.isHidden = true
|
||||
self.conferenceActiveSpeakerView?.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,22 +149,20 @@ import linphonesw
|
|||
conferenceActiveSpeakerView?.matchParentDimmensions().done()
|
||||
conferenceActiveSpeakerView?.isHidden = true
|
||||
|
||||
// Conference mode switching
|
||||
|
||||
// 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.conferenceGridView!.isHidden = conferenceMode != .Grid
|
||||
self.conferenceActiveSpeakerView!.isHidden = conferenceMode != .ActiveSpeaker
|
||||
self.conferenceActiveSpeakerView?.conferenceViewModel = ConferenceViewModel.shared
|
||||
} else {
|
||||
self.conferenceActiveSpeakerView?.isHidden = true
|
||||
self.displaySelectedConferenceLayout()
|
||||
}
|
||||
}
|
||||
|
||||
ConferenceViewModel.shared.conferenceExists.readCurrentAndObserve { (isInConference) in
|
||||
self.updateNavigation()
|
||||
}
|
||||
|
||||
// Calls List
|
||||
ControlsViewModel.shared.goToCallsListEvent.observe { (_) in
|
||||
self.dismissableView = CallsListView()
|
||||
|
|
@ -189,9 +188,8 @@ import linphonesw
|
|||
self.view.addSubview(self.dismissableView!)
|
||||
self.dismissableView?.matchParentDimmensions().done()
|
||||
let activeDisplayMode = ConferenceViewModel.shared.conferenceDisplayMode.value!
|
||||
let indexPath = IndexPath(row: activeDisplayMode == .Grid ? 0 : 1, section: 0)
|
||||
let indexPath = IndexPath(row: activeDisplayMode == .Grid ? 0 : activeDisplayMode == .ActiveSpeaker ? 1 : 2, section: 0)
|
||||
(self.dismissableView as! VoipConferenceDisplayModeSelectionView).optionsListView.selectRow(at:indexPath, animated: true, scrollPosition: .bottom)
|
||||
|
||||
}
|
||||
|
||||
// Shading mask, everything before will be shaded upon displaying of the mask
|
||||
|
|
@ -292,6 +290,22 @@ import linphonesw
|
|||
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
import SnapKit
|
||||
import linphonesw
|
||||
|
||||
class VoipAudioOnlyParticipantCell: UICollectionViewCell {
|
||||
|
||||
// Layout Constants
|
||||
static let cell_height = 80.0
|
||||
static let avatar_size = 40.0
|
||||
static let mute_size = 30.0
|
||||
let corner_radius = 6.7
|
||||
let common_margin = 10.0
|
||||
|
||||
|
||||
let avatar = Avatar(diameter:VoipCallCell.avatar_size,color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_small)
|
||||
let paused = UIImageView(image: UIImage(named: "voip_pause")?.tinted(with: .white))
|
||||
let muted = UIImageView(image: UIImage(named: "voip_micro_off")?.tinted(with: .white))
|
||||
|
||||
let displayName = StyledLabel(VoipTheme.conference_participant_name_font_as)
|
||||
|
||||
var participantData: ConferenceParticipantDeviceData? = nil {
|
||||
didSet {
|
||||
if let data = participantData {
|
||||
self.displayName.text = ""
|
||||
data.isInConference.clearObservers()
|
||||
data.isInConference.readCurrentAndObserve { (isIn) in
|
||||
self.avatar.isHidden = isIn != true
|
||||
self.paused.isHidden = isIn == true
|
||||
data.participantDevice.address.map {
|
||||
self.avatar.fillFromAddress(address: $0)
|
||||
if let displayName = $0.addressBookEnhancedDisplayName() {
|
||||
self.displayName.text = displayName + (isIn == true ? "" : " \(VoipTexts.conference_participant_paused)")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data.participantDevice.address == nil) {
|
||||
avatar.isHidden = true
|
||||
}
|
||||
data.activeSpeaker.clearObservers()
|
||||
data.activeSpeaker.readCurrentAndObserve { (active) in
|
||||
if (active == true) {
|
||||
self.layer.borderWidth = 2
|
||||
} else {
|
||||
self.layer.borderWidth = 0
|
||||
}
|
||||
}
|
||||
data.micMuted.clearObservers()
|
||||
data.micMuted.readCurrentAndObserve { (muted) in
|
||||
self.muted.isHidden = muted != true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override init(frame:CGRect) {
|
||||
super.init(frame:.zero)
|
||||
contentView.height(VoipAudioOnlyParticipantCell.cell_height).matchParentSideBorders().done()
|
||||
|
||||
layer.cornerRadius = corner_radius
|
||||
clipsToBounds = true
|
||||
contentView.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get()
|
||||
layer.borderColor = VoipTheme.primary_color.cgColor
|
||||
|
||||
contentView.addSubview(avatar)
|
||||
avatar.size(w: VoipCallCell.avatar_size, h: VoipCallCell.avatar_size).centerY().alignParentLeft(withMargin: common_margin).done()
|
||||
|
||||
contentView.addSubview(paused)
|
||||
paused.layer.cornerRadius = VoipAudioOnlyParticipantCell.avatar_size/2
|
||||
paused.clipsToBounds = true
|
||||
paused.backgroundColor = VoipTheme.voip_gray
|
||||
paused.size(w: VoipAudioOnlyParticipantCell.avatar_size, h: VoipAudioOnlyParticipantCell.avatar_size).alignParentLeft(withMargin: common_margin).centerY().done()
|
||||
|
||||
contentView.addSubview(displayName)
|
||||
displayName.centerY().toRightOf(avatar,withLeftMargin: common_margin).done()
|
||||
displayName.numberOfLines = 3
|
||||
|
||||
contentView.addSubview(muted)
|
||||
muted.layer.cornerRadius = VoipAudioOnlyParticipantCell.avatar_size/2
|
||||
muted.clipsToBounds = true
|
||||
muted.backgroundColor = VoipTheme.voip_dark_gray
|
||||
muted.size(w: VoipAudioOnlyParticipantCell.mute_size, h: VoipAudioOnlyParticipantCell.mute_size).alignParentRight(withMargin: common_margin).toRightOf(displayName,withLeftMargin: common_margin).centerY().done()
|
||||
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
import SnapKit
|
||||
import linphonesw
|
||||
|
||||
class VoipConferenceAudioOnlyView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
|
||||
|
||||
// Layout constants :
|
||||
let inter_cell = 5.0
|
||||
let record_pause_button_margin = 10.0
|
||||
let duration_margin_top = 4.0
|
||||
let record_pause_button_size = 40
|
||||
let record_pause_button_inset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
|
||||
|
||||
|
||||
let subjectLabel = StyledLabel(VoipTheme.call_display_name_duration)
|
||||
let duration = CallTimer(nil, VoipTheme.call_display_name_duration)
|
||||
|
||||
let remotelyRecording = RemotelyRecordingView(height: ActiveCallView.remote_recording_height,text: VoipTexts.call_remote_recording)
|
||||
var recordCallButtons : [CallControlButton] = []
|
||||
var pauseCallButtons : [CallControlButton] = []
|
||||
var grid : UICollectionView
|
||||
var gridContainer = UIView()
|
||||
|
||||
|
||||
var conferenceViewModel: ConferenceViewModel? = nil {
|
||||
didSet {
|
||||
if let model = conferenceViewModel {
|
||||
model.subject.clearObservers()
|
||||
model.subject.readCurrentAndObserve { (subject) in
|
||||
self.subjectLabel.text = subject
|
||||
}
|
||||
duration.conference = model.conference.value
|
||||
self.remotelyRecording.isRemotelyRecorded = model.isRemotelyRecorded
|
||||
model.conferenceParticipantDevices.clearObservers()
|
||||
model.conferenceParticipantDevices.readCurrentAndObserve { (_) in
|
||||
self.reloadData()
|
||||
}
|
||||
model.isConferenceLocallyPaused.clearObservers()
|
||||
model.isConferenceLocallyPaused.readCurrentAndObserve { (paused) in
|
||||
self.pauseCallButtons.forEach {
|
||||
$0.isSelected = paused == true
|
||||
}
|
||||
}
|
||||
model.isRecording.clearObservers()
|
||||
model.isRecording.readCurrentAndObserve { (selected) in
|
||||
self.recordCallButtons.forEach {
|
||||
$0.isSelected = selected == true
|
||||
}
|
||||
}
|
||||
}
|
||||
self.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
|
||||
layout.minimumInteritemSpacing = 0
|
||||
layout.minimumLineSpacing = 0
|
||||
layout.estimatedItemSize = .zero
|
||||
grid = UICollectionView(frame:.zero, collectionViewLayout: layout)
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
let headerView = UIStackView()
|
||||
addSubview(headerView)
|
||||
|
||||
headerView.distribution = .equalSpacing
|
||||
headerView.alignment = .bottom
|
||||
headerView.spacing = record_pause_button_margin
|
||||
headerView.axis = .vertical
|
||||
|
||||
let subjectDuration = UIView()
|
||||
|
||||
subjectDuration.addSubview(subjectLabel)
|
||||
subjectLabel.alignParentLeft().done()
|
||||
|
||||
subjectDuration.addSubview(duration)
|
||||
duration.alignParentLeft().alignUnder(view: subjectLabel,withMargin:duration_margin_top).done()
|
||||
|
||||
let upperSection = UIStackView()
|
||||
upperSection.distribution = .equalSpacing
|
||||
upperSection.alignment = .center
|
||||
upperSection.spacing = record_pause_button_margin
|
||||
upperSection.axis = .horizontal
|
||||
|
||||
upperSection.addArrangedSubview(subjectDuration)
|
||||
subjectDuration.wrapContentY().done()
|
||||
|
||||
// Record (with video)
|
||||
let recordCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_record, onClickAction: {
|
||||
self.conferenceViewModel?.toggleRecording()
|
||||
})
|
||||
|
||||
let recordPauseView = UIStackView()
|
||||
recordPauseView.spacing = record_pause_button_margin
|
||||
recordCallButtons.append(recordCall)
|
||||
recordPauseView.addArrangedSubview(recordCall)
|
||||
|
||||
// Pause (with video)
|
||||
let pauseCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_pause, onClickAction: {
|
||||
self.conferenceViewModel?.togglePlayPause()
|
||||
|
||||
})
|
||||
pauseCallButtons.append(pauseCall)
|
||||
recordPauseView.addArrangedSubview(pauseCall)
|
||||
|
||||
upperSection.addArrangedSubview(recordPauseView)
|
||||
|
||||
headerView.addArrangedSubview(upperSection)
|
||||
upperSection.matchParentSideBorders().alignParentTop(withMargin:ActiveCallView.top_displayname_margin_top).done()
|
||||
|
||||
headerView.addArrangedSubview(remotelyRecording)
|
||||
remotelyRecording.matchParentSideBorders().alignUnder(view:upperSection, withMargin:ActiveCallView.remote_recording_margin_top).height(CGFloat(ActiveCallView.remote_recording_height)).done()
|
||||
|
||||
// CollectionView
|
||||
grid.dataSource = self
|
||||
grid.delegate = self
|
||||
grid.register(VoipAudioOnlyParticipantCell.self, forCellWithReuseIdentifier: "VoipAudioOnlyParticipantCell")
|
||||
grid.backgroundColor = .clear
|
||||
grid.isScrollEnabled = false
|
||||
addSubview(gridContainer)
|
||||
gridContainer.addSubview(grid)
|
||||
gridContainer.backgroundColor = VoipTheme.voipBackgroundColor.get()
|
||||
|
||||
gridContainer.matchParentSideBorders(insetedByDx: inter_cell).alignUnder(view:headerView,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom(withMargin: inter_cell).done()
|
||||
grid.matchParentDimmensions().done()
|
||||
|
||||
headerView.matchParentSideBorders().alignParentTop().done()
|
||||
|
||||
}
|
||||
|
||||
|
||||
// UICollectionView related delegates
|
||||
|
||||
func reloadData() {
|
||||
conferenceViewModel?.conferenceParticipantDevices.value?.forEach {
|
||||
$0.clearObservers()
|
||||
}
|
||||
if (self.isHidden) {
|
||||
self.grid.reloadData()
|
||||
return
|
||||
}
|
||||
self.grid.reloadData()
|
||||
}
|
||||
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
|
||||
return inter_cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, layout
|
||||
collectionViewLayout: UICollectionViewLayout,
|
||||
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
|
||||
return inter_cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
if (self.isHidden) {
|
||||
return 0
|
||||
}
|
||||
guard let participantsCount = conferenceViewModel?.conferenceParticipantDevices.value?.count else {
|
||||
return .zero
|
||||
}
|
||||
return participantsCount
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell:VoipAudioOnlyParticipantCell = collectionView.dequeueReusableCell(withReuseIdentifier: "VoipAudioOnlyParticipantCell", for: indexPath) as! VoipAudioOnlyParticipantCell
|
||||
guard let participantData = conferenceViewModel?.conferenceParticipantDevices.value?[indexPath.row] else {
|
||||
return cell
|
||||
}
|
||||
cell.participantData = participantData
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView,
|
||||
layout collectionViewLayout: UICollectionViewLayout,
|
||||
sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
|
||||
guard let participantsCount:Int = conferenceViewModel?.conferenceParticipantDevices.value?.count else {
|
||||
return .zero
|
||||
}
|
||||
|
||||
return participantsCount == 1 ? CGSize(width:collectionView.frame.size.width,height:VoipAudioOnlyParticipantCell.cell_height) : CGSize(width:collectionView.frame.size.width / 2.0 - inter_cell / 2.0,height:VoipAudioOnlyParticipantCell.cell_height)
|
||||
}
|
||||
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ import linphonesw
|
|||
// TableView datasource delegate
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return 2
|
||||
return 3
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
|
@ -66,6 +66,14 @@ import linphonesw
|
|||
}, image:(UIImage(named: "voip_conference_active_speaker")?.tinted(with: VoipTheme.voipDrawableColor.get())!)!)
|
||||
cell.isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
if (indexPath.row == 2) {
|
||||
cell.setOption(title: VoipTexts.conference_display_mode_audio_only, onSelectAction: {
|
||||
ConferenceViewModel.shared.changeLayout(layout: .AudioOnly)
|
||||
ConferenceViewModel.shared.conferenceDisplayMode.value = .AudioOnly
|
||||
}, image:(UIImage(named: "voip_conference_audio_only")?.tinted(with: VoipTheme.voipDrawableColor.get())!)!)
|
||||
cell.isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
cell.separatorInset = .zero
|
||||
cell.selectionStyle = .none
|
||||
|
|
@ -78,9 +86,15 @@ import linphonesw
|
|||
cell.isSelected = true
|
||||
if (indexPath.row == 0) {
|
||||
tableView.deselectRow(at: IndexPath(row: 1, section: 0), animated: false)
|
||||
tableView.deselectRow(at: IndexPath(row: 2, section: 0), animated: false)
|
||||
}
|
||||
if (indexPath.row == 1) {
|
||||
tableView.deselectRow(at: IndexPath(row: 0, section: 0), animated: false)
|
||||
tableView.deselectRow(at: IndexPath(row: 2, section: 0), animated: false)
|
||||
}
|
||||
if (indexPath.row == 2) {
|
||||
tableView.deselectRow(at: IndexPath(row: 0, section: 0), animated: false)
|
||||
tableView.deselectRow(at: IndexPath(row: 1, section: 0), animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,19 +46,23 @@ class VoipConferenceGridView: UIView, UICollectionViewDataSource, UICollectionVi
|
|||
var conferenceViewModel: ConferenceViewModel? = nil {
|
||||
didSet {
|
||||
if let model = conferenceViewModel {
|
||||
model.subject.clearObservers()
|
||||
model.subject.readCurrentAndObserve { (subject) in
|
||||
self.subjectLabel.text = subject
|
||||
}
|
||||
duration.conference = model.conference.value
|
||||
self.remotelyRecording.isRemotelyRecorded = model.isRemotelyRecorded
|
||||
model.conferenceParticipantDevices.clearObservers()
|
||||
model.conferenceParticipantDevices.readCurrentAndObserve { (_) in
|
||||
self.reloadData()
|
||||
}
|
||||
model.isConferenceLocallyPaused.clearObservers()
|
||||
model.isConferenceLocallyPaused.readCurrentAndObserve { (paused) in
|
||||
self.pauseCallButtons.forEach {
|
||||
$0.isSelected = paused == true
|
||||
}
|
||||
}
|
||||
model.isRecording.clearObservers()
|
||||
model.isRecording.readCurrentAndObserve { (selected) in
|
||||
self.recordCallButtons.forEach {
|
||||
$0.isSelected = selected == true
|
||||
|
|
@ -178,7 +182,7 @@ class VoipConferenceGridView: UIView, UICollectionViewDataSource, UICollectionVi
|
|||
conferenceViewModel?.conferenceParticipantDevices.value?.forEach {
|
||||
$0.clearObservers()
|
||||
}
|
||||
if (self.isHidden || conferenceViewModel?.conference.value?.call?.params?.conferenceVideoLayout != .Grid) {
|
||||
if (self.isHidden) {
|
||||
self.grid.reloadData()
|
||||
return
|
||||
}
|
||||
|
|
@ -205,7 +209,7 @@ class VoipConferenceGridView: UIView, UICollectionViewDataSource, UICollectionVi
|
|||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
if (self.isHidden || conferenceViewModel?.conference.value?.call?.params?.conferenceVideoLayout != .Grid) {
|
||||
if (self.isHidden) {
|
||||
return 0
|
||||
}
|
||||
guard let participantsCount = conferenceViewModel?.conferenceParticipantDevices.value?.count else {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class ConferenceLayoutPickerView: UIStackView {
|
|||
addArrangedSubview(activeSpeaker)
|
||||
|
||||
let audioOnly = CallControlButton(imageInset : UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5),buttonTheme: VoipTheme.conf_waiting_room_layout_picker, onClickAction: {
|
||||
ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value = .Grid
|
||||
ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value = .AudioOnly
|
||||
ConferenceWaitingRoomViewModel.sharedModel.showLayoutPicker.value = false
|
||||
})
|
||||
audioOnly.applyTintedIcons(tintedIcons: [UIButton.State.normal.rawValue : TintableIcon(name: "voip_conference_audio_only" ,tintColor: LightDarkColor(.white,.white))])
|
||||
|
|
@ -66,13 +66,12 @@ class ConferenceLayoutPickerView: UIStackView {
|
|||
ConferenceWaitingRoomViewModel.sharedModel.joinLayout.readCurrentAndObserve { layout in
|
||||
grid.isSelected = layout == .Grid
|
||||
activeSpeaker.isSelected = layout == .ActiveSpeaker
|
||||
audioOnly.isSelected = false // Todo when doing auioonly layout == .Grid
|
||||
audioOnly.isSelected = layout == .AudioOnly
|
||||
}
|
||||
|
||||
let padding2 = UIView()
|
||||
padding2.height(margin/2).done()
|
||||
addArrangedSubview(padding2)
|
||||
|
||||
|
||||
size(w:CGFloat(CallControlButton.default_size)+margin, h : 3*CGFloat(CallControlButton.default_size)+3*CGFloat(ControlsView.controls_button_spacing)+2*margin).done()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue