From f5a05a69cfb5da011f9cd72b153c427e6ca1471b Mon Sep 17 00:00:00 2001 From: sentinelweb Date: Sat, 28 Jun 2025 11:53:29 +0200 Subject: [PATCH 1/7] #496 - refactor FilesViewModel.kt --- .../app/ui/filebrowser/FileBrowserFragment.kt | 14 +- .../hub/ui/filebrowser/FilesUiCoordinator.kt | 11 +- .../ui/common/viewmodel/ViewModelEffects.kt | 22 ++ .../app/ui/filebrowser/FilesComposeables.kt | 18 +- .../cuer/app/ui/filebrowser/FilesContract.kt | 21 +- .../app/ui/filebrowser/FilesModelMapper.kt | 3 +- .../cuer/app/ui/filebrowser/FilesViewModel.kt | 202 +++++++++++------- 7 files changed, 178 insertions(+), 113 deletions(-) create mode 100644 shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/common/viewmodel/ViewModelEffects.kt diff --git a/app/src/main/java/uk/co/sentinelweb/cuer/app/ui/filebrowser/FileBrowserFragment.kt b/app/src/main/java/uk/co/sentinelweb/cuer/app/ui/filebrowser/FileBrowserFragment.kt index b6f49dadc..96712544f 100644 --- a/app/src/main/java/uk/co/sentinelweb/cuer/app/ui/filebrowser/FileBrowserFragment.kt +++ b/app/src/main/java/uk/co/sentinelweb/cuer/app/ui/filebrowser/FileBrowserFragment.kt @@ -19,7 +19,7 @@ import uk.co.sentinelweb.cuer.app.ui.common.ktx.bindFlow import uk.co.sentinelweb.cuer.app.ui.common.navigation.* import uk.co.sentinelweb.cuer.app.ui.common.navigation.NavigationModel.Param.BACK_PARAMS import uk.co.sentinelweb.cuer.app.ui.common.navigation.NavigationModel.Target.NAV_BACK -import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.Label +import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.Effect import uk.co.sentinelweb.cuer.app.ui.play_control.CompactPlayerScroll import uk.co.sentinelweb.cuer.app.ui.player.PlayerContract import uk.co.sentinelweb.cuer.app.util.extension.fragmentScopeWithSource @@ -90,18 +90,18 @@ class FileBrowserFragment : Fragment(), AndroidScopeComponent { ) } statusBarColor.setStatusBarColorResource(R.color.black) - bindFlow(viewModel.labels, ::observeLabels) + bindFlow(viewModel.effects, ::observeLabels) remoteIdArg?.apply { viewModel.init(this, filePathArg) } } - private fun observeLabels(label: Label) { + private fun observeLabels(label: Effect) { when (label) { - Label.Init -> {} - Label.Up -> { + Effect.Init -> {} + Effect.Up -> { navRouter.navigate(NavigationModel(NAV_BACK, mapOf(BACK_PARAMS to R.id.navigation_remotes))) } - Label.Settings -> navigationProvider.navigate(R.id.navigation_settings_root) + Effect.Settings -> navigationProvider.navigate(R.id.navigation_settings_root) else -> Unit } } @@ -135,7 +135,7 @@ class FileBrowserFragment : Fragment(), AndroidScopeComponent { scope(named()) { viewModel { FilesViewModel( - state = FilesContract.State(), + initialState = FilesContract.State(), filesInteractor = get(), remotesRepository = get(), mapper = get(), diff --git a/hub/src/main/kotlin/uk/co/sentinelweb/cuer/hub/ui/filebrowser/FilesUiCoordinator.kt b/hub/src/main/kotlin/uk/co/sentinelweb/cuer/hub/ui/filebrowser/FilesUiCoordinator.kt index f6a90cb85..9a2b938d9 100644 --- a/hub/src/main/kotlin/uk/co/sentinelweb/cuer/hub/ui/filebrowser/FilesUiCoordinator.kt +++ b/hub/src/main/kotlin/uk/co/sentinelweb/cuer/hub/ui/filebrowser/FilesUiCoordinator.kt @@ -1,9 +1,12 @@ package uk.co.sentinelweb.cuer.hub.ui.filebrowser import androidx.compose.runtime.Composable -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.qualifier.named import org.koin.core.scope.Scope @@ -11,7 +14,7 @@ import org.koin.dsl.module import uk.co.sentinelweb.cuer.app.ui.cast.CastController import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesComposeables.FileBrowserDesktopUi import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract -import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.Label.ErrorMessage +import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.Effect.ErrorMessage import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.Model.Companion.Initial import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.State import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesViewModel @@ -52,7 +55,7 @@ class FilesUiCoordinator( private fun bindLabels() { coordinatorScope.launch { - viewModel.labels.collectLatest { + viewModel.effects.collectLatest { when (it) { is ErrorMessage -> parent.showError(it.message) else -> Unit @@ -82,7 +85,7 @@ class FilesUiCoordinator( scope(named()) { scoped { FilesViewModel( - state = State(), + initialState = State(), filesInteractor = get(), remotesRepository = get(), mapper = get(), diff --git a/shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/common/viewmodel/ViewModelEffects.kt b/shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/common/viewmodel/ViewModelEffects.kt new file mode 100644 index 000000000..b18f83a1b --- /dev/null +++ b/shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/common/viewmodel/ViewModelEffects.kt @@ -0,0 +1,22 @@ +package uk.co.sentinelweb.cuer.app.ui.common.viewmodel + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch + +class ViewModelEffects { + + private val _effects = MutableSharedFlow( + replay = 0, + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val labels: Flow = _effects + + fun emit(label: Effect, scope: CoroutineScope) = + scope.launch { + _effects.emit(label) + } +} diff --git a/shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/filebrowser/FilesComposeables.kt b/shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/filebrowser/FilesComposeables.kt index c5721e088..0e24e517f 100644 --- a/shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/filebrowser/FilesComposeables.kt +++ b/shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/filebrowser/FilesComposeables.kt @@ -23,8 +23,8 @@ import uk.co.sentinelweb.cuer.app.ui.common.compose.CuerSharedAppBarComposables. import uk.co.sentinelweb.cuer.app.ui.common.compose.CuerSharedTheme import uk.co.sentinelweb.cuer.app.ui.common.compose.CustomSnackbar import uk.co.sentinelweb.cuer.app.ui.common.compose.colorTransparentYellow -import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.Label -import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.Label.None +import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.Effect +import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.Effect.None import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.ListItem.ListItemType.* import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.Model.Companion.Initial import uk.co.sentinelweb.cuer.app.ui.filebrowser.FilesContract.Sort.Alpha @@ -40,12 +40,12 @@ object FilesComposeables { fun FileBrowserAppUi(viewModel: FilesContract.ViewModel) { val model = viewModel.modelObservable.collectAsState(initial = Initial) val snackbarHostState = remember { SnackbarHostState() } - val label = viewModel.labels.collectAsState(initial = None) + val effects = viewModel.effects.collectAsState(initial = None) - LaunchedEffect(label.value) { - when (label.value) { - is Label.ErrorMessage -> snackbarHostState.showSnackbar( - message = (label.value as Label.ErrorMessage).message, + LaunchedEffect(effects.value) { + when (effects.value) { + is Effect.ErrorMessage -> snackbarHostState.showSnackbar( + message = (effects.value as Effect.ErrorMessage).message, ) else -> Unit @@ -222,14 +222,14 @@ object FilesComposeables { text = text, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(4.dp).align(Alignment.CenterVertically) + modifier = Modifier.padding(4.dp).align(CenterVertically) ) } Text( text = listItem.timeSince, style = MaterialTheme.typography.bodySmall, color = Color.Gray, - modifier = Modifier.padding(2.dp).align(Alignment.CenterVertically) + modifier = Modifier.padding(2.dp).align(CenterVertically) ) } } diff --git a/shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/filebrowser/FilesContract.kt b/shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/filebrowser/FilesContract.kt index 7df8c42de..06407369a 100644 --- a/shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/filebrowser/FilesContract.kt +++ b/shared/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/ui/filebrowser/FilesContract.kt @@ -11,7 +11,7 @@ interface FilesContract { interface ViewModel { val modelObservable: Flow - val labels: Flow