You've already forked element-android
mirror of
https://github.com/vector-im/element-android.git
synced 2025-08-09 01:42:06 +03:00
Pause voice broadcast if there is no network
This commit is contained in:
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_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_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_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 -->
|
<!-- 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="voice_broadcast_recording_time_left">%1$s left</string>
|
||||||
<string name="stop_voice_broadcast_dialog_title">Stop live broadcasting?</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
|
package im.vector.app.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.constraintlayout.widget.Group
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
@@ -55,11 +57,11 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun renderLiveIndicator(holder: Holder) {
|
override fun renderLiveIndicator(holder: Holder) {
|
||||||
when (voiceBroadcastState) {
|
when (recorder?.recordingState) {
|
||||||
VoiceBroadcastState.STARTED,
|
VoiceBroadcastRecorder.State.Recording -> renderPlayingLiveIndicator(holder)
|
||||||
VoiceBroadcastState.RESUMED -> renderPlayingLiveIndicator(holder)
|
VoiceBroadcastRecorder.State.Error,
|
||||||
VoiceBroadcastState.PAUSED -> renderPausedLiveIndicator(holder)
|
VoiceBroadcastRecorder.State.Paused -> renderPausedLiveIndicator(holder)
|
||||||
VoiceBroadcastState.STOPPED, null -> renderNoLiveIndicator(holder)
|
VoiceBroadcastRecorder.State.Idle, null -> renderNoLiveIndicator(holder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +87,9 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
|
|||||||
VoiceBroadcastRecorder.State.Recording -> renderRecordingState(holder)
|
VoiceBroadcastRecorder.State.Recording -> renderRecordingState(holder)
|
||||||
VoiceBroadcastRecorder.State.Paused -> renderPausedState(holder)
|
VoiceBroadcastRecorder.State.Paused -> renderPausedState(holder)
|
||||||
VoiceBroadcastRecorder.State.Idle -> renderStoppedState(holder)
|
VoiceBroadcastRecorder.State.Idle -> renderStoppedState(holder)
|
||||||
|
VoiceBroadcastRecorder.State.Error -> renderErrorState(holder, true)
|
||||||
}
|
}
|
||||||
|
renderLiveIndicator(holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderVoiceBroadcastState(holder: Holder) {
|
private fun renderVoiceBroadcastState(holder: Holder) {
|
||||||
@@ -101,6 +105,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
|
|||||||
private fun renderRecordingState(holder: Holder) = with(holder) {
|
private fun renderRecordingState(holder: Holder) = with(holder) {
|
||||||
stopRecordButton.isEnabled = true
|
stopRecordButton.isEnabled = true
|
||||||
recordButton.isEnabled = true
|
recordButton.isEnabled = true
|
||||||
|
renderErrorState(holder, false)
|
||||||
|
|
||||||
val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
||||||
val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor)
|
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) {
|
private fun renderPausedState(holder: Holder) = with(holder) {
|
||||||
stopRecordButton.isEnabled = true
|
stopRecordButton.isEnabled = true
|
||||||
recordButton.isEnabled = true
|
recordButton.isEnabled = true
|
||||||
|
renderErrorState(holder, false)
|
||||||
|
|
||||||
recordButton.setImageResource(R.drawable.ic_recording_dot)
|
recordButton.setImageResource(R.drawable.ic_recording_dot)
|
||||||
recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record)
|
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) {
|
private fun renderStoppedState(holder: Holder) = with(holder) {
|
||||||
recordButton.isEnabled = false
|
recordButton.isEnabled = false
|
||||||
stopRecordButton.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) {
|
override fun unbind(holder: Holder) {
|
||||||
@@ -142,6 +154,8 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
|
|||||||
val remainingTimeMetadata by bind<VoiceBroadcastMetadataView>(R.id.remainingTimeMetadata)
|
val remainingTimeMetadata by bind<VoiceBroadcastMetadataView>(R.id.remainingTimeMetadata)
|
||||||
val recordButton by bind<ImageButton>(R.id.recordButton)
|
val recordButton by bind<ImageButton>(R.id.recordButton)
|
||||||
val stopRecordButton by bind<ImageButton>(R.id.stopRecordButton)
|
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 {
|
companion object {
|
||||||
|
@@ -33,6 +33,8 @@ interface VoiceBroadcastRecorder : VoiceRecorder {
|
|||||||
val currentRemainingTime: Long?
|
val currentRemainingTime: Long?
|
||||||
|
|
||||||
fun startRecordVoiceBroadcast(voiceBroadcast: VoiceBroadcast, chunkLength: Int, maxLength: Int)
|
fun startRecordVoiceBroadcast(voiceBroadcast: VoiceBroadcast, chunkLength: Int, maxLength: Int)
|
||||||
|
|
||||||
|
fun pauseOnError()
|
||||||
fun addListener(listener: Listener)
|
fun addListener(listener: Listener)
|
||||||
fun removeListener(listener: Listener)
|
fun removeListener(listener: Listener)
|
||||||
|
|
||||||
@@ -46,5 +48,6 @@ interface VoiceBroadcastRecorder : VoiceRecorder {
|
|||||||
Recording,
|
Recording,
|
||||||
Paused,
|
Paused,
|
||||||
Idle,
|
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.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
|
||||||
import im.vector.lib.core.utils.timer.CountUpTimer
|
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
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.CopyOnWriteArrayList
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@@ -47,6 +51,7 @@ class VoiceBroadcastRecorderQ(
|
|||||||
private val sessionScope get() = session.coroutineScope
|
private val sessionScope get() = session.coroutineScope
|
||||||
|
|
||||||
private var voiceBroadcastStateObserver: Job? = null
|
private var voiceBroadcastStateObserver: Job? = null
|
||||||
|
private var syncStateObserver: Job? = null
|
||||||
|
|
||||||
private var maxFileSize = 0L // zero or negative for no limit
|
private var maxFileSize = 0L // zero or negative for no limit
|
||||||
private var currentVoiceBroadcast: VoiceBroadcast? = null
|
private var currentVoiceBroadcast: VoiceBroadcast? = null
|
||||||
@@ -96,21 +101,36 @@ class VoiceBroadcastRecorderQ(
|
|||||||
observeVoiceBroadcastStateEvent(voiceBroadcast)
|
observeVoiceBroadcastStateEvent(voiceBroadcast)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pauseRecord() {
|
override fun startRecord(roomId: String) {
|
||||||
|
super.startRecord(roomId)
|
||||||
|
observeConnectionState()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pauseOnError() {
|
||||||
if (recordingState != VoiceBroadcastRecorder.State.Recording) return
|
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
|
recordingState = VoiceBroadcastRecorder.State.Paused
|
||||||
recordingTicker.pause()
|
|
||||||
notifyOutputFileCreated()
|
notifyOutputFileCreated()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resumeRecord() {
|
override fun resumeRecord() {
|
||||||
if (recordingState != VoiceBroadcastRecorder.State.Paused) return
|
if (recordingState != VoiceBroadcastRecorder.State.Paused) return
|
||||||
|
|
||||||
currentSequence++
|
currentSequence++
|
||||||
currentVoiceBroadcast?.let { startRecord(it.roomId) }
|
currentVoiceBroadcast?.let { startRecord(it.roomId) }
|
||||||
recordingState = VoiceBroadcastRecorder.State.Recording
|
recordingState = VoiceBroadcastRecorder.State.Recording
|
||||||
recordingTicker.resume()
|
recordingTicker.resume()
|
||||||
|
observeConnectionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopRecord() {
|
override fun stopRecord() {
|
||||||
@@ -128,6 +148,8 @@ class VoiceBroadcastRecorderQ(
|
|||||||
voiceBroadcastStateObserver?.cancel()
|
voiceBroadcastStateObserver?.cancel()
|
||||||
voiceBroadcastStateObserver = null
|
voiceBroadcastStateObserver = null
|
||||||
|
|
||||||
|
stopObservingConnectionState()
|
||||||
|
|
||||||
// Reset data
|
// Reset data
|
||||||
currentSequence = 0
|
currentSequence = 0
|
||||||
currentMaxLength = 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 inner class RecordingTicker(
|
||||||
private var recordingTicker: CountUpTimer? = null,
|
private var recordingTicker: CountUpTimer? = null,
|
||||||
) {
|
) {
|
||||||
|
@@ -16,17 +16,25 @@
|
|||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.recording.usecase
|
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.VoiceBroadcastConstants
|
||||||
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
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
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.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
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.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
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.Room
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
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 timber.log.Timber
|
||||||
import javax.inject.Inject
|
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")
|
Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
||||||
|
|
||||||
// save the last sequence number and immediately pause the recording
|
try {
|
||||||
val lastSequence = voiceBroadcastRecorder?.currentSequence
|
// save the last sequence number and immediately pause the recording
|
||||||
pauseRecording()
|
val lastSequence = voiceBroadcastRecorder?.currentSequence
|
||||||
|
|
||||||
room.stateService().sendStateEvent(
|
room.stateService().sendStateEvent(
|
||||||
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
stateKey = session.myUserId,
|
stateKey = session.myUserId,
|
||||||
body = MessageVoiceBroadcastInfoContent(
|
body = MessageVoiceBroadcastInfoContent(
|
||||||
relatesTo = reference,
|
relatesTo = reference,
|
||||||
voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value,
|
voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value,
|
||||||
lastChunkSequence = lastSequence,
|
lastChunkSequence = lastSequence,
|
||||||
).toContent(),
|
).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 buildMeta: BuildMeta,
|
||||||
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||||
private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase,
|
private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase,
|
||||||
|
private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
||||||
@@ -103,6 +104,14 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
|||||||
session.coroutineScope.launch { stopVoiceBroadcastUseCase.execute(room.roomId) }
|
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)
|
voiceBroadcastRecorder?.startRecordVoiceBroadcast(voiceBroadcast, chunkLength, maxLength)
|
||||||
}
|
}
|
||||||
|
@@ -107,4 +107,27 @@
|
|||||||
android:contentDescription="@string/a11y_stop_voice_broadcast_record"
|
android:contentDescription="@string/a11y_stop_voice_broadcast_record"
|
||||||
android:src="@drawable/ic_stop" />
|
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@@ -60,7 +60,8 @@ class StartVoiceBroadcastUseCaseTest {
|
|||||||
context = FakeContext().instance,
|
context = FakeContext().instance,
|
||||||
buildMeta = mockk(),
|
buildMeta = mockk(),
|
||||||
getRoomLiveVoiceBroadcastsUseCase = fakeGetRoomLiveVoiceBroadcastsUseCase,
|
getRoomLiveVoiceBroadcastsUseCase = fakeGetRoomLiveVoiceBroadcastsUseCase,
|
||||||
stopVoiceBroadcastUseCase = mockk()
|
stopVoiceBroadcastUseCase = mockk(),
|
||||||
|
pauseVoiceBroadcastUseCase = mockk(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user