Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Attention: don't forget to add the flag for F-Droid before release
- [Feature] More UI elements for remote-controls
- [Feature] Add How to Use dialog into remote-controls
- [Feature] Skip infrared signals on setup screen
- [Feature] Better user-ux when configuring 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,5 +1,10 @@
package com.flipperdevices.remotecontrols.impl.createcontrol.composable

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
Expand All @@ -9,49 +14,126 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.flipperdevices.core.ktx.jre.roundPercentToString
import com.flipperdevices.core.ui.theme.FlipperThemeInternal
import com.flipperdevices.core.ui.theme.LocalPallet
import com.flipperdevices.core.ui.theme.LocalPalletV2
import com.flipperdevices.core.ui.theme.LocalTypography
import com.flipperdevices.remotecontrols.grid.createcontrol.impl.R
import com.flipperdevices.remotecontrols.impl.createcontrol.viewmodel.SaveRemoteControlViewModel.State

@Composable
internal fun CreateControlComposable() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.configuring_title),
color = LocalPalletV2.current.text.body.primary,
style = LocalTypography.current.titleB18
)
Spacer(Modifier.height(24.dp))
CircularProgressIndicator(
modifier = Modifier.size(48.dp),
color = LocalPallet.current.accentSecond
)
Spacer(Modifier.height(8.dp))
Text(
text = stringResource(R.string.configuring_desc),
color = LocalPalletV2.current.text.body.secondary,
style = LocalTypography.current.subtitleM12,
textAlign = TextAlign.Center
)
internal fun CreateControlComposable(state: State) {
AnimatedContent(
targetState = state,
transitionSpec = { fadeIn().togetherWith(fadeOut()) },
contentKey = {
when (it) {
State.CouldNotModifyFiles -> 0
is State.Finished -> 1
State.InProgress.ModifyingFiles -> 2
is State.InProgress.Synchronizing -> 3
State.KeyNotFound -> 4
State.Pending -> 5
}
},
content = { animatedState ->
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
TitleComposable(animatedState)
Spacer(Modifier.height(24.dp))
ProgressIndicatorComposable((animatedState as? State.InProgress.Synchronizing)?.progress)
Spacer(Modifier.height(8.dp))
Text(
text = stringResource(R.string.configuring_desc),
color = LocalPalletV2.current.text.body.secondary,
style = LocalTypography.current.subtitleM12,
textAlign = TextAlign.Center
)
}
}
)
}

@Composable
private fun TitleComposable(
state: State,
modifier: Modifier = Modifier
) {
Text(
text = when (state) {
State.InProgress.ModifyingFiles -> stringResource(R.string.configuring_files_title)
is State.InProgress.Synchronizing -> {
val progressAnimated by animateFloatAsState(state.progress)
LocalContext.current.getString(
R.string.archive_sync_percent,
progressAnimated.roundPercentToString()
)
}

else -> stringResource(R.string.configuring_title)
},
color = LocalPalletV2.current.text.body.primary,
style = LocalTypography.current.titleB18,
modifier = modifier
)
}

@Composable
private fun ProgressIndicatorComposable(
progress: Float?,
modifier: Modifier = Modifier
) {
when {
progress != null -> {
val progressAnimated by animateFloatAsState(progress)
CircularProgressIndicator(
modifier = modifier.size(48.dp),
color = LocalPallet.current.accentSecond,
progress = progressAnimated
)
}

else -> {
CircularProgressIndicator(
modifier = modifier.size(48.dp),
color = LocalPallet.current.accentSecond,
)
}
}
}

@Preview
@Composable
private fun CreateControlComposablePendingPreview() {
FlipperThemeInternal {
CreateControlComposable(State.Pending)
}
}

@Preview
@Composable
private fun CreateControlComposableModifyingPreview() {
FlipperThemeInternal {
CreateControlComposable(State.InProgress.ModifyingFiles)
}
}

@Preview
@Composable
private fun CreateControlComposablePreview() {
private fun CreateControlComposableSyncingPreview() {
FlipperThemeInternal {
CreateControlComposable()
CreateControlComposable(State.InProgress.Synchronizing(progress = 0.3f))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.flipperdevices.remotecontrols.impl.createcontrol.decompose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import com.arkivanov.decompose.ComponentContext
import com.flipperdevices.bridge.dao.api.model.FlipperKeyPath
import com.flipperdevices.core.di.AppGraph
Expand Down Expand Up @@ -35,6 +37,7 @@ class CreateControlDecomposeComponentImpl @AssistedInject constructor(
key = null,
factory = { saveRemoteControlViewModelFactory.get() }
)
val state by saveRemoteControlViewModel.state.collectAsState()
val rootNavigation = LocalRootNavigation.current
LaunchedEffect(saveRemoteControlViewModel) {
saveRemoteControlViewModel.state
Expand All @@ -51,14 +54,14 @@ class CreateControlDecomposeComponentImpl @AssistedInject constructor(
}

SaveRemoteControlViewModel.State.Pending,
SaveRemoteControlViewModel.State.Updating -> Unit
is SaveRemoteControlViewModel.State.InProgress -> Unit
}
}.launchIn(this)
saveRemoteControlViewModel.moveAndUpdate(
savedKeyPath = savedKey,
originalKey = originalKey,
)
}
CreateControlComposable()
CreateControlComposable(state)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import com.flipperdevices.bridge.service.api.provider.FlipperServiceProvider
import com.flipperdevices.bridge.synchronization.api.SynchronizationApi
import com.flipperdevices.bridge.synchronization.api.SynchronizationState
import com.flipperdevices.core.log.LogTagProvider
import com.flipperdevices.core.log.info
import com.flipperdevices.core.ui.lifecycle.DecomposeViewModel
import com.flipperdevices.keyedit.api.NotSavedFlipperFile
import com.flipperdevices.keyedit.api.NotSavedFlipperKey
Expand All @@ -21,10 +20,13 @@ import com.flipperdevices.protobuf.storage.deleteRequest
import com.flipperdevices.protobuf.storage.renameRequest
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.job
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -77,18 +79,24 @@ class SaveRemoteControlViewModel @Inject constructor(
}
}

private suspend fun awaitSynchronization() {
private suspend fun awaitSynchronization(
onChange: (progress: Float) -> Unit
): Unit = coroutineScope {
if (!synchronizationApi.isSynchronizationRunning()) {
synchronizationApi.startSynchronization(force = true)
}
val progressJon = synchronizationApi.getSynchronizationState()
.filterIsInstance<SynchronizationState.InProgress>()
.onEach { onChange.invoke(it.progress) }
.launchIn(this)
synchronizationApi.getSynchronizationState()
.onEach { info { "#moveAndUpdate $it" } }
.filterIsInstance<SynchronizationState.InProgress>()
.onEach { onChange.invoke(it.progress) }
.first()
synchronizationApi.getSynchronizationState()
.onEach { info { "#moveAndUpdate $it" } }
.filterIsInstance<SynchronizationState.Finished>()
.first()
progressJon.cancelAndJoin()
}

/**
Expand Down Expand Up @@ -124,12 +132,10 @@ class SaveRemoteControlViewModel @Inject constructor(
originalKey: NotSavedFlipperKey,
) {
viewModelScope.launch {
_state.emit(State.Updating)
_state.emit(State.InProgress.ModifyingFiles)
if (lastMoveJob != null) lastMoveJob?.join()
lastMoveJob = coroutineContext.job

awaitSynchronization()

val flipperKey = simpleKeyApi.getKey(savedKeyPath) ?: run {
_state.emit(State.KeyNotFound)
return@launch
Expand All @@ -153,7 +159,7 @@ class SaveRemoteControlViewModel @Inject constructor(
}.toImmutableList()
)
)
awaitSynchronization()
awaitSynchronization(onChange = { _state.value = State.InProgress.Synchronizing(it) })
val keyPath = FlipperKeyPath(
path = flipperKey.mainFile.path.toNonTempPath(),
deleted = false
Expand All @@ -164,7 +170,11 @@ class SaveRemoteControlViewModel @Inject constructor(

sealed interface State {
data object Pending : State
data object Updating : State
sealed interface InProgress : State {
data object ModifyingFiles : InProgress
data class Synchronizing(val progress: Float) : InProgress
}

data class Finished(val keyPath: FlipperKeyPath) : State
data object KeyNotFound : State
data object CouldNotModifyFiles : State
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
<resources>
<!-- Settings-->
<string name="configuring_title">Configuring</string>
<string name="configuring_files_title">Configuring</string>
<string name="configuring_desc">Remote control is being configured. Please do not close this screen</string>
<string name="archive_sync_percent">Syncing %1$s</string>
</resources>
Loading