diff --git a/apps/vbox/client/androidApp/src/main/kotlin/ru/shadowsparky/videobox/multiplatform/MainActivity.kt b/apps/vbox/client/androidApp/src/main/kotlin/ru/shadowsparky/videobox/multiplatform/MainActivity.kt index 6826fe0..53da1ff 100644 --- a/apps/vbox/client/androidApp/src/main/kotlin/ru/shadowsparky/videobox/multiplatform/MainActivity.kt +++ b/apps/vbox/client/androidApp/src/main/kotlin/ru/shadowsparky/videobox/multiplatform/MainActivity.kt @@ -16,14 +16,14 @@ import kotlinx.coroutines.launch import ru.shadowsparky.koin import ru.shadowsparky.ui.theme.LocalColorSchemeProvider import ru.shadowsparky.vbox.shared.presentation.Nav -import ru.shadowsparky.vbox.shared.presentation.RootComponent import ru.shadowsparky.vbox.shared.presentation.RootFactory import ru.shadowsparky.vbox.shared.presentation.UrlRoutes +import ru.shadowsparky.vbox.shared.presentation.VBoxRootComponent import ru.shadowsparky.vbox.shared.presentation.nav.Route import ru.shadowsparky.videobox.multiplatform.piggy.getPiggyColorScheme class MainActivity : ComponentActivity() { - private lateinit var root: RootComponent + private lateinit var root: VBoxRootComponent override fun onCreate(savedInstanceState: Bundle?) { edgeToEdge(false) @@ -46,10 +46,10 @@ class MainActivity : ComponentActivity() { setContent { if (isPiggyActivity) { CompositionLocalProvider(LocalColorSchemeProvider provides getPiggyColorScheme()) { - Nav(rootComponent = root) + Nav(root = root) } } else { - Nav(rootComponent = root) + Nav(root = root) } edgeToEdge(isSystemInDarkTheme()) } diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/BaseScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/BaseScreen.kt index fc462dd..71a3c97 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/BaseScreen.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/BaseScreen.kt @@ -42,15 +42,15 @@ fun Modifier.imePaddingInternal(): Modifier { } @Composable -fun Nav(rootComponent: RootComponent) { +fun Nav(root: VBoxRootComponent) { CompositionLocalProvider( - LocalImageLoader provides remember { rootComponent.imageLoaderProvider.provide() } - ) { NavInternal(rootComponent) } + LocalImageLoader provides remember { root.imageLoaderProvider.provide() } + ) { NavInternal(root) } } @Composable -private fun NavInternal(rootComponent: RootComponent) { - Nav(rootComponent) { child, route -> +private fun NavInternal(root: VBoxRootComponent) { + Nav(root) { child, route -> when (child) { is Child.PlayerChild -> PlayerScreen(child.component) is Child.OverviewChild -> OverviewScreen(child.component) @@ -73,12 +73,12 @@ private fun NavInternal(rootComponent: RootComponent) { MainScreen( route as Route, child, - rootComponent + root ) } } } - UpdateDialog(rootComponent.updateComponent) + UpdateDialog(root.updateComponent) } } diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/MainScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/MainScreen.kt index dd2e775..fcfbce0 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/MainScreen.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/MainScreen.kt @@ -22,15 +22,15 @@ import ru.shadowsparky.vbox.shared.presentation.search.SearchScreen import ru.shadowsparky.vbox.shared.presentation.settings.SettingsContent @Composable -fun MainScreen(route: Route, child: Child, rootComponent: RootComponent) { +fun MainScreen(route: Route, child: Child, root: VBoxRootComponent) { when (child) { - is Child.SearchChild -> SearchScreen(child.component, route) { rootComponent.nav(it, true) } + is Child.SearchChild -> SearchScreen(child.component, route) { root.nav(it, true) } is Child.SavedMovieChild -> SavedMovieScreen( child.component, route - ) { rootComponent.navReplace(it) } + ) { root.navReplace(it) } - else -> MainScreenInternal(route, child) { rootComponent.navReplace(it) } + else -> MainScreenInternal(route, child) { root.navReplace(it) } } } diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/RootComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/VBoxRootComponent.kt similarity index 90% rename from apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/RootComponent.kt rename to apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/VBoxRootComponent.kt index 611d1de..e090292 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/RootComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/VBoxRootComponent.kt @@ -12,23 +12,23 @@ import kotlinx.serialization.KSerializer import org.koin.core.Koin import org.koin.core.annotation.Factory import ru.shadowsparky.http.domain.TokenStorage -import ru.shadowsparky.ui.nav.BaseRootComponent +import ru.shadowsparky.ui.nav.RootComponent import ru.shadowsparky.updater.presentation.UpdateComponentFactory import ru.shadowsparky.vbox.shared.di.factory.ImageLoaderProvider import ru.shadowsparky.vbox.shared.domain.HeathCheck import ru.shadowsparky.vbox.shared.presentation.nav.Route @OptIn(ExperimentalDecomposeApi::class) -class RootComponent( +class VBoxRootComponent( componentContext: ComponentContext, tokenStorage: TokenStorage, override val initStack: List, private val healthCheck: HeathCheck, val imageLoaderProvider: ImageLoaderProvider, - private val updateComponentFactory: UpdateComponentFactory, + updateComponentFactory: UpdateComponentFactory, override val serializer: KSerializer = Route.serializer(), override val koin: Koin = ru.shadowsparky.koin -) : BaseRootComponent(componentContext), WebNavigationOwner { +) : RootComponent(componentContext), WebNavigationOwner { val updateComponent = updateComponentFactory.create(childContext("UpdateComponentRoot")) private val scope = createComponentScope() val isLoggedIn = tokenStorage.token.map { @@ -68,15 +68,15 @@ class RootFactory( fun create( context: ComponentContext, initStack: Route - ): RootComponent { + ): VBoxRootComponent { return create(context, listOf(initStack)) } fun create( context: ComponentContext, initStack: List = listOf(Route.default) - ): RootComponent { - return RootComponent( + ): VBoxRootComponent { + return VBoxRootComponent( context, tokenStorage, listOf(Route.Loading(next = initStack.toSet())), diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/list/VideoScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/list/VideoScreen.kt index afce804..665bf15 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/list/VideoScreen.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/list/VideoScreen.kt @@ -4,6 +4,7 @@ import androiddev.apps.vbox.client.shared.generated.resources.Res import androiddev.apps.vbox.client.shared.generated.resources.not_found import androiddev.apps.vbox.client.shared.generated.resources.recommendations import androiddev.apps.vbox.client.shared.generated.resources.retry +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -80,7 +81,6 @@ fun VideoScreen(component: VideoScreenComponent) { onRefresh = { component.load() }, onShowOverview = { component.showOverview(it) }, emptyResource = Res.string.not_found, - onLoad = { component.load() } ) }, appBar = { @@ -107,8 +107,7 @@ fun VideoContent(component: VideoScreenComponent, paddingValues: PaddingValues) items, onRefresh = { component.load() }, onShowOverview = { component.showOverview(it) }, - emptyResource = Res.string.not_found, - onLoad = { component.load() } + emptyResource = Res.string.not_found ) } @@ -119,20 +118,21 @@ fun VideoScreenContent( items: List?, emptyResource: StringResource, onRefresh: () -> Unit, - onShowOverview: (Long) -> Unit, - onLoad: () -> Unit + onShowOverview: (Long) -> Unit ) { - LaunchedEffect(Unit) { - onLoad() - } if (error != null) { ErrorAndRetry(error) { onRefresh() } } else if (items != null) { - VideosInfo(items = items, paddingValues, emptyResource) { - onShowOverview(it.id) - } + AnimatedContent( + items, + content = { + VideosInfo(items = items, paddingValues, emptyResource) { + onShowOverview(it.id) + } + } + ) } else { Loading() } diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/list/VideoScreenComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/list/VideoScreenComponent.kt index 5d9d7f6..8f24741 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/list/VideoScreenComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/list/VideoScreenComponent.kt @@ -9,8 +9,8 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.koin.core.annotation.Factory import org.koin.core.annotation.Named -import ru.shadowsparky.ui.nav.BaseRootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider import ru.shadowsparky.vbox.shared.domain.VideoApi import ru.shadowsparky.vbox.shared.domain.model.VideoItem @@ -39,6 +39,8 @@ class VideoScreenComponent( val items: StateFlow?> = _items.asStateFlow() val errorCode = _errorMsg.asSharedFlow() + init { load() } + fun load() { componentScope.launch(dispatcherProvider.main + exceptionHandler) { _errorMsg.emit(null) @@ -58,7 +60,7 @@ class VideosChildFactory( override fun create( route: Route.Videos, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.VideosChild { val comp = VideoScreenComponent( child, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/loading/LoadingComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/loading/LoadingComponent.kt index c1fafd0..5bb4c3b 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/loading/LoadingComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/loading/LoadingComponent.kt @@ -6,8 +6,8 @@ import kotlinx.coroutines.flow.map import org.koin.core.annotation.Factory import org.koin.core.annotation.Named import ru.shadowsparky.http.domain.TokenStorage -import ru.shadowsparky.ui.nav.BaseRootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.presentation.nav.Child import ru.shadowsparky.vbox.shared.presentation.nav.Route import ru.shadowsparky.vbox.shared.presentation.nav.RouteIds @@ -26,7 +26,7 @@ class LoadingComponentFactory( override fun create( route: Route.Loading, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.LoadingChild { return Child.LoadingChild( LoadingComponent( diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Child.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Child.kt index e7ac687..9133d2d 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Child.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Child.kt @@ -7,7 +7,7 @@ import ru.shadowsparky.vbox.shared.presentation.overview.OverviewComponent import ru.shadowsparky.vbox.shared.presentation.overview.episode.EpisodeOverviewComponent import ru.shadowsparky.vbox.shared.presentation.overview.season.SeasonOverviewComponent import ru.shadowsparky.vbox.shared.presentation.player.PlayerComponent -import ru.shadowsparky.vbox.shared.presentation.saved.SavedMovieScreenComponent +import ru.shadowsparky.vbox.shared.presentation.saved.SavedMovieComponent import ru.shadowsparky.vbox.shared.presentation.search.SearchScreenComponent import ru.shadowsparky.vbox.shared.presentation.settings.SettingsComponent import ru.shadowsparky.vbox.shared.presentation.settings.auth.AuthComponent @@ -27,7 +27,7 @@ sealed class Child : ru.shadowsparky.ui.nav.Child { class EditServerAddrChild(val component: EditServComponent) : Child() class AuthChild(val component: AuthComponent) : Child() class RegisterChild(val component: RegisterComponent) : Child() - class SavedMovieChild(val component: SavedMovieScreenComponent) : Child() + class SavedMovieChild(val component: SavedMovieComponent) : Child() class EditPasswordChild(val component: EditPasswordComponent) : Child() class LoadingChild(val component: LoadingComponent) : Child() class PlayerChild(val component: PlayerComponent) : Child() diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Route.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Route.kt index 645170c..74cceaa 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Route.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Route.kt @@ -23,7 +23,7 @@ sealed class Route : ru.shadowsparky.ui.nav.Route() { @Serializable data class Loading( - val authRequired: Boolean = isJs, + val authRequired: Boolean = true, val next: Collection = listOf(default), override val routeId: String = RouteIds.LOADING ) : Route() diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/offline/OfflineVideoComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/offline/OfflineVideoComponent.kt index 1cf4aab..5ee10b4 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/offline/OfflineVideoComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/offline/OfflineVideoComponent.kt @@ -4,8 +4,8 @@ import com.arkivanov.decompose.ComponentContext import kotlinx.coroutines.launch import org.koin.core.annotation.Factory import org.koin.core.annotation.Named -import ru.shadowsparky.ui.nav.BaseRootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.domain.OfflineState import ru.shadowsparky.vbox.shared.domain.OfflineVideo import ru.shadowsparky.vbox.shared.domain.OfflineVideoInteractor @@ -50,7 +50,7 @@ class OfflineVideoComponentFactory( override fun create( route: Route.OfflineVideo, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.OfflineVideoChild { val comp = OfflineVideoComponent( child, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/OverviewComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/OverviewComponent.kt index 14731cc..f991741 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/OverviewComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/OverviewComponent.kt @@ -15,8 +15,9 @@ import org.koin.core.annotation.Factory import org.koin.core.annotation.Named import ru.shadowsparky.domain.Clip import ru.shadowsparky.domain.ClipboardManager -import ru.shadowsparky.ui.nav.BaseRootComponent +import ru.shadowsparky.ui.nav.RootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider import ru.shadowsparky.vbox.shared.domain.AddOfflineVideoUseCase import ru.shadowsparky.vbox.shared.domain.OfflineStorage @@ -165,7 +166,7 @@ class OverviewFactory( override fun create( route: Route.Overview, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.OverviewChild { val comp = OverviewComponent( savedMovieRepository, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewComponent.kt index 90d9667..9ba977d 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewComponent.kt @@ -20,8 +20,9 @@ import org.koin.core.annotation.Named import ru.shadowsparky.domain.Clip import ru.shadowsparky.domain.ClipboardManager import ru.shadowsparky.domain.DispatcherProvider -import ru.shadowsparky.ui.nav.BaseRootComponent +import ru.shadowsparky.ui.nav.RootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.domain.AddOfflineVideoUseCase import ru.shadowsparky.vbox.shared.domain.OfflineStorage import ru.shadowsparky.vbox.shared.domain.OfflineVideo @@ -178,7 +179,7 @@ class EpisodeOverviewFactory( override fun create( route: Route.Overview.Episode, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.EpisodeOverviewChild { val comp = EpisodeOverviewComponent( child, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/season/SeasonOverviewComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/season/SeasonOverviewComponent.kt index f2b5ac2..f50bdbb 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/season/SeasonOverviewComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/season/SeasonOverviewComponent.kt @@ -8,8 +8,8 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.koin.core.annotation.Factory import org.koin.core.annotation.Named -import ru.shadowsparky.ui.nav.BaseRootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider import ru.shadowsparky.vbox.shared.domain.VideoApi import ru.shadowsparky.vbox.shared.domain.model.Season @@ -54,7 +54,7 @@ class SeasonOverviewFactory( override fun create( route: Route.Overview.Season, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.SeasonOverviewChild { val comp = SeasonOverviewComponent( videoApi, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/player/PlayerComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/player/PlayerComponent.kt index da60515..e8ea752 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/player/PlayerComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/player/PlayerComponent.kt @@ -9,8 +9,9 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.launch import org.koin.core.annotation.Factory import org.koin.core.annotation.Named -import ru.shadowsparky.ui.nav.BaseRootComponent +import ru.shadowsparky.ui.nav.RootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.domain.PlayerInteractor import ru.shadowsparky.vbox.shared.presentation.createComponentScope import ru.shadowsparky.vbox.shared.presentation.nav.Child @@ -112,7 +113,7 @@ class PlayerComponentFactory( override fun create( route: Route.Overview.Player, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.PlayerChild { val component = PlayerComponent( child, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/saved/SavedMovieScreenComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/saved/SavedMovieComponent.kt similarity index 93% rename from apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/saved/SavedMovieScreenComponent.kt rename to apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/saved/SavedMovieComponent.kt index 9717661..6029caa 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/saved/SavedMovieScreenComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/saved/SavedMovieComponent.kt @@ -8,8 +8,9 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import org.koin.core.annotation.Factory import org.koin.core.annotation.Named -import ru.shadowsparky.ui.nav.BaseRootComponent +import ru.shadowsparky.ui.nav.RootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.domain.SavedMovieRepository import ru.shadowsparky.vbox.shared.domain.model.VideoDetails import ru.shadowsparky.vbox.shared.domain.model.VideoItem @@ -19,7 +20,7 @@ import ru.shadowsparky.vbox.shared.presentation.nav.Child import ru.shadowsparky.vbox.shared.presentation.nav.Route import ru.shadowsparky.vbox.shared.presentation.nav.RouteIds -class SavedMovieScreenComponent( +class SavedMovieComponent( private val context: ComponentContext, repo: SavedMovieRepository, userTagRepository: UserTagRepository, @@ -70,9 +71,9 @@ class SavedMovieFactory( override fun create( route: Route.Bookmark, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.SavedMovieChild { - val comp = SavedMovieScreenComponent( + val comp = SavedMovieComponent( child, repo, doBack = { root.onBack() }, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/saved/SavedMovieScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/saved/SavedMovieScreen.kt index f508ad2..4f603cb 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/saved/SavedMovieScreen.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/saved/SavedMovieScreen.kt @@ -7,21 +7,16 @@ import androiddev.apps.vbox.client.shared.generated.resources.saved_movies_not_f import androiddev.apps.vbox.client.shared.generated.resources.user_tags import androiddev.apps.vbox.client.shared.generated.resources.user_tags_filter_disable import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.items -import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults @@ -38,6 +33,7 @@ import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource import ru.shadowsparky.ui.LocalBlurState import ru.shadowsparky.ui.applyBlurEffect +import ru.shadowsparky.ui.components.AlertContent import ru.shadowsparky.ui.components.LazyColumn import ru.shadowsparky.ui.theme.ComponentDefaults import ru.shadowsparky.vbox.shared.domain.tag.TagInfo @@ -47,7 +43,7 @@ import ru.shadowsparky.vbox.shared.presentation.nav.Route @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SavedMovieScreen(comp: SavedMovieScreenComponent, route: Route, onNavigate: (Route) -> Unit) { +fun SavedMovieScreen(comp: SavedMovieComponent, route: Route, onNavigate: (Route) -> Unit) { val tags by comp.userTags.collectAsStateWithLifecycle(null) var filterBottomSheetVisible by rememberSaveable { mutableStateOf(false) } BaseMainScreen( @@ -86,7 +82,7 @@ fun SavedMovieScreen(comp: SavedMovieScreenComponent, route: Route, onNavigate: } @Composable -private fun SavedMovieContent(comp: SavedMovieScreenComponent, paddingValues: PaddingValues) { +private fun SavedMovieContent(comp: SavedMovieComponent, paddingValues: PaddingValues) { val items by comp.all.collectAsStateWithLifecycle(null) VideoScreenContent( paddingValues, @@ -94,8 +90,7 @@ private fun SavedMovieContent(comp: SavedMovieScreenComponent, paddingValues: Pa items, onRefresh = {}, onShowOverview = { comp.showOverview(it) }, - emptyResource = Res.string.saved_movies_not_found, - onLoad = {} + emptyResource = Res.string.saved_movies_not_found ) } @@ -108,38 +103,30 @@ private fun FilterDialog( onFilterBy: (String?) -> Unit ) { allTags ?: return - BasicAlertDialog(onDismissRequest = onDismiss) { - Surface(shape = MaterialTheme.shapes.extraLarge) { - Column(modifier = Modifier.padding(16.dp)) { - Text( - text = stringResource(Res.string.user_tags), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface - ) - Spacer(Modifier.height(16.dp)) - LazyColumn(modifier = Modifier.fillMaxWidth()) { - item { - FilterItemCheckBox( - isChecked = filterTag == null, - name = stringResource(Res.string.user_tags_filter_disable) - ) { - onFilterBy(null) - onDismiss() - } - } - items( - items = allTags.filter { it.id != null }, - key = { tag -> tag.id ?: tag.tag } - ) { tag -> - Spacer(Modifier.height(4.dp)) - FilterItemCheckBox( - isChecked = filterTag == tag.tag, - name = tag.tag - ) { - onFilterBy(tag.tag) - onDismiss() - } - } + AlertContent( + onDismiss, + title = stringResource(Res.string.user_tags) + ) { + LazyColumn(modifier = Modifier.fillMaxWidth()) { + item { + FilterItemCheckBox( + isChecked = filterTag == null, + name = stringResource(Res.string.user_tags_filter_disable) + ) { + onFilterBy(null) + onDismiss() + } + } + items( + items = allTags.filter { it.id != null }, + key = { tag -> tag.id ?: tag.tag } + ) { tag -> + FilterItemCheckBox( + isChecked = filterTag == tag.tag, + name = tag.tag + ) { + onFilterBy(tag.tag) + onDismiss() } } } diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/search/SearchScreenComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/search/SearchScreenComponent.kt index cc2f714..71e9b28 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/search/SearchScreenComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/search/SearchScreenComponent.kt @@ -4,8 +4,9 @@ import com.arkivanov.decompose.ComponentContext import kotlinx.coroutines.launch import org.koin.core.annotation.Factory import org.koin.core.annotation.Named -import ru.shadowsparky.ui.nav.BaseRootComponent +import ru.shadowsparky.ui.nav.RootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.domain.SearchRepository import ru.shadowsparky.vbox.shared.presentation.createComponentScope import ru.shadowsparky.vbox.shared.presentation.nav.Child @@ -43,7 +44,7 @@ class SearchScreenFactory( override fun create( route: Route.SearchVideos, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.SearchChild { val comp = SearchScreenComponent( child, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/SettingsComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/SettingsComponent.kt index cb32f7e..98d38d0 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/SettingsComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/SettingsComponent.kt @@ -9,8 +9,8 @@ import org.koin.core.annotation.Named import ru.shadowsparky.domain.AppConfig import ru.shadowsparky.http.domain.LogoutAction import ru.shadowsparky.http.domain.TokenStorage -import ru.shadowsparky.ui.nav.BaseRootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.updater.presentation.UpdateComponentFactory import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider import ru.shadowsparky.vbox.shared.domain.OfflineStorage @@ -83,7 +83,7 @@ class SettingsComponentFactory( override fun create( route: Route.Settings, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.SettingsChild { val comp = SettingsComponent( child, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/auth/AuthComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/auth/AuthComponent.kt index 165e940..741c77f 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/auth/AuthComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/auth/AuthComponent.kt @@ -7,8 +7,8 @@ import kotlinx.coroutines.launch import org.koin.core.annotation.Factory import org.koin.core.annotation.Named import ru.shadowsparky.domain.Log -import ru.shadowsparky.ui.nav.BaseRootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider import ru.shadowsparky.vbox.shared.domain.AuthTokenRepository import ru.shadowsparky.vbox.shared.domain.model.LoginException @@ -64,7 +64,7 @@ class AuthComponentFactory( override fun create( route: Route.Settings.Auth, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.AuthChild { val comp = AuthComponent( route.required, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/pass/EditPasswordComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/pass/EditPasswordComponent.kt index dd05751..2bd1699 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/pass/EditPasswordComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/pass/EditPasswordComponent.kt @@ -6,8 +6,9 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.koin.core.annotation.Factory import org.koin.core.annotation.Named -import ru.shadowsparky.ui.nav.BaseRootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RootComponent +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.domain.AuthTokenRepository import ru.shadowsparky.vbox.shared.domain.ChangePasswordRequest import ru.shadowsparky.vbox.shared.domain.model.LoginException @@ -53,7 +54,7 @@ class EditPasswordComponentFactory( override fun create( route: Route.Settings.EditPassword, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.EditPasswordChild { val component = EditPasswordComponent( child, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/register/RegisterComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/register/RegisterComponent.kt index affcc47..ac3796e 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/register/RegisterComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/register/RegisterComponent.kt @@ -8,8 +8,8 @@ import org.koin.core.annotation.Factory import org.koin.core.annotation.Named import org.koin.core.component.KoinComponent import ru.shadowsparky.domain.Log -import ru.shadowsparky.ui.nav.BaseRootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider import ru.shadowsparky.vbox.shared.domain.AuthTokenRepository import ru.shadowsparky.vbox.shared.domain.model.LoginException @@ -55,7 +55,7 @@ class RegisterComponentFactory( override fun create( route: Route.Settings.Register, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.RegisterChild { val comp = RegisterComponent( child, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/serv/EditServComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/serv/EditServComponent.kt index fce9965..5876c02 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/serv/EditServComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/serv/EditServComponent.kt @@ -9,8 +9,9 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.koin.core.annotation.Factory import org.koin.core.annotation.Named -import ru.shadowsparky.ui.nav.BaseRootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RootComponent +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider import ru.shadowsparky.vbox.shared.domain.ServerConfiguration import ru.shadowsparky.vbox.shared.domain.ServerConfigurationRepository @@ -27,7 +28,6 @@ class EditServComponent( ) : ComponentContext by context { private val componentScope = createComponentScope() val serverUrl = serverConfigurationRepository.serverConfiguration - .filterNotNull() .map { it.asHttpStr() } .onEach { onUrlChanged(it) } private val _state = MutableStateFlow(EditServState.Idle) @@ -67,7 +67,7 @@ class EditServComponentFactory( override fun create( route: Route.Settings.EditServ, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.EditServerAddrChild { val comp = EditServComponent( child, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/LinkTagBottomSheet.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/LinkTagBottomSheet.kt index 60ffc2b..b129d75 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/LinkTagBottomSheet.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/LinkTagBottomSheet.kt @@ -4,8 +4,6 @@ import androiddev.apps.vbox.client.shared.generated.resources.Res import androiddev.apps.vbox.client.shared.generated.resources.movie_tags_empty import androiddev.apps.vbox.client.shared.generated.resources.user_tags import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -14,15 +12,14 @@ import androidx.compose.material3.Button import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import org.jetbrains.compose.resources.stringResource +import ru.shadowsparky.ui.components.AlertContent import ru.shadowsparky.ui.components.LazyColumn import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.vbox.shared.domain.tag.TagInfo @@ -36,34 +33,25 @@ fun LinkTagBottomSheet( requestTagsScreen: () -> Unit, updateTag: (String, Boolean) -> Unit ) { - ModalBottomSheet( + AlertContent( onDismissRequest = onDismiss, - sheetState = rememberModalBottomSheetState(true) + title = stringResource(Res.string.user_tags) ) { - Column( - modifier = Modifier.fillMaxWidth().padding(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = stringResource(Res.string.user_tags), - style = MaterialTheme.typography.titleLarge - ) - if (allTags == null || linkedTags == null) { - Loading(Modifier.fillMaxWidth()) - } else { - if (allTags.isEmpty()) { - Button( - onClick = { - onDismiss() - requestTagsScreen() - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(Res.string.movie_tags_empty)) - } - } else { - ShowTags(allTags, linkedTags, updateTag) + if (allTags == null || linkedTags == null) { + Loading(Modifier.fillMaxWidth()) + } else { + if (allTags.isEmpty()) { + Button( + onClick = { + onDismiss() + requestTagsScreen() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(Res.string.movie_tags_empty)) } + } else { + ShowTags(allTags, linkedTags, updateTag) } } } @@ -79,28 +67,22 @@ private fun ShowTags( linkedTags.mapNotNull { it.id }.toSet() } - LazyColumn( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { + LazyColumn(modifier = Modifier.fillMaxWidth()) { items( items = allTags.filter { it.id != null }, key = { tag -> tag.id ?: tag.tag } ) { tag -> val tagId = tag.id ?: return@items val isChecked = tagId in linkedTagIds - Row( modifier = Modifier .fillMaxWidth() - .clickable { updateTag(tag.tag, !isChecked) } - .padding(vertical = 8.dp), + .clickable { updateTag(tag.tag, !isChecked) }, verticalAlignment = Alignment.CenterVertically ) { Text( text = tag.tag, - modifier = Modifier.weight(1f) - .padding(start = 8.dp), + modifier = Modifier.weight(1f).padding(start = 8.dp), style = MaterialTheme.typography.bodyLarge ) Checkbox( diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagComponent.kt index 2446730..ec14bfd 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagComponent.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagComponent.kt @@ -4,8 +4,8 @@ import com.arkivanov.decompose.ComponentContext import kotlinx.coroutines.launch import org.koin.core.annotation.Factory import org.koin.core.annotation.Named -import ru.shadowsparky.ui.nav.BaseRootComponent import ru.shadowsparky.ui.nav.ComponentFactory +import ru.shadowsparky.ui.nav.RouteNavigator import ru.shadowsparky.vbox.shared.domain.tag.UserTagRepository import ru.shadowsparky.vbox.shared.presentation.createComponentScope import ru.shadowsparky.vbox.shared.presentation.nav.Child @@ -48,7 +48,7 @@ class TagComponentFactory( override fun create( route: Route.Settings.TagOverview, child: ComponentContext, - root: BaseRootComponent + root: RouteNavigator ): Child.TagChild { val component = TagComponent( child, diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagScreen.kt index 2d53798..dbc452c 100644 --- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagScreen.kt +++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagScreen.kt @@ -9,7 +9,6 @@ import androiddev.apps.vbox.client.shared.generated.resources.user_tags_new_tag_ import androiddev.apps.vbox.client.shared.generated.resources.user_tags_save import androiddev.apps.vbox.client.shared.generated.resources.user_tags_tag_delete import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -17,12 +16,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.material3.TextField -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -39,6 +35,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.jetbrains.compose.resources.stringResource import ru.shadowsparky.ui.AppScaffold +import ru.shadowsparky.ui.components.AlertContent import ru.shadowsparky.ui.components.ExpressiveLazyColumn import ru.shadowsparky.ui.components.TextPreference import ru.shadowsparky.vbox.shared.domain.tag.TagInfo @@ -122,35 +119,26 @@ private fun AddTagBottomSheet( keyboardController?.show() } - ModalBottomSheet( + AlertContent( onDismissRequest = onDismiss, - sheetState = rememberModalBottomSheetState(true) + title = stringResource(Res.string.user_tags_add) ) { - Column( - modifier = Modifier.padding(8.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - Text( - text = stringResource(Res.string.user_tags_add), - style = MaterialTheme.typography.titleLarge - ) - TextField( - value = tag, - onValueChange = { tag = it }, - modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester), - singleLine = true - ) - Button( - onClick = { - onAdd(tag.trim()) - onDismiss() - }, - modifier = Modifier.fillMaxWidth(), - enabled = tag.isNotBlank() && canSave(tag) - ) { Text(stringResource(Res.string.user_tags_save)) } - } + TextField( + value = tag, + onValueChange = { tag = it }, + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester), + singleLine = true + ) + Button( + onClick = { + onAdd(tag.trim()) + onDismiss() + }, + modifier = Modifier.fillMaxWidth(), + enabled = tag.isNotBlank() && canSave(tag) + ) { Text(stringResource(Res.string.user_tags_save)) } } } @@ -169,44 +157,38 @@ private fun EditTagBottomSheet( val tagId = tag.id - ModalBottomSheet(onDismissRequest = onDismiss) { - Column( - modifier = Modifier.fillMaxWidth().padding(8.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - Text( - text = stringResource(Res.string.user_tags_edit_title), - style = MaterialTheme.typography.titleLarge + AlertContent( + onDismissRequest = onDismiss, + title = stringResource(Res.string.user_tags_edit_title) + ) { + TextField( + value = newTag, + onValueChange = { newTag = it }, + modifier = Modifier.fillMaxWidth(), + label = { Text(stringResource(Res.string.user_tags_new_tag)) }, + singleLine = true + ) + Button( + onClick = { + if (tagId != null && newTag.isNotEmpty()) { + onEdit(tag.tag, newTag) + onDismiss() + } + }, + modifier = Modifier.fillMaxWidth(), + enabled = tagId != null && newTag.isNotEmpty() && newTag != tag.tag && canSave( + newTag ) - TextField( - value = newTag, - onValueChange = { newTag = it }, - modifier = Modifier.fillMaxWidth(), - label = { Text(stringResource(Res.string.user_tags_new_tag)) }, - singleLine = true - ) - Button( - onClick = { - if (tagId != null && newTag.isNotEmpty()) { - onEdit(tag.tag, newTag) - onDismiss() - } - }, - modifier = Modifier.fillMaxWidth(), - enabled = tagId != null && newTag.isNotEmpty() && newTag != tag.tag && canSave( - newTag - ) - ) { Text(stringResource(Res.string.user_tags_new_tag_save)) } - OutlinedButton( - onClick = { - if (tagId != null) { - onDelete(tag.tag) - onDismiss() - } - }, - modifier = Modifier.fillMaxWidth(), - enabled = tagId != null - ) { Text(stringResource(Res.string.user_tags_tag_delete)) } - } + ) { Text(stringResource(Res.string.user_tags_new_tag_save)) } + OutlinedButton( + onClick = { + if (tagId != null) { + onDelete(tag.tag) + onDismiss() + } + }, + modifier = Modifier.fillMaxWidth(), + enabled = tagId != null + ) { Text(stringResource(Res.string.user_tags_tag_delete)) } } } diff --git a/apps/vbox/client/web/src/wasmJsMain/kotlin/ru/shadowsparky/vbox/Main.kt b/apps/vbox/client/web/src/wasmJsMain/kotlin/ru/shadowsparky/vbox/Main.kt index 0c82fd1..c64bdcd 100644 --- a/apps/vbox/client/web/src/wasmJsMain/kotlin/ru/shadowsparky/vbox/Main.kt +++ b/apps/vbox/client/web/src/wasmJsMain/kotlin/ru/shadowsparky/vbox/Main.kt @@ -30,9 +30,9 @@ import ru.shadowsparky.koin import ru.shadowsparky.ui.hideElementById import ru.shadowsparky.ui.hookPageVisibility import ru.shadowsparky.vbox.shared.presentation.Nav -import ru.shadowsparky.vbox.shared.presentation.RootComponent import ru.shadowsparky.vbox.shared.presentation.RootFactory import ru.shadowsparky.vbox.shared.presentation.UrlRoutes +import ru.shadowsparky.vbox.shared.presentation.VBoxRootComponent import ru.shadowsparky.vbox.shared.presentation.nav.Route @KoinApplication @@ -67,9 +67,9 @@ fun mainInternal() { val composeRoot = document.getElementById("compose-root") as HTMLElement - var rootRef: RootComponent? = null + var rootRef: VBoxRootComponent? = null - val root: RootComponent = withWebHistory { stateKeeper: StateKeeper, deepLinkUrl: String? -> + val root: VBoxRootComponent = withWebHistory { stateKeeper: StateKeeper, deepLinkUrl: String? -> if (rootRef == null) { rootRef = createRoot(lifecycle, stateKeeper, deepLinkUrl) } @@ -103,7 +103,7 @@ private fun createRoot( lifecycle: LifecycleRegistry, stateKeeper: StateKeeper, deepLinkUrl: String?, -): RootComponent { +): VBoxRootComponent { val ctx = DefaultComponentContext( lifecycle = lifecycle, stateKeeper = stateKeeper ) diff --git a/libs/ui/build.gradle.kts b/libs/ui/build.gradle.kts index 27a2c93..7664252 100644 --- a/libs/ui/build.gradle.kts +++ b/libs/ui/build.gradle.kts @@ -12,8 +12,13 @@ kotlin { dependencies { implementation(libs.haze) implementation(libs.haze.materials) + implementation(libs.ui.tooling.preview) } } } android { namespace = "ru.shadowsparky.ui" } } + +dependencies { + androidRuntimeClasspath(libs.ui.tooling) +} diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/AlertContent.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/AlertContent.kt new file mode 100644 index 0000000..2b82d0f --- /dev/null +++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/AlertContent.kt @@ -0,0 +1,69 @@ +package ru.shadowsparky.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.ModalBottomSheetProperties +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogProperties + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AlertContent( + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + title: String? = null, + canDismiss: Boolean = true, + content: @Composable () -> Unit, +) { + @Composable + fun BaseDialogLayout() { + Column( + modifier = Modifier.padding(8.dp), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + title?.let { + Text( + text = it, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + content() + } + } + if (isTv()) { + BasicAlertDialog( + onDismissRequest = onDismissRequest, + modifier = modifier, + properties = DialogProperties( + dismissOnBackPress = canDismiss, + dismissOnClickOutside = canDismiss + ) + ) { + Surface( + shape = MaterialTheme.shapes.extraLarge, + color = MaterialTheme.colorScheme.surface, + tonalElevation = 6.dp + ) { BaseDialogLayout() } + } + } else { + ModalBottomSheet( + onDismissRequest = onDismissRequest, + modifier = modifier, + properties = ModalBottomSheetProperties( + shouldDismissOnBackPress = canDismiss, + shouldDismissOnClickOutside = canDismiss + ), + shape = MaterialTheme.shapes.extraLarge + ) { BaseDialogLayout() } + } +} diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/BackButton.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/BackButton.kt index b3ca821..e35001c 100644 --- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/BackButton.kt +++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/BackButton.kt @@ -8,6 +8,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview import org.jetbrains.compose.resources.painterResource @Composable @@ -22,3 +23,9 @@ fun BackButton(doBack: () -> Unit) { ) ) { Icon(painterResource(Res.drawable.back), contentDescription = null) } } + +@Preview +@Composable +private fun BackButtonPreview() { + BackButton {} +} diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Expressive.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Expressive.kt index 2079c47..0269972 100644 --- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Expressive.kt +++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Expressive.kt @@ -11,6 +11,7 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -139,3 +140,23 @@ fun ExpressiveCard( modifier = modifier.padding(horizontal = horizontalPadding, vertical = verticalSpacing) ) { content() } } + +@Composable +@Preview +private fun ExpressiveLazyColumnPreview() { + ExpressiveLazyColumn { + section { + item { + TextPreference(text = "item one") + } + item { + TextPreference(text = "item two") + } + } + section { + item { + TextPreference(text = "next section started") + } + } + } +} diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/ListPreference.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/ListPreference.kt deleted file mode 100644 index 242cdbf..0000000 --- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/ListPreference.kt +++ /dev/null @@ -1,66 +0,0 @@ -package ru.shadowsparky.ui.components - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuAnchorType -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ListPreference( - title: String, - items: Map, - selectedItem: T, - onItemSelected: (T) -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, -) { - var expanded by rememberSaveable { mutableStateOf(false) } - val entries = remember(items) { items.entries.toList() } - val selectedLabel = remember(items, selectedItem) { items[selectedItem].orEmpty() } - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { if (enabled) expanded = !expanded }, - modifier = modifier - ) { - OutlinedTextField( - value = selectedLabel, - onValueChange = {}, // readOnly - readOnly = true, - label = { Text(title) }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, - modifier = Modifier - .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable, enabled) - .fillMaxWidth(), - enabled = enabled, - singleLine = true - ) - - ExposedDropdownMenu( - expanded = expanded && enabled, - onDismissRequest = { expanded = false } - ) { - entries.forEach { (value, label) -> - DropdownMenuItem( - text = { Text(label) }, - onClick = { - expanded = false - if (value != selectedItem) onItemSelected(value) - } - ) - } - } - } -} diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Loading.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Loading.kt index cf313a2..fa1d18b 100644 --- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Loading.kt +++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Loading.kt @@ -7,6 +7,7 @@ import androidx.compose.material3.LoadingIndicator import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @OptIn(ExperimentalMaterial3ExpressiveApi::class) @@ -16,3 +17,9 @@ fun Loading(modifier: Modifier) { LoadingIndicator(Modifier.size(96.dp)) } } + +@Preview +@Composable +fun LoadingPreview() { + Loading(Modifier) +} diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/PasswordTextField.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/PasswordTextField.kt index d2984d9..086339c 100644 --- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/PasswordTextField.kt +++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/PasswordTextField.kt @@ -60,4 +60,3 @@ fun PasswordTextField( } ) } - diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/PreferenceCategory.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/PreferenceCategory.kt deleted file mode 100644 index b5569dd..0000000 --- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/PreferenceCategory.kt +++ /dev/null @@ -1,27 +0,0 @@ -package ru.shadowsparky.ui.components - -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -fun PreferenceCategory( - title: String, - modifier: Modifier = Modifier, -) { - Text( - text = title, - modifier = modifier - .padding( - start = 16.dp, - top = 24.dp, - end = 16.dp, - bottom = 8.dp, - ), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.titleSmall, - ) -} diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/ComponentFactory.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/ComponentFactory.kt index 6e1c7bc..52c97c2 100644 --- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/ComponentFactory.kt +++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/ComponentFactory.kt @@ -3,5 +3,5 @@ package ru.shadowsparky.ui.nav import com.arkivanov.decompose.ComponentContext interface ComponentFactory { - fun create(route: R, child: ComponentContext, root: BaseRootComponent): T + fun create(route: R, child: ComponentContext, root: RouteNavigator): T } diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/Nav.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/Nav.kt index 9fdea6e..159fe4a 100644 --- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/Nav.kt +++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/Nav.kt @@ -14,7 +14,7 @@ import ru.shadowsparky.ui.theme.getColorScheme @OptIn(ExperimentalDecomposeApi::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun Nav( - rootComponent: BaseRootComponent<*>, + rootComponent: RootComponent<*>, process: @Composable (child: Child, route: Route) -> Unit ) { Children( diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/BaseRootComponent.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/RootComponent.kt similarity index 68% rename from libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/BaseRootComponent.kt rename to libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/RootComponent.kt index 2754552..8a85955 100644 --- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/BaseRootComponent.kt +++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/RootComponent.kt @@ -18,18 +18,18 @@ import kotlinx.serialization.KSerializer import org.koin.core.Koin import org.koin.core.qualifier.named -abstract class BaseRootComponent( +abstract class RootComponent( private val componentContext: ComponentContext, -) : ComponentContext by componentContext, BackHandlerOwner { +) : ComponentContext by componentContext, BackHandlerOwner, RouteNavigator { + protected abstract val initStack: List protected abstract val serializer: KSerializer protected abstract val koin: Koin private val scope = coroutineScope() - protected val nav = StackNavigation() - protected val childStack by lazy { + val childStack by lazy { childStack( source = nav, serializer = serializer, @@ -41,36 +41,26 @@ abstract class BaseRootComponent( private val _onFinish = MutableStateFlow(null) val onFinish = _onFinish.filterNotNull() - fun navReplace(vararg route: R) { + override fun navReplace(vararg route: R) { nav.replaceAll(*route) } - fun nav(route: R, bringToFront: Boolean = false) { - if (bringToFront) { - nav.bringToFront(route) - } else { - nav.pushNew(route) - } + override fun nav(route: R, bringToFront: Boolean) { + if (bringToFront) nav.bringToFront(route) else nav.pushNew(route) } - fun onBack() { - nav.pop { popped -> - if (!popped) finish() - } + override fun onBack() { + nav.pop { popped -> if (!popped) finish() } } - fun finish() { + override fun finish() { scope.launch { _onFinish.emit(Unit) } } val valueChildStack: Value> by lazy { childStack } protected open fun newChild(route: Route, ctx: ComponentContext): Child { - val factory = route.provideFactory(koin) - return factory.create(route, ctx, this as BaseRootComponent) - } - - private fun Route.provideFactory(koin: Koin): ComponentFactory { - return koin.get>(named(routeId)) + val factory = koin.get>(named(route.routeId)) + return factory.create(route, ctx, this as RouteNavigator) } } diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/RouteNavigator.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/RouteNavigator.kt new file mode 100644 index 0000000..444deca --- /dev/null +++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/nav/RouteNavigator.kt @@ -0,0 +1,8 @@ +package ru.shadowsparky.ui.nav + +interface RouteNavigator { + fun navReplace(vararg route: @UnsafeVariance R) + fun nav(route: @UnsafeVariance R, bringToFront: Boolean = false) + fun onBack() + fun finish() +} diff --git a/libs/updater/updater-client/src/commonMain/kotlin/ru/shadowsparky/updater/presentation/UpdateDialog.kt b/libs/updater/updater-client/src/commonMain/kotlin/ru/shadowsparky/updater/presentation/UpdateDialog.kt index f14b306..ffbc44b 100644 --- a/libs/updater/updater-client/src/commonMain/kotlin/ru/shadowsparky/updater/presentation/UpdateDialog.kt +++ b/libs/updater/updater-client/src/commonMain/kotlin/ru/shadowsparky/updater/presentation/UpdateDialog.kt @@ -18,18 +18,14 @@ import androiddev.libs.updater.updater_client.generated.resources.updater_title_ import androiddev.libs.updater.updater_client.generated.resources.updater_title_permission import androiddev.libs.updater.updater_client.generated.resources.updater_title_ready 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.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -38,10 +34,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.DialogProperties import com.arkivanov.decompose.extensions.compose.subscribeAsState import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource +import ru.shadowsparky.ui.components.AlertContent import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.updater.domain.UpdateState @@ -55,50 +51,50 @@ fun UpdateDialog( val canDismiss = remember(state) { !component.isForceUpdate } val isVisible = remember(state) { state !is UpdateState.Idle } if (isVisible) { - BasicAlertDialog( + AlertContent( modifier = modifier, onDismissRequest = { component.reset() }, - properties = DialogProperties( - canDismiss, - canDismiss, - ), + canDismiss = canDismiss, content = { - Surface(shape = MaterialTheme.shapes.extraLarge) { - Box(modifier = Modifier.padding(16.dp)) { - when (val currentState = state) { - is UpdateState.Checking -> UpdateCheckingContent() - is UpdateState.UpdateAvailable -> UpdateAvailableContent( - versionName = currentState.versionName, - changeLog = currentState.changeLog, - onDownloadClick = { component.downloadUpdate(currentState.url) } - ) - is UpdateState.InstallPermissionRequired -> UpdatePermissionContent( - onGrantClick = { component.requestPermission() } - ) - is UpdateState.Downloading -> UpdateDownloadingContent( - progress = currentState.progress, - downloadedBytes = currentState.downloadedBytes, - totalBytes = currentState.totalBytes - ) - is UpdateState.ReadyToInstall -> UpdateReadyContent( - onInstallClick = { component.installApk(currentState.filePath) } - ) - is UpdateState.Error -> UpdateErrorContent( - message = currentState.message, - onRetryClick = { component.checkUpdates() } - ) - is UpdateState.UpToDate -> { - UpdateFinishedContent(onSkip = { component.reset() }) - } - is UpdateState.Completed -> { - UpdateFinishedContent( - Res.string.updater_title_completed, - onSkip = { component.reset() } - ) - } - else -> Unit - } + when (val currentState = state) { + is UpdateState.Checking -> UpdateCheckingContent() + is UpdateState.UpdateAvailable -> UpdateAvailableContent( + versionName = currentState.versionName, + changeLog = currentState.changeLog, + onDownloadClick = { component.downloadUpdate(currentState.url) } + ) + + is UpdateState.InstallPermissionRequired -> UpdatePermissionContent( + onGrantClick = { component.requestPermission() } + ) + + is UpdateState.Downloading -> UpdateDownloadingContent( + progress = currentState.progress, + downloadedBytes = currentState.downloadedBytes, + totalBytes = currentState.totalBytes + ) + + is UpdateState.ReadyToInstall -> UpdateReadyContent( + onInstallClick = { component.installApk(currentState.filePath) } + ) + + is UpdateState.Error -> UpdateErrorContent( + message = currentState.message, + onRetryClick = { component.checkUpdates() } + ) + + is UpdateState.UpToDate -> { + UpdateFinishedContent(onSkip = { component.reset() }) } + + is UpdateState.Completed -> { + UpdateFinishedContent( + Res.string.updater_title_completed, + onSkip = { component.reset() } + ) + } + + else -> Unit } } ) @@ -138,7 +134,10 @@ private fun UpdateAvailableContent( ) } Button(onClick = onDownloadClick, modifier = Modifier.fillMaxWidth()) { - Text(stringResource(Res.string.updater_btn_download), style = MaterialTheme.typography.labelLarge) + Text( + stringResource(Res.string.updater_btn_download), + style = MaterialTheme.typography.labelLarge + ) } } } @@ -153,10 +152,19 @@ private fun UpdatePermissionContent( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text(text = stringResource(Res.string.updater_title_permission), style = MaterialTheme.typography.titleLarge) - Text(text = stringResource(Res.string.updater_desc_permission), style = MaterialTheme.typography.bodyMedium) + Text( + text = stringResource(Res.string.updater_title_permission), + style = MaterialTheme.typography.titleLarge + ) + Text( + text = stringResource(Res.string.updater_desc_permission), + style = MaterialTheme.typography.bodyMedium + ) Button(onClick = onGrantClick, modifier = Modifier.fillMaxWidth()) { - Text(stringResource(Res.string.updater_btn_grant), style = MaterialTheme.typography.labelLarge) + Text( + stringResource(Res.string.updater_btn_grant), + style = MaterialTheme.typography.labelLarge + ) } } } @@ -173,14 +181,21 @@ private fun UpdateDownloadingContent( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text(text = stringResource(Res.string.updater_title_downloading), style = MaterialTheme.typography.titleMedium) + Text( + text = stringResource(Res.string.updater_title_downloading), + style = MaterialTheme.typography.titleMedium + ) LinearProgressIndicator(progress = { progress }, modifier = Modifier.fillMaxWidth()) val downloadedMb = ((downloadedBytes / (1024f * 1024f)) * 10).toInt() / 10.0 val totalMb = ((totalBytes / (1024f * 1024f)) * 10).toInt() / 10.0 Text( - text = stringResource(Res.string.updater_download_progress, downloadedMb.toString(), totalMb.toString()), + text = stringResource( + Res.string.updater_download_progress, + downloadedMb.toString(), + totalMb.toString() + ), style = MaterialTheme.typography.bodySmall ) } @@ -196,9 +211,15 @@ private fun UpdateReadyContent( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text(text = stringResource(Res.string.updater_title_ready), style = MaterialTheme.typography.titleLarge) + Text( + text = stringResource(Res.string.updater_title_ready), + style = MaterialTheme.typography.titleLarge + ) Button(onClick = onInstallClick, modifier = Modifier.fillMaxWidth()) { - Text(stringResource(Res.string.updater_btn_install), style = MaterialTheme.typography.labelLarge) + Text( + stringResource(Res.string.updater_btn_install), + style = MaterialTheme.typography.labelLarge + ) } } } @@ -216,7 +237,10 @@ private fun UpdateFinishedContent( ) { Text(text = stringResource(text), style = MaterialTheme.typography.titleLarge) Button(onClick = onSkip, modifier = Modifier.fillMaxWidth()) { - Text(stringResource(Res.string.updater_btn_close), style = MaterialTheme.typography.labelLarge) + Text( + stringResource(Res.string.updater_btn_close), + style = MaterialTheme.typography.labelLarge + ) } } } @@ -232,10 +256,17 @@ private fun UpdateErrorContent( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text(text = stringResource(Res.string.updater_title_error), style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.error) + Text( + text = stringResource(Res.string.updater_title_error), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.error + ) Text(text = message, style = MaterialTheme.typography.bodyMedium) Button(onClick = onRetryClick, modifier = Modifier.fillMaxWidth()) { - Text(stringResource(Res.string.updater_btn_retry), style = MaterialTheme.typography.labelLarge) + Text( + stringResource(Res.string.updater_btn_retry), + style = MaterialTheme.typography.labelLarge + ) } } }