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.koin
|
||||||
import ru.shadowsparky.ui.theme.LocalColorSchemeProvider
|
import ru.shadowsparky.ui.theme.LocalColorSchemeProvider
|
||||||
import ru.shadowsparky.vbox.shared.presentation.Nav
|
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.RootFactory
|
||||||
import ru.shadowsparky.vbox.shared.presentation.UrlRoutes
|
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.vbox.shared.presentation.nav.Route
|
||||||
import ru.shadowsparky.videobox.multiplatform.piggy.getPiggyColorScheme
|
import ru.shadowsparky.videobox.multiplatform.piggy.getPiggyColorScheme
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private lateinit var root: RootComponent
|
private lateinit var root: VBoxRootComponent
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
edgeToEdge(false)
|
edgeToEdge(false)
|
||||||
@@ -46,10 +46,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
setContent {
|
setContent {
|
||||||
if (isPiggyActivity) {
|
if (isPiggyActivity) {
|
||||||
CompositionLocalProvider(LocalColorSchemeProvider provides getPiggyColorScheme()) {
|
CompositionLocalProvider(LocalColorSchemeProvider provides getPiggyColorScheme()) {
|
||||||
Nav(rootComponent = root)
|
Nav(root = root)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Nav(rootComponent = root)
|
Nav(root = root)
|
||||||
}
|
}
|
||||||
edgeToEdge(isSystemInDarkTheme())
|
edgeToEdge(isSystemInDarkTheme())
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -42,15 +42,15 @@ fun Modifier.imePaddingInternal(): Modifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Nav(rootComponent: RootComponent) {
|
fun Nav(root: VBoxRootComponent) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalImageLoader provides remember { rootComponent.imageLoaderProvider.provide() }
|
LocalImageLoader provides remember { root.imageLoaderProvider.provide() }
|
||||||
) { NavInternal(rootComponent) }
|
) { NavInternal(root) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NavInternal(rootComponent: RootComponent) {
|
private fun NavInternal(root: VBoxRootComponent) {
|
||||||
Nav(rootComponent) { child, route ->
|
Nav(root) { child, route ->
|
||||||
when (child) {
|
when (child) {
|
||||||
is Child.PlayerChild -> PlayerScreen(child.component)
|
is Child.PlayerChild -> PlayerScreen(child.component)
|
||||||
is Child.OverviewChild -> OverviewScreen(child.component)
|
is Child.OverviewChild -> OverviewScreen(child.component)
|
||||||
@@ -73,12 +73,12 @@ private fun NavInternal(rootComponent: RootComponent) {
|
|||||||
MainScreen(
|
MainScreen(
|
||||||
route as Route,
|
route as Route,
|
||||||
child,
|
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
|
import ru.shadowsparky.vbox.shared.presentation.settings.SettingsContent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(route: Route, child: Child, rootComponent: RootComponent) {
|
fun MainScreen(route: Route, child: Child, root: VBoxRootComponent) {
|
||||||
when (child) {
|
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(
|
is Child.SavedMovieChild -> SavedMovieScreen(
|
||||||
child.component,
|
child.component,
|
||||||
route
|
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.Koin
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import ru.shadowsparky.http.domain.TokenStorage
|
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.updater.presentation.UpdateComponentFactory
|
||||||
import ru.shadowsparky.vbox.shared.di.factory.ImageLoaderProvider
|
import ru.shadowsparky.vbox.shared.di.factory.ImageLoaderProvider
|
||||||
import ru.shadowsparky.vbox.shared.domain.HeathCheck
|
import ru.shadowsparky.vbox.shared.domain.HeathCheck
|
||||||
import ru.shadowsparky.vbox.shared.presentation.nav.Route
|
import ru.shadowsparky.vbox.shared.presentation.nav.Route
|
||||||
|
|
||||||
@OptIn(ExperimentalDecomposeApi::class)
|
@OptIn(ExperimentalDecomposeApi::class)
|
||||||
class RootComponent(
|
class VBoxRootComponent(
|
||||||
componentContext: ComponentContext,
|
componentContext: ComponentContext,
|
||||||
tokenStorage: TokenStorage,
|
tokenStorage: TokenStorage,
|
||||||
override val initStack: List<Route>,
|
override val initStack: List<Route>,
|
||||||
private val healthCheck: HeathCheck,
|
private val healthCheck: HeathCheck,
|
||||||
val imageLoaderProvider: ImageLoaderProvider,
|
val imageLoaderProvider: ImageLoaderProvider,
|
||||||
private val updateComponentFactory: UpdateComponentFactory,
|
updateComponentFactory: UpdateComponentFactory,
|
||||||
override val serializer: KSerializer<Route> = Route.serializer(),
|
override val serializer: KSerializer<Route> = Route.serializer(),
|
||||||
override val koin: Koin = ru.shadowsparky.koin
|
override val koin: Koin = ru.shadowsparky.koin
|
||||||
) : BaseRootComponent<Route>(componentContext), WebNavigationOwner {
|
) : RootComponent<Route>(componentContext), WebNavigationOwner {
|
||||||
val updateComponent = updateComponentFactory.create(childContext("UpdateComponentRoot"))
|
val updateComponent = updateComponentFactory.create(childContext("UpdateComponentRoot"))
|
||||||
private val scope = createComponentScope()
|
private val scope = createComponentScope()
|
||||||
val isLoggedIn = tokenStorage.token.map {
|
val isLoggedIn = tokenStorage.token.map {
|
||||||
@@ -68,15 +68,15 @@ class RootFactory(
|
|||||||
fun create(
|
fun create(
|
||||||
context: ComponentContext,
|
context: ComponentContext,
|
||||||
initStack: Route
|
initStack: Route
|
||||||
): RootComponent {
|
): VBoxRootComponent {
|
||||||
return create(context, listOf(initStack))
|
return create(context, listOf(initStack))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
context: ComponentContext,
|
context: ComponentContext,
|
||||||
initStack: List<Route> = listOf(Route.default)
|
initStack: List<Route> = listOf(Route.default)
|
||||||
): RootComponent {
|
): VBoxRootComponent {
|
||||||
return RootComponent(
|
return VBoxRootComponent(
|
||||||
context,
|
context,
|
||||||
tokenStorage,
|
tokenStorage,
|
||||||
listOf(Route.Loading(next = initStack.toSet())),
|
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.not_found
|
||||||
import androiddev.apps.vbox.client.shared.generated.resources.recommendations
|
import androiddev.apps.vbox.client.shared.generated.resources.recommendations
|
||||||
import androiddev.apps.vbox.client.shared.generated.resources.retry
|
import androiddev.apps.vbox.client.shared.generated.resources.retry
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
@@ -80,7 +81,6 @@ fun VideoScreen(component: VideoScreenComponent) {
|
|||||||
onRefresh = { component.load() },
|
onRefresh = { component.load() },
|
||||||
onShowOverview = { component.showOverview(it) },
|
onShowOverview = { component.showOverview(it) },
|
||||||
emptyResource = Res.string.not_found,
|
emptyResource = Res.string.not_found,
|
||||||
onLoad = { component.load() }
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
appBar = {
|
appBar = {
|
||||||
@@ -107,8 +107,7 @@ fun VideoContent(component: VideoScreenComponent, paddingValues: PaddingValues)
|
|||||||
items,
|
items,
|
||||||
onRefresh = { component.load() },
|
onRefresh = { component.load() },
|
||||||
onShowOverview = { component.showOverview(it) },
|
onShowOverview = { component.showOverview(it) },
|
||||||
emptyResource = Res.string.not_found,
|
emptyResource = Res.string.not_found
|
||||||
onLoad = { component.load() }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,20 +118,21 @@ fun VideoScreenContent(
|
|||||||
items: List<VideoItem>?,
|
items: List<VideoItem>?,
|
||||||
emptyResource: StringResource,
|
emptyResource: StringResource,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
onShowOverview: (Long) -> Unit,
|
onShowOverview: (Long) -> Unit
|
||||||
onLoad: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
onLoad()
|
|
||||||
}
|
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
ErrorAndRetry(error) {
|
ErrorAndRetry(error) {
|
||||||
onRefresh()
|
onRefresh()
|
||||||
}
|
}
|
||||||
} else if (items != null) {
|
} else if (items != null) {
|
||||||
VideosInfo(items = items, paddingValues, emptyResource) {
|
AnimatedContent(
|
||||||
onShowOverview(it.id)
|
items,
|
||||||
}
|
content = {
|
||||||
|
VideosInfo(items = items, paddingValues, emptyResource) {
|
||||||
|
onShowOverview(it.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Loading()
|
Loading()
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -9,8 +9,8 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.ui.nav.BaseRootComponent
|
|
||||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
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.di.factory.DispatcherProvider
|
||||||
import ru.shadowsparky.vbox.shared.domain.VideoApi
|
import ru.shadowsparky.vbox.shared.domain.VideoApi
|
||||||
import ru.shadowsparky.vbox.shared.domain.model.VideoItem
|
import ru.shadowsparky.vbox.shared.domain.model.VideoItem
|
||||||
@@ -39,6 +39,8 @@ class VideoScreenComponent(
|
|||||||
val items: StateFlow<List<VideoItem>?> = _items.asStateFlow()
|
val items: StateFlow<List<VideoItem>?> = _items.asStateFlow()
|
||||||
val errorCode = _errorMsg.asSharedFlow()
|
val errorCode = _errorMsg.asSharedFlow()
|
||||||
|
|
||||||
|
init { load() }
|
||||||
|
|
||||||
fun load() {
|
fun load() {
|
||||||
componentScope.launch(dispatcherProvider.main + exceptionHandler) {
|
componentScope.launch(dispatcherProvider.main + exceptionHandler) {
|
||||||
_errorMsg.emit(null)
|
_errorMsg.emit(null)
|
||||||
@@ -58,7 +60,7 @@ class VideosChildFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Videos,
|
route: Route.Videos,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.VideosChild {
|
): Child.VideosChild {
|
||||||
val comp = VideoScreenComponent(
|
val comp = VideoScreenComponent(
|
||||||
child,
|
child,
|
||||||
|
|||||||
+2
-2
@@ -6,8 +6,8 @@ import kotlinx.coroutines.flow.map
|
|||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.http.domain.TokenStorage
|
import ru.shadowsparky.http.domain.TokenStorage
|
||||||
import ru.shadowsparky.ui.nav.BaseRootComponent
|
|
||||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
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.Child
|
||||||
import ru.shadowsparky.vbox.shared.presentation.nav.Route
|
import ru.shadowsparky.vbox.shared.presentation.nav.Route
|
||||||
import ru.shadowsparky.vbox.shared.presentation.nav.RouteIds
|
import ru.shadowsparky.vbox.shared.presentation.nav.RouteIds
|
||||||
@@ -26,7 +26,7 @@ class LoadingComponentFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Loading,
|
route: Route.Loading,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.LoadingChild {
|
): Child.LoadingChild {
|
||||||
return Child.LoadingChild(
|
return Child.LoadingChild(
|
||||||
LoadingComponent(
|
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.episode.EpisodeOverviewComponent
|
||||||
import ru.shadowsparky.vbox.shared.presentation.overview.season.SeasonOverviewComponent
|
import ru.shadowsparky.vbox.shared.presentation.overview.season.SeasonOverviewComponent
|
||||||
import ru.shadowsparky.vbox.shared.presentation.player.PlayerComponent
|
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.search.SearchScreenComponent
|
||||||
import ru.shadowsparky.vbox.shared.presentation.settings.SettingsComponent
|
import ru.shadowsparky.vbox.shared.presentation.settings.SettingsComponent
|
||||||
import ru.shadowsparky.vbox.shared.presentation.settings.auth.AuthComponent
|
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 EditServerAddrChild(val component: EditServComponent) : Child()
|
||||||
class AuthChild(val component: AuthComponent) : Child()
|
class AuthChild(val component: AuthComponent) : Child()
|
||||||
class RegisterChild(val component: RegisterComponent) : 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 EditPasswordChild(val component: EditPasswordComponent) : Child()
|
||||||
class LoadingChild(val component: LoadingComponent) : Child()
|
class LoadingChild(val component: LoadingComponent) : Child()
|
||||||
class PlayerChild(val component: PlayerComponent) : Child()
|
class PlayerChild(val component: PlayerComponent) : Child()
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ sealed class Route : ru.shadowsparky.ui.nav.Route() {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Loading(
|
data class Loading(
|
||||||
val authRequired: Boolean = isJs,
|
val authRequired: Boolean = true,
|
||||||
val next: Collection<Route> = listOf(default),
|
val next: Collection<Route> = listOf(default),
|
||||||
override val routeId: String = RouteIds.LOADING
|
override val routeId: String = RouteIds.LOADING
|
||||||
) : Route()
|
) : Route()
|
||||||
|
|||||||
+2
-2
@@ -4,8 +4,8 @@ import com.arkivanov.decompose.ComponentContext
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.ui.nav.BaseRootComponent
|
|
||||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
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.OfflineState
|
||||||
import ru.shadowsparky.vbox.shared.domain.OfflineVideo
|
import ru.shadowsparky.vbox.shared.domain.OfflineVideo
|
||||||
import ru.shadowsparky.vbox.shared.domain.OfflineVideoInteractor
|
import ru.shadowsparky.vbox.shared.domain.OfflineVideoInteractor
|
||||||
@@ -50,7 +50,7 @@ class OfflineVideoComponentFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.OfflineVideo,
|
route: Route.OfflineVideo,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.OfflineVideoChild {
|
): Child.OfflineVideoChild {
|
||||||
val comp = OfflineVideoComponent(
|
val comp = OfflineVideoComponent(
|
||||||
child,
|
child,
|
||||||
|
|||||||
+3
-2
@@ -15,8 +15,9 @@ import org.koin.core.annotation.Factory
|
|||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.domain.Clip
|
import ru.shadowsparky.domain.Clip
|
||||||
import ru.shadowsparky.domain.ClipboardManager
|
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.ComponentFactory
|
||||||
|
import ru.shadowsparky.ui.nav.RouteNavigator
|
||||||
import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider
|
import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider
|
||||||
import ru.shadowsparky.vbox.shared.domain.AddOfflineVideoUseCase
|
import ru.shadowsparky.vbox.shared.domain.AddOfflineVideoUseCase
|
||||||
import ru.shadowsparky.vbox.shared.domain.OfflineStorage
|
import ru.shadowsparky.vbox.shared.domain.OfflineStorage
|
||||||
@@ -165,7 +166,7 @@ class OverviewFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Overview,
|
route: Route.Overview,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.OverviewChild {
|
): Child.OverviewChild {
|
||||||
val comp = OverviewComponent(
|
val comp = OverviewComponent(
|
||||||
savedMovieRepository,
|
savedMovieRepository,
|
||||||
|
|||||||
+3
-2
@@ -20,8 +20,9 @@ import org.koin.core.annotation.Named
|
|||||||
import ru.shadowsparky.domain.Clip
|
import ru.shadowsparky.domain.Clip
|
||||||
import ru.shadowsparky.domain.ClipboardManager
|
import ru.shadowsparky.domain.ClipboardManager
|
||||||
import ru.shadowsparky.domain.DispatcherProvider
|
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.ComponentFactory
|
||||||
|
import ru.shadowsparky.ui.nav.RouteNavigator
|
||||||
import ru.shadowsparky.vbox.shared.domain.AddOfflineVideoUseCase
|
import ru.shadowsparky.vbox.shared.domain.AddOfflineVideoUseCase
|
||||||
import ru.shadowsparky.vbox.shared.domain.OfflineStorage
|
import ru.shadowsparky.vbox.shared.domain.OfflineStorage
|
||||||
import ru.shadowsparky.vbox.shared.domain.OfflineVideo
|
import ru.shadowsparky.vbox.shared.domain.OfflineVideo
|
||||||
@@ -178,7 +179,7 @@ class EpisodeOverviewFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Overview.Episode,
|
route: Route.Overview.Episode,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.EpisodeOverviewChild {
|
): Child.EpisodeOverviewChild {
|
||||||
val comp = EpisodeOverviewComponent(
|
val comp = EpisodeOverviewComponent(
|
||||||
child,
|
child,
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.ui.nav.BaseRootComponent
|
|
||||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
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.di.factory.DispatcherProvider
|
||||||
import ru.shadowsparky.vbox.shared.domain.VideoApi
|
import ru.shadowsparky.vbox.shared.domain.VideoApi
|
||||||
import ru.shadowsparky.vbox.shared.domain.model.Season
|
import ru.shadowsparky.vbox.shared.domain.model.Season
|
||||||
@@ -54,7 +54,7 @@ class SeasonOverviewFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Overview.Season,
|
route: Route.Overview.Season,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.SeasonOverviewChild {
|
): Child.SeasonOverviewChild {
|
||||||
val comp = SeasonOverviewComponent(
|
val comp = SeasonOverviewComponent(
|
||||||
videoApi,
|
videoApi,
|
||||||
|
|||||||
+3
-2
@@ -9,8 +9,9 @@ import kotlinx.coroutines.flow.filterNotNull
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
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.ComponentFactory
|
||||||
|
import ru.shadowsparky.ui.nav.RouteNavigator
|
||||||
import ru.shadowsparky.vbox.shared.domain.PlayerInteractor
|
import ru.shadowsparky.vbox.shared.domain.PlayerInteractor
|
||||||
import ru.shadowsparky.vbox.shared.presentation.createComponentScope
|
import ru.shadowsparky.vbox.shared.presentation.createComponentScope
|
||||||
import ru.shadowsparky.vbox.shared.presentation.nav.Child
|
import ru.shadowsparky.vbox.shared.presentation.nav.Child
|
||||||
@@ -112,7 +113,7 @@ class PlayerComponentFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Overview.Player,
|
route: Route.Overview.Player,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.PlayerChild {
|
): Child.PlayerChild {
|
||||||
val component = PlayerComponent(
|
val component = PlayerComponent(
|
||||||
child,
|
child,
|
||||||
|
|||||||
+5
-4
@@ -8,8 +8,9 @@ import kotlinx.coroutines.flow.flowOf
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
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.ComponentFactory
|
||||||
|
import ru.shadowsparky.ui.nav.RouteNavigator
|
||||||
import ru.shadowsparky.vbox.shared.domain.SavedMovieRepository
|
import ru.shadowsparky.vbox.shared.domain.SavedMovieRepository
|
||||||
import ru.shadowsparky.vbox.shared.domain.model.VideoDetails
|
import ru.shadowsparky.vbox.shared.domain.model.VideoDetails
|
||||||
import ru.shadowsparky.vbox.shared.domain.model.VideoItem
|
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.Route
|
||||||
import ru.shadowsparky.vbox.shared.presentation.nav.RouteIds
|
import ru.shadowsparky.vbox.shared.presentation.nav.RouteIds
|
||||||
|
|
||||||
class SavedMovieScreenComponent(
|
class SavedMovieComponent(
|
||||||
private val context: ComponentContext,
|
private val context: ComponentContext,
|
||||||
repo: SavedMovieRepository,
|
repo: SavedMovieRepository,
|
||||||
userTagRepository: UserTagRepository,
|
userTagRepository: UserTagRepository,
|
||||||
@@ -70,9 +71,9 @@ class SavedMovieFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Bookmark,
|
route: Route.Bookmark,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.SavedMovieChild {
|
): Child.SavedMovieChild {
|
||||||
val comp = SavedMovieScreenComponent(
|
val comp = SavedMovieComponent(
|
||||||
child,
|
child,
|
||||||
repo,
|
repo,
|
||||||
doBack = { root.onBack() },
|
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
|
||||||
import androiddev.apps.vbox.client.shared.generated.resources.user_tags_filter_disable
|
import androiddev.apps.vbox.client.shared.generated.resources.user_tags_filter_disable
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.BasicAlertDialog
|
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
@@ -38,6 +33,7 @@ import org.jetbrains.compose.resources.painterResource
|
|||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import ru.shadowsparky.ui.LocalBlurState
|
import ru.shadowsparky.ui.LocalBlurState
|
||||||
import ru.shadowsparky.ui.applyBlurEffect
|
import ru.shadowsparky.ui.applyBlurEffect
|
||||||
|
import ru.shadowsparky.ui.components.AlertContent
|
||||||
import ru.shadowsparky.ui.components.LazyColumn
|
import ru.shadowsparky.ui.components.LazyColumn
|
||||||
import ru.shadowsparky.ui.theme.ComponentDefaults
|
import ru.shadowsparky.ui.theme.ComponentDefaults
|
||||||
import ru.shadowsparky.vbox.shared.domain.tag.TagInfo
|
import ru.shadowsparky.vbox.shared.domain.tag.TagInfo
|
||||||
@@ -47,7 +43,7 @@ import ru.shadowsparky.vbox.shared.presentation.nav.Route
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@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)
|
val tags by comp.userTags.collectAsStateWithLifecycle(null)
|
||||||
var filterBottomSheetVisible by rememberSaveable { mutableStateOf(false) }
|
var filterBottomSheetVisible by rememberSaveable { mutableStateOf(false) }
|
||||||
BaseMainScreen(
|
BaseMainScreen(
|
||||||
@@ -86,7 +82,7 @@ fun SavedMovieScreen(comp: SavedMovieScreenComponent, route: Route, onNavigate:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SavedMovieContent(comp: SavedMovieScreenComponent, paddingValues: PaddingValues) {
|
private fun SavedMovieContent(comp: SavedMovieComponent, paddingValues: PaddingValues) {
|
||||||
val items by comp.all.collectAsStateWithLifecycle(null)
|
val items by comp.all.collectAsStateWithLifecycle(null)
|
||||||
VideoScreenContent(
|
VideoScreenContent(
|
||||||
paddingValues,
|
paddingValues,
|
||||||
@@ -94,8 +90,7 @@ private fun SavedMovieContent(comp: SavedMovieScreenComponent, paddingValues: Pa
|
|||||||
items,
|
items,
|
||||||
onRefresh = {},
|
onRefresh = {},
|
||||||
onShowOverview = { comp.showOverview(it) },
|
onShowOverview = { comp.showOverview(it) },
|
||||||
emptyResource = Res.string.saved_movies_not_found,
|
emptyResource = Res.string.saved_movies_not_found
|
||||||
onLoad = {}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,38 +103,30 @@ private fun FilterDialog(
|
|||||||
onFilterBy: (String?) -> Unit
|
onFilterBy: (String?) -> Unit
|
||||||
) {
|
) {
|
||||||
allTags ?: return
|
allTags ?: return
|
||||||
BasicAlertDialog(onDismissRequest = onDismiss) {
|
AlertContent(
|
||||||
Surface(shape = MaterialTheme.shapes.extraLarge) {
|
onDismiss,
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
title = stringResource(Res.string.user_tags)
|
||||||
Text(
|
) {
|
||||||
text = stringResource(Res.string.user_tags),
|
LazyColumn(modifier = Modifier.fillMaxWidth()) {
|
||||||
style = MaterialTheme.typography.titleLarge,
|
item {
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
FilterItemCheckBox(
|
||||||
)
|
isChecked = filterTag == null,
|
||||||
Spacer(Modifier.height(16.dp))
|
name = stringResource(Res.string.user_tags_filter_disable)
|
||||||
LazyColumn(modifier = Modifier.fillMaxWidth()) {
|
) {
|
||||||
item {
|
onFilterBy(null)
|
||||||
FilterItemCheckBox(
|
onDismiss()
|
||||||
isChecked = filterTag == null,
|
}
|
||||||
name = stringResource(Res.string.user_tags_filter_disable)
|
}
|
||||||
) {
|
items(
|
||||||
onFilterBy(null)
|
items = allTags.filter { it.id != null },
|
||||||
onDismiss()
|
key = { tag -> tag.id ?: tag.tag }
|
||||||
}
|
) { tag ->
|
||||||
}
|
FilterItemCheckBox(
|
||||||
items(
|
isChecked = filterTag == tag.tag,
|
||||||
items = allTags.filter { it.id != null },
|
name = tag.tag
|
||||||
key = { tag -> tag.id ?: tag.tag }
|
) {
|
||||||
) { tag ->
|
onFilterBy(tag.tag)
|
||||||
Spacer(Modifier.height(4.dp))
|
onDismiss()
|
||||||
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 kotlinx.coroutines.launch
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
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.ComponentFactory
|
||||||
|
import ru.shadowsparky.ui.nav.RouteNavigator
|
||||||
import ru.shadowsparky.vbox.shared.domain.SearchRepository
|
import ru.shadowsparky.vbox.shared.domain.SearchRepository
|
||||||
import ru.shadowsparky.vbox.shared.presentation.createComponentScope
|
import ru.shadowsparky.vbox.shared.presentation.createComponentScope
|
||||||
import ru.shadowsparky.vbox.shared.presentation.nav.Child
|
import ru.shadowsparky.vbox.shared.presentation.nav.Child
|
||||||
@@ -43,7 +44,7 @@ class SearchScreenFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.SearchVideos,
|
route: Route.SearchVideos,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.SearchChild {
|
): Child.SearchChild {
|
||||||
val comp = SearchScreenComponent(
|
val comp = SearchScreenComponent(
|
||||||
child,
|
child,
|
||||||
|
|||||||
+2
-2
@@ -9,8 +9,8 @@ import org.koin.core.annotation.Named
|
|||||||
import ru.shadowsparky.domain.AppConfig
|
import ru.shadowsparky.domain.AppConfig
|
||||||
import ru.shadowsparky.http.domain.LogoutAction
|
import ru.shadowsparky.http.domain.LogoutAction
|
||||||
import ru.shadowsparky.http.domain.TokenStorage
|
import ru.shadowsparky.http.domain.TokenStorage
|
||||||
import ru.shadowsparky.ui.nav.BaseRootComponent
|
|
||||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
import ru.shadowsparky.ui.nav.ComponentFactory
|
||||||
|
import ru.shadowsparky.ui.nav.RouteNavigator
|
||||||
import ru.shadowsparky.updater.presentation.UpdateComponentFactory
|
import ru.shadowsparky.updater.presentation.UpdateComponentFactory
|
||||||
import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider
|
import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider
|
||||||
import ru.shadowsparky.vbox.shared.domain.OfflineStorage
|
import ru.shadowsparky.vbox.shared.domain.OfflineStorage
|
||||||
@@ -83,7 +83,7 @@ class SettingsComponentFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Settings,
|
route: Route.Settings,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.SettingsChild {
|
): Child.SettingsChild {
|
||||||
val comp = SettingsComponent(
|
val comp = SettingsComponent(
|
||||||
child,
|
child,
|
||||||
|
|||||||
+2
-2
@@ -7,8 +7,8 @@ import kotlinx.coroutines.launch
|
|||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.domain.Log
|
import ru.shadowsparky.domain.Log
|
||||||
import ru.shadowsparky.ui.nav.BaseRootComponent
|
|
||||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
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.di.factory.DispatcherProvider
|
||||||
import ru.shadowsparky.vbox.shared.domain.AuthTokenRepository
|
import ru.shadowsparky.vbox.shared.domain.AuthTokenRepository
|
||||||
import ru.shadowsparky.vbox.shared.domain.model.LoginException
|
import ru.shadowsparky.vbox.shared.domain.model.LoginException
|
||||||
@@ -64,7 +64,7 @@ class AuthComponentFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Settings.Auth,
|
route: Route.Settings.Auth,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.AuthChild {
|
): Child.AuthChild {
|
||||||
val comp = AuthComponent(
|
val comp = AuthComponent(
|
||||||
route.required,
|
route.required,
|
||||||
|
|||||||
+3
-2
@@ -6,8 +6,9 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.ui.nav.BaseRootComponent
|
|
||||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
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.AuthTokenRepository
|
||||||
import ru.shadowsparky.vbox.shared.domain.ChangePasswordRequest
|
import ru.shadowsparky.vbox.shared.domain.ChangePasswordRequest
|
||||||
import ru.shadowsparky.vbox.shared.domain.model.LoginException
|
import ru.shadowsparky.vbox.shared.domain.model.LoginException
|
||||||
@@ -53,7 +54,7 @@ class EditPasswordComponentFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Settings.EditPassword,
|
route: Route.Settings.EditPassword,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.EditPasswordChild {
|
): Child.EditPasswordChild {
|
||||||
val component = EditPasswordComponent(
|
val component = EditPasswordComponent(
|
||||||
child,
|
child,
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@ import org.koin.core.annotation.Factory
|
|||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import ru.shadowsparky.domain.Log
|
import ru.shadowsparky.domain.Log
|
||||||
import ru.shadowsparky.ui.nav.BaseRootComponent
|
|
||||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
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.di.factory.DispatcherProvider
|
||||||
import ru.shadowsparky.vbox.shared.domain.AuthTokenRepository
|
import ru.shadowsparky.vbox.shared.domain.AuthTokenRepository
|
||||||
import ru.shadowsparky.vbox.shared.domain.model.LoginException
|
import ru.shadowsparky.vbox.shared.domain.model.LoginException
|
||||||
@@ -55,7 +55,7 @@ class RegisterComponentFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Settings.Register,
|
route: Route.Settings.Register,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.RegisterChild {
|
): Child.RegisterChild {
|
||||||
val comp = RegisterComponent(
|
val comp = RegisterComponent(
|
||||||
child,
|
child,
|
||||||
|
|||||||
+3
-3
@@ -9,8 +9,9 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.ui.nav.BaseRootComponent
|
|
||||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
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.di.factory.DispatcherProvider
|
||||||
import ru.shadowsparky.vbox.shared.domain.ServerConfiguration
|
import ru.shadowsparky.vbox.shared.domain.ServerConfiguration
|
||||||
import ru.shadowsparky.vbox.shared.domain.ServerConfigurationRepository
|
import ru.shadowsparky.vbox.shared.domain.ServerConfigurationRepository
|
||||||
@@ -27,7 +28,6 @@ class EditServComponent(
|
|||||||
) : ComponentContext by context {
|
) : ComponentContext by context {
|
||||||
private val componentScope = createComponentScope()
|
private val componentScope = createComponentScope()
|
||||||
val serverUrl = serverConfigurationRepository.serverConfiguration
|
val serverUrl = serverConfigurationRepository.serverConfiguration
|
||||||
.filterNotNull()
|
|
||||||
.map { it.asHttpStr() }
|
.map { it.asHttpStr() }
|
||||||
.onEach { onUrlChanged(it) }
|
.onEach { onUrlChanged(it) }
|
||||||
private val _state = MutableStateFlow<EditServState>(EditServState.Idle)
|
private val _state = MutableStateFlow<EditServState>(EditServState.Idle)
|
||||||
@@ -67,7 +67,7 @@ class EditServComponentFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Settings.EditServ,
|
route: Route.Settings.EditServ,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.EditServerAddrChild {
|
): Child.EditServerAddrChild {
|
||||||
val comp = EditServComponent(
|
val comp = EditServComponent(
|
||||||
child,
|
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.movie_tags_empty
|
||||||
import androiddev.apps.vbox.client.shared.generated.resources.user_tags
|
import androiddev.apps.vbox.client.shared.generated.resources.user_tags
|
||||||
import androidx.compose.foundation.clickable
|
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.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -14,15 +12,14 @@ import androidx.compose.material3.Button
|
|||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import ru.shadowsparky.ui.components.AlertContent
|
||||||
import ru.shadowsparky.ui.components.LazyColumn
|
import ru.shadowsparky.ui.components.LazyColumn
|
||||||
import ru.shadowsparky.ui.components.Loading
|
import ru.shadowsparky.ui.components.Loading
|
||||||
import ru.shadowsparky.vbox.shared.domain.tag.TagInfo
|
import ru.shadowsparky.vbox.shared.domain.tag.TagInfo
|
||||||
@@ -36,34 +33,25 @@ fun LinkTagBottomSheet(
|
|||||||
requestTagsScreen: () -> Unit,
|
requestTagsScreen: () -> Unit,
|
||||||
updateTag: (String, Boolean) -> Unit
|
updateTag: (String, Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
ModalBottomSheet(
|
AlertContent(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
sheetState = rememberModalBottomSheetState(true)
|
title = stringResource(Res.string.user_tags)
|
||||||
) {
|
) {
|
||||||
Column(
|
if (allTags == null || linkedTags == null) {
|
||||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
Loading(Modifier.fillMaxWidth())
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
} else {
|
||||||
) {
|
if (allTags.isEmpty()) {
|
||||||
Text(
|
Button(
|
||||||
text = stringResource(Res.string.user_tags),
|
onClick = {
|
||||||
style = MaterialTheme.typography.titleLarge
|
onDismiss()
|
||||||
)
|
requestTagsScreen()
|
||||||
if (allTags == null || linkedTags == null) {
|
},
|
||||||
Loading(Modifier.fillMaxWidth())
|
modifier = Modifier.fillMaxWidth()
|
||||||
} else {
|
) {
|
||||||
if (allTags.isEmpty()) {
|
Text(stringResource(Res.string.movie_tags_empty))
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
onDismiss()
|
|
||||||
requestTagsScreen()
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Text(stringResource(Res.string.movie_tags_empty))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ShowTags(allTags, linkedTags, updateTag)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ShowTags(allTags, linkedTags, updateTag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,28 +67,22 @@ private fun ShowTags(
|
|||||||
linkedTags.mapNotNull { it.id }.toSet()
|
linkedTags.mapNotNull { it.id }.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(modifier = Modifier.fillMaxWidth()) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
items(
|
items(
|
||||||
items = allTags.filter { it.id != null },
|
items = allTags.filter { it.id != null },
|
||||||
key = { tag -> tag.id ?: tag.tag }
|
key = { tag -> tag.id ?: tag.tag }
|
||||||
) { tag ->
|
) { tag ->
|
||||||
val tagId = tag.id ?: return@items
|
val tagId = tag.id ?: return@items
|
||||||
val isChecked = tagId in linkedTagIds
|
val isChecked = tagId in linkedTagIds
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { updateTag(tag.tag, !isChecked) }
|
.clickable { updateTag(tag.tag, !isChecked) },
|
||||||
.padding(vertical = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = tag.tag,
|
text = tag.tag,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f).padding(start = 8.dp),
|
||||||
.padding(start = 8.dp),
|
|
||||||
style = MaterialTheme.typography.bodyLarge
|
style = MaterialTheme.typography.bodyLarge
|
||||||
)
|
)
|
||||||
Checkbox(
|
Checkbox(
|
||||||
|
|||||||
+2
-2
@@ -4,8 +4,8 @@ import com.arkivanov.decompose.ComponentContext
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.ui.nav.BaseRootComponent
|
|
||||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
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.domain.tag.UserTagRepository
|
||||||
import ru.shadowsparky.vbox.shared.presentation.createComponentScope
|
import ru.shadowsparky.vbox.shared.presentation.createComponentScope
|
||||||
import ru.shadowsparky.vbox.shared.presentation.nav.Child
|
import ru.shadowsparky.vbox.shared.presentation.nav.Child
|
||||||
@@ -48,7 +48,7 @@ class TagComponentFactory(
|
|||||||
override fun create(
|
override fun create(
|
||||||
route: Route.Settings.TagOverview,
|
route: Route.Settings.TagOverview,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: BaseRootComponent<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child.TagChild {
|
): Child.TagChild {
|
||||||
val component = TagComponent(
|
val component = TagComponent(
|
||||||
child,
|
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_save
|
||||||
import androiddev.apps.vbox.client.shared.generated.resources.user_tags_tag_delete
|
import androiddev.apps.vbox.client.shared.generated.resources.user_tags_tag_delete
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
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.foundation.layout.padding
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextField
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -39,6 +35,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import ru.shadowsparky.ui.AppScaffold
|
import ru.shadowsparky.ui.AppScaffold
|
||||||
|
import ru.shadowsparky.ui.components.AlertContent
|
||||||
import ru.shadowsparky.ui.components.ExpressiveLazyColumn
|
import ru.shadowsparky.ui.components.ExpressiveLazyColumn
|
||||||
import ru.shadowsparky.ui.components.TextPreference
|
import ru.shadowsparky.ui.components.TextPreference
|
||||||
import ru.shadowsparky.vbox.shared.domain.tag.TagInfo
|
import ru.shadowsparky.vbox.shared.domain.tag.TagInfo
|
||||||
@@ -122,35 +119,26 @@ private fun AddTagBottomSheet(
|
|||||||
keyboardController?.show()
|
keyboardController?.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
ModalBottomSheet(
|
AlertContent(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
sheetState = rememberModalBottomSheetState(true)
|
title = stringResource(Res.string.user_tags_add)
|
||||||
) {
|
) {
|
||||||
Column(
|
TextField(
|
||||||
modifier = Modifier.padding(8.dp),
|
value = tag,
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
onValueChange = { tag = it },
|
||||||
) {
|
modifier = Modifier
|
||||||
Text(
|
.fillMaxWidth()
|
||||||
text = stringResource(Res.string.user_tags_add),
|
.focusRequester(focusRequester),
|
||||||
style = MaterialTheme.typography.titleLarge
|
singleLine = true
|
||||||
)
|
)
|
||||||
TextField(
|
Button(
|
||||||
value = tag,
|
onClick = {
|
||||||
onValueChange = { tag = it },
|
onAdd(tag.trim())
|
||||||
modifier = Modifier
|
onDismiss()
|
||||||
.fillMaxWidth()
|
},
|
||||||
.focusRequester(focusRequester),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true
|
enabled = tag.isNotBlank() && canSave(tag)
|
||||||
)
|
) { Text(stringResource(Res.string.user_tags_save)) }
|
||||||
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
|
val tagId = tag.id
|
||||||
|
|
||||||
ModalBottomSheet(onDismissRequest = onDismiss) {
|
AlertContent(
|
||||||
Column(
|
onDismissRequest = onDismiss,
|
||||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
title = stringResource(Res.string.user_tags_edit_title)
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
) {
|
||||||
) {
|
TextField(
|
||||||
Text(
|
value = newTag,
|
||||||
text = stringResource(Res.string.user_tags_edit_title),
|
onValueChange = { newTag = it },
|
||||||
style = MaterialTheme.typography.titleLarge
|
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(
|
) { Text(stringResource(Res.string.user_tags_new_tag_save)) }
|
||||||
value = newTag,
|
OutlinedButton(
|
||||||
onValueChange = { newTag = it },
|
onClick = {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
if (tagId != null) {
|
||||||
label = { Text(stringResource(Res.string.user_tags_new_tag)) },
|
onDelete(tag.tag)
|
||||||
singleLine = true
|
onDismiss()
|
||||||
)
|
}
|
||||||
Button(
|
},
|
||||||
onClick = {
|
modifier = Modifier.fillMaxWidth(),
|
||||||
if (tagId != null && newTag.isNotEmpty()) {
|
enabled = tagId != null
|
||||||
onEdit(tag.tag, newTag)
|
) { Text(stringResource(Res.string.user_tags_tag_delete)) }
|
||||||
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)) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ import ru.shadowsparky.koin
|
|||||||
import ru.shadowsparky.ui.hideElementById
|
import ru.shadowsparky.ui.hideElementById
|
||||||
import ru.shadowsparky.ui.hookPageVisibility
|
import ru.shadowsparky.ui.hookPageVisibility
|
||||||
import ru.shadowsparky.vbox.shared.presentation.Nav
|
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.RootFactory
|
||||||
import ru.shadowsparky.vbox.shared.presentation.UrlRoutes
|
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.vbox.shared.presentation.nav.Route
|
||||||
|
|
||||||
@KoinApplication
|
@KoinApplication
|
||||||
@@ -67,9 +67,9 @@ fun mainInternal() {
|
|||||||
|
|
||||||
val composeRoot = document.getElementById("compose-root") as HTMLElement
|
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) {
|
if (rootRef == null) {
|
||||||
rootRef = createRoot(lifecycle, stateKeeper, deepLinkUrl)
|
rootRef = createRoot(lifecycle, stateKeeper, deepLinkUrl)
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ private fun createRoot(
|
|||||||
lifecycle: LifecycleRegistry,
|
lifecycle: LifecycleRegistry,
|
||||||
stateKeeper: StateKeeper,
|
stateKeeper: StateKeeper,
|
||||||
deepLinkUrl: String?,
|
deepLinkUrl: String?,
|
||||||
): RootComponent {
|
): VBoxRootComponent {
|
||||||
val ctx = DefaultComponentContext(
|
val ctx = DefaultComponentContext(
|
||||||
lifecycle = lifecycle, stateKeeper = stateKeeper
|
lifecycle = lifecycle, stateKeeper = stateKeeper
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,8 +12,13 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.haze)
|
implementation(libs.haze)
|
||||||
implementation(libs.haze.materials)
|
implementation(libs.haze.materials)
|
||||||
|
implementation(libs.ui.tooling.preview)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
android { namespace = "ru.shadowsparky.ui" }
|
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.IconButtonDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -22,3 +23,9 @@ fun BackButton(doBack: () -> Unit) {
|
|||||||
)
|
)
|
||||||
) { Icon(painterResource(Res.drawable.back), contentDescription = null) }
|
) { 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.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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)
|
modifier = modifier.padding(horizontal = horizontalPadding, vertical = verticalSpacing)
|
||||||
) { content() }
|
) { 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.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@@ -16,3 +17,9 @@ fun Loading(modifier: Modifier) {
|
|||||||
LoadingIndicator(Modifier.size(96.dp))
|
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
|
import com.arkivanov.decompose.ComponentContext
|
||||||
|
|
||||||
interface ComponentFactory<T : Child, R : Route, BaseRoute : Route> {
|
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)
|
@OptIn(ExperimentalDecomposeApi::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Nav(
|
fun Nav(
|
||||||
rootComponent: BaseRootComponent<*>,
|
rootComponent: RootComponent<*>,
|
||||||
process: @Composable (child: Child, route: Route) -> Unit
|
process: @Composable (child: Child, route: Route) -> Unit
|
||||||
) {
|
) {
|
||||||
Children(
|
Children(
|
||||||
|
|||||||
+12
-22
@@ -18,18 +18,18 @@ import kotlinx.serialization.KSerializer
|
|||||||
import org.koin.core.Koin
|
import org.koin.core.Koin
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
|
|
||||||
abstract class BaseRootComponent<R : Route>(
|
abstract class RootComponent<R : Route>(
|
||||||
private val componentContext: ComponentContext,
|
private val componentContext: ComponentContext,
|
||||||
) : ComponentContext by componentContext, BackHandlerOwner {
|
) : ComponentContext by componentContext, BackHandlerOwner, RouteNavigator<R> {
|
||||||
|
|
||||||
protected abstract val initStack: List<R>
|
protected abstract val initStack: List<R>
|
||||||
protected abstract val serializer: KSerializer<R>
|
protected abstract val serializer: KSerializer<R>
|
||||||
protected abstract val koin: Koin
|
protected abstract val koin: Koin
|
||||||
|
|
||||||
private val scope = coroutineScope()
|
private val scope = coroutineScope()
|
||||||
|
|
||||||
protected val nav = StackNavigation<R>()
|
protected val nav = StackNavigation<R>()
|
||||||
|
|
||||||
protected val childStack by lazy {
|
val childStack by lazy {
|
||||||
childStack(
|
childStack(
|
||||||
source = nav,
|
source = nav,
|
||||||
serializer = serializer,
|
serializer = serializer,
|
||||||
@@ -41,36 +41,26 @@ abstract class BaseRootComponent<R : Route>(
|
|||||||
private val _onFinish = MutableStateFlow<Unit?>(null)
|
private val _onFinish = MutableStateFlow<Unit?>(null)
|
||||||
val onFinish = _onFinish.filterNotNull()
|
val onFinish = _onFinish.filterNotNull()
|
||||||
|
|
||||||
fun navReplace(vararg route: R) {
|
override fun navReplace(vararg route: R) {
|
||||||
nav.replaceAll(*route)
|
nav.replaceAll(*route)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nav(route: R, bringToFront: Boolean = false) {
|
override fun nav(route: R, bringToFront: Boolean) {
|
||||||
if (bringToFront) {
|
if (bringToFront) nav.bringToFront(route) else nav.pushNew(route)
|
||||||
nav.bringToFront(route)
|
|
||||||
} else {
|
|
||||||
nav.pushNew(route)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onBack() {
|
override fun onBack() {
|
||||||
nav.pop { popped ->
|
nav.pop { popped -> if (!popped) finish() }
|
||||||
if (!popped) finish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun finish() {
|
override fun finish() {
|
||||||
scope.launch { _onFinish.emit(Unit) }
|
scope.launch { _onFinish.emit(Unit) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val valueChildStack: Value<ChildStack<*, Child>> by lazy { childStack }
|
val valueChildStack: Value<ChildStack<*, Child>> by lazy { childStack }
|
||||||
|
|
||||||
protected open fun newChild(route: Route, ctx: ComponentContext): Child {
|
protected open fun newChild(route: Route, ctx: ComponentContext): Child {
|
||||||
val factory = route.provideFactory<Child, Route>(koin)
|
val factory = koin.get<ComponentFactory<Child, Route, R>>(named(route.routeId))
|
||||||
return factory.create(route, ctx, this as BaseRootComponent<Route>)
|
return factory.create(route, ctx, this as RouteNavigator<R>)
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T : Child, R : Route> Route.provideFactory(koin: Koin): ComponentFactory<T, R, Route> {
|
|
||||||
return koin.get<ComponentFactory<T, R, Route>>(named(routeId))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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_permission
|
||||||
import androiddev.libs.updater.updater_client.generated.resources.updater_title_ready
|
import androiddev.libs.updater.updater_client.generated.resources.updater_title_ready
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
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.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -38,10 +34,10 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||||
import org.jetbrains.compose.resources.StringResource
|
import org.jetbrains.compose.resources.StringResource
|
||||||
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.ui.components.Loading
|
||||||
import ru.shadowsparky.updater.domain.UpdateState
|
import ru.shadowsparky.updater.domain.UpdateState
|
||||||
|
|
||||||
@@ -55,50 +51,50 @@ fun UpdateDialog(
|
|||||||
val canDismiss = remember(state) { !component.isForceUpdate }
|
val canDismiss = remember(state) { !component.isForceUpdate }
|
||||||
val isVisible = remember(state) { state !is UpdateState.Idle }
|
val isVisible = remember(state) { state !is UpdateState.Idle }
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
BasicAlertDialog(
|
AlertContent(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onDismissRequest = { component.reset() },
|
onDismissRequest = { component.reset() },
|
||||||
properties = DialogProperties(
|
canDismiss = canDismiss,
|
||||||
canDismiss,
|
|
||||||
canDismiss,
|
|
||||||
),
|
|
||||||
content = {
|
content = {
|
||||||
Surface(shape = MaterialTheme.shapes.extraLarge) {
|
when (val currentState = state) {
|
||||||
Box(modifier = Modifier.padding(16.dp)) {
|
is UpdateState.Checking -> UpdateCheckingContent()
|
||||||
when (val currentState = state) {
|
is UpdateState.UpdateAvailable -> UpdateAvailableContent(
|
||||||
is UpdateState.Checking -> UpdateCheckingContent()
|
versionName = currentState.versionName,
|
||||||
is UpdateState.UpdateAvailable -> UpdateAvailableContent(
|
changeLog = currentState.changeLog,
|
||||||
versionName = currentState.versionName,
|
onDownloadClick = { component.downloadUpdate(currentState.url) }
|
||||||
changeLog = currentState.changeLog,
|
)
|
||||||
onDownloadClick = { component.downloadUpdate(currentState.url) }
|
|
||||||
)
|
is UpdateState.InstallPermissionRequired -> UpdatePermissionContent(
|
||||||
is UpdateState.InstallPermissionRequired -> UpdatePermissionContent(
|
onGrantClick = { component.requestPermission() }
|
||||||
onGrantClick = { component.requestPermission() }
|
)
|
||||||
)
|
|
||||||
is UpdateState.Downloading -> UpdateDownloadingContent(
|
is UpdateState.Downloading -> UpdateDownloadingContent(
|
||||||
progress = currentState.progress,
|
progress = currentState.progress,
|
||||||
downloadedBytes = currentState.downloadedBytes,
|
downloadedBytes = currentState.downloadedBytes,
|
||||||
totalBytes = currentState.totalBytes
|
totalBytes = currentState.totalBytes
|
||||||
)
|
)
|
||||||
is UpdateState.ReadyToInstall -> UpdateReadyContent(
|
|
||||||
onInstallClick = { component.installApk(currentState.filePath) }
|
is UpdateState.ReadyToInstall -> UpdateReadyContent(
|
||||||
)
|
onInstallClick = { component.installApk(currentState.filePath) }
|
||||||
is UpdateState.Error -> UpdateErrorContent(
|
)
|
||||||
message = currentState.message,
|
|
||||||
onRetryClick = { component.checkUpdates() }
|
is UpdateState.Error -> UpdateErrorContent(
|
||||||
)
|
message = currentState.message,
|
||||||
is UpdateState.UpToDate -> {
|
onRetryClick = { component.checkUpdates() }
|
||||||
UpdateFinishedContent(onSkip = { component.reset() })
|
)
|
||||||
}
|
|
||||||
is UpdateState.Completed -> {
|
is UpdateState.UpToDate -> {
|
||||||
UpdateFinishedContent(
|
UpdateFinishedContent(onSkip = { component.reset() })
|
||||||
Res.string.updater_title_completed,
|
|
||||||
onSkip = { component.reset() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()) {
|
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,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(Res.string.updater_title_permission), style = MaterialTheme.typography.titleLarge)
|
Text(
|
||||||
Text(text = stringResource(Res.string.updater_desc_permission), style = MaterialTheme.typography.bodyMedium)
|
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()) {
|
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,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
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())
|
LinearProgressIndicator(progress = { progress }, modifier = Modifier.fillMaxWidth())
|
||||||
|
|
||||||
val downloadedMb = ((downloadedBytes / (1024f * 1024f)) * 10).toInt() / 10.0
|
val downloadedMb = ((downloadedBytes / (1024f * 1024f)) * 10).toInt() / 10.0
|
||||||
val totalMb = ((totalBytes / (1024f * 1024f)) * 10).toInt() / 10.0
|
val totalMb = ((totalBytes / (1024f * 1024f)) * 10).toInt() / 10.0
|
||||||
|
|
||||||
Text(
|
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
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -196,9 +211,15 @@ private fun UpdateReadyContent(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
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()) {
|
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)
|
Text(text = stringResource(text), style = MaterialTheme.typography.titleLarge)
|
||||||
Button(onClick = onSkip, modifier = Modifier.fillMaxWidth()) {
|
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,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
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)
|
Text(text = message, style = MaterialTheme.typography.bodyMedium)
|
||||||
Button(onClick = onRetryClick, modifier = Modifier.fillMaxWidth()) {
|
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