Added broadcast mode for scheduled conferences

This commit is contained in:
Sylvain Berfini 2023-08-21 14:41:11 +02:00
parent 2334a0e6a8
commit 4b27d1180a
29 changed files with 660 additions and 50 deletions

View file

@ -63,6 +63,8 @@ class ChatMessageContentData(
val isConferenceSchedule = MutableLiveData<Boolean>() val isConferenceSchedule = MutableLiveData<Boolean>()
val isConferenceUpdated = MutableLiveData<Boolean>() val isConferenceUpdated = MutableLiveData<Boolean>()
val isConferenceCancelled = MutableLiveData<Boolean>() val isConferenceCancelled = MutableLiveData<Boolean>()
val isBroadcast = MutableLiveData<Boolean>()
val isSpeaker = MutableLiveData<Boolean>()
val fileName = MutableLiveData<String>() val fileName = MutableLiveData<String>()
val filePath = MutableLiveData<String>() val filePath = MutableLiveData<String>()
@ -378,14 +380,26 @@ class ChatMessageContentData(
var participantsCount = conferenceInfo.participants.size var participantsCount = conferenceInfo.participants.size
val organizer = conferenceInfo.organizer val organizer = conferenceInfo.organizer
var organizerFound = false var organizerFound = false
if (organizer != null) { var allSpeaker = true
for (participant in conferenceInfo.participants) { isSpeaker.value = true
for (info in conferenceInfo.participantInfos) {
val participant = info.address
if (participant.weakEqual(chatMessage.chatRoom.localAddress)) {
isSpeaker.value = info.role == Participant.Role.Speaker
}
if (info.role == Participant.Role.Listener) {
allSpeaker = false
}
if (organizer != null) {
if (participant.weakEqual(organizer)) { if (participant.weakEqual(organizer)) {
organizerFound = true organizerFound = true
break
} }
} }
} }
isBroadcast.value = allSpeaker == false
if (!organizerFound) participantsCount += 1 // +1 for organizer if (!organizerFound) participantsCount += 1 // +1 for organizer
conferenceParticipantCount.value = String.format( conferenceParticipantCount.value = String.format(
AppUtils.getString(R.string.conference_invite_participants_count), AppUtils.getString(R.string.conference_invite_participants_count),

View file

@ -19,15 +19,35 @@
*/ */
package org.linphone.activities.main.conference.data package org.linphone.activities.main.conference.data
import androidx.lifecycle.MutableLiveData
import org.linphone.contact.GenericContactData import org.linphone.contact.GenericContactData
import org.linphone.core.Address import org.linphone.core.Address
import org.linphone.utils.LinphoneUtils import org.linphone.utils.LinphoneUtils
class ConferenceSchedulingParticipantData( class ConferenceSchedulingParticipantData(
private val sipAddress: Address, val sipAddress: Address,
val showLimeBadge: Boolean = false, val showLimeBadge: Boolean = false,
val showDivider: Boolean = true val showDivider: Boolean = true,
val showBroadcastControls: Boolean = false,
val speaker: Boolean = false,
private val onAddedToSpeakers: ((data: ConferenceSchedulingParticipantData) -> Unit)? = null,
private val onRemovedFromSpeakers: ((data: ConferenceSchedulingParticipantData) -> Unit)? = null
) : ) :
GenericContactData(sipAddress) { GenericContactData(sipAddress) {
val isSpeaker = MutableLiveData<Boolean>()
val sipUri: String get() = LinphoneUtils.getDisplayableAddress(sipAddress) val sipUri: String get() = LinphoneUtils.getDisplayableAddress(sipAddress)
init {
isSpeaker.value = speaker
}
fun changeIsSpeaker() {
isSpeaker.value = isSpeaker.value == false
if (isSpeaker.value == true) {
onAddedToSpeakers?.invoke(this)
} else {
onRemovedFromSpeakers?.invoke(this)
}
}
} }

View file

@ -25,6 +25,7 @@ import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R import org.linphone.R
import org.linphone.core.ConferenceInfo import org.linphone.core.ConferenceInfo
import org.linphone.core.ConferenceInfo.State import org.linphone.core.ConferenceInfo.State
import org.linphone.core.Participant
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.LinphoneUtils import org.linphone.utils.LinphoneUtils
import org.linphone.utils.TimestampUtils import org.linphone.utils.TimestampUtils
@ -45,9 +46,12 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo, private val is
val participantsExpanded = MutableLiveData<String>() val participantsExpanded = MutableLiveData<String>()
val showDuration = MutableLiveData<Boolean>() val showDuration = MutableLiveData<Boolean>()
val isConferenceCancelled = MutableLiveData<Boolean>() val isConferenceCancelled = MutableLiveData<Boolean>()
val isBroadcast = MutableLiveData<Boolean>()
val speakersExpanded = MutableLiveData<String>()
init { init {
expanded.value = false expanded.value = false
isBroadcast.value = false
address.value = conferenceInfo.uri?.asStringUriOnly() address.value = conferenceInfo.uri?.asStringUriOnly()
subject.value = conferenceInfo.subject subject.value = conferenceInfo.subject
@ -141,24 +145,48 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo, private val is
private fun computeParticipantsLists() { private fun computeParticipantsLists() {
var participantsListShort = "" var participantsListShort = ""
var participantsListExpanded = "" var participantsListExpanded = ""
var speakersListExpanded = ""
var allSpeaker = true
for (info in conferenceInfo.participantInfos) {
val participant = info.address
Log.i(
"[Scheduled Conference] Conference [${subject.value}] participant [${participant.asStringUriOnly()}] is a [${info.role}]"
)
for (participant in conferenceInfo.participants) {
val contact = coreContext.contactsManager.findContactByAddress(participant) val contact = coreContext.contactsManager.findContactByAddress(participant)
val name = if (contact != null) { val name = if (contact != null) {
contact.name contact.name
} else { } else {
LinphoneUtils.getDisplayName( LinphoneUtils.getDisplayName(participant)
participant
)
} }
val address = participant.asStringUriOnly() val address = participant.asStringUriOnly()
participantsListShort += "$name, " participantsListShort += "$name, "
participantsListExpanded += "$name ($address)\n" when (info.role) {
Participant.Role.Speaker -> {
speakersListExpanded += "$name ($address)\n"
}
Participant.Role.Listener -> {
participantsListExpanded += "$name ($address)\n"
allSpeaker = false
}
else -> { // For meetings created before 5.3 SDK
participantsListExpanded += "$name ($address)\n"
}
}
} }
participantsListShort = participantsListShort.dropLast(2) participantsListShort = participantsListShort.dropLast(2)
participantsListExpanded = participantsListExpanded.dropLast(1) participantsListExpanded = participantsListExpanded.dropLast(1)
speakersListExpanded = speakersListExpanded.dropLast(1)
participantsShort.value = participantsListShort participantsShort.value = participantsListShort
participantsExpanded.value = participantsListExpanded participantsExpanded.value = participantsListExpanded
speakersExpanded.value = speakersListExpanded
// If all participants have Speaker role then it is a meeting, else it is a broadcast
isBroadcast.value = allSpeaker == false
Log.i(
"[Scheduled Conference] Conference [${subject.value}] is a ${if (allSpeaker) "meeting" else "broadcast"}"
)
} }
} }

View file

@ -53,6 +53,9 @@ class ConferenceWaitingRoomFragment : GenericFragment<ConferenceWaitingRoomFragm
val conferenceSubject = arguments?.getString("Subject") val conferenceSubject = arguments?.getString("Subject")
viewModel.subject.value = conferenceSubject viewModel.subject.value = conferenceSubject
val address = arguments?.getString("Address")
viewModel.findConferenceInfoByAddress(address)
viewModel.cancelConferenceJoiningEvent.observe( viewModel.cancelConferenceJoiningEvent.observe(
viewLifecycleOwner viewLifecycleOwner
) { ) {

View file

@ -23,6 +23,7 @@ import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import java.util.* import java.util.*
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R import org.linphone.R
import org.linphone.activities.main.conference.data.ConferenceSchedulingParticipantData import org.linphone.activities.main.conference.data.ConferenceSchedulingParticipantData
import org.linphone.activities.main.conference.data.Duration import org.linphone.activities.main.conference.data.Duration
@ -30,6 +31,7 @@ import org.linphone.activities.main.conference.data.TimeZoneData
import org.linphone.contact.ContactsSelectionViewModel import org.linphone.contact.ContactsSelectionViewModel
import org.linphone.core.* import org.linphone.core.*
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils import org.linphone.utils.LinphoneUtils
import org.linphone.utils.TimestampUtils import org.linphone.utils.TimestampUtils
@ -41,6 +43,10 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
val scheduleForLater = MutableLiveData<Boolean>() val scheduleForLater = MutableLiveData<Boolean>()
val isUpdate = MutableLiveData<Boolean>() val isUpdate = MutableLiveData<Boolean>()
val isBroadcastAllowed = MutableLiveData<Boolean>()
val mode = MutableLiveData<String>()
val modesList: List<String>
val formattedDate = MutableLiveData<String>() val formattedDate = MutableLiveData<String>()
val formattedTime = MutableLiveData<String>() val formattedTime = MutableLiveData<String>()
@ -50,6 +56,7 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
val sendInviteViaEmail = MutableLiveData<Boolean>() val sendInviteViaEmail = MutableLiveData<Boolean>()
val participantsData = MutableLiveData<List<ConferenceSchedulingParticipantData>>() val participantsData = MutableLiveData<List<ConferenceSchedulingParticipantData>>()
val speakersData = MutableLiveData<List<ConferenceSchedulingParticipantData>>()
val address = MutableLiveData<Address>() val address = MutableLiveData<Address>()
@ -74,6 +81,8 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
private var confInfo: ConferenceInfo? = null private var confInfo: ConferenceInfo? = null
private val conferenceScheduler = coreContext.core.createConferenceScheduler() private val conferenceScheduler = coreContext.core.createConferenceScheduler()
private val selectedSpeakersAddresses = MutableLiveData<ArrayList<Address>>()
private val listener = object : ConferenceSchedulerListenerStub() { private val listener = object : ConferenceSchedulerListenerStub() {
override fun onStateChanged( override fun onStateChanged(
conferenceScheduler: ConferenceScheduler, conferenceScheduler: ConferenceScheduler,
@ -154,6 +163,13 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
scheduleForLater.value = false scheduleForLater.value = false
isUpdate.value = false isUpdate.value = false
isBroadcastAllowed.value = !corePreferences.disableBroadcastConference
modesList = arrayListOf(
AppUtils.getString(R.string.conference_schedule_mode_meeting),
AppUtils.getString(R.string.conference_schedule_mode_broadcast)
)
mode.value = modesList.first() // Meeting by default
isEncrypted.value = false isEncrypted.value = false
sendInviteViaChat.value = true sendInviteViaChat.value = true
sendInviteViaEmail.value = false sendInviteViaEmail.value = false
@ -185,6 +201,7 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
override fun onCleared() { override fun onCleared() {
conferenceScheduler.removeListener(listener) conferenceScheduler.removeListener(listener)
participantsData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy) participantsData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy)
speakersData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy)
super.onCleared() super.onCleared()
} }
@ -195,6 +212,7 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
} }
fun populateFromConferenceInfo(conferenceInfo: ConferenceInfo) { fun populateFromConferenceInfo(conferenceInfo: ConferenceInfo) {
// Pre-set data from existing conference info, used when editing an already scheduled broadcast or meeting
confInfo = conferenceInfo confInfo = conferenceInfo
address.value = conferenceInfo.uri address.value = conferenceInfo.uri
@ -213,10 +231,26 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
scheduleForLater.value = conferenceDuration > 0 scheduleForLater.value = conferenceDuration > 0
val participantsList = arrayListOf<Address>() val participantsList = arrayListOf<Address>()
for (participant in conferenceInfo.participants) { val speakersList = arrayListOf<Address>()
for (info in conferenceInfo.participantInfos) {
val participant = info.address
participantsList.add(participant) participantsList.add(participant)
if (info.role == Participant.Role.Speaker) {
speakersList.add(participant)
}
}
if (participantsList.count() == speakersList.count()) {
// All participants are speaker, this is a meeting, clear speakers
Log.i("[Conference Creation] Conference info is a meeting")
speakersList.clear()
mode.value = modesList.first()
} else {
Log.i("[Conference Creation] Conference info is a broadcast")
mode.value = modesList.last()
} }
selectedAddresses.value = participantsList selectedAddresses.value = participantsList
selectedSpeakersAddresses.value = speakersList
computeParticipantsData() computeParticipantsData()
} }
@ -241,14 +275,57 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
fun computeParticipantsData() { fun computeParticipantsData() {
participantsData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy) participantsData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy)
val list = arrayListOf<ConferenceSchedulingParticipantData>() speakersData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy)
val participantsList = arrayListOf<ConferenceSchedulingParticipantData>()
val speakersList = arrayListOf<ConferenceSchedulingParticipantData>()
for (address in selectedAddresses.value.orEmpty()) { for (address in selectedAddresses.value.orEmpty()) {
val data = ConferenceSchedulingParticipantData(address, isEncrypted.value == true) val isSpeaker = address in selectedSpeakersAddresses.value.orEmpty()
list.add(data) val data = ConferenceSchedulingParticipantData(
address,
showLimeBadge = isEncrypted.value == true,
showBroadcastControls = isModeBroadcastCurrentlySelected(),
speaker = isSpeaker,
onAddedToSpeakers = { data ->
Log.i(
"[Conference Creation] Participant [${address.asStringUriOnly()}] added to speakers"
)
val participants = arrayListOf<ConferenceSchedulingParticipantData>()
participants.addAll(participantsData.value.orEmpty())
participants.remove(data)
participantsData.value = participants
val speakers = arrayListOf<ConferenceSchedulingParticipantData>()
speakers.addAll(speakersData.value.orEmpty())
speakers.add(data)
speakersData.value = speakers
},
onRemovedFromSpeakers = { data ->
Log.i(
"[Conference Creation] Participant [${address.asStringUriOnly()}] removed from speakers"
)
val speakers = arrayListOf<ConferenceSchedulingParticipantData>()
speakers.addAll(speakersData.value.orEmpty())
speakers.remove(data)
speakersData.value = speakers
val participants = arrayListOf<ConferenceSchedulingParticipantData>()
participants.addAll(participantsData.value.orEmpty())
participants.add(data)
participantsData.value = participants
}
)
if (isSpeaker) {
speakersList.add(data)
} else {
participantsList.add(data)
}
} }
participantsData.value = list participantsData.value = participantsList
speakersData.value = speakersList
} }
fun createConference() { fun createConference() {
@ -260,8 +337,6 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
conferenceCreationInProgress.value = true conferenceCreationInProgress.value = true
val core = coreContext.core val core = coreContext.core
val participants = arrayOfNulls<Address>(selectedAddresses.value.orEmpty().size)
selectedAddresses.value?.toArray(participants)
val localAccount = core.defaultAccount val localAccount = core.defaultAccount
val localAddress = localAccount?.params?.identityAddress val localAddress = localAccount?.params?.identityAddress
@ -273,7 +348,25 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
conferenceInfo.organizer = localAddress conferenceInfo.organizer = localAddress
conferenceInfo.subject = subject.value conferenceInfo.subject = subject.value
conferenceInfo.description = description.value conferenceInfo.description = description.value
conferenceInfo.setParticipants(participants)
val participants = arrayOfNulls<ParticipantInfo>(selectedAddresses.value.orEmpty().size)
var index = 0
val isBroadcast = isModeBroadcastCurrentlySelected()
for (participant in participantsData.value.orEmpty()) {
val info = Factory.instance().createParticipantInfo(participant.sipAddress)
// For meetings, all participants must have Speaker role
info?.role = if (isBroadcast) Participant.Role.Listener else Participant.Role.Speaker
participants[index] = info
index += 1
}
for (speaker in speakersData.value.orEmpty()) {
val info = Factory.instance().createParticipantInfo(speaker.sipAddress)
info?.role = Participant.Role.Speaker
participants[index] = info
index += 1
}
conferenceInfo.setParticipantInfos(participants)
if (scheduleForLater.value == true) { if (scheduleForLater.value == true) {
val startTime = getConferenceStartTimestamp() val startTime = getConferenceStartTimestamp()
conferenceInfo.dateTime = startTime conferenceInfo.dateTime = startTime
@ -287,6 +380,10 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
conferenceScheduler.info = conferenceInfo conferenceScheduler.info = conferenceInfo
} }
fun isModeBroadcastCurrentlySelected(): Boolean {
return mode.value == AppUtils.getString(R.string.conference_schedule_mode_broadcast)
}
private fun computeTimeZonesList(): List<TimeZoneData> { private fun computeTimeZonesList(): List<TimeZoneData> {
return TimeZone.getAvailableIDs().map { id -> TimeZoneData(TimeZone.getTimeZone(id)) }.toList().sorted() return TimeZone.getAvailableIDs().map { id -> TimeZoneData(TimeZone.getTimeZone(id)) }.toList().sorted()
} }

View file

@ -61,6 +61,8 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
val networkReachable = MutableLiveData<Boolean>() val networkReachable = MutableLiveData<Boolean>()
val isConferenceBroadcastWithListenerRole = MutableLiveData<Boolean>()
val askPermissionEvent: MutableLiveData<Event<String>> by lazy { val askPermissionEvent: MutableLiveData<Event<String>> by lazy {
MutableLiveData<Event<String>>() MutableLiveData<Event<String>>()
} }
@ -198,7 +200,10 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
} }
layoutMenuSelected.value = false layoutMenuSelected.value = false
updateLayout() when (core.defaultConferenceLayout) {
Conference.Layout.Grid -> setMosaicLayout()
else -> setActiveSpeakerLayout()
}
if (AudioRouteUtils.isBluetoothAudioRouteAvailable()) { if (AudioRouteUtils.isBluetoothAudioRouteAvailable()) {
setBluetoothAudioRoute() setBluetoothAudioRoute()
@ -216,6 +221,44 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
super.onCleared() super.onCleared()
} }
fun findConferenceInfoByAddress(stringAddress: String?) {
if (stringAddress != null) {
val address = Factory.instance().createAddress(stringAddress)
if (address != null) {
val conferenceInfo = coreContext.core.findConferenceInformationFromUri(address)
if (conferenceInfo != null) {
val myself = conferenceInfo.participantInfos.find {
it.address.asStringUriOnly() == coreContext.core.defaultAccount?.params?.identityAddress?.asStringUriOnly()
}
if (myself != null) {
Log.i(
"[Conference Waiting Room] Found our participant, it's role is [${myself.role}]"
)
val areWeListener = myself.role == Participant.Role.Listener
isConferenceBroadcastWithListenerRole.value = areWeListener
if (areWeListener) {
callParams.isVideoEnabled = false
callParams.videoDirection = MediaDirection.Inactive
updateVideoState()
updateLayout()
}
} else {
Log.e(
"[Conference Waiting Room] Failed to find ourselves in participants info"
)
}
} else {
Log.e(
"[Conference Waiting Room] Failed to find conference info using address [$stringAddress]"
)
}
}
} else {
Log.e("[Conference Waiting Room] Can't find conference info using null address!")
}
}
fun cancel() { fun cancel() {
cancelConferenceJoiningEvent.value = Event(true) cancelConferenceJoiningEvent.value = Event(true)
} }

View file

@ -35,8 +35,16 @@ class ConferencesSettingsViewModel : GenericSettingsViewModel() {
val layoutLabels = MutableLiveData<ArrayList<String>>() val layoutLabels = MutableLiveData<ArrayList<String>>()
private val layoutValues = arrayListOf<Int>() private val layoutValues = arrayListOf<Int>()
val enableBroadcastListener = object : SettingListenerStub() {
override fun onBoolValueChanged(newValue: Boolean) {
prefs.disableBroadcastConference = !newValue
}
}
val enableBroadcast = MutableLiveData<Boolean>()
init { init {
initLayoutsList() initLayoutsList()
enableBroadcast.value = !prefs.disableBroadcastConference
} }
private fun initLayoutsList() { private fun initLayoutsList() {

View file

@ -34,10 +34,13 @@ class ConferenceParticipantData(
val isAdmin = MutableLiveData<Boolean>() val isAdmin = MutableLiveData<Boolean>()
val isMeAdmin = MutableLiveData<Boolean>() val isMeAdmin = MutableLiveData<Boolean>()
val isSpeaker = MutableLiveData<Boolean>()
init { init {
isAdmin.value = participant.isAdmin isAdmin.value = participant.isAdmin
isMeAdmin.value = conference.me.isAdmin isMeAdmin.value = conference.me.isAdmin
isSpeaker.value = participant.role == Participant.Role.Speaker
Log.i( Log.i(
"[Conference Participant] Participant ${participant.address.asStringUriOnly()} is ${if (participant.isAdmin) "admin" else "not admin"}" "[Conference Participant] Participant ${participant.address.asStringUriOnly()} is ${if (participant.isAdmin) "admin" else "not admin"}"
) )

View file

@ -56,9 +56,13 @@ class ConferenceViewModel : ViewModel() {
val twoOrMoreParticipants = MutableLiveData<Boolean>() val twoOrMoreParticipants = MutableLiveData<Boolean>()
val moreThanTwoParticipants = MutableLiveData<Boolean>() val moreThanTwoParticipants = MutableLiveData<Boolean>()
val speakingParticipantFound = MutableLiveData<Boolean>()
val speakingParticipant = MutableLiveData<ConferenceParticipantDeviceData>() val speakingParticipant = MutableLiveData<ConferenceParticipantDeviceData>()
val meParticipant = MutableLiveData<ConferenceParticipantDeviceData>() val meParticipant = MutableLiveData<ConferenceParticipantDeviceData>()
val isBroadcast = MutableLiveData<Boolean>()
val isMeListenerOnly = MutableLiveData<Boolean>()
val participantAdminStatusChangedEvent: MutableLiveData<Event<ConferenceParticipantData>> by lazy { val participantAdminStatusChangedEvent: MutableLiveData<Event<ConferenceParticipantData>> by lazy {
MutableLiveData<Event<ConferenceParticipantData>>() MutableLiveData<Event<ConferenceParticipantData>>()
} }
@ -207,6 +211,7 @@ class ConferenceViewModel : ViewModel() {
speakingParticipant.value?.isActiveSpeaker?.value = false speakingParticipant.value?.isActiveSpeaker?.value = false
device.isActiveSpeaker.value = true device.isActiveSpeaker.value = true
speakingParticipant.value = device!! speakingParticipant.value = device!!
speakingParticipantFound.value = true
} else if (device == null) { } else if (device == null) {
Log.w( Log.w(
"[Conference] Participant device [${participantDevice.address.asStringUriOnly()}] is the active speaker but couldn't find it in devices list" "[Conference] Participant device [${participantDevice.address.asStringUriOnly()}] is the active speaker but couldn't find it in devices list"
@ -528,6 +533,23 @@ class ConferenceViewModel : ViewModel() {
val activelySpeakingParticipantDevice = conference.activeSpeakerParticipantDevice val activelySpeakingParticipantDevice = conference.activeSpeakerParticipantDevice
var foundActivelySpeakingParticipantDevice = false var foundActivelySpeakingParticipantDevice = false
speakingParticipantFound.value = false
val conferenceInfo = conference.core.findConferenceInformationFromUri(
conference.conferenceAddress
)
var allSpeaker = true
for (info in conferenceInfo?.participantInfos.orEmpty()) {
if (info.role == Participant.Role.Listener) {
allSpeaker = false
}
}
isBroadcast.value = !allSpeaker
if (!allSpeaker) {
Log.i(
"[Conference] Not all participants are speaker, considering it is a broadcast"
)
}
for (participant in participantsList) { for (participant in participantsList) {
val participantDevices = participant.devices val participantDevices = participant.devices
@ -539,6 +561,18 @@ class ConferenceViewModel : ViewModel() {
Log.i( Log.i(
"[Conference] Participant device found: ${device.name} (${device.address.asStringUriOnly()})" "[Conference] Participant device found: ${device.name} (${device.address.asStringUriOnly()})"
) )
val info = conferenceInfo?.participantInfos?.find {
it.address.weakEqual(participant.address)
}
if (info != null) {
Log.i("[Conference] Participant role is [${info.role.name}]")
val listener = info.role == Participant.Role.Listener || info.role == Participant.Role.Unknown
if (listener) {
continue
}
}
val deviceData = ConferenceParticipantDeviceData(device, false) val deviceData = ConferenceParticipantDeviceData(device, false)
devices.add(deviceData) devices.add(deviceData)
@ -549,6 +583,7 @@ class ConferenceViewModel : ViewModel() {
speakingParticipant.value = deviceData speakingParticipant.value = deviceData
deviceData.isActiveSpeaker.value = true deviceData.isActiveSpeaker.value = true
foundActivelySpeakingParticipantDevice = true foundActivelySpeakingParticipantDevice = true
speakingParticipantFound.value = true
} }
} }
} }
@ -560,12 +595,26 @@ class ConferenceViewModel : ViewModel() {
val deviceData = devices.first() val deviceData = devices.first()
speakingParticipant.value = deviceData speakingParticipant.value = deviceData
deviceData.isActiveSpeaker.value = true deviceData.isActiveSpeaker.value = true
speakingParticipantFound.value = false
} }
for (device in conference.me.devices) { for (device in conference.me.devices) {
Log.i( Log.i(
"[Conference] Participant device for myself found: ${device.name} (${device.address.asStringUriOnly()})" "[Conference] Participant device for myself found: ${device.name} (${device.address.asStringUriOnly()})"
) )
val info = conferenceInfo?.participantInfos?.find {
it.address.weakEqual(device.address)
}
if (info != null) {
Log.i("[Conference] Me role is [${info.role.name}]")
val listener = info.role == Participant.Role.Listener || info.role == Participant.Role.Unknown
isMeListenerOnly.value = listener
if (listener) {
continue
}
}
val deviceData = ConferenceParticipantDeviceData(device, true) val deviceData = ConferenceParticipantDeviceData(device, true)
devices.add(deviceData) devices.add(deviceData)
meParticipant.value = deviceData meParticipant.value = deviceData
@ -601,6 +650,7 @@ class ConferenceViewModel : ViewModel() {
if (speakingParticipant.value == null) { if (speakingParticipant.value == null) {
speakingParticipant.value = deviceData speakingParticipant.value = deviceData
deviceData.isActiveSpeaker.value = true deviceData.isActiveSpeaker.value = true
speakingParticipantFound.value = false
} }
conferenceParticipantDevices.value = sortedDevices conferenceParticipantDevices.value = sortedDevices
@ -641,6 +691,7 @@ class ConferenceViewModel : ViewModel() {
val deviceData = devices[1] val deviceData = devices[1]
speakingParticipant.value = deviceData speakingParticipant.value = deviceData
deviceData.isActiveSpeaker.value = true deviceData.isActiveSpeaker.value = true
speakingParticipantFound.value = false
} }
conferenceParticipantDevices.value = devices conferenceParticipantDevices.value = devices

View file

@ -377,6 +377,12 @@ class CorePreferences constructor(private val context: Context) {
config.setBool("app", "enter_video_conference_enable_full_screen_mode", value) config.setBool("app", "enter_video_conference_enable_full_screen_mode", value)
} }
var disableBroadcastConference: Boolean
get() = config.getBool("app", "disable_broadcast_conference_feature", true)
set(value) {
config.setBool("app", "disable_broadcast_conference_feature", value)
}
/* Assistant */ /* Assistant */
var firstStart: Boolean var firstStart: Boolean

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:drawable="@drawable/shape_rect_orange_disabled_button" />
<item android:state_pressed="true"
android:drawable="@drawable/shape_rect_green_button" />
<item
android:drawable="@drawable/shape_rect_orange_button" />
</selector>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="5dp" />
<size android:height="20dp" android:width="20dp"/>
<solid android:color="@color/primary_color_disabled"/>
</shape>

View file

@ -166,6 +166,7 @@
android:id="@+id/active_speaker_background" android:id="@+id/active_speaker_background"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:visibility="@{conferenceViewModel.speakingParticipantFound ? View.VISIBLE : View.GONE}"
app:layout_constraintTop_toBottomOf="@id/top_barrier" app:layout_constraintTop_toBottomOf="@id/top_barrier"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/miniatures" app:layout_constraintEnd_toStartOf="@id/miniatures"
@ -179,6 +180,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:contentDescription="@null" android:contentDescription="@null"
android:visibility="@{conferenceViewModel.speakingParticipantFound ? View.VISIBLE : View.GONE}"
coilVoipContact="@{conferenceViewModel.speakingParticipant}" coilVoipContact="@{conferenceViewModel.speakingParticipant}"
android:background="@drawable/generated_avatar_bg" android:background="@drawable/generated_avatar_bg"
app:layout_constraintBottom_toBottomOf="@id/active_speaker_background" app:layout_constraintBottom_toBottomOf="@id/active_speaker_background"
@ -196,7 +198,7 @@
android:background="@drawable/shape_button_background" android:background="@drawable/shape_button_background"
android:contentDescription="@string/content_description_participant_is_paused" android:contentDescription="@string/content_description_participant_is_paused"
android:src="@drawable/icon_pause" android:src="@drawable/icon_pause"
android:visibility="@{conferenceViewModel.speakingParticipant.isInConference ? View.GONE : View.VISIBLE, default=gone}" android:visibility="@{conferenceViewModel.speakingParticipantFound &amp;&amp; conferenceViewModel.speakingParticipant.isInConference ? View.GONE : View.VISIBLE, default=gone}"
app:layout_constraintDimensionRatio="1:1" app:layout_constraintDimensionRatio="1:1"
app:layout_constraintBottom_toBottomOf="@id/active_speaker_background" app:layout_constraintBottom_toBottomOf="@id/active_speaker_background"
app:layout_constraintEnd_toEndOf="@id/active_speaker_background" app:layout_constraintEnd_toEndOf="@id/active_speaker_background"
@ -253,6 +255,7 @@
android:id="@+id/local_participant_background" android:id="@+id/local_participant_background"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:visibility="@{conferenceViewModel.isMeListenerOnly ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toBottomOf="@id/top_barrier" app:layout_constraintTop_toBottomOf="@id/top_barrier"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -267,6 +270,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_margin="10dp" android:layout_margin="10dp"
android:contentDescription="@null" android:contentDescription="@null"
android:visibility="@{conferenceViewModel.isMeListenerOnly ? View.GONE : View.VISIBLE}"
coilSelfAvatar="@{conferenceViewModel.meParticipant}" coilSelfAvatar="@{conferenceViewModel.meParticipant}"
android:background="@drawable/generated_avatar_bg" android:background="@drawable/generated_avatar_bg"
app:layout_constraintDimensionRatio="1:1" app:layout_constraintDimensionRatio="1:1"
@ -297,6 +301,7 @@
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:visibility="@{conferenceViewModel.isMeListenerOnly ? View.GONE : View.VISIBLE}"
android:text="@{conferenceViewModel.meParticipant.contact.name ?? conferenceViewModel.meParticipant.displayName}" android:text="@{conferenceViewModel.meParticipant.contact.name ?? conferenceViewModel.meParticipant.displayName}"
app:layout_constraintBottom_toBottomOf="@id/local_participant_background" app:layout_constraintBottom_toBottomOf="@id/local_participant_background"
app:layout_constraintStart_toStartOf="@id/local_participant_background" app:layout_constraintStart_toStartOf="@id/local_participant_background"

View file

@ -30,7 +30,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/conference_invite_title_font" style="@style/conference_invite_title_font"
android:text="@string/conference_invite_title" android:text="@{data.isBroadcast ? @string/conference_broadcast_invite_title : @string/conference_invite_title, default=@string/conference_invite_title}"
android:visibility="@{data.isConferenceUpdated || data.isConferenceCancelled ? View.GONE : View.VISIBLE}"/> android:visibility="@{data.isConferenceUpdated || data.isConferenceCancelled ? View.GONE : View.VISIBLE}"/>
<TextView <TextView
@ -67,6 +67,15 @@
android:text="@{data.conferenceParticipantCount, default=`3 participants`}" android:text="@{data.conferenceParticipantCount, default=`3 participants`}"
app:drawableStartCompat="@drawable/icon_schedule_participants"/> app:drawableStartCompat="@drawable/icon_schedule_participants"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="23dp"
android:layout_marginBottom="2dp"
android:visibility="@{data.isBroadcast &amp;&amp; data.isSpeaker ? View.VISIBLE : View.GONE}"
style="@style/conference_invite_dat_time_font"
android:text="@string/conference_broadcast_invite_you_are_speaker"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -109,7 +109,7 @@
</RelativeLayout> </RelativeLayout>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
@ -118,15 +118,32 @@
android:visibility="@{data.isConferenceCancelled ? View.VISIBLE : View.GONE, default=gone}" android:visibility="@{data.isConferenceCancelled ? View.VISIBLE : View.GONE, default=gone}"
android:text="@{data.canEdit ? @string/conference_scheduled_cancelled_by_me : @string/conference_scheduled_cancelled_by_organizer, default=@string/conference_scheduled_cancelled_by_organizer}"/> android:text="@{data.canEdit ? @string/conference_scheduled_cancelled_by_me : @string/conference_scheduled_cancelled_by_organizer, default=@string/conference_scheduled_cancelled_by_organizer}"/>
<TextView <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="5dp" android:orientation="horizontal">
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp" <TextView
style="@style/conference_invite_subject_font" android:id="@+id/meeting_type"
android:maxLines="2" android:layout_width="wrap_content"
android:text="@{data.subject, default=`Lorem Ipsum`}"/> android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
style="@style/conference_invite_subject_font"
android:text="@{data.isBroadcast() ? @string/conference_broadcast_title : @string/conference_meeting_title, default=@string/conference_meeting_title}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
style="@style/conference_invite_subject_font"
android:maxLines="2"
android:text="@{data.subject, default=`Lorem Ipsum`}"/>
</LinearLayout>
<LinearLayout <LinearLayout
android:visibility="@{data.expanded ? View.GONE : View.VISIBLE}" android:visibility="@{data.expanded ? View.GONE : View.VISIBLE}"
@ -182,24 +199,61 @@
android:contentDescription="@string/content_description_toggle_conference_info_details" android:contentDescription="@string/content_description_toggle_conference_info_details"
android:src="@drawable/button_conference_info" /> android:src="@drawable/button_conference_info" />
<ImageView <TextView
android:id="@+id/participants_icon" style="@style/conference_invite_desc_font"
android:id="@+id/speakers_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:contentDescription="@string/content_description_conference_participants" android:text="@string/conference_invite_speakers_list_title"
android:textSize="16sp"
android:textColor="@color/voip_dark_gray"
android:drawableStart="@drawable/icon_schedule_participants"
android:drawablePadding="5dp"
android:visibility="@{data.isConferenceCancelled || !data.isBroadcast ? View.GONE : View.VISIBLE}"
android:src="@drawable/icon_schedule_participants"/>
<TextView
android:id="@+id/speakers_list"
style="@style/conference_invite_desc_font"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="23dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:layout_toStartOf="@id/selected_info"
android:layout_below="@id/speakers_title"
android:background="@color/transparent_color"
android:drawablePadding="5dp"
android:visibility="@{data.isConferenceCancelled || !data.isBroadcast ? View.GONE : View.VISIBLE}"
android:text="@{data.speakersExpanded, default=`Daryl Martin\nMartin Pecheur`}"
tools:ignore="LabelFor" />
<TextView
style="@style/conference_invite_desc_font"
android:id="@+id/participants_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/speakers_list"
android:layout_alignParentStart="true"
android:text="@string/conference_invite_participants_list_title"
android:textSize="16sp"
android:textColor="@color/voip_dark_gray"
android:drawableStart="@drawable/icon_schedule_participants"
android:drawablePadding="5dp"
android:visibility="@{data.isConferenceCancelled ? View.GONE : View.VISIBLE}" android:visibility="@{data.isConferenceCancelled ? View.GONE : View.VISIBLE}"
android:src="@drawable/icon_schedule_participants"/> android:src="@drawable/icon_schedule_participants"/>
<TextView <TextView
android:id="@+id/participants_list"
style="@style/conference_invite_desc_font" style="@style/conference_invite_desc_font"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp" android:layout_marginStart="23dp"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:layout_toStartOf="@id/selected_info" android:layout_toStartOf="@id/selected_info"
android:layout_toEndOf="@id/participants_icon" android:layout_below="@id/participants_title"
android:background="@color/transparent_color" android:background="@color/transparent_color"
android:drawablePadding="5dp" android:drawablePadding="5dp"
android:visibility="@{data.isConferenceCancelled ? View.GONE : View.VISIBLE}" android:visibility="@{data.isConferenceCancelled ? View.GONE : View.VISIBLE}"
@ -241,7 +295,7 @@
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:visibility="@{data.isConferenceCancelled ? View.GONE : View.VISIBLE}" android:visibility="@{data.isConferenceCancelled ? View.GONE : View.VISIBLE}"
android:text="@string/conference_schedule_address_title"/> android:text="@{data.isBroadcast ? @string/conference_schedule_broadcast_address_title : @string/conference_schedule_address_title, default=@string/conference_schedule_address_title}"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -137,6 +137,31 @@
android:layout_margin="10dp" android:layout_margin="10dp"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="vertical"
android:visibility="@{viewModel.isBroadcastAllowed ? View.VISIBLE : View.GONE, default=gone}">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/conference_scheduling_font"
android:text="@string/conference_schedule_mode"/>
<Spinner
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_marginTop="5dp"
style="@style/conference_scheduling_font"
android:background="@drawable/icon_spinner_background"
app:entries="@{viewModel.modesList}"
app:selectedValue="@={viewModel.mode}"/>
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -11,7 +11,8 @@
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/conference_scheduling_participant_cell_height" android:layout_height="@dimen/conference_scheduling_participant_cell_height"
android:gravity="center_vertical"> android:gravity="center_vertical"
android:background="@color/white_color">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -76,6 +77,17 @@
</LinearLayout> </LinearLayout>
<ImageView
android:onClick="@{() -> data.changeIsSpeaker()}"
android:visibility="@{data.showBroadcastControls ? View.VISIBLE : View.GONE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:src="@{data.isSpeaker ? @drawable/field_remove : @drawable/field_add, default=@drawable/field_add}"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="5dp" />
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"

View file

@ -43,7 +43,7 @@
android:gravity="center" android:gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
style="@style/calls_list_header_font" style="@style/calls_list_header_font"
android:text="@{viewModel.scheduleForLater ? @string/conference_schedule_summary : @string/conference_group_call_summary, default=@string/conference_group_call_summary}"/> android:text="@{viewModel.isModeBroadcastCurrentlySelected() ? @string/conference_schedule_broadcast_summary : viewModel.scheduleForLater ? @string/conference_schedule_summary : @string/conference_group_call_summary, default=@string/conference_group_call_summary}"/>
<View <View
android:layout_width="0dp" android:layout_width="0dp"
@ -268,14 +268,49 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginBottom="20dp"
style="@style/conference_scheduling_font" style="@style/conference_scheduling_font"
android:visibility="@{viewModel.scheduleForLater &amp;&amp; viewModel.sendInviteViaChat ? View.VISIBLE : View.GONE}" android:visibility="@{viewModel.scheduleForLater &amp;&amp; viewModel.sendInviteViaChat ? View.VISIBLE : View.GONE}"
android:text="@string/conference_schedule_send_invite_chat_summary"/> android:text="@string/conference_schedule_send_invite_chat_summary"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{viewModel.isModeBroadcastCurrentlySelected() ? View.VISIBLE : View.GONE}"
android:background="?attr/voipFormDisabledFieldBackgroundColor">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:layout_marginLeft="20dp"
style="@style/calls_list_header_font"
android:text="@string/conference_schedule_speakers_list"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="@{viewModel.isModeBroadcastCurrentlySelected() ? View.VISIBLE : View.GONE}"
app:entries="@{viewModel.speakersData}"
app:layout="@{@layout/conference_scheduling_participant_cell}" />
<TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_marginLeft="10dp"
style="@style/calls_list_header_font"
android:textColor="@color/light_grey_color"
android:visibility="@{viewModel.isModeBroadcastCurrentlySelected() &amp;&amp; viewModel.speakersData.size() == 0 ? View.VISIBLE : View.GONE}"
android:text="@string/conference_schedule_speakers_list_empty"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/voipFormDisabledFieldBackgroundColor"> android:background="?attr/voipFormDisabledFieldBackgroundColor">
<TextView <TextView
@ -288,6 +323,16 @@
</LinearLayout> </LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="10dp"
style="@style/calls_list_header_font"
android:textColor="@color/light_grey_color"
android:visibility="@{viewModel.participantsData.size() == 0 ? View.VISIBLE : View.GONE, default=gone}"
android:text="@string/conference_schedule_participants_list_empty"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -310,10 +355,28 @@
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:layout_gravity="center" android:layout_gravity="center"
android:background="@drawable/shape_rect_orange_button" android:background="@drawable/rect_orange_button"
android:visibility="@{viewModel.isModeBroadcastCurrentlySelected() ? View.GONE : View.VISIBLE}"
android:text="@{viewModel.isUpdate ? @string/conference_schedule_edit : viewModel.scheduleForLater ? @string/conference_schedule_start : @string/conference_group_call_create, default=@string/conference_group_call_create}" android:text="@{viewModel.isUpdate ? @string/conference_schedule_edit : viewModel.scheduleForLater ? @string/conference_schedule_start : @string/conference_group_call_create, default=@string/conference_group_call_create}"
style="@style/big_orange_button_font"/> style="@style/big_orange_button_font"/>
<TextView
android:onClick="@{() -> viewModel.createConference()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="20dp"
android:paddingTop="8dp"
android:paddingRight="20dp"
android:paddingBottom="8dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_gravity="center"
android:background="@drawable/rect_orange_button"
android:enabled="@{viewModel.participantsData.size() != 0 &amp;&amp; viewModel.speakersData.size() != 0}"
android:visibility="@{viewModel.isModeBroadcastCurrentlySelected() ? View.VISIBLE : View.GONE, default=gone}"
android:text="@{viewModel.isUpdate ? @string/conference_broadcast_edit : @string/conference_broadcast_start, default=@string/conference_broadcast_start}"
style="@style/big_orange_button_font"/>
</LinearLayout> </LinearLayout>
<include <include

View file

@ -34,7 +34,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:text="@string/conference_waiting_room_video_disabled" android:text="@{viewModel.isConferenceBroadcastWithListenerRole ? @string/conference_waiting_room_listener_broadcast : @string/conference_waiting_room_video_disabled, default=@string/conference_waiting_room_video_disabled}"
android:visibility="@{viewModel.isVideoEnabled ? View.GONE : View.VISIBLE}" /> android:visibility="@{viewModel.isVideoEnabled ? View.GONE : View.VISIBLE}" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -234,7 +234,7 @@
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:background="@drawable/button_background_reverse" android:background="@drawable/button_background_reverse"
android:contentDescription="@{viewModel.isMicrophoneMuted ? @string/content_description_disable_mic_mute : @string/content_description_enable_mic_mute}" android:contentDescription="@{viewModel.isMicrophoneMuted ? @string/content_description_disable_mic_mute : @string/content_description_enable_mic_mute}"
android:enabled="@{!viewModel.joinInProgress}" android:enabled="@{!viewModel.isConferenceBroadcastWithListenerRole &amp;&amp; !viewModel.joinInProgress}"
android:onClick="@{() -> viewModel.toggleMuteMicrophone()}" android:onClick="@{() -> viewModel.toggleMuteMicrophone()}"
android:padding="5dp" android:padding="5dp"
android:selected="@{viewModel.isMicrophoneMuted}" android:selected="@{viewModel.isMicrophoneMuted}"
@ -284,7 +284,7 @@
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
android:background="@drawable/button_background_reverse" android:background="@drawable/button_background_reverse"
android:contentDescription="@{viewModel.isVideoEnabled ? @string/content_description_disable_video : @string/content_description_enable_video}" android:contentDescription="@{viewModel.isVideoEnabled ? @string/content_description_disable_video : @string/content_description_enable_video}"
android:enabled="@{!viewModel.joinInProgress &amp;&amp; viewModel.isVideoAvailable &amp;&amp; !viewModel.isLowBandwidth}" android:enabled="@{!viewModel.isConferenceBroadcastWithListenerRole &amp;&amp; !viewModel.joinInProgress &amp;&amp; viewModel.isVideoAvailable &amp;&amp; !viewModel.isLowBandwidth}"
android:onClick="@{() -> viewModel.toggleVideo()}" android:onClick="@{() -> viewModel.toggleVideo()}"
android:padding="5dp" android:padding="5dp"
android:selected="@{viewModel.isVideoEnabled}" android:selected="@{viewModel.isVideoEnabled}"

View file

@ -240,7 +240,6 @@
linphone:title="@{@string/account_setting_conference_factory_address}" linphone:title="@{@string/account_setting_conference_factory_address}"
linphone:listener="@{viewModel.conferenceFactoryUriListener}" linphone:listener="@{viewModel.conferenceFactoryUriListener}"
linphone:defaultValue="@{viewModel.conferenceFactoryUri}" linphone:defaultValue="@{viewModel.conferenceFactoryUri}"
android:visibility="gone"
linphone:inputType="@{InputType.TYPE_CLASS_TEXT}"/> linphone:inputType="@{InputType.TYPE_CLASS_TEXT}"/>
<include <include
@ -248,7 +247,6 @@
linphone:title="@{@string/account_setting_audio_video_conference_factory_address}" linphone:title="@{@string/account_setting_audio_video_conference_factory_address}"
linphone:listener="@{viewModel.audioVideoConferenceFactoryUriListener}" linphone:listener="@{viewModel.audioVideoConferenceFactoryUriListener}"
linphone:defaultValue="@{viewModel.audioVideoConferenceFactoryUri}" linphone:defaultValue="@{viewModel.audioVideoConferenceFactoryUri}"
android:visibility="gone"
linphone:inputType="@{InputType.TYPE_CLASS_TEXT}"/> linphone:inputType="@{InputType.TYPE_CLASS_TEXT}"/>
<include <include
@ -256,7 +254,6 @@
linphone:title="@{@string/account_setting_end_to_end_encryption_keys_server_url}" linphone:title="@{@string/account_setting_end_to_end_encryption_keys_server_url}"
linphone:listener="@{viewModel.limeServerUrlListener}" linphone:listener="@{viewModel.limeServerUrlListener}"
linphone:defaultValue="@{viewModel.limeServerUrl}" linphone:defaultValue="@{viewModel.limeServerUrl}"
android:visibility="gone"
linphone:inputType="@{InputType.TYPE_CLASS_TEXT}"/> linphone:inputType="@{InputType.TYPE_CLASS_TEXT}"/>
<include <include

View file

@ -72,6 +72,13 @@
linphone:selectedIndex="@{viewModel.layoutIndex}" linphone:selectedIndex="@{viewModel.layoutIndex}"
linphone:labels="@{viewModel.layoutLabels}"/> linphone:labels="@{viewModel.layoutLabels}"/>
<include
layout="@layout/settings_widget_switch"
linphone:title="@{@string/conferences_settings_enable_broadcast_title}"
linphone:subtitle="@{@string/conferences_settings_enable_broadcast_summary}"
linphone:listener="@{viewModel.enableBroadcastListener}"
linphone:checked="@={viewModel.enableBroadcast}"/>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View file

@ -46,10 +46,10 @@
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:background="@drawable/button_background_reverse" android:background="@drawable/button_background_reverse"
android:contentDescription="@{callsViewModel.isMicrophoneMuted ? @string/content_description_disable_mic_mute : @string/content_description_enable_mic_mute}" android:contentDescription="@{callsViewModel.isMicrophoneMuted ? @string/content_description_disable_mic_mute : @string/content_description_enable_mic_mute}"
android:enabled="@{callsViewModel.isMuteMicrophoneEnabled}" android:enabled="@{callsViewModel.isMuteMicrophoneEnabled &amp;&amp; !conferenceViewModel.isMeListenerOnly}"
android:onClick="@{() -> callsViewModel.toggleMuteMicrophone()}" android:onClick="@{() -> callsViewModel.toggleMuteMicrophone()}"
android:padding="5dp" android:padding="5dp"
android:selected="@{callsViewModel.isMicrophoneMuted}" android:selected="@{callsViewModel.isMicrophoneMuted || conferenceViewModel.isMeListenerOnly}"
android:src="@drawable/icon_toggle_mic" android:src="@drawable/icon_toggle_mic"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="W,1:1" app:layout_constraintDimensionRatio="W,1:1"
@ -96,10 +96,10 @@
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
android:background="@drawable/button_background_reverse" android:background="@drawable/button_background_reverse"
android:contentDescription="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.isSendingVideo ? @string/content_description_disable_video : @string/content_description_enable_video}" android:contentDescription="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.isSendingVideo ? @string/content_description_disable_video : @string/content_description_enable_video}"
android:enabled="@{controlsViewModel.isVideoAvailable &amp;&amp; !controlsViewModel.isVideoUpdateInProgress}" android:enabled="@{controlsViewModel.isVideoAvailable &amp;&amp; !controlsViewModel.isVideoUpdateInProgress &amp;&amp; !conferenceViewModel.isMeListenerOnly}"
android:onClick="@{() -> (!conferenceViewModel.conferenceExists || conferenceViewModel.conferenceDisplayMode != ConferenceDisplayMode.AUDIO_ONLY) ? controlsViewModel.toggleVideo() : conferenceViewModel.switchLayoutFromAudioOnlyToActiveSpeaker()}" android:onClick="@{() -> (!conferenceViewModel.conferenceExists || conferenceViewModel.conferenceDisplayMode != ConferenceDisplayMode.AUDIO_ONLY) ? controlsViewModel.toggleVideo() : conferenceViewModel.switchLayoutFromAudioOnlyToActiveSpeaker()}"
android:padding="5dp" android:padding="5dp"
android:selected="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.isSendingVideo}" android:selected="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.isSendingVideo &amp;&amp; !conferenceViewModel.isMeListenerOnly}"
android:visibility="@{controlsViewModel.hideVideo ? View.GONE : View.VISIBLE}" android:visibility="@{controlsViewModel.hideVideo ? View.GONE : View.VISIBLE}"
android:src="@drawable/icon_toggle_camera" android:src="@drawable/icon_toggle_camera"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View file

@ -39,6 +39,7 @@
android:gravity="center" android:gravity="center"
android:onClick="@{() -> controlsViewModel.showNumpad()}" android:onClick="@{() -> controlsViewModel.showNumpad()}"
android:text="@string/call_action_numpad" android:text="@string/call_action_numpad"
android:enabled="@{!conferenceViewModel.isBroadcast}"
app:drawableTopCompat="@drawable/icon_call_numpad" app:drawableTopCompat="@drawable/icon_call_numpad"
app:layout_constraintBottom_toBottomOf="@id/chat" app:layout_constraintBottom_toBottomOf="@id/chat"
app:layout_constraintEnd_toStartOf="@id/call_stats" app:layout_constraintEnd_toStartOf="@id/call_stats"
@ -175,6 +176,7 @@
android:gravity="center" android:gravity="center"
android:onClick="@{() -> controlsViewModel.goToCallsList()}" android:onClick="@{() -> controlsViewModel.goToCallsList()}"
android:text="@string/call_action_calls_list" android:text="@string/call_action_calls_list"
android:enabled="@{!conferenceViewModel.isBroadcast}"
app:drawableTopCompat="@drawable/icon_calls_list" app:drawableTopCompat="@drawable/icon_calls_list"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -125,6 +125,7 @@
android:id="@+id/active_speaker_background" android:id="@+id/active_speaker_background"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:visibility="@{conferenceViewModel.speakingParticipantFound ? View.VISIBLE : View.GONE}"
app:layout_constraintTop_toBottomOf="@id/top_barrier" app:layout_constraintTop_toBottomOf="@id/top_barrier"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -139,6 +140,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_margin="10dp" android:layout_margin="10dp"
android:contentDescription="@null" android:contentDescription="@null"
android:visibility="@{conferenceViewModel.speakingParticipantFound ? View.VISIBLE : View.GONE}"
coilVoipContact="@{conferenceViewModel.speakingParticipant}" coilVoipContact="@{conferenceViewModel.speakingParticipant}"
android:background="@drawable/generated_avatar_bg" android:background="@drawable/generated_avatar_bg"
app:layout_constraintBottom_toBottomOf="@id/active_speaker_background" app:layout_constraintBottom_toBottomOf="@id/active_speaker_background"
@ -157,7 +159,7 @@
android:background="@drawable/shape_button_background" android:background="@drawable/shape_button_background"
android:contentDescription="@string/content_description_participant_is_paused" android:contentDescription="@string/content_description_participant_is_paused"
android:src="@drawable/icon_pause" android:src="@drawable/icon_pause"
android:visibility="@{conferenceViewModel.speakingParticipant.isInConference ? View.GONE : View.VISIBLE, default=gone}" android:visibility="@{conferenceViewModel.speakingParticipantFound &amp;&amp; conferenceViewModel.speakingParticipant.isInConference ? View.GONE : View.VISIBLE, default=gone}"
app:layout_constraintDimensionRatio="1:1" app:layout_constraintDimensionRatio="1:1"
app:layout_constraintBottom_toBottomOf="@id/active_speaker_background" app:layout_constraintBottom_toBottomOf="@id/active_speaker_background"
app:layout_constraintEnd_toEndOf="@id/active_speaker_background" app:layout_constraintEnd_toEndOf="@id/active_speaker_background"
@ -254,6 +256,7 @@
android:id="@+id/local_participant_background" android:id="@+id/local_participant_background"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:visibility="@{conferenceViewModel.isMeListenerOnly ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toBottomOf="@id/top_barrier" app:layout_constraintTop_toBottomOf="@id/top_barrier"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -268,6 +271,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_margin="10dp" android:layout_margin="10dp"
android:contentDescription="@null" android:contentDescription="@null"
android:visibility="@{conferenceViewModel.isMeListenerOnly ? View.GONE : View.VISIBLE}"
coilSelfAvatar="@{conferenceViewModel.meParticipant}" coilSelfAvatar="@{conferenceViewModel.meParticipant}"
android:background="@drawable/generated_avatar_bg" android:background="@drawable/generated_avatar_bg"
app:layout_constraintDimensionRatio="1:1" app:layout_constraintDimensionRatio="1:1"
@ -298,6 +302,7 @@
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:visibility="@{conferenceViewModel.isMeListenerOnly ? View.GONE : View.VISIBLE}"
android:text="@{conferenceViewModel.meParticipant.contact.name ?? conferenceViewModel.meParticipant.displayName}" android:text="@{conferenceViewModel.meParticipant.contact.name ?? conferenceViewModel.meParticipant.displayName}"
app:layout_constraintBottom_toBottomOf="@id/local_participant_background" app:layout_constraintBottom_toBottomOf="@id/local_participant_background"
app:layout_constraintStart_toStartOf="@id/local_participant_background" app:layout_constraintStart_toStartOf="@id/local_participant_background"

View file

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View"/>
<variable
name="data"
type="org.linphone.activities.voip.data.ConferenceParticipantData" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_margin="5dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/avatar"
android:layout_width="@dimen/contact_avatar_size"
android:layout_height="@dimen/contact_avatar_size"
android:contentDescription="@null"
coilContact="@{data}"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_centerVertical="true"
android:background="@drawable/generated_avatar_bg"
android:src="@drawable/voip_single_contact_avatar"/>
<ImageView
android:id="@+id/delete"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_centerInParent="true"
android:contentDescription="@string/content_description_remove_contact_from_chat_room"
android:onClick="@{() -> data.removeParticipantFromConference()}"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:src="@drawable/icon_cancel_alt"
android:visibility="@{data.isMeAdmin ? View.VISIBLE : View.INVISIBLE}" />
<LinearLayout
android:id="@+id/speakerLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/delete"
android:layout_marginStart="5dp"
android:layout_marginEnd="10dp"
android:visibility="@{data.isSpeaker ? View.VISIBLE : View.INVISIBLE}">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:padding="5dp"
android:scaleType="centerInside"
android:contentDescription="@string/content_description_contact_is_speaker"
android:src="@drawable/check_selected" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/conference_participants_list_speaker_label"
android:textColor="?attr/primaryTextColor" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:layout_toStartOf="@id/speakerLayout"
android:layout_toEndOf="@id/avatar"
android:orientation="vertical">
<org.linphone.views.MarqueeTextView
style="@style/contact_name_list_cell_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@{data.contact.name ?? data.displayName, default=`Bilbo Baggins`}" />
<org.linphone.views.MarqueeTextView
android:id="@+id/sipUri"
style="@style/sip_uri_small_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:text="@{data.sipUri, default=`sip:bilbo.baggins@sip.linphone.org`}" />
</LinearLayout>
</RelativeLayout>
</layout>

View file

@ -76,7 +76,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:entries="@{conferenceViewModel.conferenceParticipants}" app:entries="@{conferenceViewModel.conferenceParticipants}"
app:layout="@{@layout/voip_conference_participant_cell}" /> app:layout="@{conferenceViewModel.isBroadcast ? @layout/voip_conference_participant_broadcast_cell : @layout/voip_conference_participant_cell}" />
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View file

@ -799,4 +799,25 @@
<string name="chat_message_many_reactions">%s réactions</string> <string name="chat_message_many_reactions">%s réactions</string>
<string name="conference_invitation_notification_short_desc">invitation à une conférence</string> <string name="conference_invitation_notification_short_desc">invitation à une conférence</string>
<string name="chat_message_voice_recording_notification_short_desc">message vocal</string> <string name="chat_message_voice_recording_notification_short_desc">message vocal</string>
<string name="conference_schedule_mode">Mode</string>
<string name="conference_schedule_mode_meeting">Réunion</string>
<string name="conference_schedule_mode_broadcast">Webinar</string>
<string name="conference_schedule_broadcast_summary">Informations du webinar</string>
<string name="conference_schedule_speakers_list">Liste des intervenants</string>
<string name="conference_schedule_speakers_list_empty">Choisissez au moins un intervenant</string>
<string name="conference_schedule_participants_list_empty">Choisissez au moins un participant</string>
<string name="conference_broadcast_start">Planifier un webinar</string>
<string name="conference_broadcast_edit">Modifier le webinar</string>
<string name="conference_meeting_title">Réunion :</string>
<string name="conference_broadcast_title">Webinar :</string>
<string name="conference_invite_speakers_list_title">Intervenants</string>
<string name="conference_invite_participants_list_title">Participants</string>
<string name="conference_broadcast_invite_title">Invitation au webinar :</string>
<string name="conference_broadcast_invite_you_are_speaker">(vous êtes un intervenant)</string>
<string name="conference_schedule_broadcast_address_title">Adresse du webinar</string>
<string name="conference_waiting_room_listener_broadcast">Vous n\'êtes pas un intervenant de ce webinar</string>
<string name="conferences_settings_enable_broadcast_title">Autoriser les webinar</string>
<string name="conferences_settings_enable_broadcast_summary">Fonctionalité encore en béta !</string>
<string name="conference_participants_list_speaker_label">Intervenant</string>
<string name="content_description_contact_is_speaker">Contact is a speaker</string>
</resources> </resources>

View file

@ -6,6 +6,7 @@
<!-- You can use https://material.io/design/color/#tools-for-picking-colors to get dark color value from primary --> <!-- You can use https://material.io/design/color/#tools-for-picking-colors to get dark color value from primary -->
<color name="primary_dark_color">#e65000</color> <color name="primary_dark_color">#e65000</color>
<color name="primary_light_color">#ffab4d</color> <color name="primary_light_color">#ffab4d</color>
<color name="primary_color_disabled">#4DFE5E00</color> <!-- Try to have a color that renders well on both light & dark theme -->
<color name="notification_led_color">#ff8000</color> <color name="notification_led_color">#ff8000</color>
<color name="black_color">#000000</color> <color name="black_color">#000000</color>

View file

@ -274,11 +274,15 @@
<string name="conference_schedule_subject_hint">Meeting subject</string> <string name="conference_schedule_subject_hint">Meeting subject</string>
<string name="conference_group_call_subject_hint">Group call subject</string> <string name="conference_group_call_subject_hint">Group call subject</string>
<string name="conference_schedule_address_title">Meeting address</string> <string name="conference_schedule_address_title">Meeting address</string>
<string name="conference_schedule_broadcast_address_title">Broadcast address</string>
<string name="conference_schedule_description_title">Add a description</string> <string name="conference_schedule_description_title">Add a description</string>
<string name="conference_schedule_description_hint">Description</string> <string name="conference_schedule_description_hint">Description</string>
<string name="conference_schedule_date">Date</string> <string name="conference_schedule_date">Date</string>
<string name="conference_schedule_time">Time</string> <string name="conference_schedule_time">Time</string>
<string name="conference_schedule_duration">Duration</string> <string name="conference_schedule_duration">Duration</string>
<string name="conference_schedule_mode">Mode</string>
<string name="conference_schedule_mode_meeting">Meeting</string>
<string name="conference_schedule_mode_broadcast">Broadcast</string>
<string name="conference_schedule_timezone">Timezone</string> <string name="conference_schedule_timezone">Timezone</string>
<string name="conference_schedule_send_invite_chat">Send invite via &appName;</string> <string name="conference_schedule_send_invite_chat">Send invite via &appName;</string>
<string name="conference_schedule_send_updated_invite_chat">Send update via &appName;</string> <string name="conference_schedule_send_updated_invite_chat">Send update via &appName;</string>
@ -287,11 +291,17 @@
<string name="conference_schedule_encryption">Would you like to encrypt the meeting?</string> <string name="conference_schedule_encryption">Would you like to encrypt the meeting?</string>
<string name="conference_schedule_send_invite_chat_summary">Invite will be sent out from my &appName; account</string> <string name="conference_schedule_send_invite_chat_summary">Invite will be sent out from my &appName; account</string>
<string name="conference_schedule_participants_list">Participants list</string> <string name="conference_schedule_participants_list">Participants list</string>
<string name="conference_schedule_speakers_list">Speakers list</string>
<string name="conference_schedule_speakers_list_empty">Select at least one speaker</string>
<string name="conference_schedule_participants_list_empty">Select at least one participant</string>
<string name="conference_organizer">Organizer</string> <string name="conference_organizer">Organizer</string>
<string name="conference_schedule_summary">Meeting info</string> <string name="conference_schedule_summary">Meeting info</string>
<string name="conference_group_call_summary">Group call info</string> <string name="conference_group_call_summary">Group call info</string>
<string name="conference_schedule_broadcast_summary">Broadcast info</string>
<string name="conference_schedule_start">Schedule meeting</string> <string name="conference_schedule_start">Schedule meeting</string>
<string name="conference_schedule_edit">Edit meeting</string> <string name="conference_schedule_edit">Edit meeting</string>
<string name="conference_broadcast_start">Schedule broadcast</string>
<string name="conference_broadcast_edit">Edit broadcast</string>
<string name="conference_group_call_create">Start group call</string> <string name="conference_group_call_create">Start group call</string>
<string name="conference_schedule_address_copied_to_clipboard">Meeting address copied into clipboard</string> <string name="conference_schedule_address_copied_to_clipboard">Meeting address copied into clipboard</string>
<string name="conference_schedule_info_not_sent_to_participant">Failed to send meeting info to a participant</string> <string name="conference_schedule_info_not_sent_to_participant">Failed to send meeting info to a participant</string>
@ -300,6 +310,8 @@
<string name="conference_default_title">Remote group call</string> <string name="conference_default_title">Remote group call</string>
<string name="conference_local_title">Local group call</string> <string name="conference_local_title">Local group call</string>
<string name="conference_invite_title">Meeting invite:</string> <string name="conference_invite_title">Meeting invite:</string>
<string name="conference_broadcast_invite_title">Broadcast invite:</string>
<string name="conference_broadcast_invite_you_are_speaker">(you are a speaker)</string>
<string name="conference_update_title">Meeting has been updated:</string> <string name="conference_update_title">Meeting has been updated:</string>
<string name="conference_cancel_title">Meeting has been cancelled:</string> <string name="conference_cancel_title">Meeting has been cancelled:</string>
<string name="conference_description_title">Description</string> <string name="conference_description_title">Description</string>
@ -311,6 +323,7 @@
<string name="conference_waiting_room_start_call">Start</string> <string name="conference_waiting_room_start_call">Start</string>
<string name="conference_waiting_room_cancel_call">Cancel</string> <string name="conference_waiting_room_cancel_call">Cancel</string>
<string name="conference_waiting_room_video_disabled">Video is currently disabled</string> <string name="conference_waiting_room_video_disabled">Video is currently disabled</string>
<string name="conference_waiting_room_listener_broadcast">You aren\'t a speaker in this broadcast</string>
<string name="conference_scheduled">Meetings</string> <string name="conference_scheduled">Meetings</string>
<string name="conference_layout_too_many_participants_for_mosaic">You can\'t change group call layout as there is too many participants</string> <string name="conference_layout_too_many_participants_for_mosaic">You can\'t change group call layout as there is too many participants</string>
<string name="conference_too_many_participants_for_mosaic_layout">There is too many participants for mosaic layout, switching to active speaker</string> <string name="conference_too_many_participants_for_mosaic_layout">There is too many participants for mosaic layout, switching to active speaker</string>
@ -346,6 +359,11 @@
</plurals> </plurals>
<string name="conference_scheduled_cancelled_by_organizer">Conference has been cancelled by organizer</string> <string name="conference_scheduled_cancelled_by_organizer">Conference has been cancelled by organizer</string>
<string name="conference_scheduled_cancelled_by_me">You have cancelled the conference</string> <string name="conference_scheduled_cancelled_by_me">You have cancelled the conference</string>
<string name="conference_meeting_title">Meeting:</string>
<string name="conference_broadcast_title">Broadcast:</string>
<string name="conference_invite_speakers_list_title">Speakers</string>
<string name="conference_invite_participants_list_title">Participants</string>
<string name="conference_participants_list_speaker_label">Speaker</string>
<!-- Call --> <!-- Call -->
<string name="call_incoming_title">Incoming Call</string> <string name="call_incoming_title">Incoming Call</string>
@ -746,6 +764,8 @@
<!-- Conferences settings --> <!-- Conferences settings -->
<string name="conferences_settings_layout_title">Default layout</string> <string name="conferences_settings_layout_title">Default layout</string>
<string name="conferences_settings_enable_broadcast_title">Allow broadcasts</string>
<string name="conferences_settings_enable_broadcast_summary">Feature currently in beta!</string>
<!-- Notifications --> <!-- Notifications -->
<string name="notification_channel_service_id" translatable="false">linphone_notification_service_id</string> <string name="notification_channel_service_id" translatable="false">linphone_notification_service_id</string>
@ -851,6 +871,7 @@
<string name="content_description_remove_contact_from_chat_room">Remove contact from conversation</string> <string name="content_description_remove_contact_from_chat_room">Remove contact from conversation</string>
<string name="content_description_contact_is_admin">Contact is an admin in this conversation</string> <string name="content_description_contact_is_admin">Contact is an admin in this conversation</string>
<string name="content_description_contact_is_not_admin">Contact isn\'t an admin in this conversation</string> <string name="content_description_contact_is_not_admin">Contact isn\'t an admin in this conversation</string>
<string name="content_description_contact_is_speaker">Contact is a speaker</string>
<string name="content_description_ephemeral_chat_room">Messages are ephemeral in this conversation</string> <string name="content_description_ephemeral_chat_room">Messages are ephemeral in this conversation</string>
<string name="content_description_muted_chat_room">Notifications are disabled for this conversation</string> <string name="content_description_muted_chat_room">Notifications are disabled for this conversation</string>
<string name="content_description_contact_can_do_encryption">Contact can be invited in encrypted conversations</string> <string name="content_description_contact_can_do_encryption">Contact can be invited in encrypted conversations</string>