mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 09:25:49 +03:00
Merge pull request #7993 from vector-im/feature/fre/vb_handle_connection_error
Pause voice broadcast if there is no network
This commit is contained in:
commit
71b7fbdf15
10 changed files with 208 additions and 81 deletions
1
changelog.d/7890.feature
Normal file
1
changelog.d/7890.feature
Normal file
|
@ -0,0 +1 @@
|
|||
[Voice Broadcast] Handle connection errors while recording
|
|
@ -3125,6 +3125,7 @@
|
|||
<string name="error_voice_broadcast_blocked_by_someone_else_message">Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.</string>
|
||||
<string name="error_voice_broadcast_already_in_progress_message">You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.</string>
|
||||
<string name="error_voice_broadcast_unable_to_play">Unable to play this voice broadcast.</string>
|
||||
<string name="error_voice_broadcast_no_connection_recording">Connection error - Recording paused</string>
|
||||
<!-- Examples of usage: 6h 15min 30sec left / 15min 30sec left / 30sec left -->
|
||||
<string name="voice_broadcast_recording_time_left">%1$s left</string>
|
||||
<string name="stop_voice_broadcast_dialog_title">Stop live broadcasting?</string>
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package im.vector.app.features.home.room.detail.timeline.item
|
||||
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.Group
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
|
@ -55,11 +57,11 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
|
|||
}
|
||||
|
||||
override fun renderLiveIndicator(holder: Holder) {
|
||||
when (voiceBroadcastState) {
|
||||
VoiceBroadcastState.STARTED,
|
||||
VoiceBroadcastState.RESUMED -> renderPlayingLiveIndicator(holder)
|
||||
VoiceBroadcastState.PAUSED -> renderPausedLiveIndicator(holder)
|
||||
VoiceBroadcastState.STOPPED, null -> renderNoLiveIndicator(holder)
|
||||
when (recorder?.recordingState) {
|
||||
VoiceBroadcastRecorder.State.Recording -> renderPlayingLiveIndicator(holder)
|
||||
VoiceBroadcastRecorder.State.Error,
|
||||
VoiceBroadcastRecorder.State.Paused -> renderPausedLiveIndicator(holder)
|
||||
VoiceBroadcastRecorder.State.Idle, null -> renderNoLiveIndicator(holder)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +87,9 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
|
|||
VoiceBroadcastRecorder.State.Recording -> renderRecordingState(holder)
|
||||
VoiceBroadcastRecorder.State.Paused -> renderPausedState(holder)
|
||||
VoiceBroadcastRecorder.State.Idle -> renderStoppedState(holder)
|
||||
VoiceBroadcastRecorder.State.Error -> renderErrorState(holder, true)
|
||||
}
|
||||
renderLiveIndicator(holder)
|
||||
}
|
||||
|
||||
private fun renderVoiceBroadcastState(holder: Holder) {
|
||||
|
@ -101,6 +105,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
|
|||
private fun renderRecordingState(holder: Holder) = with(holder) {
|
||||
stopRecordButton.isEnabled = true
|
||||
recordButton.isEnabled = true
|
||||
renderErrorState(holder, false)
|
||||
|
||||
val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
||||
val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor)
|
||||
|
@ -113,6 +118,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
|
|||
private fun renderPausedState(holder: Holder) = with(holder) {
|
||||
stopRecordButton.isEnabled = true
|
||||
recordButton.isEnabled = true
|
||||
renderErrorState(holder, false)
|
||||
|
||||
recordButton.setImageResource(R.drawable.ic_recording_dot)
|
||||
recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record)
|
||||
|
@ -123,6 +129,12 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
|
|||
private fun renderStoppedState(holder: Holder) = with(holder) {
|
||||
recordButton.isEnabled = false
|
||||
stopRecordButton.isEnabled = false
|
||||
renderErrorState(holder, false)
|
||||
}
|
||||
|
||||
private fun renderErrorState(holder: Holder, isOnError: Boolean) = with(holder) {
|
||||
controlsGroup.isVisible = !isOnError
|
||||
errorView.isVisible = isOnError
|
||||
}
|
||||
|
||||
override fun unbind(holder: Holder) {
|
||||
|
@ -142,6 +154,8 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
|
|||
val remainingTimeMetadata by bind<VoiceBroadcastMetadataView>(R.id.remainingTimeMetadata)
|
||||
val recordButton by bind<ImageButton>(R.id.recordButton)
|
||||
val stopRecordButton by bind<ImageButton>(R.id.stopRecordButton)
|
||||
val errorView by bind<TextView>(R.id.errorView)
|
||||
val controlsGroup by bind<Group>(R.id.controlsGroup)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -33,6 +33,8 @@ interface VoiceBroadcastRecorder : VoiceRecorder {
|
|||
val currentRemainingTime: Long?
|
||||
|
||||
fun startRecordVoiceBroadcast(voiceBroadcast: VoiceBroadcast, chunkLength: Int, maxLength: Int)
|
||||
|
||||
fun pauseOnError()
|
||||
fun addListener(listener: Listener)
|
||||
fun removeListener(listener: Listener)
|
||||
|
||||
|
@ -46,5 +48,6 @@ interface VoiceBroadcastRecorder : VoiceRecorder {
|
|||
Recording,
|
||||
Paused,
|
||||
Idle,
|
||||
Error,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,10 +29,14 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
|||
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
|
||||
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
@ -47,6 +51,7 @@ class VoiceBroadcastRecorderQ(
|
|||
private val sessionScope get() = session.coroutineScope
|
||||
|
||||
private var voiceBroadcastStateObserver: Job? = null
|
||||
private var syncStateObserver: Job? = null
|
||||
|
||||
private var maxFileSize = 0L // zero or negative for no limit
|
||||
private var currentVoiceBroadcast: VoiceBroadcast? = null
|
||||
|
@ -96,21 +101,36 @@ class VoiceBroadcastRecorderQ(
|
|||
observeVoiceBroadcastStateEvent(voiceBroadcast)
|
||||
}
|
||||
|
||||
override fun pauseRecord() {
|
||||
override fun startRecord(roomId: String) {
|
||||
super.startRecord(roomId)
|
||||
observeConnectionState()
|
||||
}
|
||||
|
||||
override fun pauseOnError() {
|
||||
if (recordingState != VoiceBroadcastRecorder.State.Recording) return
|
||||
tryOrNull { mediaRecorder?.stop() }
|
||||
mediaRecorder?.reset()
|
||||
|
||||
pauseRecorder()
|
||||
stopObservingConnectionState()
|
||||
recordingState = VoiceBroadcastRecorder.State.Error
|
||||
}
|
||||
|
||||
override fun pauseRecord() {
|
||||
if (recordingState !in arrayOf(VoiceBroadcastRecorder.State.Recording, VoiceBroadcastRecorder.State.Error)) return
|
||||
|
||||
pauseRecorder()
|
||||
stopObservingConnectionState()
|
||||
recordingState = VoiceBroadcastRecorder.State.Paused
|
||||
recordingTicker.pause()
|
||||
notifyOutputFileCreated()
|
||||
}
|
||||
|
||||
override fun resumeRecord() {
|
||||
if (recordingState != VoiceBroadcastRecorder.State.Paused) return
|
||||
|
||||
currentSequence++
|
||||
currentVoiceBroadcast?.let { startRecord(it.roomId) }
|
||||
recordingState = VoiceBroadcastRecorder.State.Recording
|
||||
recordingTicker.resume()
|
||||
observeConnectionState()
|
||||
}
|
||||
|
||||
override fun stopRecord() {
|
||||
|
@ -128,6 +148,8 @@ class VoiceBroadcastRecorderQ(
|
|||
voiceBroadcastStateObserver?.cancel()
|
||||
voiceBroadcastStateObserver = null
|
||||
|
||||
stopObservingConnectionState()
|
||||
|
||||
// Reset data
|
||||
currentSequence = 0
|
||||
currentMaxLength = 0
|
||||
|
@ -197,6 +219,27 @@ class VoiceBroadcastRecorderQ(
|
|||
}
|
||||
}
|
||||
|
||||
private fun pauseRecorder() {
|
||||
if (recordingState != VoiceBroadcastRecorder.State.Recording) return
|
||||
|
||||
tryOrNull { mediaRecorder?.stop() }
|
||||
mediaRecorder?.reset()
|
||||
recordingTicker.pause()
|
||||
}
|
||||
|
||||
private fun observeConnectionState() {
|
||||
syncStateObserver = session.flow().liveSyncState()
|
||||
.distinctUntilChanged()
|
||||
.filter { it is SyncState.NoNetwork }
|
||||
.onEach { pauseOnError() }
|
||||
.launchIn(sessionScope)
|
||||
}
|
||||
|
||||
private fun stopObservingConnectionState() {
|
||||
syncStateObserver?.cancel()
|
||||
syncStateObserver = null
|
||||
}
|
||||
|
||||
private inner class RecordingTicker(
|
||||
private var recordingTicker: CountUpTimer? = null,
|
||||
) {
|
||||
|
|
|
@ -16,17 +16,25 @@
|
|||
|
||||
package im.vector.app.features.voicebroadcast.recording.usecase
|
||||
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.take
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -51,25 +59,35 @@ class PauseVoiceBroadcastUseCase @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?) {
|
||||
private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?, remainingRetry: Int = 3) {
|
||||
Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
||||
|
||||
// save the last sequence number and immediately pause the recording
|
||||
val lastSequence = voiceBroadcastRecorder?.currentSequence
|
||||
pauseRecording()
|
||||
try {
|
||||
// save the last sequence number and immediately pause the recording
|
||||
val lastSequence = voiceBroadcastRecorder?.currentSequence
|
||||
|
||||
room.stateService().sendStateEvent(
|
||||
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||
stateKey = session.myUserId,
|
||||
body = MessageVoiceBroadcastInfoContent(
|
||||
relatesTo = reference,
|
||||
voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value,
|
||||
lastChunkSequence = lastSequence,
|
||||
).toContent(),
|
||||
)
|
||||
}
|
||||
room.stateService().sendStateEvent(
|
||||
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||
stateKey = session.myUserId,
|
||||
body = MessageVoiceBroadcastInfoContent(
|
||||
relatesTo = reference,
|
||||
voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value,
|
||||
lastChunkSequence = lastSequence,
|
||||
).toContent(),
|
||||
)
|
||||
|
||||
private fun pauseRecording() {
|
||||
voiceBroadcastRecorder?.pauseRecord()
|
||||
voiceBroadcastRecorder?.pauseRecord()
|
||||
} catch (e: Failure) {
|
||||
if (remainingRetry > 0) {
|
||||
voiceBroadcastRecorder?.pauseOnError()
|
||||
// Retry if there is no network issue (sync is running well)
|
||||
session.flow().liveSyncState()
|
||||
.filter { it is SyncState.Running }
|
||||
.take(1)
|
||||
.onEach { pauseVoiceBroadcast(room, reference, remainingRetry - 1) }
|
||||
.launchIn(session.coroutineScope)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
|||
private val buildMeta: BuildMeta,
|
||||
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||
private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase,
|
||||
private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase,
|
||||
) {
|
||||
|
||||
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
||||
|
@ -103,6 +104,14 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
|||
session.coroutineScope.launch { stopVoiceBroadcastUseCase.execute(room.roomId) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateUpdated(state: VoiceBroadcastRecorder.State) {
|
||||
if (state == VoiceBroadcastRecorder.State.Error) {
|
||||
session.coroutineScope.launch {
|
||||
pauseVoiceBroadcastUseCase.execute(room.roomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
voiceBroadcastRecorder?.startRecordVoiceBroadcast(voiceBroadcast, chunkLength, maxLength)
|
||||
}
|
||||
|
|
|
@ -40,51 +40,59 @@
|
|||
<TextView
|
||||
android:id="@+id/titleText"
|
||||
style="@style/Widget.Vector.TextView.Body.Medium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:contentDescription="@string/avatar"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
app:layout_constraintEnd_toStartOf="@id/liveIndicator"
|
||||
app:layout_constraintStart_toEndOf="@id/avatarRightBarrier"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@sample/rooms.json/data/name" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:id="@+id/metadataFlow"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:id="@+id/metadataGroup"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:orientation="vertical"
|
||||
app:constraint_referenced_ids="broadcasterNameMetadata,bufferingMetadata,voiceBroadcastMetadata,listenersCountMetadata"
|
||||
app:flow_horizontalAlign="start"
|
||||
app:flow_verticalGap="4dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/liveIndicator"
|
||||
app:layout_constraintStart_toEndOf="@id/avatarRightBarrier"
|
||||
app:layout_constraintTop_toBottomOf="@id/titleText" />
|
||||
app:layout_constraintTop_toBottomOf="@id/titleText">
|
||||
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/broadcasterNameMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:metadataIcon="@drawable/ic_voice_broadcast_mic"
|
||||
tools:metadataValue="@sample/users.json/data/displayName" />
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/broadcasterNameMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
app:metadataIcon="@drawable/ic_voice_broadcast_mic"
|
||||
tools:metadataValue="@sample/users.json/data/displayName" />
|
||||
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastBufferingView
|
||||
android:id="@+id/bufferingMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastBufferingView
|
||||
android:id="@+id/bufferingMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/voiceBroadcastMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:metadataIcon="@drawable/ic_voice_broadcast"
|
||||
app:metadataValue="@string/attachment_type_voice_broadcast" />
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/voiceBroadcastMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
app:metadataIcon="@drawable/ic_voice_broadcast"
|
||||
app:metadataValue="@string/attachment_type_voice_broadcast" />
|
||||
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/listenersCountMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:metadataIcon="@drawable/ic_member_small"
|
||||
app:metadataValue="@string/no_value_placeholder"
|
||||
tools:metadataValue="5 listeners" />
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/listenersCountMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
app:metadataIcon="@drawable/ic_member_small"
|
||||
app:metadataValue="@string/no_value_placeholder"
|
||||
tools:metadataValue="5 listeners" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/headerBottomBarrier"
|
||||
|
@ -92,7 +100,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:barrierMargin="10dp"
|
||||
app:constraint_referenced_ids="roomAvatarImageView,titleText,metadataFlow" />
|
||||
app:constraint_referenced_ids="roomAvatarImageView,titleText,metadataGroup" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:id="@+id/controllerButtonsFlow"
|
||||
|
|
|
@ -38,39 +38,45 @@
|
|||
<TextView
|
||||
android:id="@+id/titleText"
|
||||
style="@style/Widget.Vector.TextView.Body.Medium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:contentDescription="@string/avatar"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
app:layout_constraintEnd_toStartOf="@id/liveIndicator"
|
||||
app:layout_constraintStart_toEndOf="@id/avatarRightBarrier"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@sample/users.json/data/displayName" />
|
||||
tools:text="@sample/rooms.json/data/name" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:id="@+id/metadataFlow"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:id="@+id/metadataGroup"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:orientation="vertical"
|
||||
app:constraint_referenced_ids="listenersCountMetadata,remainingTimeMetadata"
|
||||
app:flow_horizontalAlign="start"
|
||||
app:flow_verticalGap="4dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/liveIndicator"
|
||||
app:layout_constraintStart_toEndOf="@id/avatarRightBarrier"
|
||||
app:layout_constraintTop_toBottomOf="@id/titleText" />
|
||||
app:layout_constraintTop_toBottomOf="@id/titleText">
|
||||
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/listenersCountMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:metadataIcon="@drawable/ic_member_small"
|
||||
app:metadataValue="@string/no_value_placeholder"
|
||||
tools:metadataValue="5 listening" />
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/listenersCountMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
app:metadataIcon="@drawable/ic_member_small"
|
||||
app:metadataValue="@string/no_value_placeholder"
|
||||
tools:metadataValue="5 listening" />
|
||||
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/remainingTimeMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:metadataIcon="@drawable/ic_timer"
|
||||
tools:metadataValue="3h 2m 50s left" />
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/remainingTimeMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
app:metadataIcon="@drawable/ic_timer"
|
||||
tools:metadataValue="3h 2m 50s left" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/headerBottomBarrier"
|
||||
|
@ -78,7 +84,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:barrierMargin="12dp"
|
||||
app:constraint_referenced_ids="roomAvatarImageView,titleText,metadataFlow" />
|
||||
app:constraint_referenced_ids="roomAvatarImageView,titleText,metadataGroup" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:id="@+id/controllerButtonsFlow"
|
||||
|
@ -107,4 +113,27 @@
|
|||
android:contentDescription="@string/a11y_stop_voice_broadcast_record"
|
||||
android:src="@drawable/ic_stop" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/controlsGroup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="controllerButtonsFlow,recordButton,stopRecordButton" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorView"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/error_voice_broadcast_no_connection_recording"
|
||||
android:textColor="?colorError"
|
||||
android:visibility="gone"
|
||||
app:drawableStartCompat="@drawable/ic_voice_broadcast_error"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/headerBottomBarrier"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -60,7 +60,8 @@ class StartVoiceBroadcastUseCaseTest {
|
|||
context = FakeContext().instance,
|
||||
buildMeta = mockk(),
|
||||
getRoomLiveVoiceBroadcastsUseCase = fakeGetRoomLiveVoiceBroadcastsUseCase,
|
||||
stopVoiceBroadcastUseCase = mockk()
|
||||
stopVoiceBroadcastUseCase = mockk(),
|
||||
pauseVoiceBroadcastUseCase = mockk(),
|
||||
)
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue