add AlertContent, add RouteNavigator
This commit is contained in:
+4
-4
@@ -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())
|
||||
}
|
||||
|
||||
+7
-7
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -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<Route>,
|
||||
private val healthCheck: HeathCheck,
|
||||
val imageLoaderProvider: ImageLoaderProvider,
|
||||
private val updateComponentFactory: UpdateComponentFactory,
|
||||
updateComponentFactory: UpdateComponentFactory,
|
||||
override val serializer: KSerializer<Route> = Route.serializer(),
|
||||
override val koin: Koin = ru.shadowsparky.koin
|
||||
) : BaseRootComponent<Route>(componentContext), WebNavigationOwner {
|
||||
) : RootComponent<Route>(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<Route> = listOf(Route.default)
|
||||
): RootComponent {
|
||||
return RootComponent(
|
||||
): VBoxRootComponent {
|
||||
return VBoxRootComponent(
|
||||
context,
|
||||
tokenStorage,
|
||||
listOf(Route.Loading(next = initStack.toSet())),
|
||||
+11
-11
@@ -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<VideoItem>?,
|
||||
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()
|
||||
}
|
||||
|
||||
+4
-2
@@ -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<List<VideoItem>?> = _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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.VideosChild {
|
||||
val comp = VideoScreenComponent(
|
||||
child,
|
||||
|
||||
+2
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.LoadingChild {
|
||||
return Child.LoadingChild(
|
||||
LoadingComponent(
|
||||
|
||||
+2
-2
@@ -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()
|
||||
|
||||
+1
-1
@@ -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<Route> = listOf(default),
|
||||
override val routeId: String = RouteIds.LOADING
|
||||
) : Route()
|
||||
|
||||
+2
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.OfflineVideoChild {
|
||||
val comp = OfflineVideoComponent(
|
||||
child,
|
||||
|
||||
+3
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.OverviewChild {
|
||||
val comp = OverviewComponent(
|
||||
savedMovieRepository,
|
||||
|
||||
+3
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.EpisodeOverviewChild {
|
||||
val comp = EpisodeOverviewComponent(
|
||||
child,
|
||||
|
||||
+2
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.SeasonOverviewChild {
|
||||
val comp = SeasonOverviewComponent(
|
||||
videoApi,
|
||||
|
||||
+3
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.PlayerChild {
|
||||
val component = PlayerComponent(
|
||||
child,
|
||||
|
||||
+5
-4
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.SavedMovieChild {
|
||||
val comp = SavedMovieScreenComponent(
|
||||
val comp = SavedMovieComponent(
|
||||
child,
|
||||
repo,
|
||||
doBack = { root.onBack() },
|
||||
+28
-41
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.SearchChild {
|
||||
val comp = SearchScreenComponent(
|
||||
child,
|
||||
|
||||
+2
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.SettingsChild {
|
||||
val comp = SettingsComponent(
|
||||
child,
|
||||
|
||||
+2
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.AuthChild {
|
||||
val comp = AuthComponent(
|
||||
route.required,
|
||||
|
||||
+3
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.EditPasswordChild {
|
||||
val component = EditPasswordComponent(
|
||||
child,
|
||||
|
||||
+2
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.RegisterChild {
|
||||
val comp = RegisterComponent(
|
||||
child,
|
||||
|
||||
+3
-3
@@ -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>(EditServState.Idle)
|
||||
@@ -67,7 +67,7 @@ class EditServComponentFactory(
|
||||
override fun create(
|
||||
route: Route.Settings.EditServ,
|
||||
child: ComponentContext,
|
||||
root: BaseRootComponent<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.EditServerAddrChild {
|
||||
val comp = EditServComponent(
|
||||
child,
|
||||
|
||||
+20
-38
@@ -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(
|
||||
|
||||
+2
-2
@@ -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<Route>
|
||||
root: RouteNavigator<Route>
|
||||
): Child.TagChild {
|
||||
val component = TagComponent(
|
||||
child,
|
||||
|
||||
+51
-69
@@ -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)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <T> ListPreference(
|
||||
title: String,
|
||||
items: Map<T, String>,
|
||||
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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -60,4 +60,3 @@ fun PasswordTextField(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -3,5 +3,5 @@ package ru.shadowsparky.ui.nav
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
|
||||
interface ComponentFactory<T : Child, R : Route, BaseRoute : Route> {
|
||||
fun create(route: R, child: ComponentContext, root: BaseRootComponent<BaseRoute>): T
|
||||
fun create(route: R, child: ComponentContext, root: RouteNavigator<BaseRoute>): T
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
+12
-22
@@ -18,18 +18,18 @@ import kotlinx.serialization.KSerializer
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.qualifier.named
|
||||
|
||||
abstract class BaseRootComponent<R : Route>(
|
||||
abstract class RootComponent<R : Route>(
|
||||
private val componentContext: ComponentContext,
|
||||
) : ComponentContext by componentContext, BackHandlerOwner {
|
||||
) : ComponentContext by componentContext, BackHandlerOwner, RouteNavigator<R> {
|
||||
|
||||
protected abstract val initStack: List<R>
|
||||
protected abstract val serializer: KSerializer<R>
|
||||
protected abstract val koin: Koin
|
||||
|
||||
private val scope = coroutineScope()
|
||||
|
||||
protected val nav = StackNavigation<R>()
|
||||
|
||||
protected val childStack by lazy {
|
||||
val childStack by lazy {
|
||||
childStack(
|
||||
source = nav,
|
||||
serializer = serializer,
|
||||
@@ -41,36 +41,26 @@ abstract class BaseRootComponent<R : Route>(
|
||||
private val _onFinish = MutableStateFlow<Unit?>(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<ChildStack<*, Child>> by lazy { childStack }
|
||||
|
||||
protected open fun newChild(route: Route, ctx: ComponentContext): Child {
|
||||
val factory = route.provideFactory<Child, Route>(koin)
|
||||
return factory.create(route, ctx, this as BaseRootComponent<Route>)
|
||||
}
|
||||
|
||||
private fun <T : Child, R : Route> Route.provideFactory(koin: Koin): ComponentFactory<T, R, Route> {
|
||||
return koin.get<ComponentFactory<T, R, Route>>(named(routeId))
|
||||
val factory = koin.get<ComponentFactory<Child, Route, R>>(named(route.routeId))
|
||||
return factory.create(route, ctx, this as RouteNavigator<R>)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ru.shadowsparky.ui.nav
|
||||
|
||||
interface RouteNavigator<out R : Route> {
|
||||
fun navReplace(vararg route: @UnsafeVariance R)
|
||||
fun nav(route: @UnsafeVariance R, bringToFront: Boolean = false)
|
||||
fun onBack()
|
||||
fun finish()
|
||||
}
|
||||
+87
-56
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user