From bdfdf6e609f7c49f4776d86a85e0691bf3908bdd Mon Sep 17 00:00:00 2001 From: Roman Makeev <57789105+makeevrserg@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:02:28 +0000 Subject: [PATCH] Fix expandable signal verify (#892) **Background** When selecting signal inside Infrared remote setup - the "bottom sheet" displayed from the beginning. It should be displayed only when signal is dispatched. **Changes** - Add state to display "bottom sheet" only after signal is dispatched **Test plan** - Open signal select screen - Press signal - See the confirm menu animated and shown only after signal is dispatched - Press yes/no - See "bottom sheet" is hidden again --- CHANGELOG.md | 1 + .../remotecontrols/api/DispatchSignalApi.kt | 3 ++ .../impl/setup/composable/SetupScreen.kt | 1 - .../composable/components/LoadedContent.kt | 39 +++++++++++-------- .../presentation/decompose/SetupComponent.kt | 3 +- .../decompose/internal/SetupComponentImpl.kt | 11 ++++-- .../viewmodel/DispatchSignalViewModel.kt | 14 +++++++ 7 files changed, 51 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9787d3631f..01ccdd1554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [FIX] Button arrow tint - [FIX] Paddings for update button - [FIX] Crash on app startup with WearOS app +- [FIX] Expand verify signal bottom sheet only after signal is dispatched - [CI] Add https://github.com/LionZXY/detekt-decompose-rule - [CI] Enabling detekt module for android and kmp modules - [CI] Bump target SDK to 34 diff --git a/components/remote-controls/setup/api/src/main/kotlin/com/flipperdevices/remotecontrols/api/DispatchSignalApi.kt b/components/remote-controls/setup/api/src/main/kotlin/com/flipperdevices/remotecontrols/api/DispatchSignalApi.kt index 395594dae2..6c6620449c 100644 --- a/components/remote-controls/setup/api/src/main/kotlin/com/flipperdevices/remotecontrols/api/DispatchSignalApi.kt +++ b/components/remote-controls/setup/api/src/main/kotlin/com/flipperdevices/remotecontrols/api/DispatchSignalApi.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.StateFlow interface DispatchSignalApi : InstanceKeeper.Instance { val state: StateFlow + val isEmulated: StateFlow fun dismissBusyDialog() @@ -17,6 +18,8 @@ interface DispatchSignalApi : InstanceKeeper.Instance { */ fun dispatch(config: EmulateConfig) + fun reset() + /** * Dispatch specific key from custom located remote */ diff --git a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/composable/SetupScreen.kt b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/composable/SetupScreen.kt index 1b52742bc2..37df2e3be5 100644 --- a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/composable/SetupScreen.kt +++ b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/composable/SetupScreen.kt @@ -83,7 +83,6 @@ fun SetupScreen( } LoadedContent( model = model, - isEmulating = model.isEmulating, modifier = Modifier.padding(scaffoldPaddings), onPositiveClicked = setupComponent::onSuccessClicked, onNegativeClicked = setupComponent::onFailedClicked, diff --git a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/composable/components/LoadedContent.kt b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/composable/components/LoadedContent.kt index 95481760d9..d4b963f7aa 100644 --- a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/composable/components/LoadedContent.kt +++ b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/composable/components/LoadedContent.kt @@ -1,11 +1,14 @@ package com.flipperdevices.remotecontrols.impl.setup.composable.components import android.content.res.Configuration -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -18,7 +21,6 @@ import com.flipperdevices.remotecontrols.setup.impl.R as SetupR @Composable fun LoadedContent( model: SetupComponent.Model.Loaded, - isEmulating: Boolean, onPositiveClicked: () -> Unit, onNegativeClicked: () -> Unit, onDispatchSignalClicked: () -> Unit, @@ -26,28 +28,33 @@ fun LoadedContent( ) { val ifrFileModel = model.response.ifrFileModel val signalResponse = model.response.signalResponse - Column( - modifier = modifier.fillMaxSize(), - verticalArrangement = Arrangement.SpaceBetween - ) { + Box(modifier = modifier.fillMaxSize()) { when { ifrFileModel != null -> Unit signalResponse != null -> { - Box(modifier = Modifier) ButtonContent( onClicked = onDispatchSignalClicked, - modifier = Modifier, + modifier = Modifier.align(Alignment.Center), data = signalResponse.data, categoryName = signalResponse.categoryName, - isEmulating = isEmulating + isEmulating = model.isEmulating, ) - ConfirmContent( - text = signalResponse.message, - onNegativeClicked = onNegativeClicked, - onPositiveClicked = onPositiveClicked, + AnimatedVisibility( + visible = model.isEmulated, + enter = slideInVertically(initialOffsetY = { it / 2 }), + exit = slideOutVertically(), modifier = Modifier - ) + .fillMaxWidth() + .align(Alignment.BottomCenter), + ) { + ConfirmContent( + text = signalResponse.message, + onNegativeClicked = onNegativeClicked, + onPositiveClicked = onPositiveClicked, + modifier = Modifier.align(Alignment.BottomCenter) + ) + } } else -> { @@ -70,8 +77,8 @@ private fun LoadedContentPreview() { LoadedContent( model = SetupComponent.Model.Loaded( response = SignalResponseModel(), + isEmulated = true ), - isEmulating = true, onPositiveClicked = {}, onNegativeClicked = {}, onDispatchSignalClicked = {} diff --git a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/decompose/SetupComponent.kt b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/decompose/SetupComponent.kt index ca8f52548e..c0a1324eea 100644 --- a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/decompose/SetupComponent.kt +++ b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/decompose/SetupComponent.kt @@ -31,7 +31,8 @@ interface SetupComponent { data class Loaded( val response: SignalResponseModel, val isFlipperBusy: Boolean = false, - val isEmulating: Boolean = false + val isEmulating: Boolean = false, + val isEmulated: Boolean ) : Model data object Error : Model diff --git a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/decompose/internal/SetupComponentImpl.kt b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/decompose/internal/SetupComponentImpl.kt index 79614f5020..140be094cc 100644 --- a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/decompose/internal/SetupComponentImpl.kt +++ b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/decompose/internal/SetupComponentImpl.kt @@ -74,7 +74,8 @@ class SetupComponentImpl @AssistedInject constructor( createCurrentSignalViewModel.state, saveSignalApi.state, dispatchSignalApi.state, - transform = { signalState, saveState, dispatchState -> + dispatchSignalApi.isEmulated, + transform = { signalState, saveState, dispatchState, isEmulated -> when (signalState) { CurrentSignalViewModel.State.Error -> SetupComponent.Model.Error is CurrentSignalViewModel.State.Loaded -> { @@ -83,13 +84,15 @@ class SetupComponentImpl @AssistedInject constructor( SaveTempSignalApi.State.Pending -> SetupComponent.Model.Loaded( response = signalState.response, isFlipperBusy = dispatchState is DispatchSignalApi.State.FlipperIsBusy, - isEmulating = dispatchState is DispatchSignalApi.State.Emulating + isEmulating = dispatchState is DispatchSignalApi.State.Emulating, + isEmulated = isEmulated ) SaveTempSignalApi.State.Uploaded -> SetupComponent.Model.Loaded( response = signalState.response, isFlipperBusy = dispatchState is DispatchSignalApi.State.FlipperIsBusy, - isEmulating = dispatchState is DispatchSignalApi.State.Emulating + isEmulating = dispatchState is DispatchSignalApi.State.Emulating, + isEmulated = isEmulated ) is SaveTempSignalApi.State.Uploading -> SetupComponent.Model.Loading( @@ -115,6 +118,8 @@ class SetupComponentImpl @AssistedInject constructor( } override fun tryLoad() { + if (dispatchSignalApi.state.value is DispatchSignalApi.State.Emulating) return + dispatchSignalApi.reset() createCurrentSignalViewModel.load( successResults = historyViewModel.state.value.successfulSignals, failedResults = historyViewModel.state.value.failedSignals diff --git a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/viewmodel/DispatchSignalViewModel.kt b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/viewmodel/DispatchSignalViewModel.kt index 6a4cfe6fa8..708ca8fbef 100644 --- a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/viewmodel/DispatchSignalViewModel.kt +++ b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/viewmodel/DispatchSignalViewModel.kt @@ -22,6 +22,7 @@ import com.flipperdevices.remotecontrols.impl.setup.util.toByteArray import com.squareup.anvil.annotations.ContributesBinding import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -40,8 +41,20 @@ class DispatchSignalViewModel @Inject constructor( private val _state = MutableStateFlow(DispatchSignalApi.State.Pending) override val state = _state.asStateFlow() + + private val _isEmulated = MutableStateFlow(false) + override val isEmulated = _isEmulated.asStateFlow() + private var latestDispatchJob: Job? = null + override fun reset() { + viewModelScope.launch { + latestDispatchJob?.cancelAndJoin() + _state.value = DispatchSignalApi.State.Pending + _isEmulated.value = false + } + } + override fun dispatch( identifier: IfrKeyIdentifier, remotes: List, @@ -101,6 +114,7 @@ class DispatchSignalViewModel @Inject constructor( delay(DEFAULT_SIGNAL_DELAY) emulateHelper.stopEmulate(this, serviceApi.requestApi) _state.emit(DispatchSignalApi.State.Pending) + _isEmulated.emit(true) } catch (ignored: AlreadyOpenedAppException) { _state.emit(DispatchSignalApi.State.FlipperIsBusy) } catch (e: Exception) {