Skip to content

Commit

Permalink
Infrared Editor errors view (#729)
Browse files Browse the repository at this point in the history
**Background**

Now, we can not display some error after editor infrared editor

**Changes**

* Active Editor Text Field
* PoC shake error remotes

**Test plan**

Go to infrared editor and try edit remote to same name or empty
  • Loading branch information
Programistich authored Nov 5, 2023
1 parent b73bb0d commit 04007a7
Show file tree
Hide file tree
Showing 9 changed files with 469 additions and 179 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

# 1.6.7 - In progress

- [Feature] Infrared Editor process error
- [Feature] Optimization FapHub by compose metrics
- [FIX] Splashscreen WearOS icon

# 1.6.6

- [Feature] Bump deps
- [Feature] Optimization FapHub by compose metrics
- [FIX] Fix faphub minor version supported suggest to update
- [FIX] Fix faphub manifest minor api version
- [FIX] Fix faphub manifest image base64
Expand Down
3 changes: 3 additions & 0 deletions components/infrared/editor/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ dependencies {

implementation(projects.components.bridge.dao.api)
implementation(projects.components.bridge.synchronization.api)
implementation(projects.components.bridge.api)

implementation(projects.components.core.di)
implementation(projects.components.core.ktx)
implementation(projects.components.core.log)
implementation(projects.components.core.ui.navigation)
implementation(projects.components.core.ui.theme)
implementation(projects.components.core.ui.tabswitch)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,88 @@
package com.flipperdevices.infrared.editor.compose.components

import android.content.res.Configuration
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import com.flipperdevices.core.ui.ktx.clickableRipple
import com.flipperdevices.core.ui.theme.FlipperThemeInternal
import com.flipperdevices.core.ui.theme.LocalPallet
import com.flipperdevices.core.ui.theme.LocalTypography
import com.flipperdevices.infrared.editor.R
import kotlin.math.roundToInt
import com.flipperdevices.core.ui.res.R as DesignSystem

private const val BUTTON_HEIGHT = 55
private const val COUNT_OF_SHAKE = 10
private const val TARGET_SHAKE = 5f
private const val DELTA_SHAKE = 100_000f

@Composable
internal fun ComposableInfraredEditorItem(
remoteName: String,
onTap: () -> Unit,
onChangeName: (String) -> Unit,
onChangeIndexEditor: () -> Unit,
onDelete: () -> Unit,
isActive: Boolean,
isError: Boolean,
modifier: Modifier = Modifier,
dragModifier: Modifier = Modifier
dragModifier: Modifier = Modifier,
) {
val shake = getShakeAnimation(isError)
Row(
modifier = modifier.fillMaxWidth(),
modifier = modifier
.fillMaxWidth()
.offset { IntOffset(shake, y = 0) },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
ComposableInfraredEditorButton(
modifier = Modifier.weight(1f),
dragModifier = dragModifier,
remoteName = remoteName,
onTap = onTap
onChangeName = onChangeName,
isActiveEditor = isActive,
onChangeIndexEditor = onChangeIndexEditor
)
Icon(
modifier = Modifier
Expand All @@ -57,20 +96,46 @@ internal fun ComposableInfraredEditorItem(
}
}

@Composable
private fun getShakeAnimation(isError: Boolean): Int {
val shake = remember { Animatable(0f) }
var trigger by remember { mutableStateOf(false) }

LaunchedEffect(isError) {
if (isError) { trigger = true }
}

LaunchedEffect(trigger) {
if (trigger.not()) return@LaunchedEffect
for (i in 0..COUNT_OF_SHAKE) {
when (i % 2) {
0 -> shake.animateTo(TARGET_SHAKE, spring(stiffness = DELTA_SHAKE))
else -> shake.animateTo(-TARGET_SHAKE, spring(stiffness = DELTA_SHAKE))
}
}
shake.animateTo(0f)
}

return shake.value.roundToInt()
}

@Composable
private fun ComposableInfraredEditorButton(
remoteName: String,
onTap: () -> Unit,
onChangeName: (String) -> Unit,
onChangeIndexEditor: () -> Unit,
isActiveEditor: Boolean,
modifier: Modifier = Modifier,
dragModifier: Modifier = Modifier
dragModifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.heightIn(min = BUTTON_HEIGHT.dp)
.clip(shape = RoundedCornerShape(12.dp))
.clickable(onClick = onTap)
.background(LocalPallet.current.accent)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
modifier = dragModifier
Expand All @@ -80,24 +145,96 @@ private fun ComposableInfraredEditorButton(
contentDescription = null,
tint = LocalPallet.current.infraredEditorDrag
)
Text(
text = remoteName,
modifier = Modifier.weight(1f),
style = LocalTypography.current.infraredEditButton,
color = LocalPallet.current.infraredEditorKeyName,
textAlign = TextAlign.Center

if (isActiveEditor) {
ComposableInfraredEditorField(remoteName, onChangeName)
} else {
ComposableInfraredEditorText(remoteName, onChangeIndexEditor)
}
}
}

@Composable
private fun RowScope.ComposableInfraredEditorField(
remoteName: String,
onChangeName: (String) -> Unit,
) {
val focusManager = LocalFocusManager.current
val focusRequester = remember { FocusRequester() }

val customTextSelectionColors = TextSelectionColors(
handleColor = LocalPallet.current.accentSecond,
backgroundColor = LocalPallet.current.accent.copy(alpha = ContentAlpha.high)
)
val textState by remember(remoteName) {
mutableStateOf(
TextFieldValue(
text = remoteName,
selection = TextRange(remoteName.length)
)
)
}

CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) {
TextField(
value = textState,
onValueChange = {
onChangeName(it.text)
},
singleLine = true,
modifier = Modifier
.focusRequester(focusRequester)
.weight(1f),
textStyle = LocalTypography.current.infraredEditButton.copy(
textAlign = TextAlign.Center
),
keyboardActions = KeyboardActions(
onDone = {
focusManager.clearFocus()
},
),
colors = TextFieldDefaults.textFieldColors(
textColor = LocalPallet.current.infraredEditorKeyName,
cursorColor = LocalPallet.current.infraredEditorKeyName,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
backgroundColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent,
)
)
}

LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}

@Composable
private fun RowScope.ComposableInfraredEditorText(remoteName: String, onClick: () -> Unit) {
Text(
text = remoteName,
modifier = Modifier
.clickable(onClick = onClick)
.weight(1f),
style = LocalTypography.current.infraredEditButton,
color = LocalPallet.current.infraredEditorKeyName,
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}

@Preview
@Composable
private fun PreviewComposableInfraredEditorItem() {
FlipperThemeInternal {
ComposableInfraredEditorItem(
remoteName = "012345678901234567891",
remoteName = "01234567890123456789124242424",
onDelete = {},
onTap = {}
onChangeName = {},
onChangeIndexEditor = {},
isActive = true,
isError = false
)
}
}
Expand All @@ -107,9 +244,27 @@ private fun PreviewComposableInfraredEditorItem() {
private fun PreviewComposableInfraredEditorItemDark() {
FlipperThemeInternal {
ComposableInfraredEditorItem(
remoteName = "Off",
remoteName = "01234567890123456789124242424",
onDelete = {},
onChangeName = {},
onChangeIndexEditor = {},
isActive = true,
isError = false
)
}
}

@Preview
@Composable
private fun PreviewComposableInfraredEditorItemNotActive() {
FlipperThemeInternal {
ComposableInfraredEditorItem(
remoteName = "0123456789012345678912424242433333",
onDelete = {},
onTap = {}
onChangeName = {},
onChangeIndexEditor = {},
isActive = false,
isError = false
)
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
package com.flipperdevices.infrared.editor.compose.screen

import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import com.flipperdevices.infrared.editor.model.InfraredEditorState
import com.flipperdevices.infrared.editor.viewmodel.InfraredViewModel
import com.flipperdevices.infrared.editor.viewmodel.InfraredEditorViewModel
import com.flipperdevices.keyscreen.shared.screen.ComposableKeyScreenError
import com.flipperdevices.keyscreen.shared.screen.ComposableKeyScreenLoading
import tangle.viewmodel.compose.tangleViewModel

@Composable
internal fun ComposableInfraredEditorScreen(
onBack: () -> Unit,
viewModel: InfraredViewModel = tangleViewModel()
viewModel: InfraredEditorViewModel = tangleViewModel()
) {
val keyState by viewModel.getKeyState().collectAsState()
val dialogState by viewModel.getDialogState().collectAsState()

BackHandler {
viewModel.processCancel(keyState, onBack)
}

when (val localState = keyState) {
InfraredEditorState.InProgress -> ComposableKeyScreenLoading()
is InfraredEditorState.Error -> ComposableKeyScreenError(
Expand All @@ -29,11 +34,35 @@ internal fun ComposableInfraredEditorScreen(
dialogState = dialogState,
onDoNotSave = onBack,
onDismissDialog = viewModel::onDismissDialog,
onCancel = { viewModel.processCancel(onBack) },
onSave = { viewModel.processSave(onBack) },
onTapRemote = {},
onDelete = viewModel::processDeleteRemote,
onEditOrder = viewModel::processEditOrder
onCancel = {
viewModel.processCancel(keyState, onBack)
},
onSave = {
viewModel.processSave(currentState = localState, onBack)
},
onChangeName = { index, value ->
viewModel.editRemoteName(
currentState = localState,
index = index,
source = value
)
},
onDelete = {
viewModel.processDeleteRemote(
currentState = localState,
index = it
)
},
onEditOrder = { from, to ->
viewModel.processEditOrder(
currentState = localState,
from = from,
to = to
)
},
onChangeIndexEditor = {
viewModel.processChangeIndexEditor(localState, it)
}
)
}
}
Loading

0 comments on commit 04007a7

Please sign in to comment.