diff --git a/CHANGELOG.md b/CHANGELOG.md index ba60605d96..82d0fded8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [Feature] Add count subfolders for new file manager - [Feature] Add file downloading for new file manager - [Feature] Add move-to to new file manager +- [Feature] Single tap for infrared remotes - [Refactor] Move rename and file create to separated modules - [Refactor] Improve and refactor new FileManager Editor - [FIX] Migrate url host from metric.flipperdevices.com to metric.flipp.dev diff --git a/components/bridge/api/src/main/java/com/flipperdevices/bridge/api/utils/Constants.kt b/components/bridge/api/src/main/java/com/flipperdevices/bridge/api/utils/Constants.kt index 27228afdca..1d5dcc9704 100644 --- a/components/bridge/api/src/main/java/com/flipperdevices/bridge/api/utils/Constants.kt +++ b/components/bridge/api/src/main/java/com/flipperdevices/bridge/api/utils/Constants.kt @@ -33,6 +33,10 @@ object Constants { majorVersion = 0, minorVersion = 21 ) + val API_SUPPORTED_INFRARED_PRESS_RELEASE = SemVer( + majorVersion = 0, + minorVersion = 25 + ) val API_SUPPORTED_GET_REQUEST = API_SUPPORTED_FLIPPER_ERROR const val LAGS_FLIPPER_DETECT_TIMEOUT_MS = 10 * 1000L // 10 seconds diff --git a/components/bridge/pbutils/src/main/proto b/components/bridge/pbutils/src/main/proto index 23ad19a756..1c84fa4891 160000 --- a/components/bridge/pbutils/src/main/proto +++ b/components/bridge/pbutils/src/main/proto @@ -1 +1 @@ -Subproject commit 23ad19a756649ed9f6677b598e5361c5cce6847b +Subproject commit 1c84fa48919cbb71d1cc65236fc0ee36740e24c6 diff --git a/components/infrared/impl/src/main/kotlin/com/flipperdevices/infrared/impl/composable/components/ComposableInfraredRemotes.kt b/components/infrared/impl/src/main/kotlin/com/flipperdevices/infrared/impl/composable/components/ComposableInfraredRemotes.kt index 3fa446fc4e..d7bcd20aea 100644 --- a/components/infrared/impl/src/main/kotlin/com/flipperdevices/infrared/impl/composable/components/ComposableInfraredRemotes.kt +++ b/components/infrared/impl/src/main/kotlin/com/flipperdevices/infrared/impl/composable/components/ComposableInfraredRemotes.kt @@ -116,7 +116,7 @@ private fun KeyScreenState.Ready.toEmulateConfigs(): ImmutableList Unit, serviceApi: FlipperServiceApi ): Boolean { @@ -125,7 +132,17 @@ class StartEmulateHelperImpl @Inject constructor( val appButtonPressResponse = serviceApi.requestApi.request( flowOf( main { - appButtonPressRequest = getAppButtonPressRequest(config, isIndexEmulateSupport) + if (config.isPressRelease && isPressReleaseSupported) { + appButtonPressReleaseRequest = getAppButtonPressReleaseRequest( + config, + isIndexEmulateSupport + ) + } else { + appButtonPressRequest = getAppButtonPressRequest( + config, + isIndexEmulateSupport + ) + } }.wrapToRequest(FlipperRequestPriority.FOREGROUND) ) ) @@ -164,6 +181,29 @@ class StartEmulateHelperImpl @Inject constructor( } } + private fun getAppButtonPressReleaseRequest( + config: EmulateConfig, + isIndexEmulateSupport: Boolean, + ): Application.AppButtonPressReleaseRequest { + return when (config.keyType) { + FlipperKeyType.INFRARED -> if (isIndexEmulateSupport) { + val indexArgs = config.index ?: error("Index args is null") + info { "#getAppButtonPressReleaseRequest by index with $config" } + appButtonPressReleaseRequest { + index = indexArgs + } + } else { + val configArgs = config.args ?: error("Config args is null") + info { "#getAppButtonPressReleaseRequest by args with $config" } + appButtonPressReleaseRequest { + args = configArgs + } + } + + else -> error("#getAppButtonPressReleaseRequest Unknown button press request with config $config") + } + } + private fun getAppButtonPressRequest( config: EmulateConfig, isIndexEmulateSupport: Boolean, @@ -183,6 +223,7 @@ class StartEmulateHelperImpl @Inject constructor( args = configArgs } } + else -> error("Unknown button press request with config $config") } } diff --git a/components/keyemulate/impl/src/main/java/com/flipperdevices/keyemulate/helpers/StopEmulateHelper.kt b/components/keyemulate/impl/src/main/java/com/flipperdevices/keyemulate/helpers/StopEmulateHelper.kt index 7f73987d99..48d0ec2380 100644 --- a/components/keyemulate/impl/src/main/java/com/flipperdevices/keyemulate/helpers/StopEmulateHelper.kt +++ b/components/keyemulate/impl/src/main/java/com/flipperdevices/keyemulate/helpers/StopEmulateHelper.kt @@ -14,24 +14,29 @@ import kotlinx.coroutines.flow.flowOf import javax.inject.Inject interface StopEmulateHelper { - suspend fun onStop(requestApi: FlipperRequestApi) + suspend fun onStop( + requestApi: FlipperRequestApi, + isPressRelease: Boolean = false + ) } @ContributesBinding(AppGraph::class, StopEmulateHelper::class) class StopEmulateHelperImpl @Inject constructor() : StopEmulateHelper, LogTagProvider { override val TAG = "StopEmulateHelper" - override suspend fun onStop(requestApi: FlipperRequestApi) { + override suspend fun onStop(requestApi: FlipperRequestApi, isPressRelease: Boolean) { info { "stopEmulateInternal" } - val appButtonResponse = requestApi.request( - flowOf( - main { - appButtonReleaseRequest = appButtonReleaseRequest { } - }.wrapToRequest(FlipperRequestPriority.FOREGROUND) + if (!isPressRelease) { + val appButtonResponse = requestApi.request( + flowOf( + main { + appButtonReleaseRequest = appButtonReleaseRequest { } + }.wrapToRequest(FlipperRequestPriority.FOREGROUND) + ) ) - ) - info { "App button stop response: $appButtonResponse" } + info { "App button stop response: $appButtonResponse" } + } val appExitResponse = requestApi.request( flowOf( diff --git a/components/keyemulate/impl/src/main/java/com/flipperdevices/keyemulate/viewmodel/InfraredViewModel.kt b/components/keyemulate/impl/src/main/java/com/flipperdevices/keyemulate/viewmodel/InfraredViewModel.kt index d7fb655728..5bc541db6f 100644 --- a/components/keyemulate/impl/src/main/java/com/flipperdevices/keyemulate/viewmodel/InfraredViewModel.kt +++ b/components/keyemulate/impl/src/main/java/com/flipperdevices/keyemulate/viewmodel/InfraredViewModel.kt @@ -2,6 +2,7 @@ package com.flipperdevices.keyemulate.viewmodel import android.app.Application import androidx.datastore.core.DataStore +import com.flipperdevices.bridge.api.utils.Constants import com.flipperdevices.bridge.dao.api.model.FlipperKeyType import com.flipperdevices.bridge.service.api.FlipperServiceApi import com.flipperdevices.bridge.service.api.provider.FlipperServiceProvider @@ -116,6 +117,7 @@ class InfraredViewModel @Inject constructor( emulateHelper.stopEmulate(viewModelScope, serviceApi.requestApi) } + @Suppress("CyclomaticComplexMethod", "LongMethod") private suspend fun calculateTimeoutAndStartEmulate( scope: CoroutineScope, serviceApi: FlipperServiceApi, @@ -125,12 +127,13 @@ class InfraredViewModel @Inject constructor( val requestApi = serviceApi.requestApi val timeout = config.minEmulateTime val appStarted: Boolean? - + val isPressReleaseSupported = + serviceApi.flipperVersionApi.isSupported(Constants.API_SUPPORTED_INFRARED_PRESS_RELEASE) try { appStarted = emulateHelper.startEmulate( - scope, - serviceApi, - config + scope = scope, + serviceApi = serviceApi, + config = config, ) if (appStarted && timeout != null) { if (oneTimePress) { @@ -150,22 +153,34 @@ class InfraredViewModel @Inject constructor( } } } catch (ignored: AlreadyOpenedAppException) { - emulateHelper.stopEmulateForce(requestApi) + emulateHelper.stopEmulateForce( + requestApi = requestApi, + isPressRelease = isPressReleaseSupported && oneTimePress + ) emulateButtonStateFlow.emit(EmulateButtonState.AppAlreadyOpenDialog) return false } catch (ignored: ForbiddenFrequencyException) { - emulateHelper.stopEmulateForce(requestApi) + emulateHelper.stopEmulateForce( + requestApi = requestApi, + isPressRelease = isPressReleaseSupported && oneTimePress + ) emulateButtonStateFlow.emit(EmulateButtonState.ForbiddenFrequencyDialog) return false } catch (fatal: Throwable) { error(fatal) { "Handle fatal exception on emulate infrared" } - emulateHelper.stopEmulateForce(requestApi) + emulateHelper.stopEmulateForce( + requestApi = requestApi, + isPressRelease = isPressReleaseSupported && oneTimePress + ) emulateButtonStateFlow.emit(EmulateButtonState.Inactive()) return false } if (!appStarted) { info { "Failed start emulation" } - emulateHelper.stopEmulateForce(requestApi) + emulateHelper.stopEmulateForce( + requestApi = requestApi, + isPressRelease = isPressReleaseSupported && oneTimePress + ) emulateButtonStateFlow.emit(EmulateButtonState.Inactive()) } 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 e7d82ee2b2..f72899c601 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 @@ -18,7 +18,6 @@ interface DispatchSignalApi : InstanceKeeper.Instance { fun dispatch( config: EmulateConfig, identifier: IfrKeyIdentifier, - isOneTime: Boolean = true, onDispatched: () -> Unit = {} ) diff --git a/components/remote-controls/setup/impl/build.gradle.kts b/components/remote-controls/setup/impl/build.gradle.kts index 574da10af5..68fe25f123 100644 --- a/components/remote-controls/setup/impl/build.gradle.kts +++ b/components/remote-controls/setup/impl/build.gradle.kts @@ -9,6 +9,7 @@ dependencies { implementation(projects.components.core.di) implementation(projects.components.core.ktx) implementation(projects.components.core.log) + implementation(projects.components.core.data) implementation(projects.components.core.ui.lifecycle) implementation(projects.components.core.ui.theme) 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 879148280e..d7156f9582 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 @@ -205,7 +205,8 @@ class SetupComponentImpl @AssistedInject constructor( ), keyType = FlipperKeyType.INFRARED, args = signalModel.remote.name, - index = 0 + index = 0, + isPressRelease = true ) val keyIdentifier = (loadedState.response.signalResponse?.data as? SingleKeyButtonData) ?.keyIdentifier 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 e3c8f99c5e..41a1f0f720 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 @@ -4,6 +4,7 @@ import android.content.Context import android.os.Vibrator import androidx.core.content.ContextCompat import androidx.datastore.core.DataStore +import com.flipperdevices.bridge.api.utils.Constants import com.flipperdevices.bridge.dao.api.model.FlipperFilePath import com.flipperdevices.bridge.dao.api.model.FlipperKeyType import com.flipperdevices.bridge.service.api.FlipperServiceApi @@ -114,12 +115,12 @@ class DispatchSignalViewModel @Inject constructor( keyPath = ffPath, keyType = FlipperKeyType.INFRARED, args = remote.name, - index = i + index = i, + isPressRelease = isOneTime ) dispatch( config = config, identifier = identifier, - isOneTime = isOneTime, onDispatched = onDispatched ) } @@ -131,7 +132,6 @@ class DispatchSignalViewModel @Inject constructor( override fun dispatch( config: EmulateConfig, identifier: IfrKeyIdentifier, - isOneTime: Boolean, onDispatched: () -> Unit ) { if (latestDispatchJob?.isActive == true) return @@ -142,6 +142,8 @@ class DispatchSignalViewModel @Inject constructor( onError = { _state.value = DispatchSignalApi.State.Error }, onBleManager = { serviceApi -> launch { + val isPressReleaseSupported = + serviceApi.flipperVersionApi.isSupported(Constants.API_SUPPORTED_INFRARED_PRESS_RELEASE) vibrator?.vibrateCompat( VIBRATOR_TIME, settings.data.first().disabled_vibration @@ -151,14 +153,20 @@ class DispatchSignalViewModel @Inject constructor( emulateHelper.startEmulate( scope = this, serviceApi = serviceApi, - config = config + config = config, ) - if (isOneTime) { + if (config.isPressRelease && isPressReleaseSupported) { + _state.emit(DispatchSignalApi.State.Pending) + } else if (config.isPressRelease) { delay(DEFAULT_SIGNAL_DELAY) - emulateHelper.stopEmulate(this, serviceApi.requestApi) _state.emit(DispatchSignalApi.State.Pending) - onDispatched.invoke() } + emulateHelper.stopEmulate( + scope = this, + requestApi = serviceApi.requestApi, + isPressRelease = config.isPressRelease && isPressReleaseSupported + ) + onDispatched.invoke() } catch (ignored: AlreadyOpenedAppException) { _state.emit(DispatchSignalApi.State.FlipperIsBusy) } catch (e: Exception) {