Skip to content

Commit

Permalink
Save and view infrared remote control (#918)
Browse files Browse the repository at this point in the history
**Background**

This PR introduces saving and opening saved infrared remote controls
from mobile application.

**Changes**

- Save Remote Control
- Open Remote Control from infrared screen

**Test plan**

- Find some remote control
- Click save to save remote on grid screen
- Enter name and save it on infrared remote rename screen
- See configuring screen which will configure remote control for further
usage
- Go to archive and try open saved remote control
- See the grid screen is displayed
  • Loading branch information
makeevrserg authored Aug 20, 2024
1 parent 1ed726f commit 45b8202
Show file tree
Hide file tree
Showing 110 changed files with 2,489 additions and 633 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/call-gradle-cache.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
gradle.properties
matrx_update_gradle:
name: "Build ${{ matrix.target }}"
runs-on: [ self-hosted, AndroidShell ]
runs-on: ubuntu-latest
needs: check_gradle_files_change
if: needs.check_gradle_files_change.outputs.GRADLE_FILES_CHANGED == 'true'
strategy:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
echo "github.repository=${{ github.repository }}"
test:
name: "Run unit tests"
runs-on: [ self-hosted, AndroidShell ]
runs-on: ubuntu-latest
needs: [ validate_gradle_wrapper ]
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
Expand All @@ -60,7 +60,7 @@ jobs:
arguments: testDebugUnitTest desktopTest
detekt:
name: "Check project by detekt and android`s lint"
runs-on: [ self-hosted, AndroidShell ]
runs-on: ubuntu-latest
needs: [ validate_gradle_wrapper ]
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Attention: don't forget to add the flag for F-Droid before release
- [Feature] Infrared controls
- [Feature] Remove bond on retry pair
- [Feature] Add onetap widget
- [Feature] Save, edit, share remote control
- [Refactor] Load RemoteControls from flipper, emulating animation
- [Refactor] Update to Kotlin 2.0
- [Refactor] Replace Ktorfit with Ktor requests in remote-controls
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package com.flipperdevices.bridge.dao.api.model

const val SHADOW_FILE_EXTENSION = "shd"
const val UI_INFRARED_EXTENSION = "irui"

/**
* Order is important
*/
enum class FlipperFileType {
KEY,
SHADOW_NFC,
OTHER;
OTHER,
UI_INFRARED;

companion object {
fun getByExtension(extension: String): FlipperFileType {
return when (extension) {
SHADOW_FILE_EXTENSION -> SHADOW_NFC
UI_INFRARED_EXTENSION -> UI_INFRARED
else -> if (FlipperKeyType.getByExtension(extension) != null) {
KEY
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import java.io.InputStream
* May be a stream, a file, a link, or bytes.
* Do not limit your support to only one type of content.
*/
@Serializable
sealed class FlipperKeyContent : Parcelable {
@Parcelize
data class RawData(val bytes: ByteArray) : FlipperKeyContent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,19 @@ class FlipperFileApiImpl @Inject constructor(
}

override suspend fun insert(file: FlipperFile) {
if (file.path.fileType != FlipperFileType.SHADOW_NFC) {
error("There is support only for NFC shadow files at the moment")
val extension = when (file.path.fileType) {
FlipperFileType.SHADOW_NFC -> FlipperKeyType.NFC.extension
FlipperFileType.UI_INFRARED -> FlipperKeyType.INFRARED.extension
else -> {
error("There is support only for NFC shadow and INFRARED UI files at the moment")
}
}
val pathToKeyFile = FlipperFilePath(
folder = file.path.folder,
nameWithExtension = "${file.path.nameWithoutExtension}.${FlipperKeyType.NFC.extension}"
nameWithExtension = "${file.path.nameWithoutExtension}.$extension"
)
val foundedKey = simpleKeyDao.getByPath(pathToKeyFile.pathToKey, deleted = false)
?: error("Can't find nfc key for ${file.path}")
?: error("Can't find nfc or infrared key for ${file.path}")

val faf = FlipperAdditionalFile(
path = file.path.pathToKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ class AndroidKeyStorage @Inject constructor(
FlipperFileType.KEY -> simpleKeyApi.getKey(
FlipperKeyPath(filePath, deleted = false)
)?.mainFile?.content ?: error("Can't found $filePath")
FlipperFileType.SHADOW_NFC -> flipperFileApi.getFile(filePath).content

FlipperFileType.SHADOW_NFC,
FlipperFileType.UI_INFRARED -> flipperFileApi.getFile(filePath).content

FlipperFileType.OTHER ->
error("I cannot process a file that is neither a key nor a shadow file: $filePath")
}
Expand All @@ -49,8 +52,11 @@ class AndroidKeyStorage @Inject constructor(
),
newContent
)

FlipperFileType.UI_INFRARED,
FlipperFileType.SHADOW_NFC ->
flipperFileApi.updateFileContent(filePath, newContent)

FlipperFileType.OTHER ->
error("I cannot process a file that is neither a key nor a shadow file: $filePath")
}
Expand All @@ -69,7 +75,10 @@ class AndroidKeyStorage @Inject constructor(
deleted = false
)
)

FlipperFileType.UI_INFRARED,
FlipperFileType.SHADOW_NFC -> flipperFileApi.insert(FlipperFile(filePath, keyContent))

FlipperFileType.OTHER ->
error("I cannot process a file that is neither a key nor a shadow file: $filePath")
}
Expand All @@ -79,7 +88,9 @@ class AndroidKeyStorage @Inject constructor(
info { "Mark delete key $filePath" }
when (filePath.fileType) {
FlipperFileType.KEY -> deleteKeyApi.markDeleted(filePath)
FlipperFileType.UI_INFRARED,
FlipperFileType.SHADOW_NFC -> flipperFileApi.deleteFile(filePath)

FlipperFileType.OTHER ->
error("I cannot process a file that is neither a key nor a shadow file: $filePath")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class DiffKeyExecutorImpl @Inject constructor() : DiffKeyExecutor, LogTagProvide
}

FlipperFileType.SHADOW_NFC -> FlipperKeyType.NFC.flipperDir
FlipperFileType.UI_INFRARED -> FlipperKeyType.INFRARED.flipperDir
FlipperFileType.OTHER -> error("Don't support file with this type")
}
val targetPath = FlipperFilePath(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ class FlipperHashRepositoryImpl @Inject constructor(
) {
return true
}
if (FlipperFileType.getByExtension(extension) == FlipperFileType.UI_INFRARED &&
requestedType == FlipperKeyType.INFRARED
) {
return true
}
val fileTypeByExtension = FlipperKeyType.getByExtension(extension)
if (fileTypeByExtension == null) {
debug {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ object KeyDiffCombiner {
private fun resolveConflictBothAdd(first: KeyDiff, second: KeyDiff): KeyDiff? {
return if (first.newHash.hash == second.newHash.hash) {
null
} else if (first.newHash.keyPath.fileType == FlipperFileType.SHADOW_NFC) {
} else if (listOf(FlipperFileType.SHADOW_NFC, FlipperFileType.UI_INFRARED)
.contains(first.newHash.keyPath.fileType)
) {
/**
* If the file is shadow, we always give priority
* to a copy of the file from the flipper.
Expand Down
3 changes: 3 additions & 0 deletions components/infrared/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ dependencies {
implementation(projects.components.bridge.pbutils)
implementation(projects.components.bridge.synchronization.api)

implementation(projects.components.remoteControls.grid.main.api)
implementation(projects.components.remoteControls.grid.saved.api)

implementation(projects.components.core.di)
implementation(projects.components.core.log)
implementation(projects.components.core.data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,43 @@ package com.flipperdevices.infrared.impl.api
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pushNew
import com.arkivanov.decompose.router.stack.replaceCurrent
import com.arkivanov.decompose.value.Value
import com.flipperdevices.bridge.dao.api.model.FlipperKeyPath
import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.infrared.api.InfraredDecomposeComponent
import com.flipperdevices.infrared.api.InfraredEditorDecomposeComponent
import com.flipperdevices.infrared.impl.model.InfraredNavigationConfig
import com.flipperdevices.keyedit.api.KeyEditDecomposeComponent
import com.flipperdevices.remotecontrols.impl.grid.local.api.LocalGridScreenDecomposeComponent
import com.flipperdevices.ui.decompose.DecomposeComponent
import com.flipperdevices.ui.decompose.DecomposeOnBackParameter
import com.flipperdevices.ui.decompose.popOr
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import me.gulya.anvil.assisted.ContributesAssistedFactory

@Suppress("LongParameterList")
@ContributesAssistedFactory(AppGraph::class, InfraredDecomposeComponent.Factory::class)
class InfraredDecomposeComponentImpl @AssistedInject constructor(
@Assisted componentContext: ComponentContext,
@Assisted keyPath: FlipperKeyPath,
@Assisted private val keyPath: FlipperKeyPath,
@Assisted private val onBack: DecomposeOnBackParameter,
private val infraredViewFactory: InfraredViewDecomposeComponentImpl.Factory,
private val infraredEditorFactory: InfraredEditorDecomposeComponent.Factory,
private val editorKeyFactory: KeyEditDecomposeComponent.Factory
private val editorKeyFactory: KeyEditDecomposeComponent.Factory,
private val savedGridFactory: LocalGridScreenDecomposeComponent.Factory
) : InfraredDecomposeComponent<InfraredNavigationConfig>(), ComponentContext by componentContext {

override val stack: Value<ChildStack<InfraredNavigationConfig, DecomposeComponent>> = childStack(
source = navigation,
serializer = InfraredNavigationConfig.serializer(),
initialConfiguration = InfraredNavigationConfig.View(keyPath),
handleBackButton = true,
childFactory = ::child,
)
override val stack: Value<ChildStack<InfraredNavigationConfig, DecomposeComponent>> =
childStack(
source = navigation,
serializer = InfraredNavigationConfig.serializer(),
initialConfiguration = InfraredNavigationConfig.RemoteControl(keyPath),
handleBackButton = true,
childFactory = ::child,
)

private fun child(
config: InfraredNavigationConfig,
Expand All @@ -58,5 +64,27 @@ class InfraredDecomposeComponentImpl @AssistedInject constructor(
flipperKeyPath = config.keyPath,
title = null
)

is InfraredNavigationConfig.RemoteControl -> savedGridFactory.invoke(
componentContext = componentContext,
keyPath = keyPath,
onBack = { navigation.popOr(onBack::invoke) },
onCallback = {
when (it) {
LocalGridScreenDecomposeComponent.Callback.UiFileNotFound -> {
navigation.replaceCurrent(InfraredNavigationConfig.View(config.keyPath))
}
is LocalGridScreenDecomposeComponent.Callback.ViewRemoteInfo -> {
navigation.replaceCurrent(InfraredNavigationConfig.View(it.keyPath))
}

is LocalGridScreenDecomposeComponent.Callback.Rename -> {
navigation.pushNew(InfraredNavigationConfig.Rename(it.keyPath))
}

LocalGridScreenDecomposeComponent.Callback.Deleted -> navigation.popOr(onBack::invoke)
}
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ sealed class InfraredNavigationConfig {

@Serializable
data class Rename(val keyPath: FlipperKeyPath) : InfraredNavigationConfig()

@Serializable
data class RemoteControl(val keyPath: FlipperKeyPath) : InfraredNavigationConfig()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.flipperdevices.keyedit.api

import com.arkivanov.decompose.ComponentContext
import com.flipperdevices.bridge.dao.api.model.FlipperKey
import com.flipperdevices.bridge.dao.api.model.FlipperKeyPath
import com.flipperdevices.ui.decompose.DecomposeOnBackParameter
import com.flipperdevices.ui.decompose.ScreenDecomposeComponent
Expand All @@ -12,13 +13,15 @@ abstract class KeyEditDecomposeComponent(
operator fun invoke(
componentContext: ComponentContext,
onBack: DecomposeOnBackParameter,
onSave: (FlipperKey?) -> Unit = { onBack.invoke() },
flipperKeyPath: FlipperKeyPath,
title: String?
): KeyEditDecomposeComponent

operator fun invoke(
componentContext: ComponentContext,
onBack: DecomposeOnBackParameter,
onSave: (FlipperKey?) -> Unit = { onBack.invoke() },
notSavedFlipperKey: NotSavedFlipperKey,
title: String?
): KeyEditDecomposeComponent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ data class NotSavedFlipperKey(
@Serializable
data class NotSavedFlipperFile(
val path: FlipperFilePath,
val content: FlipperKeyContent.InternalFile
val content: FlipperKeyContent
) : Parcelable

suspend fun FlipperFile.toNotSavedFlipperFile(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.flipperdevices.keyedit.impl.api

import com.arkivanov.decompose.ComponentContext
import com.flipperdevices.bridge.dao.api.model.FlipperKey
import com.flipperdevices.bridge.dao.api.model.FlipperKeyPath
import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.keyedit.api.KeyEditDecomposeComponent
Expand All @@ -17,20 +18,22 @@ class KeyEditDecomposeComponentFactory @Inject constructor(
override fun invoke(
componentContext: ComponentContext,
onBack: DecomposeOnBackParameter,
onSave: (FlipperKey?) -> Unit,
flipperKeyPath: FlipperKeyPath,
title: String?
): KeyEditDecomposeComponent {
val editableKey = EditableKey.Existed(flipperKeyPath)
return keyEditRealFactory(componentContext, onBack, editableKey, title)
return keyEditRealFactory(componentContext, onBack, onSave, editableKey, title)
}

override fun invoke(
componentContext: ComponentContext,
onBack: DecomposeOnBackParameter,
onSave: (FlipperKey?) -> Unit,
notSavedFlipperKey: NotSavedFlipperKey,
title: String?
): KeyEditDecomposeComponent {
val editableKey = EditableKey.Limb(notSavedFlipperKey)
return keyEditRealFactory(componentContext, onBack, editableKey, title)
return keyEditRealFactory(componentContext, onBack, onSave, editableKey, title)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import com.arkivanov.decompose.ComponentContext
import com.flipperdevices.bridge.dao.api.model.FlipperKey
import com.flipperdevices.core.ui.lifecycle.viewModelWithFactory
import com.flipperdevices.keyedit.api.KeyEditDecomposeComponent
import com.flipperdevices.keyedit.impl.composable.ComposableEditScreen
Expand All @@ -19,6 +20,7 @@ class KeyEditDecomposeComponentImpl @AssistedInject constructor(
@Assisted private val editableKey: EditableKey,
@Assisted private val title: String?,
@Assisted private val onBack: DecomposeOnBackParameter,
@Assisted private val onSave: (FlipperKey?) -> Unit,
private val keyEditViewModelFactory: KeyEditViewModel.Factory
) : KeyEditDecomposeComponent(componentContext) {
@Composable
Expand All @@ -35,7 +37,7 @@ class KeyEditDecomposeComponentImpl @AssistedInject constructor(
state = state,
onBack = onBack::invoke,
onSave = {
viewModel.onSave(onBack::invoke)
viewModel.onSave(onSave::invoke)
}
)
}
Expand All @@ -45,6 +47,7 @@ class KeyEditDecomposeComponentImpl @AssistedInject constructor(
operator fun invoke(
componentContext: ComponentContext,
onBack: DecomposeOnBackParameter,
onSave: (FlipperKey?) -> Unit,
editableKey: EditableKey,
title: String?
): KeyEditDecomposeComponentImpl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import android.os.Vibrator
import androidx.core.content.ContextCompat
import com.flipperdevices.bridge.api.utils.FlipperSymbolFilter
import com.flipperdevices.bridge.dao.api.model.FlipperKey
import com.flipperdevices.core.ktx.android.vibrateCompat
import com.flipperdevices.core.log.LogTagProvider
import com.flipperdevices.core.log.error
Expand Down Expand Up @@ -83,7 +84,7 @@ class KeyEditViewModel @AssistedInject constructor(
}
}

fun onSave(onEndAction: () -> Unit) {
fun onSave(onEndAction: (FlipperKey?) -> Unit) {
val savingState = keyEditState.updateAndGet {
if (it is KeyEditState.Editing) {
KeyEditState.Saving(it)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.flipperdevices.keyedit.impl.viewmodel.processors

import com.flipperdevices.bridge.dao.api.model.FlipperKey
import com.flipperdevices.keyedit.impl.model.EditableKey
import com.flipperdevices.keyedit.impl.model.KeyEditState

interface EditableKeyProcessor<T : EditableKey> {
suspend fun loadKey(editableKey: T, onStateUpdate: suspend (KeyEditState) -> Unit)
suspend fun onSave(editableKey: T, editState: KeyEditState.Editing, onEndAction: () -> Unit)
suspend fun onSave(editableKey: T, editState: KeyEditState.Editing, onEndAction: (FlipperKey?) -> Unit)
}
Loading

0 comments on commit 45b8202

Please sign in to comment.