mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 11:59:12 +03:00
VoiceBroadcast - Listening view
This commit is contained in:
parent
f1b4ebbc37
commit
f711a0ea74
5 changed files with 98 additions and 62 deletions
|
@ -3082,6 +3082,9 @@
|
||||||
<string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string>
|
<string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string>
|
||||||
<string name="a11y_pause_voice_broadcast_record">Pause voice broadcast record</string>
|
<string name="a11y_pause_voice_broadcast_record">Pause voice broadcast record</string>
|
||||||
<string name="a11y_stop_voice_broadcast_record">Stop voice broadcast record</string>
|
<string name="a11y_stop_voice_broadcast_record">Stop voice broadcast record</string>
|
||||||
|
<string name="a11y_play_voice_broadcast">Play or resume voice broadcast</string>
|
||||||
|
<string name="a11y_pause_voice_broadcast">Pause voice broadcast</string>
|
||||||
|
<string name="a11y_voice_broadcast_buffering">Buffering</string>
|
||||||
|
|
||||||
<string name="upgrade_room_for_restricted">Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.</string>
|
<string name="upgrade_room_for_restricted">Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.</string>
|
||||||
<string name="upgrade_room_for_restricted_no_param">Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.</string>
|
<string name="upgrade_room_for_restricted_no_param">Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.</string>
|
||||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadca
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem_
|
||||||
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
||||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
@ -42,6 +43,7 @@ class VoiceBroadcastItemFactory @Inject constructor(
|
||||||
private val colorProvider: ColorProvider,
|
private val colorProvider: ColorProvider,
|
||||||
private val drawableProvider: DrawableProvider,
|
private val drawableProvider: DrawableProvider,
|
||||||
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
||||||
|
private val voiceBroadcastPlayer: VoiceBroadcastPlayer,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
|
@ -53,7 +55,8 @@ class VoiceBroadcastItemFactory @Inject constructor(
|
||||||
): VectorEpoxyModel<out VectorEpoxyHolder>? {
|
): VectorEpoxyModel<out VectorEpoxyHolder>? {
|
||||||
// Only display item of the initial event with updated data
|
// Only display item of the initial event with updated data
|
||||||
if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null
|
if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null
|
||||||
val voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null
|
val eventsGroup = params.eventsGroup ?: return null
|
||||||
|
val voiceBroadcastEventsGroup = VoiceBroadcastEventsGroup(eventsGroup)
|
||||||
val mostRecentTimelineEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent()
|
val mostRecentTimelineEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent()
|
||||||
val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent()
|
val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent()
|
||||||
val mostRecentMessageContent = mostRecentEvent?.content ?: return null
|
val mostRecentMessageContent = mostRecentEvent?.content ?: return null
|
||||||
|
@ -61,7 +64,7 @@ class VoiceBroadcastItemFactory @Inject constructor(
|
||||||
return if (isRecording) {
|
return if (isRecording) {
|
||||||
createRecordingItem(params.event.roomId, highlight, callback, attributes)
|
createRecordingItem(params.event.roomId, highlight, callback, attributes)
|
||||||
} else {
|
} else {
|
||||||
createListeningItem(params.event.roomId, highlight, callback, attributes)
|
createListeningItem(params.event.roomId, eventsGroup.groupId, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +88,7 @@ class VoiceBroadcastItemFactory @Inject constructor(
|
||||||
|
|
||||||
private fun createListeningItem(
|
private fun createListeningItem(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
|
voiceBroadcastId: String,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes,
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
@ -96,7 +100,8 @@ class VoiceBroadcastItemFactory @Inject constructor(
|
||||||
.roomItem(roomSummary?.toMatrixItem())
|
.roomItem(roomSummary?.toMatrixItem())
|
||||||
.colorProvider(colorProvider)
|
.colorProvider(colorProvider)
|
||||||
.drawableProvider(drawableProvider)
|
.drawableProvider(drawableProvider)
|
||||||
.voiceBroadcastRecorder(voiceBroadcastRecorder)
|
.voiceBroadcastPlayer(voiceBroadcastPlayer)
|
||||||
|
.voiceBroadcastId(voiceBroadcastId)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.callback(callback)
|
.callback(callback)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.item
|
package im.vector.app.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
@ -23,12 +24,11 @@ import androidx.core.view.isVisible
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.tintBackground
|
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.DrawableProvider
|
import im.vector.app.core.resources.DrawableProvider
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
|
import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
@EpoxyModelClass
|
@EpoxyModelClass
|
||||||
|
@ -38,7 +38,10 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem<MessageVoiceB
|
||||||
var callback: TimelineEventController.Callback? = null
|
var callback: TimelineEventController.Callback? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var voiceBroadcastRecorder: VoiceBroadcastRecorder? = null
|
var voiceBroadcastPlayer: VoiceBroadcastPlayer? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
lateinit var voiceBroadcastId: String
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var colorProvider: ColorProvider
|
lateinit var colorProvider: ColorProvider
|
||||||
|
@ -52,7 +55,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem<MessageVoiceB
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var title: String? = null
|
var title: String? = null
|
||||||
|
|
||||||
private lateinit var recorderListener: VoiceBroadcastRecorder.Listener
|
private lateinit var playerListener: VoiceBroadcastPlayer.Listener
|
||||||
|
|
||||||
override fun isCacheable(): Boolean = false
|
override fun isCacheable(): Boolean = false
|
||||||
|
|
||||||
|
@ -62,12 +65,10 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem<MessageVoiceB
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindVoiceBroadcastItem(holder: Holder) {
|
private fun bindVoiceBroadcastItem(holder: Holder) {
|
||||||
recorderListener = object : VoiceBroadcastRecorder.Listener {
|
playerListener = VoiceBroadcastPlayer.Listener { state ->
|
||||||
override fun onStateUpdated(state: VoiceBroadcastRecorder.State) {
|
renderState(holder, state)
|
||||||
renderState(holder, state)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
voiceBroadcastRecorder?.addListener(recorderListener)
|
voiceBroadcastPlayer?.addListener(playerListener)
|
||||||
renderHeader(holder)
|
renderHeader(holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,45 +81,59 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem<MessageVoiceB
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderState(holder: Holder, state: VoiceBroadcastRecorder.State) {
|
private fun renderState(holder: Holder, state: VoiceBroadcastPlayer.State) {
|
||||||
|
if (isCurrentMediaActive()) {
|
||||||
|
renderActiveMedia(holder, state)
|
||||||
|
} else {
|
||||||
|
renderInactiveMedia(holder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun renderActiveMedia(holder: Holder, state: VoiceBroadcastPlayer.State) {
|
||||||
with(holder) {
|
with(holder) {
|
||||||
|
bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING
|
||||||
|
playPauseButton.isVisible = state != VoiceBroadcastPlayer.State.BUFFERING
|
||||||
|
liveIndicator.isVisible = false
|
||||||
|
// liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorOnError))
|
||||||
|
|
||||||
when (state) {
|
when (state) {
|
||||||
VoiceBroadcastRecorder.State.Recording -> {
|
VoiceBroadcastPlayer.State.PLAYING -> {
|
||||||
stopRecordButton.isEnabled = true
|
playPauseButton.setImageResource(R.drawable.ic_play_pause_pause)
|
||||||
|
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast)
|
||||||
liveIndicator.isVisible = true
|
playPauseButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) }
|
||||||
liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorOnError))
|
|
||||||
|
|
||||||
val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
|
||||||
val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor)
|
|
||||||
recordButton.setImageDrawable(drawable)
|
|
||||||
recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_pause_voice_broadcast_record)
|
|
||||||
recordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) }
|
|
||||||
stopRecordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) }
|
|
||||||
}
|
}
|
||||||
VoiceBroadcastRecorder.State.Paused -> {
|
VoiceBroadcastPlayer.State.IDLE,
|
||||||
stopRecordButton.isEnabled = true
|
VoiceBroadcastPlayer.State.PAUSED -> {
|
||||||
|
playPauseButton.setImageResource(R.drawable.ic_play_pause_play)
|
||||||
liveIndicator.isVisible = true
|
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast)
|
||||||
liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary))
|
playPauseButton.setOnClickListener {
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId))
|
||||||
recordButton.setImageResource(R.drawable.ic_recording_dot)
|
}
|
||||||
recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record)
|
|
||||||
recordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) }
|
|
||||||
stopRecordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) }
|
|
||||||
}
|
|
||||||
VoiceBroadcastRecorder.State.Idle -> {
|
|
||||||
recordButton.isEnabled = false
|
|
||||||
stopRecordButton.isEnabled = false
|
|
||||||
liveIndicator.isVisible = false
|
|
||||||
}
|
}
|
||||||
|
VoiceBroadcastPlayer.State.BUFFERING -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderInactiveMedia(holder: Holder) {
|
||||||
|
with(holder) {
|
||||||
|
liveIndicator.isVisible = false
|
||||||
|
bufferingView.isVisible = false
|
||||||
|
playPauseButton.isVisible = true
|
||||||
|
playPauseButton.setImageResource(R.drawable.ic_play_pause_play)
|
||||||
|
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast)
|
||||||
|
playPauseButton.setOnClickListener {
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isCurrentMediaActive() = voiceBroadcastPlayer?.currentVoiceBroadcastId == voiceBroadcastId
|
||||||
|
|
||||||
override fun unbind(holder: Holder) {
|
override fun unbind(holder: Holder) {
|
||||||
super.unbind(holder)
|
super.unbind(holder)
|
||||||
voiceBroadcastRecorder?.removeListener(recorderListener)
|
voiceBroadcastPlayer?.removeListener(playerListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getViewStubId() = STUB_ID
|
override fun getViewStubId() = STUB_ID
|
||||||
|
@ -127,8 +142,8 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem<MessageVoiceB
|
||||||
val liveIndicator by bind<TextView>(R.id.liveIndicator)
|
val liveIndicator by bind<TextView>(R.id.liveIndicator)
|
||||||
val roomAvatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
val roomAvatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
||||||
val titleText by bind<TextView>(R.id.titleText)
|
val titleText by bind<TextView>(R.id.titleText)
|
||||||
val recordButton by bind<ImageButton>(R.id.recordButton)
|
val playPauseButton by bind<ImageButton>(R.id.playPauseButton)
|
||||||
val stopRecordButton by bind<ImageButton>(R.id.stopRecordButton)
|
val bufferingView by bind<View>(R.id.bufferingView)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -73,15 +73,17 @@ class VoiceBroadcastPlayer @Inject constructor(
|
||||||
private var currentSequence: Int? = null
|
private var currentSequence: Int? = null
|
||||||
|
|
||||||
private var playlist = emptyList<MessageAudioEvent>()
|
private var playlist = emptyList<MessageAudioEvent>()
|
||||||
private val currentVoiceBroadcastId
|
val currentVoiceBroadcastId
|
||||||
get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId
|
get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId
|
||||||
|
|
||||||
private var state: State = State.IDLE
|
private var state: State = State.IDLE
|
||||||
set(value) {
|
set(value) {
|
||||||
Timber.w("## VoiceBroadcastPlayer state: $field -> $value")
|
Timber.w("## VoiceBroadcastPlayer state: $field -> $value")
|
||||||
field = value
|
field = value
|
||||||
|
listeners.forEach { it.onStateChanged(value) }
|
||||||
}
|
}
|
||||||
private var currentRoomId: String? = null
|
private var currentRoomId: String? = null
|
||||||
|
private var listeners = mutableListOf<Listener>()
|
||||||
|
|
||||||
fun playOrResume(roomId: String, eventId: String) {
|
fun playOrResume(roomId: String, eventId: String) {
|
||||||
val hasChanged = currentVoiceBroadcastId != eventId
|
val hasChanged = currentVoiceBroadcastId != eventId
|
||||||
|
@ -128,6 +130,15 @@ class VoiceBroadcastPlayer @Inject constructor(
|
||||||
currentRoomId = null
|
currentRoomId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addListener(listener: Listener) {
|
||||||
|
listeners.add(listener)
|
||||||
|
listener.onStateChanged(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeListener(listener: Listener) {
|
||||||
|
listeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
private fun startPlayback(roomId: String, eventId: String) {
|
private fun startPlayback(roomId: String, eventId: String) {
|
||||||
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
|
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
|
||||||
currentRoomId = roomId
|
currentRoomId = roomId
|
||||||
|
@ -316,4 +327,8 @@ class VoiceBroadcastPlayer @Inject constructor(
|
||||||
BUFFERING,
|
BUFFERING,
|
||||||
IDLE
|
IDLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun interface Listener {
|
||||||
|
fun onStateChanged(state: State)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,29 +65,27 @@
|
||||||
app:constraint_referenced_ids="roomAvatarImageView,titleText" />
|
app:constraint_referenced_ids="roomAvatarImageView,titleText" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/recordButton"
|
android:id="@+id/playPauseButton"
|
||||||
android:layout_width="@dimen/voice_broadcast_controller_button_size"
|
android:layout_width="@dimen/voice_broadcast_controller_button_size"
|
||||||
android:layout_height="@dimen/voice_broadcast_controller_button_size"
|
android:layout_height="@dimen/voice_broadcast_controller_button_size"
|
||||||
android:background="@drawable/bg_rounded_button"
|
android:background="@drawable/bg_rounded_button"
|
||||||
android:backgroundTint="?vctr_system"
|
android:backgroundTint="?vctr_system"
|
||||||
android:contentDescription="@string/a11y_resume_voice_broadcast_record"
|
android:contentDescription="@string/a11y_play_voice_broadcast"
|
||||||
android:src="@drawable/ic_recording_dot"
|
android:src="@drawable/ic_play_pause_play"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/stopRecordButton"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/headerBottomBarrier"
|
||||||
|
app:tint="?vctr_content_secondary" />
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:id="@+id/bufferingView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/a11y_voice_broadcast_buffering"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/playPauseButton"
|
||||||
app:layout_constraintTop_toBottomOf="@id/headerBottomBarrier" />
|
app:layout_constraintTop_toBottomOf="@id/headerBottomBarrier" />
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/stopRecordButton"
|
|
||||||
android:layout_width="@dimen/voice_broadcast_controller_button_size"
|
|
||||||
android:layout_height="@dimen/voice_broadcast_controller_button_size"
|
|
||||||
android:background="@drawable/bg_rounded_button"
|
|
||||||
android:backgroundTint="?vctr_system"
|
|
||||||
android:contentDescription="@string/a11y_stop_voice_broadcast_record"
|
|
||||||
android:src="@drawable/ic_stop"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/recordButton"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/recordButton"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/recordButton" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
Loading…
Reference in a new issue