Skip to content

Commit

Permalink
Add better information during sync (#961)
Browse files Browse the repository at this point in the history
**Background**

Current synchronization screen doesn't show us any details about current
synchronization

**Changes**

- Edit ProgressTracker to add details into it
- Add more details to current syncrhonization

**Test plan**

- Open app
- See there's now more steps and you can see what now is actually
syncing
  • Loading branch information
makeevrserg authored Sep 26, 2024
1 parent f312064 commit abce7be
Show file tree
Hide file tree
Showing 26 changed files with 443 additions and 85 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Attention: don't forget to add the flag for F-Droid before release
- [Feature] Add error display into remote controls screens
- [Feature] Add new icons for remote-controls
- [Feature] Add experimental option to enable remote controls
- [Feature] Better information during synchronization
- [Refactor] Load RemoteControls from flipper, emulating animation
- [Refactor] Update to Kotlin 2.0
- [Refactor] Replace Ktorfit with Ktor requests in remote-controls
Expand Down
2 changes: 2 additions & 0 deletions components/archive/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ dependencies {

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

implementation(libs.annotations)
implementation(libs.appcompat)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.flipperdevices.archive.impl.composable.ComposableArchive
import com.flipperdevices.archive.impl.model.ArchiveNavigationConfig
import com.flipperdevices.archive.impl.viewmodel.CategoryViewModel
import com.flipperdevices.archive.impl.viewmodel.GeneralTabViewModel
import com.flipperdevices.archive.impl.viewmodel.SpeedViewModel
import com.flipperdevices.bottombar.handlers.ResetTabDecomposeHandler
import com.flipperdevices.bridge.synchronization.api.SynchronizationUiApi
import com.flipperdevices.core.ui.lifecycle.viewModelWithFactory
Expand All @@ -31,6 +32,7 @@ class ArchiveScreenDecomposeComponentImpl @AssistedInject constructor(
private val synchronizationUiApi: SynchronizationUiApi,
private val generalTabviewModelProvider: Provider<GeneralTabViewModel>,
private val categoryViewModelProvider: Provider<CategoryViewModel>,
private val speedViewModelProvider: Provider<SpeedViewModel>,
) : ScreenDecomposeComponent(componentContext), ResetTabDecomposeHandler {
private val requestScrollToTopFlow = MutableStateFlow(false)

Expand All @@ -48,9 +50,13 @@ class ArchiveScreenDecomposeComponentImpl @AssistedInject constructor(
val categoryViewModel = viewModelWithFactory(key = null) {
categoryViewModelProvider.get()
}
val speedViewModel = viewModelWithFactory(key = null) {
speedViewModelProvider.get()
}

val categories by categoryViewModel.getCategoriesFlow().collectAsState()
val deletedCategory by categoryViewModel.getDeletedFlow().collectAsState()
val speed by speedViewModel.speedFlow.collectAsState(null)

val lazyListState = rememberLazyListState()
val requestScrollToTop by requestScrollToTopFlow.collectAsState()
Expand Down Expand Up @@ -81,7 +87,8 @@ class ArchiveScreenDecomposeComponentImpl @AssistedInject constructor(
cancelSynchronization = tabViewModel::cancelSynchronization,
categories = categories,
deletedCategory = deletedCategory,
lazyListState = lazyListState
lazyListState = lazyListState,
speed = speed,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.flipperdevices.archive.impl.composable.page.ComposableFavoriteKeysTit
import com.flipperdevices.archive.impl.composable.page.ComposableKeysGrid
import com.flipperdevices.archive.impl.model.CategoryItem
import com.flipperdevices.archive.model.CategoryType
import com.flipperdevices.bridge.api.model.FlipperSerialSpeed
import com.flipperdevices.bridge.dao.api.model.FlipperKey
import com.flipperdevices.bridge.dao.api.model.FlipperKeyPath
import com.flipperdevices.bridge.synchronization.api.SynchronizationState
Expand All @@ -55,13 +56,18 @@ fun ComposableArchive(
categories: ImmutableList<CategoryItem>,
deletedCategory: CategoryItem,
lazyListState: LazyListState,
speed: FlipperSerialSpeed?,
onRefresh: () -> Unit,
cancelSynchronization: () -> Unit
) {
val isKeysPresented = favoriteKeys.isNotEmpty() || !keys.isNullOrEmpty()

if (synchronizationState is SynchronizationState.InProgress) {
ArchiveProgressScreen(synchronizationState, cancelSynchronization)
ArchiveProgressScreen(
inProgressState = synchronizationState,
onCancel = cancelSynchronization,
speed = speed
)
} else {
ComposableArchiveReady(
synchronizationUiApi = synchronizationUiApi,
Expand All @@ -75,7 +81,7 @@ fun ComposableArchive(
onOpenCategory = onOpenCategory,
categories = categories,
deletedCategory = deletedCategory,
lazyListState = lazyListState
lazyListState = lazyListState,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package com.flipperdevices.archive.impl.composable.page

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.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
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
Expand All @@ -18,17 +27,96 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.flipperdevices.archive.impl.R
import com.flipperdevices.bridge.api.model.FlipperSerialSpeed
import com.flipperdevices.bridge.synchronization.api.SynchronizationState
import com.flipperdevices.core.ktx.jre.roundPercentToString
import com.flipperdevices.core.ktx.jre.toFormattedSize
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.LocalPalletV2
import com.flipperdevices.core.ui.theme.LocalTypography

@Composable
private fun AnimatedProgressComposable(progress: Float) {
Box(
modifier = Modifier.size(64.dp),
contentAlignment = Alignment.Center
) {
val progressAnimated by animateFloatAsState(targetValue = progress)
CircularProgressIndicator(
modifier = Modifier.fillMaxSize(),
color = LocalPalletV2.current.action.blue.background.primary.default,
backgroundColor = LocalPalletV2.current.action.neutral.background.tertiary.default,
strokeWidth = 2.dp,
progress = progressAnimated
)
Text(
modifier = Modifier,
text = progressAnimated.roundPercentToString(),
style = LocalTypography.current.bodyM14,
color = LocalPalletV2.current.text.body.secondary,
textAlign = TextAlign.Center
)
}
}

@Composable
private fun AnimatedStatusComposable(inProgressState: SynchronizationState.InProgress) {
AnimatedContent(
targetState = inProgressState,
transitionSpec = { fadeIn().togetherWith(fadeOut()) },
contentKey = {
when (it) {
is SynchronizationState.InProgress.Default -> 0
is SynchronizationState.InProgress.Favorites -> 1
is SynchronizationState.InProgress.Prepare -> 2
is SynchronizationState.InProgress.PrepareHashes -> it.keyType.name
is SynchronizationState.InProgress.FileInProgress -> it.fileName
}
},
) { animatedInProgressState ->
Text(
modifier = Modifier.fillMaxWidth(),
text = when (animatedInProgressState) {
is SynchronizationState.InProgress.FileInProgress -> {
LocalContext.current.getString(
R.string.archive_updating,
animatedInProgressState.fileName
)
}

is SynchronizationState.InProgress.Default -> {
LocalContext.current.getString(R.string.archive_sync_progress)
}

is SynchronizationState.InProgress.Favorites -> {
LocalContext.current.getString(R.string.archive_favorites)
}

is SynchronizationState.InProgress.Prepare -> {
LocalContext.current.getString(R.string.archive_preparing)
}

is SynchronizationState.InProgress.PrepareHashes -> {
LocalContext.current.getString(
R.string.archive_hashes,
animatedInProgressState.keyType.humanReadableName
)
}
},
style = LocalTypography.current.bodyM14,
color = LocalPalletV2.current.text.body.secondary,
textAlign = TextAlign.Center
)
}
}

@Composable
fun ArchiveProgressScreen(
inProgressState: SynchronizationState.InProgress,
onCancel: () -> Unit,
speed: FlipperSerialSpeed?,
modifier: Modifier = Modifier,
) {
Column(
Expand All @@ -39,24 +127,40 @@ fun ArchiveProgressScreen(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = LocalPallet.current.accentSecond,
strokeWidth = 2.dp
)
Text(
modifier = Modifier.padding(top = 14.dp),
modifier = Modifier,
text = LocalContext.current.getString(
R.string.archive_sync_percent,
inProgressState.progress.roundPercentToString()
R.string.archive_syncing,
),
style = LocalTypography.current.bodyM14,
style = LocalTypography.current.titleB18,
color = LocalPallet.current.text60,
textAlign = TextAlign.Center
)

Spacer(Modifier.height(24.dp))

AnimatedProgressComposable(inProgressState.progress)
Spacer(Modifier.height(8.dp))
AnimatedStatusComposable(inProgressState)
Spacer(Modifier.height(8.dp))
Text(
modifier = Modifier,
text = when (speed) {
null -> ""
else -> LocalContext.current.getString(
R.string.archive_speed,
speed.receiveBytesInSec.toFormattedSize()
)
},
style = LocalTypography.current.bodyM14,
color = LocalPalletV2.current.text.body.secondary,
textAlign = TextAlign.Center
)

Spacer(Modifier.height(24.dp))

Text(
modifier = Modifier
.padding(top = 14.dp)
.clickableRipple(onClick = onCancel),
text = stringResource(R.string.archive_sync_cancel),
style = LocalTypography.current.bodyM14,
Expand All @@ -72,6 +176,36 @@ fun ArchiveProgressScreen(
@Composable
private fun ComposableArchiveProgressScreenPreview() {
FlipperThemeInternal {
ArchiveProgressScreen(SynchronizationState.InProgress(0f), {})
ArchiveProgressScreen(
inProgressState = SynchronizationState.InProgress.Default(
progress = 0.3f,
),
speed = FlipperSerialSpeed(
receiveBytesInSec = 10L,
transmitBytesInSec = 10L
),
onCancel = {}
)
}
}

@Preview(
showBackground = true,
showSystemUi = true
)
@Composable
private fun ComposableArchiveProgressFileScreenPreview() {
FlipperThemeInternal {
ArchiveProgressScreen(
inProgressState = SynchronizationState.InProgress.FileInProgress(
progress = 0.3f,
fileName = "file.ir"
),
speed = FlipperSerialSpeed(
receiveBytesInSec = 10L,
transmitBytesInSec = 10L
),
onCancel = {}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.flipperdevices.archive.impl.viewmodel

import com.flipperdevices.bridge.service.api.provider.FlipperServiceProvider
import com.flipperdevices.core.ui.lifecycle.DecomposeViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
import javax.inject.Inject

class SpeedViewModel @Inject constructor(
private val serviceProvider: FlipperServiceProvider,
) : DecomposeViewModel() {
val speedFlow = flow {
serviceProvider.getServiceApi()
.requestApi
.getSpeed()
.onEach { emit(it) }
.collect()
}.shareIn(viewModelScope, SharingStarted.Eagerly, 1)
}
6 changes: 6 additions & 0 deletions components/archive/impl/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,10 @@
<string name="archive_sync_progress">Syncing with Flipper…</string>
<string name="archive_sync_percent">Syncing %1$s</string>
<string name="archive_sync_cancel">Cancel</string>
<string name="archive_speed">Speed: %s/s</string>
<string name="archive_updating">Syncing: %s</string>
<string name="archive_syncing">Syncing</string>
<string name="archive_preparing">Preparing sync: getting files from flipper</string>
<string name="archive_favorites">Syncing favorite keys…</string>
<string name="archive_hashes">Preparing sync: %s</string>
</resources>
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
package com.flipperdevices.bridge.synchronization.api

import androidx.compose.runtime.Immutable
import com.flipperdevices.bridge.dao.api.model.FlipperKeyType

@Immutable
sealed class SynchronizationState {
data object NotStarted : SynchronizationState()
data class InProgress(val progress: Float) : SynchronizationState()
data object Finished : SynchronizationState()
sealed interface SynchronizationState {
data object NotStarted : SynchronizationState

sealed interface InProgress : SynchronizationState {
val progress: Float

data class FileInProgress(
override val progress: Float,
val fileName: String
) : InProgress

data class Default(override val progress: Float) : InProgress

data class Prepare(override val progress: Float) : InProgress

data class PrepareHashes(
override val progress: Float,
val keyType: FlipperKeyType
) : InProgress

data class Favorites(override val progress: Float) : InProgress
}

data object Finished : SynchronizationState
}
Loading

0 comments on commit abce7be

Please sign in to comment.