Audio only layout & assorted fixes

This commit is contained in:
Christophe Deschamps 2022-06-13 18:45:59 +02:00
parent ecad9c86b3
commit 876f22da36
13 changed files with 418 additions and 44 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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))])
}

View file

@ -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

View file

@ -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:"")

View file

@ -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)

View file

@ -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)

View file

@ -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()

View file

@ -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")
}
}

View file

@ -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")
}
}

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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()