mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
Rewrite navigation to use Multiplatform Voyager Navigation
This commit is contained in:
@@ -8,4 +8,4 @@ package ca.gosyer.core.prefs
|
|||||||
|
|
||||||
expect class PreferenceStoreFactory() {
|
expect class PreferenceStoreFactory() {
|
||||||
fun create(vararg names: String): PreferenceStore
|
fun create(vararg names: String): PreferenceStore
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ dependencies {
|
|||||||
implementation(compose.uiTooling)
|
implementation(compose.uiTooling)
|
||||||
implementation(compose.materialIconsExtended)
|
implementation(compose.materialIconsExtended)
|
||||||
implementation(compose("org.jetbrains.compose.ui:ui-util"))
|
implementation(compose("org.jetbrains.compose.ui:ui-util"))
|
||||||
implementation(libs.composeRouter)
|
implementation(libs.voyagerCore)
|
||||||
|
implementation(libs.voyagerNavigation)
|
||||||
implementation(libs.accompanistPager)
|
implementation(libs.accompanistPager)
|
||||||
implementation(libs.accompanistFlowLayout)
|
implementation(libs.accompanistFlowLayout)
|
||||||
implementation(libs.kamel)
|
implementation(libs.kamel)
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import androidx.compose.runtime.CompositionLocalProvider
|
|||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.configureSwingGlobalsForCompose
|
import androidx.compose.ui.configureSwingGlobalsForCompose
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
@@ -35,19 +34,16 @@ import ca.gosyer.i18n.MR
|
|||||||
import ca.gosyer.ui.AppComponent
|
import ca.gosyer.ui.AppComponent
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
import ca.gosyer.ui.base.WindowDialog
|
||||||
import ca.gosyer.ui.base.theme.AppTheme
|
import ca.gosyer.ui.base.theme.AppTheme
|
||||||
import ca.gosyer.ui.main.DebugOverlay
|
|
||||||
import ca.gosyer.ui.main.MainMenu
|
import ca.gosyer.ui.main.MainMenu
|
||||||
|
import ca.gosyer.ui.main.components.DebugOverlay
|
||||||
import ca.gosyer.ui.main.components.Tray
|
import ca.gosyer.ui.main.components.Tray
|
||||||
|
import ca.gosyer.ui.util.compose.WindowGet
|
||||||
import ca.gosyer.uicore.components.LoadingScreen
|
import ca.gosyer.uicore.components.LoadingScreen
|
||||||
import ca.gosyer.uicore.prefs.asStateIn
|
import ca.gosyer.uicore.prefs.asStateIn
|
||||||
import ca.gosyer.util.compose.WindowGet
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import com.github.weisj.darklaf.LafManager
|
import com.github.weisj.darklaf.LafManager
|
||||||
import com.github.weisj.darklaf.theme.DarculaTheme
|
import com.github.weisj.darklaf.theme.DarculaTheme
|
||||||
import com.github.weisj.darklaf.theme.IntelliJTheme
|
import com.github.weisj.darklaf.theme.IntelliJTheme
|
||||||
import com.github.zsoltk.compose.backpress.BackPressHandler
|
|
||||||
import com.github.zsoltk.compose.backpress.LocalBackPressHandler
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -124,10 +120,6 @@ suspend fun main() {
|
|||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val backPressHandler = remember { BackPressHandler() }
|
|
||||||
|
|
||||||
val rootBundle = remember { Bundle() }
|
|
||||||
val windowState = rememberWindowState(
|
val windowState = rememberWindowState(
|
||||||
size = size,
|
size = size,
|
||||||
position = position,
|
position = position,
|
||||||
@@ -158,7 +150,8 @@ suspend fun main() {
|
|||||||
if (it.type == KeyEventType.KeyUp) {
|
if (it.type == KeyEventType.KeyUp) {
|
||||||
when (it.key) {
|
when (it.key) {
|
||||||
Key.Home -> {
|
Key.Home -> {
|
||||||
backPressHandler.handle()
|
// backPressHandler.handle()
|
||||||
|
false
|
||||||
}
|
}
|
||||||
Key.F3 -> {
|
Key.F3 -> {
|
||||||
displayDebugInfoFlow.value = !displayDebugInfoFlow.value
|
displayDebugInfoFlow.value = !displayDebugInfoFlow.value
|
||||||
@@ -170,29 +163,25 @@ suspend fun main() {
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
CompositionLocalProvider(
|
Crossfade(serverService.initialized.collectAsState().value) { initialized ->
|
||||||
LocalBackPressHandler provides backPressHandler,
|
when (initialized) {
|
||||||
) {
|
ServerResult.STARTED, ServerResult.UNUSED -> {
|
||||||
Crossfade(serverService.initialized.collectAsState().value) { initialized ->
|
Box {
|
||||||
when (initialized) {
|
MainMenu()
|
||||||
ServerResult.STARTED, ServerResult.UNUSED -> {
|
val displayDebugInfo by displayDebugInfoFlow.collectAsState()
|
||||||
Box {
|
if (displayDebugInfo) {
|
||||||
MainMenu(rootBundle)
|
DebugOverlay()
|
||||||
val displayDebugInfo by displayDebugInfoFlow.collectAsState()
|
|
||||||
if (displayDebugInfo) {
|
|
||||||
DebugOverlay()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ServerResult.STARTING, ServerResult.FAILED -> {
|
}
|
||||||
Surface {
|
ServerResult.STARTING, ServerResult.FAILED -> {
|
||||||
LoadingScreen(
|
Surface {
|
||||||
initialized == ServerResult.STARTING,
|
LoadingScreen(
|
||||||
errorMessage = stringResource(MR.strings.unable_to_start_server),
|
initialized == ServerResult.STARTING,
|
||||||
retryMessage = stringResource(MR.strings.action_start_anyway),
|
errorMessage = stringResource(MR.strings.unable_to_start_server),
|
||||||
retry = serverService::startAnyway
|
retryMessage = stringResource(MR.strings.action_start_anyway),
|
||||||
)
|
retry = serverService::startAnyway
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ json = "1.3.2"
|
|||||||
xmlUtil = "0.84.0"
|
xmlUtil = "0.84.0"
|
||||||
|
|
||||||
# Compose
|
# Compose
|
||||||
composeRouter = "0.24.2-jetbrains-2"
|
voyager = "1.0.0-beta15"
|
||||||
accompanist = "0.18.1"
|
accompanist = "0.18.1"
|
||||||
kamel = "0.3.0"
|
kamel = "0.3.0"
|
||||||
|
|
||||||
@@ -52,7 +52,8 @@ xmlUtilCore = { module = "io.github.pdvrieze.xmlutil:core", version.ref = "xmlUt
|
|||||||
xmlUtilSerialization = { module = "io.github.pdvrieze.xmlutil:serialization", version.ref = "xmlUtil" }
|
xmlUtilSerialization = { module = "io.github.pdvrieze.xmlutil:serialization", version.ref = "xmlUtil" }
|
||||||
|
|
||||||
# Compose
|
# Compose
|
||||||
composeRouter = { module = "ca.gosyer:compose-router", version.ref = "composeRouter" }
|
voyagerCore = { module = "cafe.adriel.voyager:voyager-core", version.ref = "voyager" }
|
||||||
|
voyagerNavigation = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
|
||||||
accompanistPager = { module = "ca.gosyer:accompanist-pager", version.ref = "accompanist" }
|
accompanistPager = { module = "ca.gosyer:accompanist-pager", version.ref = "accompanist" }
|
||||||
accompanistFlowLayout = { module = "ca.gosyer:accompanist-flowlayout", version.ref = "accompanist" }
|
accompanistFlowLayout = { module = "ca.gosyer:accompanist-flowlayout", version.ref = "accompanist" }
|
||||||
kamel = { module = "com.alialbaali.kamel:kamel-image", version.ref = "kamel" }
|
kamel = { module = "com.alialbaali.kamel:kamel-image", version.ref = "kamel" }
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ kotlin {
|
|||||||
all {
|
all {
|
||||||
languageSettings {
|
languageSettings {
|
||||||
optIn("kotlin.RequiresOptIn")
|
optIn("kotlin.RequiresOptIn")
|
||||||
|
optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
||||||
optIn("com.google.accompanist.pager.ExperimentalPagerApi")
|
optIn("com.google.accompanist.pager.ExperimentalPagerApi")
|
||||||
optIn("androidx.compose.foundation.ExperimentalFoundationApi")
|
optIn("androidx.compose.foundation.ExperimentalFoundationApi")
|
||||||
optIn("androidx.compose.material.ExperimentalMaterialApi")
|
optIn("androidx.compose.material.ExperimentalMaterialApi")
|
||||||
@@ -35,6 +36,8 @@ kotlin {
|
|||||||
api(kotlin("stdlib-common"))
|
api(kotlin("stdlib-common"))
|
||||||
api(libs.coroutinesCore)
|
api(libs.coroutinesCore)
|
||||||
api(libs.kamel)
|
api(libs.kamel)
|
||||||
|
api(libs.voyagerCore)
|
||||||
|
api(libs.voyagerNavigation)
|
||||||
api(project(":core"))
|
api(project(":core"))
|
||||||
api(project(":i18n"))
|
api(project(":i18n"))
|
||||||
api(project(":data"))
|
api(project(":data"))
|
||||||
@@ -60,7 +63,6 @@ kotlin {
|
|||||||
api(libs.coroutinesSwing)
|
api(libs.coroutinesSwing)
|
||||||
api(libs.accompanistPager)
|
api(libs.accompanistPager)
|
||||||
api(libs.accompanistFlowLayout)
|
api(libs.accompanistFlowLayout)
|
||||||
api(libs.composeRouter)
|
|
||||||
api(libs.krokiCoroutines)
|
api(libs.krokiCoroutines)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import androidx.compose.ui.window.WindowPosition
|
|||||||
import androidx.compose.ui.window.rememberWindowState
|
import androidx.compose.ui.window.rememberWindowState
|
||||||
import ca.gosyer.ui.AppComponent
|
import ca.gosyer.ui.AppComponent
|
||||||
import ca.gosyer.ui.base.theme.AppTheme
|
import ca.gosyer.ui.base.theme.AppTheme
|
||||||
import ca.gosyer.util.lang.launchApplication
|
import ca.gosyer.ui.util.lang.launchApplication
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
|||||||
@@ -12,14 +12,11 @@ import androidx.compose.runtime.MutableState
|
|||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import ca.gosyer.ui.main.Routes
|
|
||||||
import com.github.zsoltk.compose.router.BackStack
|
|
||||||
|
|
||||||
val LocalMenuController =
|
val LocalDisplayController =
|
||||||
compositionLocalOf<MenuController?> { null }
|
compositionLocalOf<DisplayController?> { null }
|
||||||
|
|
||||||
class MenuController(
|
class DisplayController(
|
||||||
val backStack: BackStack<Routes>,
|
|
||||||
private val _sideMenuVisible: MutableState<Boolean> = mutableStateOf(true),
|
private val _sideMenuVisible: MutableState<Boolean> = mutableStateOf(true),
|
||||||
private val _isDrawer: MutableState<Boolean> = mutableStateOf(false),
|
private val _isDrawer: MutableState<Boolean> = mutableStateOf(false),
|
||||||
) {
|
) {
|
||||||
@@ -38,25 +35,12 @@ class MenuController(
|
|||||||
fun setAsNotDrawer() {
|
fun setAsNotDrawer() {
|
||||||
_isDrawer.value = false
|
_isDrawer.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun push(route: Routes) {
|
|
||||||
backStack.push(route)
|
|
||||||
if (isDrawer) {
|
|
||||||
closeSideMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun newRoot(route: Routes) {
|
|
||||||
backStack.newRoot(route)
|
|
||||||
if (isDrawer) {
|
|
||||||
closeSideMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun withMenuController(controller: MenuController, content: @Composable () -> Unit) {
|
fun withDisplayController(controller: DisplayController, content: @Composable () -> Unit) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalMenuController provides controller,
|
LocalDisplayController provides controller,
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -66,13 +66,16 @@ import androidx.compose.ui.unit.sp
|
|||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.uicore.components.BoxWithTooltipSurface
|
import ca.gosyer.uicore.components.BoxWithTooltipSurface
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Toolbar(
|
fun Toolbar(
|
||||||
name: String,
|
name: String,
|
||||||
menuController: MenuController? = LocalMenuController.current,
|
displayController: DisplayController? = LocalDisplayController.current,
|
||||||
closable: Boolean,
|
navigator: Navigator? = LocalNavigator.current,
|
||||||
onClose: () -> Unit = { menuController?.backStack?.pop() },
|
closable: Boolean = (navigator?.size ?: 0) > 1,
|
||||||
|
onClose: () -> Unit = { navigator?.pop() },
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
backgroundColor: Color = MaterialTheme.colors.surface, // CustomColors.current.bars,
|
backgroundColor: Color = MaterialTheme.colors.surface, // CustomColors.current.bars,
|
||||||
@@ -98,14 +101,14 @@ fun Toolbar(
|
|||||||
Modifier.fillMaxHeight().animateContentSize(),
|
Modifier.fillMaxHeight().animateContentSize(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (menuController != null) {
|
if (displayController != null) {
|
||||||
if (menuController.isDrawer) {
|
if (displayController.isDrawer) {
|
||||||
ActionIcon(menuController::openSideMenu, "Open nav", Icons.Rounded.Menu)
|
ActionIcon(displayController::openSideMenu, "Open nav", Icons.Rounded.Menu)
|
||||||
} else {
|
} else {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
!menuController.sideMenuVisible
|
!displayController.sideMenuVisible
|
||||||
) {
|
) {
|
||||||
ActionIcon(menuController::openSideMenu, "Open nav", Icons.Rounded.Sort)
|
ActionIcon(displayController::openSideMenu, "Open nav", Icons.Rounded.Sort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ import ca.gosyer.data.ui.UiPreferences
|
|||||||
import ca.gosyer.data.ui.model.ThemeMode
|
import ca.gosyer.data.ui.model.ThemeMode
|
||||||
import ca.gosyer.uicore.theme.Theme
|
import ca.gosyer.uicore.theme.Theme
|
||||||
import ca.gosyer.uicore.theme.themes
|
import ca.gosyer.uicore.theme.themes
|
||||||
|
import ca.gosyer.uicore.vm.LocalViewModelFactory
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.cancelChildren
|
import kotlinx.coroutines.cancelChildren
|
||||||
@@ -39,7 +40,8 @@ import me.tatarka.inject.annotations.Inject
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AppTheme(content: @Composable () -> Unit) {
|
fun AppTheme(content: @Composable () -> Unit) {
|
||||||
val vm = viewModel<AppThemeViewModel>()
|
val vmFactory = LocalViewModelFactory.current
|
||||||
|
val vm = remember { vmFactory.instantiate<AppThemeViewModel>() }
|
||||||
val colors = vm.getColors()
|
val colors = vm.getColors()
|
||||||
/*val systemUiController = rememberSystemUiController()*/
|
/*val systemUiController = rememberSystemUiController()*/
|
||||||
|
|
||||||
@@ -61,6 +63,8 @@ fun AppTheme(content: @Composable () -> Unit) {
|
|||||||
class AppThemeViewModel @Inject constructor(
|
class AppThemeViewModel @Inject constructor(
|
||||||
private val uiPreferences: UiPreferences
|
private val uiPreferences: UiPreferences
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
override val scope = MainScope()
|
||||||
|
|
||||||
private val themeMode = uiPreferences.themeMode().asStateFlow()
|
private val themeMode = uiPreferences.themeMode().asStateFlow()
|
||||||
private val lightTheme = uiPreferences.lightTheme().asStateFlow()
|
private val lightTheme = uiPreferences.lightTheme().asStateFlow()
|
||||||
private val darkTheme = uiPreferences.darkTheme().asStateFlow()
|
private val darkTheme = uiPreferences.darkTheme().asStateFlow()
|
||||||
@@ -130,7 +134,8 @@ class AppThemeViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDispose() {
|
||||||
baseThemeScope.cancel()
|
baseThemeScope.cancel()
|
||||||
|
scope.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
package ca.gosyer.ui.base.vm
|
package ca.gosyer.ui.base.vm
|
||||||
|
|
||||||
import ca.gosyer.ui.base.theme.AppThemeViewModel
|
import ca.gosyer.ui.base.theme.AppThemeViewModel
|
||||||
import ca.gosyer.ui.categories.CategoriesMenuViewModel
|
import ca.gosyer.ui.categories.CategoriesScreenViewModel
|
||||||
import ca.gosyer.ui.downloads.DownloadsMenuViewModel
|
import ca.gosyer.ui.downloads.DownloadsScreenViewModel
|
||||||
import ca.gosyer.ui.extensions.ExtensionsMenuViewModel
|
import ca.gosyer.ui.extensions.ExtensionsScreenViewModel
|
||||||
import ca.gosyer.ui.library.LibraryScreenViewModel
|
import ca.gosyer.ui.library.LibraryScreenViewModel
|
||||||
import ca.gosyer.ui.main.MainViewModel
|
import ca.gosyer.ui.main.MainViewModel
|
||||||
import ca.gosyer.ui.main.components.DebugOverlayViewModel
|
import ca.gosyer.ui.main.components.DebugOverlayViewModel
|
||||||
import ca.gosyer.ui.main.components.TrayViewModel
|
import ca.gosyer.ui.main.components.TrayViewModel
|
||||||
import ca.gosyer.ui.manga.MangaMenuViewModel
|
import ca.gosyer.ui.manga.MangaScreenViewModel
|
||||||
import ca.gosyer.ui.reader.ReaderMenuViewModel
|
import ca.gosyer.ui.reader.ReaderMenuViewModel
|
||||||
import ca.gosyer.ui.settings.SettingsAdvancedViewModel
|
import ca.gosyer.ui.settings.SettingsAdvancedViewModel
|
||||||
import ca.gosyer.ui.settings.SettingsBackupViewModel
|
import ca.gosyer.ui.settings.SettingsBackupViewModel
|
||||||
@@ -23,29 +23,28 @@ import ca.gosyer.ui.settings.SettingsLibraryViewModel
|
|||||||
import ca.gosyer.ui.settings.SettingsReaderViewModel
|
import ca.gosyer.ui.settings.SettingsReaderViewModel
|
||||||
import ca.gosyer.ui.settings.SettingsServerViewModel
|
import ca.gosyer.ui.settings.SettingsServerViewModel
|
||||||
import ca.gosyer.ui.settings.ThemesViewModel
|
import ca.gosyer.ui.settings.ThemesViewModel
|
||||||
import ca.gosyer.ui.sources.SourcesMenuViewModel
|
import ca.gosyer.ui.sources.SourcesScreenViewModel
|
||||||
import ca.gosyer.ui.sources.components.SourceHomeScreenViewModel
|
import ca.gosyer.ui.sources.browse.SourceScreenViewModel
|
||||||
import ca.gosyer.ui.sources.components.SourceScreenViewModel
|
import ca.gosyer.ui.sources.browse.filter.SourceFiltersViewModel
|
||||||
import ca.gosyer.ui.sources.components.filter.SourceFiltersViewModel
|
import ca.gosyer.ui.sources.home.SourceHomeScreenViewModel
|
||||||
import ca.gosyer.ui.sources.settings.SourceSettingsViewModel
|
import ca.gosyer.ui.sources.settings.SourceSettingsScreenViewModel
|
||||||
import ca.gosyer.ui.updates.UpdatesMenuViewModel
|
import ca.gosyer.ui.updates.UpdatesScreenViewModel
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.uicore.vm.ViewModelFactory
|
import ca.gosyer.uicore.vm.ViewModelFactory
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class ViewModelFactoryImpl(
|
class ViewModelFactoryImpl(
|
||||||
private val appThemeFactory: () -> AppThemeViewModel,
|
private val appThemeFactory: () -> AppThemeViewModel,
|
||||||
private val categoryFactory: () -> CategoriesMenuViewModel,
|
private val categoryFactory: () -> CategoriesScreenViewModel,
|
||||||
private val downloadsFactory: () -> DownloadsMenuViewModel,
|
private val downloadsFactory: (Boolean) -> DownloadsScreenViewModel,
|
||||||
private val extensionsFactory: () -> ExtensionsMenuViewModel,
|
private val extensionsFactory: () -> ExtensionsScreenViewModel,
|
||||||
private val libraryFactory: (bundle: Bundle) -> LibraryScreenViewModel,
|
private val libraryFactory: () -> LibraryScreenViewModel,
|
||||||
private val debugOverlayFactory: () -> DebugOverlayViewModel,
|
private val debugOverlayFactory: () -> DebugOverlayViewModel,
|
||||||
private val trayFactory: () -> TrayViewModel,
|
private val trayFactory: () -> TrayViewModel,
|
||||||
private val mainFactory: () -> MainViewModel,
|
private val mainFactory: () -> MainViewModel,
|
||||||
private val mangaFactory: (params: MangaMenuViewModel.Params) -> MangaMenuViewModel,
|
private val mangaFactory: (params: MangaScreenViewModel.Params) -> MangaScreenViewModel,
|
||||||
private val readerFactory: (params: ReaderMenuViewModel.Params) -> ReaderMenuViewModel,
|
private val readerFactory: (params: ReaderMenuViewModel.Params) -> ReaderMenuViewModel,
|
||||||
private val settingsAdvancedFactory: () -> SettingsAdvancedViewModel,
|
private val settingsAdvancedFactory: () -> SettingsAdvancedViewModel,
|
||||||
private val themesFactory: () -> ThemesViewModel,
|
private val themesFactory: () -> ThemesViewModel,
|
||||||
@@ -55,25 +54,25 @@ class ViewModelFactoryImpl(
|
|||||||
private val settingsReaderFactory: () -> SettingsReaderViewModel,
|
private val settingsReaderFactory: () -> SettingsReaderViewModel,
|
||||||
private val settingsServerFactory: () -> SettingsServerViewModel,
|
private val settingsServerFactory: () -> SettingsServerViewModel,
|
||||||
private val sourceFiltersFactory: (params: SourceFiltersViewModel.Params) -> SourceFiltersViewModel,
|
private val sourceFiltersFactory: (params: SourceFiltersViewModel.Params) -> SourceFiltersViewModel,
|
||||||
private val sourceSettingsFactory: (params: SourceSettingsViewModel.Params) -> SourceSettingsViewModel,
|
private val sourceSettingsFactory: (params: SourceSettingsScreenViewModel.Params) -> SourceSettingsScreenViewModel,
|
||||||
private val sourceHomeFactory: (bundle: Bundle) -> SourceHomeScreenViewModel,
|
private val sourceHomeFactory: () -> SourceHomeScreenViewModel,
|
||||||
private val sourceFactory: (params: SourceScreenViewModel.Params) -> SourceScreenViewModel,
|
private val sourceFactory: (params: SourceScreenViewModel.Params) -> SourceScreenViewModel,
|
||||||
private val sourcesFactory: (bundle: Bundle) -> SourcesMenuViewModel,
|
private val sourcesFactory: () -> SourcesScreenViewModel,
|
||||||
private val updatesFactory: () -> UpdatesMenuViewModel
|
private val updatesFactory: () -> UpdatesScreenViewModel
|
||||||
): ViewModelFactory() {
|
) : ViewModelFactory() {
|
||||||
|
|
||||||
override fun <VM : ViewModel> instantiate(klass: KClass<VM>, arg1: Any?): VM {
|
override fun <VM : ViewModel> instantiate(klass: KClass<VM>, arg1: Any?): VM {
|
||||||
@Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
|
@Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
|
||||||
return when (klass) {
|
return when (klass) {
|
||||||
AppThemeViewModel::class -> appThemeFactory()
|
AppThemeViewModel::class -> appThemeFactory()
|
||||||
CategoriesMenuViewModel::class -> categoryFactory()
|
CategoriesScreenViewModel::class -> categoryFactory()
|
||||||
DownloadsMenuViewModel::class -> downloadsFactory()
|
DownloadsScreenViewModel::class -> downloadsFactory(arg1 as Boolean)
|
||||||
ExtensionsMenuViewModel::class -> extensionsFactory()
|
ExtensionsScreenViewModel::class -> extensionsFactory()
|
||||||
LibraryScreenViewModel::class -> libraryFactory(arg1 as Bundle)
|
LibraryScreenViewModel::class -> libraryFactory()
|
||||||
DebugOverlayViewModel::class -> debugOverlayFactory()
|
DebugOverlayViewModel::class -> debugOverlayFactory()
|
||||||
TrayViewModel::class -> trayFactory()
|
TrayViewModel::class -> trayFactory()
|
||||||
MainViewModel::class -> mainFactory()
|
MainViewModel::class -> mainFactory()
|
||||||
MangaMenuViewModel::class -> mangaFactory(arg1 as MangaMenuViewModel.Params)
|
MangaScreenViewModel::class -> mangaFactory(arg1 as MangaScreenViewModel.Params)
|
||||||
ReaderMenuViewModel::class -> readerFactory(arg1 as ReaderMenuViewModel.Params)
|
ReaderMenuViewModel::class -> readerFactory(arg1 as ReaderMenuViewModel.Params)
|
||||||
SettingsAdvancedViewModel::class -> settingsAdvancedFactory()
|
SettingsAdvancedViewModel::class -> settingsAdvancedFactory()
|
||||||
ThemesViewModel::class -> themesFactory()
|
ThemesViewModel::class -> themesFactory()
|
||||||
@@ -83,11 +82,11 @@ class ViewModelFactoryImpl(
|
|||||||
SettingsReaderViewModel::class -> settingsReaderFactory()
|
SettingsReaderViewModel::class -> settingsReaderFactory()
|
||||||
SettingsServerViewModel::class -> settingsServerFactory()
|
SettingsServerViewModel::class -> settingsServerFactory()
|
||||||
SourceFiltersViewModel::class -> sourceFiltersFactory(arg1 as SourceFiltersViewModel.Params)
|
SourceFiltersViewModel::class -> sourceFiltersFactory(arg1 as SourceFiltersViewModel.Params)
|
||||||
SourceSettingsViewModel::class -> sourceSettingsFactory(arg1 as SourceSettingsViewModel.Params)
|
SourceSettingsScreenViewModel::class -> sourceSettingsFactory(arg1 as SourceSettingsScreenViewModel.Params)
|
||||||
SourceHomeScreenViewModel::class -> sourceHomeFactory(arg1 as Bundle)
|
SourceHomeScreenViewModel::class -> sourceHomeFactory()
|
||||||
SourceScreenViewModel::class -> sourceFactory(arg1 as SourceScreenViewModel.Params)
|
SourceScreenViewModel::class -> sourceFactory(arg1 as SourceScreenViewModel.Params)
|
||||||
SourcesMenuViewModel::class -> sourcesFactory(arg1 as Bundle)
|
SourcesScreenViewModel::class -> sourcesFactory()
|
||||||
UpdatesMenuViewModel::class -> updatesFactory()
|
UpdatesScreenViewModel::class -> updatesFactory()
|
||||||
else -> throw IllegalArgumentException("Unknown ViewModel $klass")
|
else -> throw IllegalArgumentException("Unknown ViewModel $klass")
|
||||||
} as VM
|
} as VM
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.categories
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
|
import ca.gosyer.ui.categories.components.CategoriesScreenContent
|
||||||
|
import ca.gosyer.ui.util.compose.ThemedWindow
|
||||||
|
import ca.gosyer.ui.util.lang.launchApplication
|
||||||
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun openCategoriesMenu(notifyFinished: (() -> Unit)? = null) {
|
||||||
|
launchApplication {
|
||||||
|
ThemedWindow(
|
||||||
|
::exitApplication,
|
||||||
|
title = "${BuildKonfig.NAME} - Categories"
|
||||||
|
) {
|
||||||
|
Navigator(remember { CategoriesScreen(notifyFinished) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CategoriesScreen(
|
||||||
|
@Transient
|
||||||
|
private val notifyFinished: (() -> Unit)? = null
|
||||||
|
) : Screen {
|
||||||
|
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<CategoriesScreenViewModel>()
|
||||||
|
CategoriesScreenContent(
|
||||||
|
categories = vm.categories.collectAsState().value,
|
||||||
|
updateRemoteCategories = vm::updateRemoteCategories,
|
||||||
|
moveCategoryUp = vm::moveUp,
|
||||||
|
moveCategoryDown = vm::moveDown,
|
||||||
|
renameCategory = vm::renameCategory,
|
||||||
|
deleteCategory = vm::deleteCategory,
|
||||||
|
createCategory = vm::createCategory,
|
||||||
|
notifyFinished = notifyFinished
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class CategoriesMenuViewModel @Inject constructor(
|
class CategoriesScreenViewModel @Inject constructor(
|
||||||
private val categoryHandler: CategoryInteractionHandler
|
private val categoryHandler: CategoryInteractionHandler
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private var originalCategories = emptyList<Category>()
|
private var originalCategories = emptyList<Category>()
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.categories
|
package ca.gosyer.ui.categories.components
|
||||||
|
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.TextField
|
import androidx.compose.material.TextField
|
||||||
@@ -14,11 +14,12 @@ import androidx.compose.ui.text.input.TextFieldValue
|
|||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
import ca.gosyer.ui.base.WindowDialog
|
||||||
|
import ca.gosyer.ui.categories.CategoriesScreenViewModel
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
fun openRenameDialog(
|
fun openRenameDialog(
|
||||||
category: CategoriesMenuViewModel.MenuCategory,
|
category: CategoriesScreenViewModel.MenuCategory,
|
||||||
onRename: (String) -> Unit
|
onRename: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val newName = MutableStateFlow(TextFieldValue(category.name))
|
val newName = MutableStateFlow(TextFieldValue(category.name))
|
||||||
@@ -44,8 +45,8 @@ fun openRenameDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun openDeleteDialog(
|
fun openDeleteDialog(
|
||||||
category: CategoriesMenuViewModel.MenuCategory,
|
category: CategoriesScreenViewModel.MenuCategory,
|
||||||
onDelete: (CategoriesMenuViewModel.MenuCategory) -> Unit
|
onDelete: (CategoriesScreenViewModel.MenuCategory) -> Unit
|
||||||
) {
|
) {
|
||||||
WindowDialog(
|
WindowDialog(
|
||||||
title = "${BuildKonfig.NAME} - Categories - Delete Dialog",
|
title = "${BuildKonfig.NAME} - Categories - Delete Dialog",
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.categories
|
package ca.gosyer.ui.categories.components
|
||||||
|
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -40,16 +40,11 @@ import androidx.compose.material.icons.rounded.KeyboardArrowUp
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
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 ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
import ca.gosyer.ui.categories.CategoriesScreenViewModel.MenuCategory
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
|
||||||
import ca.gosyer.util.compose.ThemedWindow
|
|
||||||
import ca.gosyer.util.lang.launchApplication
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
@@ -57,23 +52,18 @@ import kotlinx.coroutines.GlobalScope
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun openCategoriesMenu(notifyFinished: (() -> Unit)? = null) {
|
|
||||||
launchApplication {
|
|
||||||
ThemedWindow(
|
|
||||||
::exitApplication,
|
|
||||||
title = "${BuildKonfig.NAME} - Categories"
|
|
||||||
) {
|
|
||||||
CategoriesMenu(notifyFinished)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CategoriesMenu(notifyFinished: (() -> Unit)? = null) {
|
fun CategoriesScreenContent(
|
||||||
val vm = viewModel<CategoriesMenuViewModel>()
|
categories: List<MenuCategory>,
|
||||||
val categories by vm.categories.collectAsState()
|
updateRemoteCategories: suspend () -> Unit,
|
||||||
|
moveCategoryUp: (MenuCategory) -> Unit,
|
||||||
|
moveCategoryDown: (MenuCategory) -> Unit,
|
||||||
|
renameCategory: (MenuCategory, String) -> Unit,
|
||||||
|
deleteCategory: (MenuCategory) -> Unit,
|
||||||
|
createCategory: (String) -> Unit,
|
||||||
|
notifyFinished: (() -> Unit)? = null
|
||||||
|
) {
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
val logger = KotlinLogging.logger {}
|
val logger = KotlinLogging.logger {}
|
||||||
@@ -81,7 +71,7 @@ fun CategoriesMenu(notifyFinished: (() -> Unit)? = null) {
|
|||||||
logger.debug { throwable }
|
logger.debug { throwable }
|
||||||
}
|
}
|
||||||
GlobalScope.launch(handler) {
|
GlobalScope.launch(handler) {
|
||||||
vm.updateRemoteCategories()
|
updateRemoteCategories()
|
||||||
notifyFinished?.invoke()
|
notifyFinished?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,16 +86,16 @@ fun CategoriesMenu(notifyFinished: (() -> Unit)? = null) {
|
|||||||
category = category,
|
category = category,
|
||||||
moveUpEnabled = i != 0,
|
moveUpEnabled = i != 0,
|
||||||
moveDownEnabled = i != categories.lastIndex,
|
moveDownEnabled = i != categories.lastIndex,
|
||||||
onMoveUp = { vm.moveUp(category) },
|
onMoveUp = { moveCategoryUp(category) },
|
||||||
onMoveDown = { vm.moveDown(category) },
|
onMoveDown = { moveCategoryDown(category) },
|
||||||
onRename = {
|
onRename = {
|
||||||
openRenameDialog(category) {
|
openRenameDialog(category) {
|
||||||
vm.renameCategory(category, it)
|
renameCategory(category, it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDelete = {
|
onDelete = {
|
||||||
openDeleteDialog(category) {
|
openDeleteDialog(category) {
|
||||||
vm.deleteCategory(category)
|
deleteCategory(category)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -120,7 +110,7 @@ fun CategoriesMenu(notifyFinished: (() -> Unit)? = null) {
|
|||||||
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
|
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
openCreateDialog {
|
openCreateDialog {
|
||||||
vm.createCategory(it)
|
createCategory(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -136,7 +126,7 @@ fun CategoriesMenu(notifyFinished: (() -> Unit)? = null) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CategoryRow(
|
private fun CategoryRow(
|
||||||
category: CategoriesMenuViewModel.MenuCategory,
|
category: MenuCategory,
|
||||||
moveUpEnabled: Boolean = true,
|
moveUpEnabled: Boolean = true,
|
||||||
moveDownEnabled: Boolean = true,
|
moveDownEnabled: Boolean = true,
|
||||||
onMoveUp: () -> Unit = {},
|
onMoveUp: () -> Unit = {},
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.downloads
|
||||||
|
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
|
import ca.gosyer.ui.downloads.components.DownloadsScreenContent
|
||||||
|
import ca.gosyer.ui.manga.MangaScreen
|
||||||
|
import ca.gosyer.ui.util.compose.ThemedWindow
|
||||||
|
import ca.gosyer.ui.util.lang.launchApplication
|
||||||
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun openDownloadsMenu() {
|
||||||
|
launchApplication {
|
||||||
|
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
||||||
|
Surface {
|
||||||
|
Navigator(remember { DownloadsScreen() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DownloadsScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel {
|
||||||
|
instantiate<DownloadsScreenViewModel>(false)
|
||||||
|
}
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
DownloadsScreenContent(
|
||||||
|
downloadQueue = vm.downloadQueue.collectAsState().value,
|
||||||
|
downloadStatus = vm.downloaderStatus.collectAsState().value,
|
||||||
|
startDownloading = vm::start,
|
||||||
|
pauseDownloading = vm::pause,
|
||||||
|
clearQueue = vm::clear,
|
||||||
|
onMangaClick = { navigator push MangaScreen(it) },
|
||||||
|
stopDownload = vm::stopDownload,
|
||||||
|
moveDownloadToBottom = vm::moveToBottom
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,14 +11,24 @@ import ca.gosyer.data.models.Chapter
|
|||||||
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
||||||
import ca.gosyer.data.server.interactions.DownloadInteractionHandler
|
import ca.gosyer.data.server.interactions.DownloadInteractionHandler
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class DownloadsMenuViewModel @Inject constructor(
|
class DownloadsScreenViewModel @Inject constructor(
|
||||||
private val downloadService: DownloadService,
|
private val downloadService: DownloadService,
|
||||||
private val downloadsHandler: DownloadInteractionHandler,
|
private val downloadsHandler: DownloadInteractionHandler,
|
||||||
private val chapterHandler: ChapterInteractionHandler
|
private val chapterHandler: ChapterInteractionHandler,
|
||||||
|
standalone: Boolean
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
private val uiScope = if (standalone) {
|
||||||
|
MainScope()
|
||||||
|
} else null
|
||||||
|
|
||||||
|
override val scope: CoroutineScope
|
||||||
|
get() = uiScope ?: super.scope
|
||||||
|
|
||||||
val serviceStatus get() = downloadService.status
|
val serviceStatus get() = downloadService.status
|
||||||
val downloaderStatus get() = downloadService.downloaderStatus
|
val downloaderStatus get() = downloadService.downloaderStatus
|
||||||
val downloadQueue get() = downloadService.downloadQueue
|
val downloadQueue get() = downloadService.downloadQueue
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.downloads
|
package ca.gosyer.ui.downloads.components
|
||||||
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
@@ -28,7 +28,6 @@ import androidx.compose.material.Icon
|
|||||||
import androidx.compose.material.LinearProgressIndicator
|
import androidx.compose.material.LinearProgressIndicator
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.ProgressIndicatorDefaults
|
import androidx.compose.material.ProgressIndicatorDefaults
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.ClearAll
|
import androidx.compose.material.icons.rounded.ClearAll
|
||||||
@@ -36,7 +35,6 @@ import androidx.compose.material.icons.rounded.MoreVert
|
|||||||
import androidx.compose.material.icons.rounded.Pause
|
import androidx.compose.material.icons.rounded.Pause
|
||||||
import androidx.compose.material.icons.rounded.PlayArrow
|
import androidx.compose.material.icons.rounded.PlayArrow
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -48,10 +46,8 @@ import ca.gosyer.data.download.model.DownloadChapter
|
|||||||
import ca.gosyer.data.download.model.DownloaderStatus
|
import ca.gosyer.data.download.model.DownloaderStatus
|
||||||
import ca.gosyer.data.models.Chapter
|
import ca.gosyer.data.models.Chapter
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
|
||||||
import ca.gosyer.ui.base.navigation.ActionIcon
|
import ca.gosyer.ui.base.navigation.ActionIcon
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.manga.openMangaMenu
|
|
||||||
import ca.gosyer.uicore.components.DropdownIconButton
|
import ca.gosyer.uicore.components.DropdownIconButton
|
||||||
import ca.gosyer.uicore.components.MangaListItem
|
import ca.gosyer.uicore.components.MangaListItem
|
||||||
import ca.gosyer.uicore.components.MangaListItemColumn
|
import ca.gosyer.uicore.components.MangaListItemColumn
|
||||||
@@ -59,41 +55,31 @@ import ca.gosyer.uicore.components.MangaListItemImage
|
|||||||
import ca.gosyer.uicore.components.MangaListItemSubtitle
|
import ca.gosyer.uicore.components.MangaListItemSubtitle
|
||||||
import ca.gosyer.uicore.components.MangaListItemTitle
|
import ca.gosyer.uicore.components.MangaListItemTitle
|
||||||
import ca.gosyer.uicore.components.mangaAspectRatio
|
import ca.gosyer.uicore.components.mangaAspectRatio
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
|
||||||
import ca.gosyer.util.compose.ThemedWindow
|
|
||||||
import ca.gosyer.util.lang.launchApplication
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import io.kamel.image.lazyPainterResource
|
import io.kamel.image.lazyPainterResource
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun openDownloadsMenu() {
|
|
||||||
launchApplication {
|
|
||||||
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
|
||||||
Surface {
|
|
||||||
DownloadsMenu(::openMangaMenu)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DownloadsMenu(onMangaClick: (Long) -> Unit) {
|
fun DownloadsScreenContent(
|
||||||
val vm = viewModel<DownloadsMenuViewModel>()
|
downloadQueue: List<DownloadChapter>,
|
||||||
val downloadQueue by vm.downloadQueue.collectAsState()
|
downloadStatus: DownloaderStatus,
|
||||||
|
startDownloading: () -> Unit,
|
||||||
|
pauseDownloading: () -> Unit,
|
||||||
|
clearQueue: () -> Unit,
|
||||||
|
onMangaClick: (Long) -> Unit,
|
||||||
|
stopDownload: (Chapter) -> Unit,
|
||||||
|
moveDownloadToBottom: (Chapter) -> Unit
|
||||||
|
) {
|
||||||
Column {
|
Column {
|
||||||
Toolbar(
|
Toolbar(
|
||||||
stringResource(MR.strings.location_downloads),
|
stringResource(MR.strings.location_downloads),
|
||||||
closable = false,
|
closable = false,
|
||||||
actions = {
|
actions = {
|
||||||
val downloadStatus by vm.downloaderStatus.collectAsState()
|
|
||||||
if (downloadStatus == DownloaderStatus.Started) {
|
if (downloadStatus == DownloaderStatus.Started) {
|
||||||
ActionIcon(onClick = vm::pause, stringResource(MR.strings.action_pause), Icons.Rounded.Pause)
|
ActionIcon(onClick = pauseDownloading, stringResource(MR.strings.action_pause), Icons.Rounded.Pause)
|
||||||
} else {
|
} else {
|
||||||
ActionIcon(onClick = vm::start, stringResource(MR.strings.action_continue), Icons.Rounded.PlayArrow)
|
ActionIcon(onClick = startDownloading, stringResource(MR.strings.action_continue), Icons.Rounded.PlayArrow)
|
||||||
}
|
}
|
||||||
ActionIcon(onClick = vm::clear, stringResource(MR.strings.action_clear_queue), Icons.Rounded.ClearAll)
|
ActionIcon(onClick = clearQueue, stringResource(MR.strings.action_clear_queue), Icons.Rounded.ClearAll)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Box {
|
Box {
|
||||||
@@ -103,8 +89,8 @@ fun DownloadsMenu(onMangaClick: (Long) -> Unit) {
|
|||||||
DownloadsItem(
|
DownloadsItem(
|
||||||
it,
|
it,
|
||||||
{ onMangaClick(it.mangaId) },
|
{ onMangaClick(it.mangaId) },
|
||||||
vm::stopDownload,
|
stopDownload,
|
||||||
vm::moveToBottom
|
moveDownloadToBottom
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.extensions
|
||||||
|
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.rememberWindowState
|
||||||
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
|
import ca.gosyer.ui.extensions.components.ExtensionsScreenContent
|
||||||
|
import ca.gosyer.ui.util.compose.ThemedWindow
|
||||||
|
import ca.gosyer.ui.util.lang.launchApplication
|
||||||
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun openExtensionsMenu() {
|
||||||
|
launchApplication {
|
||||||
|
val state = rememberWindowState(size = DpSize(550.dp, 700.dp))
|
||||||
|
ThemedWindow(::exitApplication, state, title = BuildKonfig.NAME) {
|
||||||
|
Surface {
|
||||||
|
Navigator(remember { ExtensionsScreen() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExtensionsScreen : Screen {
|
||||||
|
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<ExtensionsScreenViewModel>()
|
||||||
|
|
||||||
|
ExtensionsScreenContent(
|
||||||
|
extensions = vm.extensions.collectAsState().value,
|
||||||
|
isLoading = vm.isLoading.collectAsState().value,
|
||||||
|
query = vm.searchQuery.collectAsState().value,
|
||||||
|
setQuery = vm::search,
|
||||||
|
enabledLangs = vm.enabledLangs,
|
||||||
|
getSourceLanguages = vm::getSourceLanguages,
|
||||||
|
setEnabledLanguages = vm::setEnabledLanguages,
|
||||||
|
installExtension = vm::install,
|
||||||
|
updateExtension = vm::update,
|
||||||
|
uninstallExtension = vm::uninstall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ import kotlinx.coroutines.launch
|
|||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class ExtensionsMenuViewModel @Inject constructor(
|
class ExtensionsScreenViewModel @Inject constructor(
|
||||||
private val extensionHandler: ExtensionInteractionHandler,
|
private val extensionHandler: ExtensionInteractionHandler,
|
||||||
extensionPreferences: ExtensionPreferences
|
extensionPreferences: ExtensionPreferences
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.extensions
|
package ca.gosyer.ui.extensions.components
|
||||||
|
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -26,7 +26,6 @@ import androidx.compose.foundation.rememberScrollbarAdapter
|
|||||||
import androidx.compose.material.Button
|
import androidx.compose.material.Button
|
||||||
import androidx.compose.material.ContentAlpha
|
import androidx.compose.material.ContentAlpha
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.material.Switch
|
import androidx.compose.material.Switch
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -42,10 +41,8 @@ import androidx.compose.ui.graphics.FilterQuality
|
|||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.DpSize
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.rememberWindowState
|
|
||||||
import ca.gosyer.data.models.Extension
|
import ca.gosyer.data.models.Extension
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
@@ -54,59 +51,48 @@ import ca.gosyer.ui.base.navigation.TextActionIcon
|
|||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.uicore.components.LoadingScreen
|
import ca.gosyer.uicore.components.LoadingScreen
|
||||||
import ca.gosyer.uicore.image.KamelImage
|
import ca.gosyer.uicore.image.KamelImage
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
|
||||||
import ca.gosyer.util.compose.ThemedWindow
|
|
||||||
import ca.gosyer.util.compose.persistentLazyListState
|
|
||||||
import ca.gosyer.util.lang.launchApplication
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import io.kamel.image.lazyPainterResource
|
import io.kamel.image.lazyPainterResource
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun openExtensionsMenu() {
|
|
||||||
launchApplication {
|
|
||||||
val state = rememberWindowState(size = DpSize(550.dp, 700.dp))
|
|
||||||
ThemedWindow(::exitApplication, state, title = BuildKonfig.NAME) {
|
|
||||||
Surface {
|
|
||||||
ExtensionsMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionsMenu() {
|
fun ExtensionsScreenContent(
|
||||||
val vm = viewModel<ExtensionsMenuViewModel>()
|
extensions: Map<String, List<Extension>>,
|
||||||
val extensions by vm.extensions.collectAsState()
|
isLoading: Boolean,
|
||||||
val isLoading by vm.isLoading.collectAsState()
|
query: String?,
|
||||||
val search by vm.searchQuery.collectAsState()
|
setQuery: (String) -> Unit,
|
||||||
|
enabledLangs: StateFlow<Set<String>>,
|
||||||
|
getSourceLanguages: () -> Set<String>,
|
||||||
|
setEnabledLanguages: (Set<String>) -> Unit,
|
||||||
|
installExtension: (Extension) -> Unit,
|
||||||
|
updateExtension: (Extension) -> Unit,
|
||||||
|
uninstallExtension: (Extension) -> Unit
|
||||||
|
) {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
Column {
|
Column {
|
||||||
ExtensionsToolbar(
|
ExtensionsToolbar(
|
||||||
search,
|
query,
|
||||||
vm::search,
|
setQuery,
|
||||||
vm.enabledLangs,
|
enabledLangs,
|
||||||
vm::getSourceLanguages,
|
getSourceLanguages,
|
||||||
vm::setEnabledLanguages
|
setEnabledLanguages
|
||||||
)
|
)
|
||||||
LoadingScreen(isLoading)
|
LoadingScreen(isLoading)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val state = persistentLazyListState()
|
val state = rememberLazyListState()
|
||||||
|
|
||||||
Box(Modifier.fillMaxSize()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
item {
|
item {
|
||||||
ExtensionsToolbar(
|
ExtensionsToolbar(
|
||||||
search,
|
query,
|
||||||
vm::search,
|
setQuery,
|
||||||
vm.enabledLangs,
|
enabledLangs,
|
||||||
vm::getSourceLanguages,
|
getSourceLanguages,
|
||||||
vm::setEnabledLanguages
|
setEnabledLanguages
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
extensions.forEach { (header, items) ->
|
extensions.forEach { (header, items) ->
|
||||||
@@ -120,9 +106,9 @@ fun ExtensionsMenu() {
|
|||||||
items(items) { extension ->
|
items(items) { extension ->
|
||||||
ExtensionItem(
|
ExtensionItem(
|
||||||
extension,
|
extension,
|
||||||
onInstallClicked = vm::install,
|
onInstallClicked = installExtension,
|
||||||
onUpdateClicked = vm::update,
|
onUpdateClicked = updateExtension,
|
||||||
onUninstallClicked = vm::uninstall
|
onUninstallClicked = uninstallExtension
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
@@ -6,197 +6,55 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.library
|
package ca.gosyer.ui.library
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.expandVertically
|
|
||||||
import androidx.compose.animation.shrinkVertically
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.ScrollableTabRow
|
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Tab
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.util.fastForEachIndexed
|
|
||||||
import ca.gosyer.data.library.model.DisplayMode
|
|
||||||
import ca.gosyer.data.models.Category
|
|
||||||
import ca.gosyer.data.models.Manga
|
|
||||||
import ca.gosyer.i18n.MR
|
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.library.components.LibraryScreenContent
|
||||||
import ca.gosyer.ui.manga.openMangaMenu
|
import ca.gosyer.ui.manga.MangaScreen
|
||||||
import ca.gosyer.uicore.components.LoadingScreen
|
import ca.gosyer.ui.util.compose.ThemedWindow
|
||||||
|
import ca.gosyer.ui.util.lang.launchApplication
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
import ca.gosyer.util.compose.ThemedWindow
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import ca.gosyer.util.lang.launchApplication
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import com.github.zsoltk.compose.savedinstancestate.LocalSavedInstanceState
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import com.google.accompanist.pager.HorizontalPager
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
fun openLibraryMenu() {
|
fun openLibraryMenu() {
|
||||||
launchApplication {
|
launchApplication {
|
||||||
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
||||||
CompositionLocalProvider(
|
Surface {
|
||||||
LocalSavedInstanceState provides Bundle()
|
Navigator(remember { LibraryScreen() })
|
||||||
) {
|
|
||||||
Surface {
|
|
||||||
LibraryScreen()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
class LibraryScreen : Screen {
|
||||||
fun LibraryScreen(onClickManga: (Long) -> Unit = ::openMangaMenu) {
|
|
||||||
LibraryScreen(LocalSavedInstanceState.current, onClickManga)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
fun LibraryScreen(bundle: Bundle, onClickManga: (Long) -> Unit = ::openMangaMenu) {
|
|
||||||
val vm = viewModel {
|
|
||||||
instantiate<LibraryScreenViewModel>(bundle)
|
|
||||||
}
|
|
||||||
val categories by vm.categories.collectAsState()
|
|
||||||
val selectedCategoryIndex by vm.selectedCategoryIndex.collectAsState()
|
|
||||||
val displayMode by vm.displayMode.collectAsState()
|
|
||||||
val isLoading by vm.isLoading.collectAsState()
|
|
||||||
val error by vm.error.collectAsState()
|
|
||||||
val query by vm.query.collectAsState()
|
|
||||||
// val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
|
|
||||||
|
|
||||||
if (categories.isEmpty()) {
|
@Composable
|
||||||
LoadingScreen(isLoading, errorMessage = error)
|
override fun Content() {
|
||||||
} else {
|
val vm = viewModel<LibraryScreenViewModel>()
|
||||||
/*ModalBottomSheetLayout(
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
sheetState = sheetState,
|
LibraryScreenContent(
|
||||||
sheetContent = { *//*LibrarySheet()*//* }
|
categories = vm.categories.collectAsState().value,
|
||||||
) {*/
|
selectedCategoryIndex = vm.selectedCategoryIndex.collectAsState().value,
|
||||||
Column(Modifier.fillMaxWidth()) {
|
displayMode = vm.displayMode.collectAsState().value,
|
||||||
/*Toolbar(
|
isLoading = vm.isLoading.collectAsState().value,
|
||||||
title = {
|
error = vm.error.collectAsState().value,
|
||||||
val text = if (vm.showCategoryTabs) {
|
query = vm.query.collectAsState().value,
|
||||||
stringResource(R.string.library_label)
|
updateQuery = vm::updateQuery,
|
||||||
} else {
|
getLibraryForPage = { vm.getLibraryForCategoryId(it).collectAsState() },
|
||||||
vm.selectedCategory?.visibleName.orEmpty()
|
onPageChanged = vm::setSelectedPage,
|
||||||
}
|
onClickManga = { navigator push MangaScreen(it) },
|
||||||
Text(text)
|
onRemoveMangaClicked = vm::removeManga
|
||||||
},
|
)
|
||||||
actions = {
|
|
||||||
IconButton(onClick = { scope.launch { sheetState.show() }}) {
|
|
||||||
Icon(Icons.Rounded.FilterList, contentDescription = null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*/
|
|
||||||
Toolbar(
|
|
||||||
stringResource(MR.strings.location_library),
|
|
||||||
closable = false,
|
|
||||||
searchText = query,
|
|
||||||
search = vm::updateQuery
|
|
||||||
)
|
|
||||||
LibraryTabs(
|
|
||||||
visible = true, // vm.showCategoryTabs,
|
|
||||||
categories = categories,
|
|
||||||
selectedPage = selectedCategoryIndex,
|
|
||||||
onPageChanged = vm::setSelectedPage
|
|
||||||
)
|
|
||||||
LibraryPager(
|
|
||||||
categories = categories,
|
|
||||||
displayMode = displayMode,
|
|
||||||
selectedPage = selectedCategoryIndex,
|
|
||||||
getLibraryForPage = { vm.getLibraryForCategoryId(it).collectAsState() },
|
|
||||||
onPageChanged = vm::setSelectedPage,
|
|
||||||
onClickManga = onClickManga,
|
|
||||||
onRemoveMangaClicked = vm::removeManga
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun LibraryTabs(
|
|
||||||
visible: Boolean,
|
|
||||||
categories: List<Category>,
|
|
||||||
selectedPage: Int,
|
|
||||||
onPageChanged: (Int) -> Unit
|
|
||||||
) {
|
|
||||||
if (categories.isEmpty()) return
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = visible,
|
|
||||||
enter = expandVertically(),
|
|
||||||
exit = shrinkVertically()
|
|
||||||
) {
|
|
||||||
ScrollableTabRow(
|
|
||||||
selectedTabIndex = selectedPage,
|
|
||||||
backgroundColor = MaterialTheme.colors.surface,
|
|
||||||
// contentColor = CustomColors.current.onBars,
|
|
||||||
edgePadding = 0.dp
|
|
||||||
) {
|
|
||||||
categories.fastForEachIndexed { i, category ->
|
|
||||||
Tab(
|
|
||||||
selected = selectedPage == i,
|
|
||||||
onClick = { onPageChanged(i) },
|
|
||||||
text = { Text(category.name) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun LibraryPager(
|
|
||||||
categories: List<Category>,
|
|
||||||
displayMode: DisplayMode,
|
|
||||||
selectedPage: Int,
|
|
||||||
getLibraryForPage: @Composable (Long) -> State<List<Manga>>,
|
|
||||||
onPageChanged: (Int) -> Unit,
|
|
||||||
onClickManga: (Long) -> Unit,
|
|
||||||
onRemoveMangaClicked: (Long) -> Unit
|
|
||||||
) {
|
|
||||||
if (categories.isEmpty()) return
|
|
||||||
|
|
||||||
val state = rememberPagerState(categories.size, selectedPage)
|
|
||||||
LaunchedEffect(state.currentPage) {
|
|
||||||
if (state.currentPage != selectedPage) {
|
|
||||||
onPageChanged(state.currentPage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(selectedPage) {
|
|
||||||
if (state.currentPage != selectedPage) {
|
|
||||||
state.animateScrollToPage(selectedPage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HorizontalPager(state = state) {
|
|
||||||
val library by getLibraryForPage(categories[it].id)
|
|
||||||
when (displayMode) {
|
|
||||||
DisplayMode.CompactGrid -> LibraryMangaCompactGrid(
|
|
||||||
library = library,
|
|
||||||
onClickManga = onClickManga,
|
|
||||||
onRemoveMangaClicked = onRemoveMangaClicked
|
|
||||||
)
|
|
||||||
/*DisplayMode.ComfortableGrid -> LibraryMangaComfortableGrid(
|
|
||||||
library = library,
|
|
||||||
onClickManga = onClickManga
|
|
||||||
)
|
|
||||||
DisplayMode.List -> LibraryMangaList(
|
|
||||||
library = library,
|
|
||||||
onClickManga = onClickManga
|
|
||||||
)*/
|
|
||||||
else -> Box {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
|||||||
import ca.gosyer.data.server.interactions.LibraryInteractionHandler
|
import ca.gosyer.data.server.interactions.LibraryInteractionHandler
|
||||||
import ca.gosyer.data.server.interactions.UpdatesInteractionHandler
|
import ca.gosyer.data.server.interactions.UpdatesInteractionHandler
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.util.compose.saveIntInBundle
|
|
||||||
import ca.gosyer.util.compose.saveStringInBundle
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -74,13 +71,12 @@ class LibraryScreenViewModel @Inject constructor(
|
|||||||
private val categoryHandler: CategoryInteractionHandler,
|
private val categoryHandler: CategoryInteractionHandler,
|
||||||
private val libraryHandler: LibraryInteractionHandler,
|
private val libraryHandler: LibraryInteractionHandler,
|
||||||
private val updatesHandler: UpdatesInteractionHandler,
|
private val updatesHandler: UpdatesInteractionHandler,
|
||||||
libraryPreferences: LibraryPreferences,
|
libraryPreferences: LibraryPreferences
|
||||||
private val bundle: Bundle,
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val library = Library(MutableStateFlow(emptyList()), mutableMapOf())
|
private val library = Library(MutableStateFlow(emptyList()), mutableMapOf())
|
||||||
val categories = library.categories.asStateFlow()
|
val categories = library.categories.asStateFlow()
|
||||||
|
|
||||||
private val _selectedCategoryIndex = saveIntInBundle(scope, bundle, SELECTED_CATEGORY_KEY, 0)
|
private val _selectedCategoryIndex = MutableStateFlow(0)
|
||||||
val selectedCategoryIndex = _selectedCategoryIndex.asStateFlow()
|
val selectedCategoryIndex = _selectedCategoryIndex.asStateFlow()
|
||||||
|
|
||||||
val displayMode = libraryPreferences.displayMode().stateIn(scope)
|
val displayMode = libraryPreferences.displayMode().stateIn(scope)
|
||||||
@@ -91,7 +87,7 @@ class LibraryScreenViewModel @Inject constructor(
|
|||||||
private val _error = MutableStateFlow<String?>(null)
|
private val _error = MutableStateFlow<String?>(null)
|
||||||
val error = _error.asStateFlow()
|
val error = _error.asStateFlow()
|
||||||
|
|
||||||
private val _query = saveStringInBundle(scope, bundle, QUERY_KEY)
|
private val _query = MutableStateFlow("")
|
||||||
val query = _query.asStateFlow()
|
val query = _query.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.library
|
package ca.gosyer.ui.library.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -36,7 +36,9 @@ fun LibraryMangaBadges(
|
|||||||
if (downloaded != null && downloaded > 0) {
|
if (downloaded != null && downloaded > 0) {
|
||||||
Text(
|
Text(
|
||||||
text = downloaded.toString(),
|
text = downloaded.toString(),
|
||||||
modifier = Modifier.background(MaterialTheme.colors.secondary).then(BadgesInnerPadding),
|
modifier = Modifier.background(MaterialTheme.colors.secondary).then(
|
||||||
|
BadgesInnerPadding
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.caption,
|
style = MaterialTheme.typography.caption,
|
||||||
color = MaterialTheme.colors.onSecondary
|
color = MaterialTheme.colors.onSecondary
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.library.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import ca.gosyer.data.library.model.DisplayMode
|
||||||
|
import ca.gosyer.data.models.Category
|
||||||
|
import ca.gosyer.data.models.Manga
|
||||||
|
import com.google.accompanist.pager.HorizontalPager
|
||||||
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LibraryPager(
|
||||||
|
categories: List<Category>,
|
||||||
|
displayMode: DisplayMode,
|
||||||
|
selectedPage: Int,
|
||||||
|
getLibraryForPage: @Composable (Long) -> State<List<Manga>>,
|
||||||
|
onPageChanged: (Int) -> Unit,
|
||||||
|
onClickManga: (Long) -> Unit,
|
||||||
|
onRemoveMangaClicked: (Long) -> Unit
|
||||||
|
) {
|
||||||
|
if (categories.isEmpty()) return
|
||||||
|
|
||||||
|
val state = rememberPagerState(categories.size, selectedPage)
|
||||||
|
LaunchedEffect(state.currentPage) {
|
||||||
|
if (state.currentPage != selectedPage) {
|
||||||
|
onPageChanged(state.currentPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(selectedPage) {
|
||||||
|
if (state.currentPage != selectedPage) {
|
||||||
|
state.animateScrollToPage(selectedPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HorizontalPager(state = state) {
|
||||||
|
val library by getLibraryForPage(categories[it].id)
|
||||||
|
when (displayMode) {
|
||||||
|
DisplayMode.CompactGrid -> LibraryMangaCompactGrid(
|
||||||
|
library = library,
|
||||||
|
onClickManga = onClickManga,
|
||||||
|
onRemoveMangaClicked = onRemoveMangaClicked
|
||||||
|
)
|
||||||
|
/*DisplayMode.ComfortableGrid -> LibraryMangaComfortableGrid(
|
||||||
|
library = library,
|
||||||
|
onClickManga = onClickManga
|
||||||
|
)
|
||||||
|
DisplayMode.List -> LibraryMangaList(
|
||||||
|
library = library,
|
||||||
|
onClickManga = onClickManga
|
||||||
|
)*/
|
||||||
|
else -> Box {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.library.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import ca.gosyer.data.library.model.DisplayMode
|
||||||
|
import ca.gosyer.data.models.Category
|
||||||
|
import ca.gosyer.data.models.Manga
|
||||||
|
import ca.gosyer.i18n.MR
|
||||||
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
|
import ca.gosyer.uicore.components.LoadingScreen
|
||||||
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LibraryScreenContent(
|
||||||
|
categories: List<Category>,
|
||||||
|
selectedCategoryIndex: Int,
|
||||||
|
displayMode: DisplayMode,
|
||||||
|
isLoading: Boolean,
|
||||||
|
error: String?,
|
||||||
|
query: String,
|
||||||
|
updateQuery: (String) -> Unit,
|
||||||
|
getLibraryForPage: @Composable (Long) -> State<List<Manga>>,
|
||||||
|
onPageChanged: (Int) -> Unit,
|
||||||
|
onClickManga: (Long) -> Unit,
|
||||||
|
onRemoveMangaClicked: (Long) -> Unit
|
||||||
|
) {
|
||||||
|
// val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
|
||||||
|
|
||||||
|
if (categories.isEmpty()) {
|
||||||
|
LoadingScreen(isLoading, errorMessage = error)
|
||||||
|
} else {
|
||||||
|
/*ModalBottomSheetLayout(
|
||||||
|
sheetState = sheetState,
|
||||||
|
sheetContent = { *//*LibrarySheet()*//* }
|
||||||
|
) {*/
|
||||||
|
Column(Modifier.fillMaxWidth()) {
|
||||||
|
/*Toolbar(
|
||||||
|
title = {
|
||||||
|
val text = if (vm.showCategoryTabs) {
|
||||||
|
stringResource(R.string.library_label)
|
||||||
|
} else {
|
||||||
|
vm.selectedCategory?.visibleName.orEmpty()
|
||||||
|
}
|
||||||
|
Text(text)
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = { scope.launch { sheetState.show() }}) {
|
||||||
|
Icon(Icons.Rounded.FilterList, contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*/
|
||||||
|
Toolbar(
|
||||||
|
stringResource(MR.strings.location_library),
|
||||||
|
closable = false,
|
||||||
|
searchText = query,
|
||||||
|
search = updateQuery
|
||||||
|
)
|
||||||
|
LibraryTabs(
|
||||||
|
visible = true, // vm.showCategoryTabs,
|
||||||
|
categories = categories,
|
||||||
|
selectedPage = selectedCategoryIndex,
|
||||||
|
onPageChanged = onPageChanged
|
||||||
|
)
|
||||||
|
LibraryPager(
|
||||||
|
categories = categories,
|
||||||
|
displayMode = displayMode,
|
||||||
|
selectedPage = selectedCategoryIndex,
|
||||||
|
getLibraryForPage = getLibraryForPage,
|
||||||
|
onPageChanged = onPageChanged,
|
||||||
|
onClickManga = onClickManga,
|
||||||
|
onRemoveMangaClicked = onRemoveMangaClicked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.library.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.ScrollableTabRow
|
||||||
|
import androidx.compose.material.Tab
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.util.fastForEachIndexed
|
||||||
|
import ca.gosyer.data.models.Category
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LibraryTabs(
|
||||||
|
visible: Boolean,
|
||||||
|
categories: List<Category>,
|
||||||
|
selectedPage: Int,
|
||||||
|
onPageChanged: (Int) -> Unit
|
||||||
|
) {
|
||||||
|
if (categories.isEmpty()) return
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = visible,
|
||||||
|
enter = expandVertically(),
|
||||||
|
exit = shrinkVertically()
|
||||||
|
) {
|
||||||
|
ScrollableTabRow(
|
||||||
|
selectedTabIndex = selectedPage,
|
||||||
|
backgroundColor = MaterialTheme.colors.surface,
|
||||||
|
// contentColor = CustomColors.current.onBars,
|
||||||
|
edgePadding = 0.dp
|
||||||
|
) {
|
||||||
|
categories.fastForEachIndexed { i, category ->
|
||||||
|
Tab(
|
||||||
|
selected = selectedPage == i,
|
||||||
|
onClick = { onPageChanged(i) },
|
||||||
|
text = { Text(category.name) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.library
|
package ca.gosyer.ui.library.components
|
||||||
|
|
||||||
import androidx.compose.foundation.ContextMenuItem
|
import androidx.compose.foundation.ContextMenuItem
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.main
|
package ca.gosyer.ui.main
|
||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -26,45 +25,25 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ca.gosyer.ui.base.navigation.LocalMenuController
|
import ca.gosyer.ui.base.navigation.DisplayController
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
import ca.gosyer.ui.base.navigation.withDisplayController
|
||||||
import ca.gosyer.ui.base.navigation.withMenuController
|
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
|
||||||
import ca.gosyer.ui.downloads.DownloadsMenu
|
|
||||||
import ca.gosyer.ui.extensions.ExtensionsMenu
|
|
||||||
import ca.gosyer.ui.library.LibraryScreen
|
|
||||||
import ca.gosyer.ui.main.components.SideMenu
|
import ca.gosyer.ui.main.components.SideMenu
|
||||||
import ca.gosyer.ui.manga.MangaMenu
|
import ca.gosyer.uicore.vm.LocalViewModelFactory
|
||||||
import ca.gosyer.ui.reader.openReaderMenu
|
import cafe.adriel.voyager.navigator.CurrentScreen
|
||||||
import ca.gosyer.ui.settings.SettingsAdvancedScreen
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import ca.gosyer.ui.settings.SettingsAppearance
|
|
||||||
import ca.gosyer.ui.settings.SettingsBackupScreen
|
|
||||||
import ca.gosyer.ui.settings.SettingsBrowseScreen
|
|
||||||
import ca.gosyer.ui.settings.SettingsGeneralScreen
|
|
||||||
import ca.gosyer.ui.settings.SettingsLibraryScreen
|
|
||||||
import ca.gosyer.ui.settings.SettingsReaderScreen
|
|
||||||
import ca.gosyer.ui.settings.SettingsScreen
|
|
||||||
import ca.gosyer.ui.settings.SettingsServerScreen
|
|
||||||
import ca.gosyer.ui.sources.SourcesMenu
|
|
||||||
import ca.gosyer.ui.sources.settings.SourceSettingsMenu
|
|
||||||
import ca.gosyer.ui.updates.UpdatesMenu
|
|
||||||
import com.github.zsoltk.compose.router.Router
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.BundleScope
|
|
||||||
|
|
||||||
const val SIDE_MENU_EXPAND_DURATION = 500
|
const val SIDE_MENU_EXPAND_DURATION = 500
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainMenu(rootBundle: Bundle) {
|
fun MainMenu() {
|
||||||
val vm = viewModel<MainViewModel>()
|
val vmFactory = LocalViewModelFactory.current
|
||||||
|
val vm = remember { vmFactory.instantiate<MainViewModel>() }
|
||||||
Surface {
|
Surface {
|
||||||
Router("TopLevel", vm.startScreen.toRoute()) { backStack ->
|
Navigator(vm.startScreen.toScreen()) { navigator ->
|
||||||
val controller = remember {
|
val controller = remember { DisplayController() }
|
||||||
MenuController(backStack)
|
|
||||||
}
|
|
||||||
BoxWithConstraints {
|
BoxWithConstraints {
|
||||||
// if (maxWidth > 720.dp) {
|
// if (maxWidth > 720.dp) {
|
||||||
WideMainMenu(rootBundle, controller)
|
WideMainMenu(navigator, controller)
|
||||||
// } else {
|
// } else {
|
||||||
// SkinnyMainMenu(rootBundle, controller)
|
// SkinnyMainMenu(rootBundle, controller)
|
||||||
// }
|
// }
|
||||||
@@ -75,8 +54,8 @@ fun MainMenu(rootBundle: Bundle) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SkinnyMainMenu(
|
fun SkinnyMainMenu(
|
||||||
rootBundle: Bundle,
|
navigator: Navigator,
|
||||||
controller: MenuController
|
controller: DisplayController
|
||||||
) {
|
) {
|
||||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||||
LaunchedEffect(controller.sideMenuVisible) {
|
LaunchedEffect(controller.sideMenuVisible) {
|
||||||
@@ -104,21 +83,21 @@ fun SkinnyMainMenu(
|
|||||||
|
|
||||||
ModalDrawer(
|
ModalDrawer(
|
||||||
{
|
{
|
||||||
SideMenu(Modifier.fillMaxWidth(), controller)
|
SideMenu(Modifier.fillMaxWidth(), controller, navigator)
|
||||||
},
|
},
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
gesturesEnabled = drawerState.isOpen
|
gesturesEnabled = drawerState.isOpen
|
||||||
) {
|
) {
|
||||||
withMenuController(controller) {
|
withDisplayController(controller) {
|
||||||
MainWindow(Modifier, rootBundle)
|
MainWindow(Modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WideMainMenu(
|
fun WideMainMenu(
|
||||||
rootBundle: Bundle,
|
navigator: Navigator,
|
||||||
controller: MenuController
|
controller: DisplayController
|
||||||
) {
|
) {
|
||||||
Box {
|
Box {
|
||||||
val startPadding by animateDpAsState(
|
val startPadding by animateDpAsState(
|
||||||
@@ -130,72 +109,17 @@ fun WideMainMenu(
|
|||||||
animationSpec = tween(SIDE_MENU_EXPAND_DURATION)
|
animationSpec = tween(SIDE_MENU_EXPAND_DURATION)
|
||||||
)
|
)
|
||||||
if (startPadding != 0.dp) {
|
if (startPadding != 0.dp) {
|
||||||
SideMenu(Modifier.width(200.dp), controller)
|
SideMenu(Modifier.width(200.dp), controller, navigator)
|
||||||
}
|
}
|
||||||
withMenuController(controller) {
|
withDisplayController(controller) {
|
||||||
MainWindow(Modifier.padding(start = startPadding), rootBundle)
|
MainWindow(Modifier.padding(start = startPadding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainWindow(modifier: Modifier, rootBundle: Bundle) {
|
fun MainWindow(modifier: Modifier) {
|
||||||
Surface(Modifier.fillMaxSize().then(modifier)) {
|
Surface(Modifier.fillMaxSize() then modifier) {
|
||||||
val menuController = LocalMenuController.current!!
|
CurrentScreen()
|
||||||
BundleScope("K${menuController.backStack.lastIndex}", rootBundle, false) {
|
|
||||||
Crossfade(menuController.backStack.last()) { routing ->
|
|
||||||
when (routing) {
|
|
||||||
is Routes.Library -> LibraryScreen {
|
|
||||||
menuController.push(Routes.Manga(it))
|
|
||||||
}
|
|
||||||
is Routes.Updates -> UpdatesMenu(
|
|
||||||
openChapter = ::openReaderMenu,
|
|
||||||
openManga = { menuController.push(Routes.Manga(it)) }
|
|
||||||
)
|
|
||||||
is Routes.Sources -> SourcesMenu(
|
|
||||||
{
|
|
||||||
menuController.push(Routes.SourceSettings(it))
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
menuController.push(Routes.Manga(it))
|
|
||||||
}
|
|
||||||
is Routes.Extensions -> ExtensionsMenu()
|
|
||||||
is Routes.Manga -> MangaMenu(routing.mangaId)
|
|
||||||
is Routes.Downloads -> DownloadsMenu {
|
|
||||||
menuController.push(Routes.Manga(it))
|
|
||||||
}
|
|
||||||
|
|
||||||
is Routes.SourceSettings -> SourceSettingsMenu(routing.sourceId)
|
|
||||||
|
|
||||||
is Routes.Settings -> SettingsScreen(menuController)
|
|
||||||
is Routes.SettingsGeneral -> SettingsGeneralScreen(menuController)
|
|
||||||
is Routes.SettingsAppearance -> SettingsAppearance(menuController)
|
|
||||||
is Routes.SettingsServer -> SettingsServerScreen(menuController)
|
|
||||||
is Routes.SettingsLibrary -> SettingsLibraryScreen(menuController)
|
|
||||||
is Routes.SettingsReader -> SettingsReaderScreen(menuController)
|
|
||||||
/*is Route.SettingsDownloads -> SettingsDownloadsScreen(menuController)
|
|
||||||
is Route.SettingsTracking -> SettingsTrackingScreen(menuController)*/
|
|
||||||
is Routes.SettingsBrowse -> SettingsBrowseScreen(menuController)
|
|
||||||
is Routes.SettingsBackup -> SettingsBackupScreen(menuController)
|
|
||||||
/*is Route.SettingsSecurity -> SettingsSecurityScreen(menuController)
|
|
||||||
is Route.SettingsParentalControls -> SettingsParentalControlsScreen(menuController)*/
|
|
||||||
is Routes.SettingsAdvanced -> SettingsAdvancedScreen(menuController)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*Box(Modifier.padding(bottom = 32.dp).align(Alignment.BottomCenter)) {
|
|
||||||
val shape = RoundedCornerShape(50.dp)
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.width(200.dp)
|
|
||||||
.defaultMinSize(minHeight = 64.dp)
|
|
||||||
.shadow(4.dp, shape)
|
|
||||||
.background(SolidColor(Color.Gray), alpha = 0.2F)
|
|
||||||
.clip(shape),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text("Test text")
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ package ca.gosyer.ui.main
|
|||||||
|
|
||||||
import ca.gosyer.data.ui.UiPreferences
|
import ca.gosyer.data.ui.UiPreferences
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class MainViewModel @Inject constructor(
|
class MainViewModel @Inject constructor(
|
||||||
uiPreferences: UiPreferences
|
uiPreferences: UiPreferences
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
override val scope = MainScope()
|
||||||
|
|
||||||
val startScreen = uiPreferences.startScreen().get()
|
val startScreen = uiPreferences.startScreen().get()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,37 +7,14 @@
|
|||||||
package ca.gosyer.ui.main
|
package ca.gosyer.ui.main
|
||||||
|
|
||||||
import ca.gosyer.data.ui.model.StartScreen
|
import ca.gosyer.data.ui.model.StartScreen
|
||||||
|
import ca.gosyer.ui.extensions.ExtensionsScreen
|
||||||
|
import ca.gosyer.ui.library.LibraryScreen
|
||||||
|
import ca.gosyer.ui.sources.SourcesScreen
|
||||||
|
import ca.gosyer.ui.updates.UpdatesScreen
|
||||||
|
|
||||||
sealed class Routes {
|
fun StartScreen.toScreen() = when (this) {
|
||||||
object Library : Routes()
|
StartScreen.Library -> LibraryScreen()
|
||||||
object Updates : Routes()
|
StartScreen.Updates -> UpdatesScreen()
|
||||||
object Sources : Routes()
|
StartScreen.Sources -> SourcesScreen()
|
||||||
object Extensions : Routes()
|
StartScreen.Extensions -> ExtensionsScreen()
|
||||||
data class Manga(val mangaId: Long) : Routes()
|
|
||||||
object Downloads : Routes()
|
|
||||||
|
|
||||||
data class SourceSettings(val sourceId: Long) : Routes()
|
|
||||||
|
|
||||||
object Settings : Routes()
|
|
||||||
object SettingsGeneral : Routes()
|
|
||||||
object SettingsAppearance : Routes()
|
|
||||||
object SettingsLibrary : Routes()
|
|
||||||
object SettingsReader : Routes()
|
|
||||||
|
|
||||||
/*object SettingsDownloads : Route()
|
|
||||||
object SettingsTracking : Route()*/
|
|
||||||
object SettingsBrowse : Routes()
|
|
||||||
object SettingsBackup : Routes()
|
|
||||||
object SettingsServer : Routes()
|
|
||||||
|
|
||||||
/*object SettingsSecurity : Route()
|
|
||||||
object SettingsParentalControls : Route()*/
|
|
||||||
object SettingsAdvanced : Routes()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun StartScreen.toRoute() = when (this) {
|
|
||||||
StartScreen.Library -> Routes.Library
|
|
||||||
StartScreen.Updates -> Routes.Updates
|
|
||||||
StartScreen.Sources -> Routes.Sources
|
|
||||||
StartScreen.Extensions -> Routes.Extensions
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,28 +22,37 @@ import androidx.compose.material.icons.rounded.Store
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
|
import ca.gosyer.ui.downloads.DownloadsScreen
|
||||||
|
import ca.gosyer.ui.extensions.ExtensionsScreen
|
||||||
import ca.gosyer.ui.extensions.openExtensionsMenu
|
import ca.gosyer.ui.extensions.openExtensionsMenu
|
||||||
|
import ca.gosyer.ui.library.LibraryScreen
|
||||||
import ca.gosyer.ui.library.openLibraryMenu
|
import ca.gosyer.ui.library.openLibraryMenu
|
||||||
import ca.gosyer.ui.main.components.DownloadsExtraInfo
|
import ca.gosyer.ui.main.components.DownloadsExtraInfo
|
||||||
|
import ca.gosyer.ui.settings.SettingsScreen
|
||||||
|
import ca.gosyer.ui.sources.SourcesScreen
|
||||||
import ca.gosyer.ui.sources.openSourcesMenu
|
import ca.gosyer.ui.sources.openSourcesMenu
|
||||||
import com.github.zsoltk.compose.router.BackStack
|
import ca.gosyer.ui.updates.UpdatesScreen
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
enum class TopLevelMenus(
|
enum class TopLevelMenus(
|
||||||
val textKey: StringResource,
|
val textKey: StringResource,
|
||||||
val unselectedIcon: ImageVector,
|
val unselectedIcon: ImageVector,
|
||||||
val selectedIcon: ImageVector,
|
val selectedIcon: ImageVector,
|
||||||
val menu: Routes,
|
val screen: KClass<*>,
|
||||||
|
val createScreen: () -> Screen,
|
||||||
val top: Boolean,
|
val top: Boolean,
|
||||||
val openInNewWindow: () -> Unit = {},
|
val openInNewWindow: () -> Unit = {},
|
||||||
val extraInfo: (@Composable () -> Unit)? = null
|
val extraInfo: (@Composable () -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
Library(MR.strings.location_library, Icons.Outlined.Book, Icons.Rounded.Book, Routes.Library, true, ::openLibraryMenu),
|
Library(MR.strings.location_library, Icons.Outlined.Book, Icons.Rounded.Book, LibraryScreen::class, { LibraryScreen() }, true, ::openLibraryMenu),
|
||||||
Updates(MR.strings.location_updates, Icons.Outlined.NewReleases, Icons.Rounded.NewReleases, Routes.Updates, true, ::openLibraryMenu),
|
Updates(MR.strings.location_updates, Icons.Outlined.NewReleases, Icons.Rounded.NewReleases, UpdatesScreen::class, { UpdatesScreen() }, true, ::openLibraryMenu),
|
||||||
Sources(MR.strings.location_sources, Icons.Outlined.Explore, Icons.Rounded.Explore, Routes.Sources, true, ::openSourcesMenu),
|
Sources(MR.strings.location_sources, Icons.Outlined.Explore, Icons.Rounded.Explore, SourcesScreen::class, { SourcesScreen() }, true, ::openSourcesMenu),
|
||||||
Extensions(MR.strings.location_extensions, Icons.Outlined.Store, Icons.Rounded.Store, Routes.Extensions, true, ::openExtensionsMenu),
|
Extensions(MR.strings.location_extensions, Icons.Outlined.Store, Icons.Rounded.Store, ExtensionsScreen::class, { ExtensionsScreen() }, true, ::openExtensionsMenu),
|
||||||
Downloads(MR.strings.location_downloads, Icons.Outlined.Download, Icons.Rounded.Download, Routes.Downloads, false, extraInfo = { DownloadsExtraInfo() }),
|
Downloads(MR.strings.location_downloads, Icons.Outlined.Download, Icons.Rounded.Download, DownloadsScreen::class, { DownloadsScreen() }, false, extraInfo = { DownloadsExtraInfo() }),
|
||||||
Settings(MR.strings.location_settings, Icons.Outlined.Settings, Icons.Rounded.Settings, Routes.Settings, false);
|
Settings(MR.strings.location_settings, Icons.Outlined.Settings, Icons.Rounded.Settings, SettingsScreen::class, { SettingsScreen() }, false);
|
||||||
|
|
||||||
fun isSelected(backStack: BackStack<Routes>) = backStack.elements.first() == menu
|
fun isSelected(navigator: Navigator) = navigator.items.first()::class == screen
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,21 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.main
|
package ca.gosyer.ui.main.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.uicore.vm.LocalViewModelFactory
|
||||||
import ca.gosyer.ui.main.components.DebugOverlayViewModel
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DebugOverlay() {
|
fun DebugOverlay() {
|
||||||
val vm = viewModel<DebugOverlayViewModel>()
|
val vmFactory = LocalViewModelFactory.current
|
||||||
|
val vm = remember { vmFactory.instantiate<DebugOverlayViewModel>() }
|
||||||
val usedMemory by vm.usedMemoryFlow.collectAsState()
|
val usedMemory by vm.usedMemoryFlow.collectAsState()
|
||||||
Column {
|
Column {
|
||||||
Text("$usedMemory/${vm.maxMemory}", color = Color.White)
|
Text("$usedMemory/${vm.maxMemory}", color = Color.White)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
package ca.gosyer.ui.main.components
|
package ca.gosyer.ui.main.components
|
||||||
|
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -14,6 +15,8 @@ import me.tatarka.inject.annotations.Inject
|
|||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
class DebugOverlayViewModel @Inject constructor() : ViewModel() {
|
class DebugOverlayViewModel @Inject constructor() : ViewModel() {
|
||||||
|
override val scope = MainScope()
|
||||||
|
|
||||||
val runtime: Runtime = Runtime.getRuntime()
|
val runtime: Runtime = Runtime.getRuntime()
|
||||||
val maxMemory = runtime.maxMemory().formatSize()
|
val maxMemory = runtime.maxMemory().formatSize()
|
||||||
val usedMemoryFlow = MutableStateFlow(runtime.usedMemory().formatSize())
|
val usedMemoryFlow = MutableStateFlow(runtime.usedMemory().formatSize())
|
||||||
|
|||||||
@@ -15,17 +15,19 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ca.gosyer.data.base.WebsocketService
|
import ca.gosyer.data.base.WebsocketService
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.ui.downloads.DownloadsScreenViewModel
|
||||||
import ca.gosyer.ui.downloads.DownloadsMenuViewModel
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import ca.gosyer.uicore.vm.LocalViewModelFactory
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DownloadsExtraInfo() {
|
fun DownloadsExtraInfo() {
|
||||||
val vm = viewModel<DownloadsMenuViewModel>()
|
val vmFactory = LocalViewModelFactory.current
|
||||||
|
val vm = remember { vmFactory.instantiate<DownloadsScreenViewModel>(true) }
|
||||||
val status by vm.serviceStatus.collectAsState()
|
val status by vm.serviceStatus.collectAsState()
|
||||||
val list by vm.downloadQueue.collectAsState()
|
val list by vm.downloadQueue.collectAsState()
|
||||||
val text = when (status) {
|
val text = when (status) {
|
||||||
|
|||||||
@@ -29,11 +29,12 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
import ca.gosyer.ui.base.navigation.DisplayController
|
||||||
import ca.gosyer.ui.main.TopLevelMenus
|
import ca.gosyer.ui.main.TopLevelMenus
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SideMenu(modifier: Modifier, controller: MenuController) {
|
fun SideMenu(modifier: Modifier, controller: DisplayController, navigator: Navigator) {
|
||||||
Surface(modifier then Modifier.fillMaxHeight(), elevation = 2.dp) {
|
Surface(modifier then Modifier.fillMaxHeight(), elevation = 2.dp) {
|
||||||
Box(Modifier.fillMaxSize()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
Column(Modifier.fillMaxSize().padding(horizontal = 4.dp)) {
|
Column(Modifier.fillMaxSize().padding(horizontal = 4.dp)) {
|
||||||
@@ -54,19 +55,17 @@ fun SideMenu(modifier: Modifier, controller: MenuController) {
|
|||||||
Spacer(Modifier.height(20.dp))
|
Spacer(Modifier.height(20.dp))
|
||||||
remember { TopLevelMenus.values().filter(TopLevelMenus::top) }.forEach { topLevelMenu ->
|
remember { TopLevelMenus.values().filter(TopLevelMenus::top) }.forEach { topLevelMenu ->
|
||||||
SideMenuItem(
|
SideMenuItem(
|
||||||
topLevelMenu.isSelected(controller.backStack),
|
topLevelMenu.isSelected(navigator),
|
||||||
topLevelMenu,
|
topLevelMenu
|
||||||
controller::newRoot
|
) { navigator replaceAll it }
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Box(Modifier.fillMaxSize()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
Column(Modifier.align(Alignment.BottomStart).padding(bottom = 8.dp)) {
|
Column(Modifier.align(Alignment.BottomStart).padding(bottom = 8.dp)) {
|
||||||
remember { TopLevelMenus.values().filterNot(TopLevelMenus::top) }.forEach { topLevelMenu ->
|
remember { TopLevelMenus.values().filterNot(TopLevelMenus::top) }.forEach { topLevelMenu ->
|
||||||
SideMenuItem(
|
SideMenuItem(
|
||||||
topLevelMenu.isSelected(controller.backStack),
|
topLevelMenu.isSelected(navigator),
|
||||||
topLevelMenu,
|
topLevelMenu,
|
||||||
controller::newRoot
|
) { navigator replaceAll it }
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,17 +25,17 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ca.gosyer.ui.main.Routes
|
|
||||||
import ca.gosyer.ui.main.TopLevelMenus
|
import ca.gosyer.ui.main.TopLevelMenus
|
||||||
import ca.gosyer.uicore.components.combinedMouseClickable
|
import ca.gosyer.uicore.components.combinedMouseClickable
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SideMenuItem(selected: Boolean, topLevelMenu: TopLevelMenus, newRoot: (Routes) -> Unit) {
|
fun SideMenuItem(selected: Boolean, topLevelMenu: TopLevelMenus, newRoot: (Screen) -> Unit) {
|
||||||
SideMenuItem(
|
SideMenuItem(
|
||||||
selected,
|
selected,
|
||||||
stringResource(topLevelMenu.textKey),
|
stringResource(topLevelMenu.textKey),
|
||||||
topLevelMenu.menu,
|
topLevelMenu.createScreen,
|
||||||
topLevelMenu.selectedIcon,
|
topLevelMenu.selectedIcon,
|
||||||
topLevelMenu.unselectedIcon,
|
topLevelMenu.unselectedIcon,
|
||||||
topLevelMenu.openInNewWindow,
|
topLevelMenu.openInNewWindow,
|
||||||
@@ -48,12 +48,12 @@ fun SideMenuItem(selected: Boolean, topLevelMenu: TopLevelMenus, newRoot: (Route
|
|||||||
private fun SideMenuItem(
|
private fun SideMenuItem(
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
text: String,
|
text: String,
|
||||||
menu: Routes,
|
createScreen: () -> Screen,
|
||||||
selectedIcon: ImageVector,
|
selectedIcon: ImageVector,
|
||||||
unselectedIcon: ImageVector,
|
unselectedIcon: ImageVector,
|
||||||
onMiddleClick: () -> Unit,
|
onMiddleClick: () -> Unit,
|
||||||
extraInfo: (@Composable () -> Unit)? = null,
|
extraInfo: (@Composable () -> Unit)? = null,
|
||||||
onClick: (Routes) -> Unit
|
onClick: (Screen) -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
@@ -70,7 +70,7 @@ private fun SideMenuItem(
|
|||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
.height(40.dp)
|
.height(40.dp)
|
||||||
.combinedMouseClickable(
|
.combinedMouseClickable(
|
||||||
onClick = { onClick(menu) },
|
onClick = { onClick(createScreen()) },
|
||||||
onMiddleClick = { onMiddleClick() }
|
onMiddleClick = { onMiddleClick() }
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package ca.gosyer.ui.main.components
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.window.ApplicationScope
|
import androidx.compose.ui.window.ApplicationScope
|
||||||
import androidx.compose.ui.window.Notification
|
import androidx.compose.ui.window.Notification
|
||||||
@@ -15,13 +16,14 @@ import androidx.compose.ui.window.Tray
|
|||||||
import androidx.compose.ui.window.rememberTrayState
|
import androidx.compose.ui.window.rememberTrayState
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.uicore.vm.LocalViewModelFactory
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ApplicationScope.Tray(icon: Painter) {
|
fun ApplicationScope.Tray(icon: Painter) {
|
||||||
val vm = viewModel<TrayViewModel>()
|
val vmFactory = LocalViewModelFactory.current
|
||||||
|
val vm = remember { vmFactory.instantiate<TrayViewModel>() }
|
||||||
val trayState = rememberTrayState()
|
val trayState = rememberTrayState()
|
||||||
Tray(
|
Tray(
|
||||||
icon,
|
icon,
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ package ca.gosyer.ui.main.components
|
|||||||
|
|
||||||
import ca.gosyer.data.update.UpdateChecker
|
import ca.gosyer.data.update.UpdateChecker
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class TrayViewModel @Inject constructor(
|
class TrayViewModel @Inject constructor(
|
||||||
private val updateChecker: UpdateChecker
|
private val updateChecker: UpdateChecker
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
override val scope = MainScope()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
updateChecker.checkForUpdates()
|
updateChecker.checkForUpdates()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.manga
|
||||||
|
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
|
import ca.gosyer.ui.manga.components.MangaScreenContent
|
||||||
|
import ca.gosyer.ui.util.compose.ThemedWindow
|
||||||
|
import ca.gosyer.ui.util.lang.launchApplication
|
||||||
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun openMangaMenu(mangaId: Long) {
|
||||||
|
launchApplication {
|
||||||
|
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
||||||
|
Surface {
|
||||||
|
Navigator(remember { MangaScreen(mangaId) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MangaScreen(private val mangaId: Long) : Screen {
|
||||||
|
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel {
|
||||||
|
instantiate<MangaScreenViewModel>(MangaScreenViewModel.Params(mangaId))
|
||||||
|
}
|
||||||
|
|
||||||
|
MangaScreenContent(
|
||||||
|
isLoading = vm.isLoading.collectAsState().value,
|
||||||
|
manga = vm.manga.collectAsState().value,
|
||||||
|
chapters = vm.chapters.collectAsState().value,
|
||||||
|
dateTimeFormatter = vm.dateTimeFormatter.collectAsState().value,
|
||||||
|
categoriesExist = vm.categoriesExist.collectAsState().value,
|
||||||
|
chooseCategoriesFlow = vm.chooseCategoriesFlow,
|
||||||
|
addFavorite = vm::addFavorite,
|
||||||
|
setCategories = vm::setCategories,
|
||||||
|
toggleFavorite = vm::toggleFavorite,
|
||||||
|
refreshManga = vm::refreshManga,
|
||||||
|
toggleRead = vm::toggleRead,
|
||||||
|
toggleBookmarked = vm::toggleBookmarked,
|
||||||
|
markPreviousRead = vm::markPreviousRead,
|
||||||
|
downloadChapter = vm::downloadChapter,
|
||||||
|
deleteDownload = vm::deleteDownload,
|
||||||
|
stopDownloadingChapter = vm::stopDownloadingChapter,
|
||||||
|
loadChapters = vm::loadChapters,
|
||||||
|
loadManga = vm::loadManga
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ import java.time.format.DateTimeFormatter
|
|||||||
import java.time.format.FormatStyle
|
import java.time.format.FormatStyle
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class MangaMenuViewModel @Inject constructor(
|
class MangaScreenViewModel @Inject constructor(
|
||||||
private val mangaHandler: MangaInteractionHandler,
|
private val mangaHandler: MangaInteractionHandler,
|
||||||
private val chapterHandler: ChapterInteractionHandler,
|
private val chapterHandler: ChapterInteractionHandler,
|
||||||
private val categoryHandler: CategoryInteractionHandler,
|
private val categoryHandler: CategoryInteractionHandler,
|
||||||
@@ -226,7 +226,7 @@ class MangaMenuViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDispose() {
|
||||||
downloadService.removeWatch(params.mangaId)
|
downloadService.removeWatch(params.mangaId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.manga
|
package ca.gosyer.ui.manga.components
|
||||||
|
|
||||||
import androidx.compose.foundation.ContextMenuItem
|
import androidx.compose.foundation.ContextMenuItem
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -4,9 +4,8 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.manga
|
package ca.gosyer.ui.manga.components
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -32,13 +31,7 @@ import androidx.compose.material.Checkbox
|
|||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.rounded.Favorite
|
|
||||||
import androidx.compose.material.icons.rounded.FavoriteBorder
|
|
||||||
import androidx.compose.material.icons.rounded.Label
|
|
||||||
import androidx.compose.material.icons.rounded.Refresh
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -50,137 +43,12 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import ca.gosyer.data.models.Category
|
import ca.gosyer.data.models.Category
|
||||||
import ca.gosyer.data.models.Manga
|
import ca.gosyer.data.models.Manga
|
||||||
import ca.gosyer.i18n.MR
|
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
import ca.gosyer.ui.base.WindowDialog
|
||||||
import ca.gosyer.ui.base.navigation.LocalMenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.TextActionIcon
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
|
||||||
import ca.gosyer.ui.reader.openReaderMenu
|
|
||||||
import ca.gosyer.uicore.components.ErrorScreen
|
|
||||||
import ca.gosyer.uicore.components.LoadingScreen
|
|
||||||
import ca.gosyer.uicore.image.KamelImage
|
import ca.gosyer.uicore.image.KamelImage
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
|
||||||
import ca.gosyer.util.compose.ThemedWindow
|
|
||||||
import ca.gosyer.util.lang.launchApplication
|
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
|
||||||
import io.kamel.image.lazyPainterResource
|
import io.kamel.image.lazyPainterResource
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun openMangaMenu(mangaId: Long) {
|
|
||||||
launchApplication {
|
|
||||||
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
|
||||||
Surface {
|
|
||||||
MangaMenu(mangaId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MangaMenu(mangaId: Long, menuController: MenuController? = LocalMenuController.current) {
|
|
||||||
val vm = viewModel {
|
|
||||||
instantiate<MangaMenuViewModel>(MangaMenuViewModel.Params(mangaId))
|
|
||||||
}
|
|
||||||
val manga by vm.manga.collectAsState()
|
|
||||||
val chapters by vm.chapters.collectAsState()
|
|
||||||
val isLoading by vm.isLoading.collectAsState()
|
|
||||||
val dateTimeFormatter by vm.dateTimeFormatter.collectAsState()
|
|
||||||
val categoriesExist by vm.categoriesExist.collectAsState()
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
vm.chooseCategoriesFlow.collect { (availableCategories, usedCategories) ->
|
|
||||||
openCategorySelectDialog(availableCategories, usedCategories, vm::addFavorite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box {
|
|
||||||
Column {
|
|
||||||
Toolbar(
|
|
||||||
stringResource(MR.strings.location_manga),
|
|
||||||
menuController,
|
|
||||||
menuController != null,
|
|
||||||
actions = {
|
|
||||||
AnimatedVisibility(categoriesExist && manga?.inLibrary == true) {
|
|
||||||
TextActionIcon(
|
|
||||||
vm::setCategories,
|
|
||||||
stringResource(MR.strings.edit_categories),
|
|
||||||
Icons.Rounded.Label
|
|
||||||
)
|
|
||||||
}
|
|
||||||
TextActionIcon(
|
|
||||||
vm::toggleFavorite,
|
|
||||||
stringResource(if (manga?.inLibrary == true) MR.strings.action_remove_favorite else MR.strings.action_favorite),
|
|
||||||
if (manga?.inLibrary == true) {
|
|
||||||
Icons.Rounded.Favorite
|
|
||||||
} else {
|
|
||||||
Icons.Rounded.FavoriteBorder
|
|
||||||
},
|
|
||||||
manga != null
|
|
||||||
)
|
|
||||||
TextActionIcon(
|
|
||||||
vm::refreshManga,
|
|
||||||
stringResource(MR.strings.action_refresh_manga),
|
|
||||||
Icons.Rounded.Refresh,
|
|
||||||
!isLoading
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
manga.let { manga ->
|
|
||||||
if (manga != null) {
|
|
||||||
Box {
|
|
||||||
val state = rememberLazyListState()
|
|
||||||
LazyColumn(state = state) {
|
|
||||||
item {
|
|
||||||
MangaItem(manga)
|
|
||||||
}
|
|
||||||
if (chapters.isNotEmpty()) {
|
|
||||||
items(chapters) { chapter ->
|
|
||||||
ChapterItem(
|
|
||||||
chapter,
|
|
||||||
dateTimeFormatter::format,
|
|
||||||
onClick = { openReaderMenu(it, manga.id) },
|
|
||||||
toggleRead = vm::toggleRead,
|
|
||||||
toggleBookmarked = vm::toggleBookmarked,
|
|
||||||
markPreviousAsRead = vm::markPreviousRead,
|
|
||||||
onClickDownload = vm::downloadChapter,
|
|
||||||
onClickDeleteChapter = vm::deleteDownload,
|
|
||||||
onClickStopDownload = vm::stopDownloadingChapter
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (!isLoading) {
|
|
||||||
item {
|
|
||||||
ErrorScreen(
|
|
||||||
stringResource(MR.strings.no_chapters_found),
|
|
||||||
Modifier.height(400.dp).fillMaxWidth(),
|
|
||||||
retry = vm::loadChapters
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VerticalScrollbar(
|
|
||||||
modifier = Modifier.align(Alignment.CenterEnd)
|
|
||||||
.fillMaxHeight()
|
|
||||||
.padding(horizontal = 4.dp, vertical = 8.dp),
|
|
||||||
adapter = rememberScrollbarAdapter(state)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (!isLoading) {
|
|
||||||
ErrorScreen(stringResource(MR.strings.failed_manga_fetch), retry = vm::loadManga)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isLoading) {
|
|
||||||
LoadingScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaItem(manga: Manga) {
|
fun MangaItem(manga: Manga) {
|
||||||
BoxWithConstraints(Modifier.padding(8.dp)) {
|
BoxWithConstraints(Modifier.padding(8.dp)) {
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.manga.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.rememberScrollbarAdapter
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Favorite
|
||||||
|
import androidx.compose.material.icons.rounded.FavoriteBorder
|
||||||
|
import androidx.compose.material.icons.rounded.Label
|
||||||
|
import androidx.compose.material.icons.rounded.Refresh
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ca.gosyer.data.models.Category
|
||||||
|
import ca.gosyer.data.models.Manga
|
||||||
|
import ca.gosyer.i18n.MR
|
||||||
|
import ca.gosyer.ui.base.chapter.ChapterDownloadItem
|
||||||
|
import ca.gosyer.ui.base.navigation.TextActionIcon
|
||||||
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
|
import ca.gosyer.ui.reader.openReaderMenu
|
||||||
|
import ca.gosyer.uicore.components.ErrorScreen
|
||||||
|
import ca.gosyer.uicore.components.LoadingScreen
|
||||||
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MangaScreenContent(
|
||||||
|
isLoading: Boolean,
|
||||||
|
manga: Manga?,
|
||||||
|
chapters: List<ChapterDownloadItem>,
|
||||||
|
dateTimeFormatter: DateTimeFormatter,
|
||||||
|
categoriesExist: Boolean,
|
||||||
|
chooseCategoriesFlow: SharedFlow<Pair<List<Category>, List<Category>>>,
|
||||||
|
addFavorite: (List<Category>, List<Category>) -> Unit,
|
||||||
|
setCategories: () -> Unit,
|
||||||
|
toggleFavorite: () -> Unit,
|
||||||
|
refreshManga: () -> Unit,
|
||||||
|
toggleRead: (Int) -> Unit,
|
||||||
|
toggleBookmarked: (Int) -> Unit,
|
||||||
|
markPreviousRead: (Int) -> Unit,
|
||||||
|
downloadChapter: (Int) -> Unit,
|
||||||
|
deleteDownload: (Int) -> Unit,
|
||||||
|
stopDownloadingChapter: (Int) -> Unit,
|
||||||
|
loadChapters: () -> Unit,
|
||||||
|
loadManga: () -> Unit
|
||||||
|
) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
chooseCategoriesFlow.collect { (availableCategories, usedCategories) ->
|
||||||
|
openCategorySelectDialog(availableCategories, usedCategories, addFavorite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box {
|
||||||
|
Column {
|
||||||
|
Toolbar(
|
||||||
|
stringResource(MR.strings.location_manga),
|
||||||
|
actions = {
|
||||||
|
AnimatedVisibility(categoriesExist && manga?.inLibrary == true) {
|
||||||
|
TextActionIcon(
|
||||||
|
setCategories,
|
||||||
|
stringResource(MR.strings.edit_categories),
|
||||||
|
Icons.Rounded.Label
|
||||||
|
)
|
||||||
|
}
|
||||||
|
TextActionIcon(
|
||||||
|
toggleFavorite,
|
||||||
|
stringResource(if (manga?.inLibrary == true) MR.strings.action_remove_favorite else MR.strings.action_favorite),
|
||||||
|
if (manga?.inLibrary == true) {
|
||||||
|
Icons.Rounded.Favorite
|
||||||
|
} else {
|
||||||
|
Icons.Rounded.FavoriteBorder
|
||||||
|
},
|
||||||
|
manga != null
|
||||||
|
)
|
||||||
|
TextActionIcon(
|
||||||
|
refreshManga,
|
||||||
|
stringResource(MR.strings.action_refresh_manga),
|
||||||
|
Icons.Rounded.Refresh,
|
||||||
|
!isLoading
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
manga.let { manga ->
|
||||||
|
if (manga != null) {
|
||||||
|
Box {
|
||||||
|
val state = rememberLazyListState()
|
||||||
|
LazyColumn(state = state) {
|
||||||
|
item {
|
||||||
|
MangaItem(manga)
|
||||||
|
}
|
||||||
|
if (chapters.isNotEmpty()) {
|
||||||
|
items(chapters) { chapter ->
|
||||||
|
ChapterItem(
|
||||||
|
chapter,
|
||||||
|
dateTimeFormatter::format,
|
||||||
|
onClick = { openReaderMenu(it, manga.id) },
|
||||||
|
toggleRead = toggleRead,
|
||||||
|
toggleBookmarked = toggleBookmarked,
|
||||||
|
markPreviousAsRead = markPreviousRead,
|
||||||
|
onClickDownload = downloadChapter,
|
||||||
|
onClickDeleteChapter = deleteDownload,
|
||||||
|
onClickStopDownload = stopDownloadingChapter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (!isLoading) {
|
||||||
|
item {
|
||||||
|
ErrorScreen(
|
||||||
|
stringResource(MR.strings.no_chapters_found),
|
||||||
|
Modifier.height(400.dp).fillMaxWidth(),
|
||||||
|
retry = loadChapters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VerticalScrollbar(
|
||||||
|
modifier = Modifier.align(Alignment.CenterEnd)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(horizontal = 4.dp, vertical = 8.dp),
|
||||||
|
adapter = rememberScrollbarAdapter(state)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (!isLoading) {
|
||||||
|
ErrorScreen(stringResource(MR.strings.failed_manga_fetch), retry = loadManga)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isLoading) {
|
||||||
|
LoadingScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,13 +71,13 @@ import ca.gosyer.ui.reader.navigation.RightAndLeftNavigation
|
|||||||
import ca.gosyer.ui.reader.navigation.navigationClickable
|
import ca.gosyer.ui.reader.navigation.navigationClickable
|
||||||
import ca.gosyer.ui.reader.viewer.ContinuousReader
|
import ca.gosyer.ui.reader.viewer.ContinuousReader
|
||||||
import ca.gosyer.ui.reader.viewer.PagerReader
|
import ca.gosyer.ui.reader.viewer.PagerReader
|
||||||
|
import ca.gosyer.ui.util.compose.WindowGet
|
||||||
|
import ca.gosyer.ui.util.lang.launchApplication
|
||||||
import ca.gosyer.uicore.components.ErrorScreen
|
import ca.gosyer.uicore.components.ErrorScreen
|
||||||
import ca.gosyer.uicore.components.LoadingScreen
|
import ca.gosyer.uicore.components.LoadingScreen
|
||||||
import ca.gosyer.uicore.components.mangaAspectRatio
|
import ca.gosyer.uicore.components.mangaAspectRatio
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
|
||||||
import ca.gosyer.util.compose.WindowGet
|
|
||||||
import ca.gosyer.util.lang.launchApplication
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import ca.gosyer.uicore.vm.LocalViewModelFactory
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
@@ -156,9 +156,8 @@ fun ReaderMenu(
|
|||||||
mangaId: Long,
|
mangaId: Long,
|
||||||
hotkeyFlow: SharedFlow<KeyEvent>
|
hotkeyFlow: SharedFlow<KeyEvent>
|
||||||
) {
|
) {
|
||||||
val vm = viewModel {
|
val vmFactory = LocalViewModelFactory.current
|
||||||
instantiate<ReaderMenuViewModel>(ReaderMenuViewModel.Params(chapterIndex, mangaId))
|
val vm = remember { vmFactory.instantiate<ReaderMenuViewModel>(ReaderMenuViewModel.Params(chapterIndex, mangaId)) }
|
||||||
}
|
|
||||||
|
|
||||||
val state by vm.state.collectAsState()
|
val state by vm.state.collectAsState()
|
||||||
val previousChapter by vm.previousChapter.collectAsState()
|
val previousChapter by vm.previousChapter.collectAsState()
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import ca.gosyer.data.reader.ReaderPreferences
|
|||||||
import ca.gosyer.data.reader.model.Direction
|
import ca.gosyer.data.reader.model.Direction
|
||||||
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
||||||
import ca.gosyer.data.server.interactions.MangaInteractionHandler
|
import ca.gosyer.data.server.interactions.MangaInteractionHandler
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
|
||||||
import ca.gosyer.ui.reader.model.MoveTo
|
import ca.gosyer.ui.reader.model.MoveTo
|
||||||
import ca.gosyer.ui.reader.model.Navigation
|
import ca.gosyer.ui.reader.model.Navigation
|
||||||
import ca.gosyer.ui.reader.model.PageMove
|
import ca.gosyer.ui.reader.model.PageMove
|
||||||
@@ -25,6 +24,7 @@ import ca.gosyer.ui.reader.model.ReaderChapter
|
|||||||
import ca.gosyer.ui.reader.model.ReaderPage
|
import ca.gosyer.ui.reader.model.ReaderPage
|
||||||
import ca.gosyer.ui.reader.model.ViewerChapters
|
import ca.gosyer.ui.reader.model.ViewerChapters
|
||||||
import ca.gosyer.uicore.prefs.asStateIn
|
import ca.gosyer.uicore.prefs.asStateIn
|
||||||
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
@@ -279,7 +279,7 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDispose() {
|
||||||
viewerChapters.recycle()
|
viewerChapters.recycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import ca.gosyer.data.reader.ReaderPreferences
|
|||||||
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
||||||
import ca.gosyer.ui.reader.model.ReaderChapter
|
import ca.gosyer.ui.reader.model.ReaderChapter
|
||||||
import ca.gosyer.ui.reader.model.ReaderPage
|
import ca.gosyer.ui.reader.model.ReaderPage
|
||||||
import ca.gosyer.util.compose.toImageBitmap
|
import ca.gosyer.ui.util.compose.toImageBitmap
|
||||||
import io.github.kerubistan.kroki.coroutines.priorityChannel
|
import io.github.kerubistan.kroki.coroutines.priorityChannel
|
||||||
import io.ktor.client.features.onDownload
|
import io.ktor.client.features.onDownload
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import androidx.compose.ui.platform.debugInspectorInfo
|
|||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.unit.toSize
|
import androidx.compose.ui.unit.toSize
|
||||||
import ca.gosyer.ui.reader.model.Navigation
|
import ca.gosyer.ui.reader.model.Navigation
|
||||||
import ca.gosyer.util.compose.contains
|
import ca.gosyer.ui.util.compose.contains
|
||||||
|
|
||||||
fun Modifier.navigationClickable(
|
fun Modifier.navigationClickable(
|
||||||
navigation: ViewerNavigation,
|
navigation: ViewerNavigation,
|
||||||
|
|||||||
@@ -21,14 +21,29 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ca.gosyer.data.update.UpdatePreferences
|
import ca.gosyer.data.update.UpdatePreferences
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.SwitchPreference
|
import ca.gosyer.ui.base.prefs.SwitchPreference
|
||||||
|
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
|
||||||
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
|
class SettingsAdvancedScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<SettingsAdvancedViewModel>()
|
||||||
|
SettingsAdvancedScreenContent(
|
||||||
|
updatesEnabled = vm.updatesEnabled
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SettingsAdvancedViewModel @Inject constructor(
|
class SettingsAdvancedViewModel @Inject constructor(
|
||||||
updatePreferences: UpdatePreferences,
|
updatePreferences: UpdatePreferences,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -36,15 +51,16 @@ class SettingsAdvancedViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsAdvancedScreen(menuController: MenuController) {
|
fun SettingsAdvancedScreenContent(
|
||||||
val vm = viewModel<SettingsAdvancedViewModel>()
|
updatesEnabled: PreferenceMutableStateFlow<Boolean>
|
||||||
|
) {
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_advanced_screen), menuController, true)
|
Toolbar(stringResource(MR.strings.settings_advanced_screen))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
item {
|
item {
|
||||||
SwitchPreference(preference = vm.updatesEnabled, title = stringResource(MR.strings.update_checker))
|
SwitchPreference(preference = updatesEnabled, title = stringResource(MR.strings.update_checker))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VerticalScrollbar(
|
VerticalScrollbar(
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import androidx.compose.ui.unit.sp
|
|||||||
import ca.gosyer.data.ui.UiPreferences
|
import ca.gosyer.data.ui.UiPreferences
|
||||||
import ca.gosyer.data.ui.model.ThemeMode
|
import ca.gosyer.data.ui.model.ThemeMode
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.ChoicePreference
|
import ca.gosyer.ui.base.prefs.ChoicePreference
|
||||||
import ca.gosyer.ui.base.prefs.ColorPreference
|
import ca.gosyer.ui.base.prefs.ColorPreference
|
||||||
@@ -46,13 +45,33 @@ import ca.gosyer.ui.base.theme.AppColorsPreferenceState
|
|||||||
import ca.gosyer.ui.base.theme.asStateFlow
|
import ca.gosyer.ui.base.theme.asStateFlow
|
||||||
import ca.gosyer.ui.base.theme.getDarkColors
|
import ca.gosyer.ui.base.theme.getDarkColors
|
||||||
import ca.gosyer.ui.base.theme.getLightColors
|
import ca.gosyer.ui.base.theme.getLightColors
|
||||||
|
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
|
||||||
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import ca.gosyer.uicore.theme.Theme
|
import ca.gosyer.uicore.theme.Theme
|
||||||
import ca.gosyer.uicore.theme.themes
|
import ca.gosyer.uicore.theme.themes
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
|
class SettingsAppearanceScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<ThemesViewModel>()
|
||||||
|
SettingsAppearanceScreenContent(
|
||||||
|
activeColors = vm.getActiveColors(),
|
||||||
|
themeMode = vm.themeMode,
|
||||||
|
lightTheme = vm.lightTheme,
|
||||||
|
darkTheme = vm.darkTheme,
|
||||||
|
windowDecorations = vm.windowDecorations
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ThemesViewModel @Inject constructor(
|
class ThemesViewModel @Inject constructor(
|
||||||
private val uiPreferences: UiPreferences,
|
private val uiPreferences: UiPreferences,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -72,23 +91,26 @@ class ThemesViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsAppearance(menuController: MenuController) {
|
fun SettingsAppearanceScreenContent(
|
||||||
val vm = viewModel<ThemesViewModel>()
|
activeColors: AppColorsPreferenceState,
|
||||||
|
themeMode: PreferenceMutableStateFlow<ThemeMode>,
|
||||||
val activeColors = vm.getActiveColors()
|
lightTheme: PreferenceMutableStateFlow<Int>,
|
||||||
|
darkTheme: PreferenceMutableStateFlow<Int>,
|
||||||
|
windowDecorations: PreferenceMutableStateFlow<Boolean>
|
||||||
|
) {
|
||||||
val isLight = MaterialTheme.colors.isLight
|
val isLight = MaterialTheme.colors.isLight
|
||||||
val themesForCurrentMode = remember(isLight) {
|
val themesForCurrentMode = remember(isLight) {
|
||||||
themes.filter { it.colors.isLight == isLight }
|
themes.filter { it.colors.isLight == isLight }
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_appearance_screen), menuController, true)
|
Toolbar(stringResource(MR.strings.settings_appearance_screen))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
item {
|
item {
|
||||||
ChoicePreference(
|
ChoicePreference(
|
||||||
preference = vm.themeMode,
|
preference = themeMode,
|
||||||
choices = mapOf(
|
choices = mapOf(
|
||||||
ThemeMode.System to stringResource(MR.strings.theme_follow_system),
|
ThemeMode.System to stringResource(MR.strings.theme_follow_system),
|
||||||
ThemeMode.Light to stringResource(MR.strings.theme_light),
|
ThemeMode.Light to stringResource(MR.strings.theme_light),
|
||||||
@@ -107,7 +129,7 @@ fun SettingsAppearance(menuController: MenuController) {
|
|||||||
ThemeItem(
|
ThemeItem(
|
||||||
theme,
|
theme,
|
||||||
onClick = {
|
onClick = {
|
||||||
(if (isLight) vm.lightTheme else vm.darkTheme).value = it.id
|
(if (isLight) lightTheme else darkTheme).value = it.id
|
||||||
activeColors.primaryStateFlow.value = it.colors.primary
|
activeColors.primaryStateFlow.value = it.colors.primary
|
||||||
activeColors.secondaryStateFlow.value = it.colors.secondary
|
activeColors.secondaryStateFlow.value = it.colors.secondary
|
||||||
}
|
}
|
||||||
@@ -133,7 +155,7 @@ fun SettingsAppearance(menuController: MenuController) {
|
|||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
vm.windowDecorations,
|
windowDecorations,
|
||||||
stringResource(MR.strings.window_decorations),
|
stringResource(MR.strings.window_decorations),
|
||||||
stringResource(MR.strings.window_decorations_sub)
|
stringResource(MR.strings.window_decorations_sub)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import androidx.compose.material.icons.rounded.Warning
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -38,20 +37,23 @@ import ca.gosyer.core.logging.CKLogger
|
|||||||
import ca.gosyer.data.server.interactions.BackupInteractionHandler
|
import ca.gosyer.data.server.interactions.BackupInteractionHandler
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
import ca.gosyer.ui.base.WindowDialog
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.PreferenceRow
|
import ca.gosyer.ui.base.prefs.PreferenceRow
|
||||||
|
import ca.gosyer.ui.util.system.filePicker
|
||||||
|
import ca.gosyer.ui.util.system.fileSaver
|
||||||
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
import ca.gosyer.util.system.filePicker
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import ca.gosyer.util.system.fileSaver
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import io.ktor.client.features.onDownload
|
import io.ktor.client.features.onDownload
|
||||||
import io.ktor.client.features.onUpload
|
import io.ktor.client.features.onUpload
|
||||||
import io.ktor.http.isSuccess
|
import io.ktor.http.isSuccess
|
||||||
import io.ktor.utils.io.jvm.javaio.toInputStream
|
import io.ktor.utils.io.jvm.javaio.toInputStream
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -62,6 +64,29 @@ import okio.Path.Companion.toOkioPath
|
|||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.source
|
import okio.source
|
||||||
|
|
||||||
|
class SettingsBackupScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<SettingsBackupViewModel>()
|
||||||
|
SettingsBackupScreenContent(
|
||||||
|
restoring = vm.restoring.collectAsState().value,
|
||||||
|
restoringProgress = vm.restoringProgress.collectAsState().value,
|
||||||
|
restoreStatus = vm.restoreStatus.collectAsState().value,
|
||||||
|
creating = vm.creating.collectAsState().value,
|
||||||
|
creatingProgress = vm.creatingProgress.collectAsState().value,
|
||||||
|
creatingStatus = vm.creatingStatus.collectAsState().value,
|
||||||
|
missingSourceFlow = vm.missingSourceFlow,
|
||||||
|
createFlow = vm.createFlow,
|
||||||
|
restoreFile = vm::restoreFile,
|
||||||
|
restoreBackup = vm::restoreBackup,
|
||||||
|
stopRestore = vm::stopRestore,
|
||||||
|
exportBackup = vm::exportBackup
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SettingsBackupViewModel @Inject constructor(
|
class SettingsBackupViewModel @Inject constructor(
|
||||||
private val backupHandler: BackupInteractionHandler
|
private val backupHandler: BackupInteractionHandler
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -186,22 +211,28 @@ class SettingsBackupViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsBackupScreen(menuController: MenuController) {
|
private fun SettingsBackupScreenContent(
|
||||||
val vm = viewModel<SettingsBackupViewModel>()
|
restoring: Boolean,
|
||||||
val restoring by vm.restoring.collectAsState()
|
restoringProgress: Float?,
|
||||||
val restoringProgress by vm.restoringProgress.collectAsState()
|
restoreStatus: SettingsBackupViewModel.Status,
|
||||||
val restoreStatus by vm.restoreStatus.collectAsState()
|
creating: Boolean,
|
||||||
val creating by vm.creating.collectAsState()
|
creatingProgress: Float?,
|
||||||
val creatingProgress by vm.creatingProgress.collectAsState()
|
creatingStatus: SettingsBackupViewModel.Status,
|
||||||
val creatingStatus by vm.creatingStatus.collectAsState()
|
missingSourceFlow: SharedFlow<Pair<Path, List<String>>>,
|
||||||
|
createFlow: SharedFlow<Pair<String, (Path) -> Unit>>,
|
||||||
|
restoreFile: (Path?) -> Unit,
|
||||||
|
restoreBackup: (Path) -> Unit,
|
||||||
|
stopRestore: () -> Unit,
|
||||||
|
exportBackup: () -> Unit
|
||||||
|
) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
launch {
|
launch {
|
||||||
vm.missingSourceFlow.collect { (backup, sources) ->
|
missingSourceFlow.collect { (backup, sources) ->
|
||||||
openMissingSourcesDialog(sources, { vm.restoreBackup(backup) }, vm::stopRestore)
|
openMissingSourcesDialog(sources, { restoreBackup(backup) }, stopRestore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
vm.createFlow.collect { (filename, function) ->
|
createFlow.collect { (filename, function) ->
|
||||||
fileSaver(filename, "proto.gz") {
|
fileSaver(filename, "proto.gz") {
|
||||||
function(it.selectedFile.toOkioPath())
|
function(it.selectedFile.toOkioPath())
|
||||||
}
|
}
|
||||||
@@ -210,7 +241,7 @@ fun SettingsBackupScreen(menuController: MenuController) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_backup_screen), menuController, true)
|
Toolbar(stringResource(MR.strings.settings_backup_screen))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
@@ -223,7 +254,7 @@ fun SettingsBackupScreen(menuController: MenuController) {
|
|||||||
restoreStatus
|
restoreStatus
|
||||||
) {
|
) {
|
||||||
filePicker("gz") {
|
filePicker("gz") {
|
||||||
vm.restoreFile(it.selectedFile.toOkioPath())
|
restoreFile(it.selectedFile.toOkioPath())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PreferenceFile(
|
PreferenceFile(
|
||||||
@@ -232,7 +263,7 @@ fun SettingsBackupScreen(menuController: MenuController) {
|
|||||||
creating,
|
creating,
|
||||||
creatingProgress,
|
creatingProgress,
|
||||||
creatingStatus,
|
creatingStatus,
|
||||||
vm::exportBackup
|
exportBackup
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,25 @@ 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 ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
|
||||||
|
class SettingsBrowseScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
SettingsBrowseScreenContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsBrowseScreen(menuController: MenuController) {
|
fun SettingsBrowseScreenContent() {
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_browse_screen), menuController, true)
|
Toolbar(stringResource(MR.strings.settings_browse_screen))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
|
|||||||
@@ -20,14 +20,25 @@ 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 ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
|
||||||
|
class SettingsDownloadsScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
SettingsDownloadsScreenContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsDownloadsScreen(menuController: MenuController) {
|
fun SettingsDownloadsScreenContent() {
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_download_screen), menuController, true)
|
Toolbar(stringResource(MR.strings.settings_download_screen))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
|
|||||||
@@ -23,13 +23,16 @@ import androidx.compose.ui.unit.dp
|
|||||||
import ca.gosyer.data.ui.UiPreferences
|
import ca.gosyer.data.ui.UiPreferences
|
||||||
import ca.gosyer.data.ui.model.StartScreen
|
import ca.gosyer.data.ui.model.StartScreen
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.ChoicePreference
|
import ca.gosyer.ui.base.prefs.ChoicePreference
|
||||||
import ca.gosyer.ui.base.prefs.SwitchPreference
|
import ca.gosyer.ui.base.prefs.SwitchPreference
|
||||||
|
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
|
||||||
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
import okio.Path.Companion.toPath
|
import okio.Path.Companion.toPath
|
||||||
import okio.asResourceFileSystem
|
import okio.asResourceFileSystem
|
||||||
@@ -39,6 +42,24 @@ import java.time.format.DateTimeFormatter
|
|||||||
import java.time.format.FormatStyle
|
import java.time.format.FormatStyle
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
class SettingsGeneralScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<SettingsGeneralViewModel>()
|
||||||
|
SettingsGeneralScreenContent(
|
||||||
|
startScreen = vm.startScreen,
|
||||||
|
startScreenChoices = vm.getStartScreenChoices(),
|
||||||
|
confirmExit = vm.confirmExit,
|
||||||
|
language = vm.language,
|
||||||
|
languageChoices = vm.getLanguageChoices(),
|
||||||
|
dateFormat = vm.dateFormat,
|
||||||
|
dateFormatChoices = vm.getDateChoices()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SettingsGeneralViewModel @Inject constructor(
|
class SettingsGeneralViewModel @Inject constructor(
|
||||||
uiPreferences: UiPreferences,
|
uiPreferences: UiPreferences,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -97,23 +118,30 @@ class SettingsGeneralViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsGeneralScreen(menuController: MenuController) {
|
fun SettingsGeneralScreenContent(
|
||||||
val vm = viewModel<SettingsGeneralViewModel>()
|
startScreen: PreferenceMutableStateFlow<StartScreen>,
|
||||||
|
startScreenChoices: Map<StartScreen, String>,
|
||||||
|
confirmExit: PreferenceMutableStateFlow<Boolean>,
|
||||||
|
language: PreferenceMutableStateFlow<String>,
|
||||||
|
languageChoices: Map<String, String>,
|
||||||
|
dateFormat: PreferenceMutableStateFlow<String>,
|
||||||
|
dateFormatChoices: Map<String, String>
|
||||||
|
) {
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_general_screen), menuController, closable = true)
|
Toolbar(stringResource(MR.strings.settings_general_screen))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
item {
|
item {
|
||||||
ChoicePreference(
|
ChoicePreference(
|
||||||
preference = vm.startScreen,
|
preference = startScreen,
|
||||||
title = stringResource(MR.strings.start_screen),
|
title = stringResource(MR.strings.start_screen),
|
||||||
choices = vm.getStartScreenChoices()
|
choices = startScreenChoices
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
preference = vm.confirmExit,
|
preference = confirmExit,
|
||||||
title = stringResource(MR.strings.confirm_exit)
|
title = stringResource(MR.strings.confirm_exit)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -122,16 +150,16 @@ fun SettingsGeneralScreen(menuController: MenuController) {
|
|||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ChoicePreference(
|
ChoicePreference(
|
||||||
preference = vm.language,
|
preference = language,
|
||||||
title = stringResource(MR.strings.language),
|
title = stringResource(MR.strings.language),
|
||||||
choices = vm.getLanguageChoices(),
|
choices = languageChoices,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ChoicePreference(
|
ChoicePreference(
|
||||||
preference = vm.dateFormat,
|
preference = dateFormat,
|
||||||
title = stringResource(MR.strings.date_format),
|
title = stringResource(MR.strings.date_format),
|
||||||
choices = vm.getDateChoices()
|
choices = dateFormatChoices
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,19 +23,36 @@ import androidx.compose.ui.unit.dp
|
|||||||
import ca.gosyer.data.library.LibraryPreferences
|
import ca.gosyer.data.library.LibraryPreferences
|
||||||
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.PreferenceRow
|
import ca.gosyer.ui.base.prefs.PreferenceRow
|
||||||
import ca.gosyer.ui.base.prefs.SwitchPreference
|
import ca.gosyer.ui.base.prefs.SwitchPreference
|
||||||
|
import ca.gosyer.ui.categories.openCategoriesMenu
|
||||||
|
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
|
||||||
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
import ca.gosyer.ui.categories.openCategoriesMenu
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
|
class SettingsLibraryScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<SettingsLibraryViewModel>()
|
||||||
|
SettingsLibraryScreenContent(
|
||||||
|
showAllCategory = vm.showAllCategory,
|
||||||
|
refreshCategoryCount = vm::refreshCategoryCount,
|
||||||
|
categoriesSize = vm.categories.collectAsState().value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SettingsLibraryViewModel @Inject constructor(
|
class SettingsLibraryViewModel @Inject constructor(
|
||||||
libraryPreferences: LibraryPreferences,
|
libraryPreferences: LibraryPreferences,
|
||||||
private val categoryHandler: CategoryInteractionHandler
|
private val categoryHandler: CategoryInteractionHandler
|
||||||
@@ -57,25 +74,27 @@ class SettingsLibraryViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsLibraryScreen(menuController: MenuController) {
|
fun SettingsLibraryScreenContent(
|
||||||
val vm = viewModel<SettingsLibraryViewModel>()
|
showAllCategory: PreferenceMutableStateFlow<Boolean>,
|
||||||
|
refreshCategoryCount: () -> Unit,
|
||||||
|
categoriesSize: Int
|
||||||
|
) {
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_library_screen), menuController, true)
|
Toolbar(stringResource(MR.strings.settings_library_screen))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
preference = vm.showAllCategory,
|
preference = showAllCategory,
|
||||||
title = stringResource(MR.strings.show_all_category)
|
title = stringResource(MR.strings.show_all_category)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
stringResource(MR.strings.location_categories),
|
stringResource(MR.strings.location_categories),
|
||||||
onClick = { openCategoriesMenu(vm::refreshCategoryCount) },
|
onClick = { openCategoriesMenu(refreshCategoryCount) },
|
||||||
subtitle = vm.categories.collectAsState().value.toString()
|
subtitle = categoriesSize.toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,25 @@ 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 ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
|
||||||
|
class SettingsParentalControlsScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
SettingsParentalControlsScreenContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsParentalControlsScreen(menuController: MenuController) {
|
fun SettingsParentalControlsScreenContent() {
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_parental_control_screen), menuController, true)
|
Toolbar(stringResource(MR.strings.settings_parental_control_screen))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
|
|||||||
@@ -29,16 +29,18 @@ import ca.gosyer.data.reader.model.Direction
|
|||||||
import ca.gosyer.data.reader.model.ImageScale
|
import ca.gosyer.data.reader.model.ImageScale
|
||||||
import ca.gosyer.data.reader.model.NavigationMode
|
import ca.gosyer.data.reader.model.NavigationMode
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.ChoicePreference
|
import ca.gosyer.ui.base.prefs.ChoicePreference
|
||||||
import ca.gosyer.ui.base.prefs.ExpandablePreference
|
import ca.gosyer.ui.base.prefs.ExpandablePreference
|
||||||
import ca.gosyer.ui.base.prefs.SwitchPreference
|
import ca.gosyer.ui.base.prefs.SwitchPreference
|
||||||
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
|
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
|
||||||
import ca.gosyer.uicore.prefs.asStateIn
|
import ca.gosyer.uicore.prefs.asStateIn
|
||||||
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@@ -46,6 +48,25 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
|
class SettingsReaderScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<SettingsReaderViewModel>()
|
||||||
|
SettingsReaderScreenContent(
|
||||||
|
modes = vm.modes.collectAsState().value.associateWith { it },
|
||||||
|
selectedMode = vm.selectedMode,
|
||||||
|
modeSettings = vm.modeSettings.collectAsState().value,
|
||||||
|
directionChoices = vm.getDirectionChoices(),
|
||||||
|
paddingChoices = vm.getPaddingChoices(),
|
||||||
|
getMaxSizeChoices = vm::getMaxSizeChoices,
|
||||||
|
imageScaleChoices = vm.getImageScaleChoices(),
|
||||||
|
navigationModeChoices = vm.getNavigationModeChoices()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SettingsReaderViewModel @Inject constructor(
|
class SettingsReaderViewModel @Inject constructor(
|
||||||
readerPreferences: ReaderPreferences
|
readerPreferences: ReaderPreferences
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -125,18 +146,25 @@ data class ReaderModePreference(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsReaderScreen(menuController: MenuController) {
|
fun SettingsReaderScreenContent(
|
||||||
val vm = viewModel<SettingsReaderViewModel>()
|
modes: Map<String, String>,
|
||||||
val modeSettings by vm.modeSettings.collectAsState()
|
selectedMode: PreferenceMutableStateFlow<String>,
|
||||||
|
modeSettings: List<ReaderModePreference>,
|
||||||
|
directionChoices: Map<Direction, String>,
|
||||||
|
paddingChoices: Map<Int, String>,
|
||||||
|
getMaxSizeChoices: (Direction) -> Map<Int, String>,
|
||||||
|
imageScaleChoices: Map<ImageScale, String>,
|
||||||
|
navigationModeChoices: Map<NavigationMode, String>
|
||||||
|
) {
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_reader), menuController, true)
|
Toolbar(stringResource(MR.strings.settings_reader))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
item {
|
item {
|
||||||
ChoicePreference(
|
ChoicePreference(
|
||||||
vm.selectedMode,
|
selectedMode,
|
||||||
vm.modes.collectAsState().value.associateWith { it },
|
modes,
|
||||||
stringResource(MR.strings.reader_mode)
|
stringResource(MR.strings.reader_mode)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -148,7 +176,7 @@ fun SettingsReaderScreen(menuController: MenuController) {
|
|||||||
ExpandablePreference(it.mode) {
|
ExpandablePreference(it.mode) {
|
||||||
ChoicePreference(
|
ChoicePreference(
|
||||||
it.direction,
|
it.direction,
|
||||||
vm.getDirectionChoices(),
|
directionChoices,
|
||||||
stringResource(MR.strings.direction),
|
stringResource(MR.strings.direction),
|
||||||
enabled = !it.defaultMode
|
enabled = !it.defaultMode
|
||||||
)
|
)
|
||||||
@@ -162,7 +190,7 @@ fun SettingsReaderScreen(menuController: MenuController) {
|
|||||||
if (continuous) {
|
if (continuous) {
|
||||||
ChoicePreference(
|
ChoicePreference(
|
||||||
it.padding,
|
it.padding,
|
||||||
vm.getPaddingChoices(),
|
paddingChoices,
|
||||||
stringResource(MR.strings.page_padding)
|
stringResource(MR.strings.page_padding)
|
||||||
)
|
)
|
||||||
val direction by it.direction.collectAsState()
|
val direction by it.direction.collectAsState()
|
||||||
@@ -190,20 +218,20 @@ fun SettingsReaderScreen(menuController: MenuController) {
|
|||||||
}
|
}
|
||||||
ChoicePreference(
|
ChoicePreference(
|
||||||
it.maxSize,
|
it.maxSize,
|
||||||
vm.getMaxSizeChoices(direction),
|
getMaxSizeChoices(direction),
|
||||||
maxSizeTitle,
|
maxSizeTitle,
|
||||||
maxSizeSubtitle
|
maxSizeSubtitle
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ChoicePreference(
|
ChoicePreference(
|
||||||
it.imageScale,
|
it.imageScale,
|
||||||
vm.getImageScaleChoices(),
|
imageScaleChoices,
|
||||||
stringResource(MR.strings.image_scale)
|
stringResource(MR.strings.image_scale)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ChoicePreference(
|
ChoicePreference(
|
||||||
it.navigationMode,
|
it.navigationMode,
|
||||||
vm.getNavigationModeChoices(),
|
navigationModeChoices,
|
||||||
stringResource(MR.strings.navigation_mode)
|
stringResource(MR.strings.navigation_mode)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,14 +29,28 @@ 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 ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.PreferenceRow
|
import ca.gosyer.ui.base.prefs.PreferenceRow
|
||||||
import ca.gosyer.ui.main.Routes
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
|
||||||
|
class SettingsScreen : Screen {
|
||||||
|
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
SettingsScreenContent(navigator = LocalNavigator.currentOrThrow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(menuController: MenuController) {
|
fun SettingsScreenContent(navigator: Navigator) {
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.location_settings), closable = false)
|
Toolbar(stringResource(MR.strings.location_settings), closable = false)
|
||||||
Box {
|
Box {
|
||||||
@@ -46,84 +60,84 @@ fun SettingsScreen(menuController: MenuController) {
|
|||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title = stringResource(MR.strings.settings_general),
|
title = stringResource(MR.strings.settings_general),
|
||||||
icon = Icons.Rounded.Tune,
|
icon = Icons.Rounded.Tune,
|
||||||
onClick = { menuController.push(Routes.SettingsGeneral) }
|
onClick = { navigator push SettingsGeneralScreen() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title = stringResource(MR.strings.settings_appearance),
|
title = stringResource(MR.strings.settings_appearance),
|
||||||
icon = Icons.Rounded.Palette,
|
icon = Icons.Rounded.Palette,
|
||||||
onClick = { menuController.push(Routes.SettingsAppearance) }
|
onClick = { navigator push SettingsAppearanceScreen() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title = stringResource(MR.strings.settings_server),
|
title = stringResource(MR.strings.settings_server),
|
||||||
icon = Icons.Rounded.Computer,
|
icon = Icons.Rounded.Computer,
|
||||||
onClick = { menuController.push(Routes.SettingsServer) }
|
onClick = { navigator push SettingsServerScreen() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title = stringResource(MR.strings.settings_library),
|
title = stringResource(MR.strings.settings_library),
|
||||||
icon = Icons.Rounded.CollectionsBookmark,
|
icon = Icons.Rounded.CollectionsBookmark,
|
||||||
onClick = { menuController.push(Routes.SettingsLibrary) }
|
onClick = { navigator push SettingsLibraryScreen() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title = stringResource(MR.strings.settings_reader),
|
title = stringResource(MR.strings.settings_reader),
|
||||||
icon = Icons.Rounded.ChromeReaderMode,
|
icon = Icons.Rounded.ChromeReaderMode,
|
||||||
onClick = { menuController.push(Routes.SettingsReader) }
|
onClick = { navigator push SettingsReaderScreen() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/*item {
|
/*item {
|
||||||
Pref(
|
Pref(
|
||||||
title = stringResource(MR.strings.settings_download),
|
title = stringResource(MR.strings.settings_download),
|
||||||
icon = Icons.Rounded.GetApp,
|
icon = Icons.Rounded.GetApp,
|
||||||
onClick = { navController.push(Route.SettingsDownloads) }
|
onClick = { navigator push SettingsDownloadsScreen() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
Pref(
|
Pref(
|
||||||
title = stringResource(MR.strings.settings_tracking),
|
title = stringResource(MR.strings.settings_tracking),
|
||||||
icon = Icons.Rounded.Sync,
|
icon = Icons.Rounded.Sync,
|
||||||
onClick = { navController.push(Route.SettingsTracking) }
|
onClick = { navigator push SettingsTrackingScreen() }
|
||||||
)
|
)
|
||||||
}*/
|
}*/
|
||||||
item {
|
item {
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title = stringResource(MR.strings.settings_browse),
|
title = stringResource(MR.strings.settings_browse),
|
||||||
icon = Icons.Rounded.Explore,
|
icon = Icons.Rounded.Explore,
|
||||||
onClick = { menuController.push(Routes.SettingsBrowse) }
|
onClick = { navigator push SettingsBrowseScreen() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title = stringResource(MR.strings.settings_backup),
|
title = stringResource(MR.strings.settings_backup),
|
||||||
icon = Icons.Rounded.Backup,
|
icon = Icons.Rounded.Backup,
|
||||||
onClick = { menuController.push(Routes.SettingsBackup) }
|
onClick = { navigator push SettingsBackupScreen() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/*item {
|
/*item {
|
||||||
Pref(
|
Pref(
|
||||||
title = stringResource(MR.strings.settings_security),
|
title = stringResource(MR.strings.settings_security),
|
||||||
icon = Icons.Rounded.Security,
|
icon = Icons.Rounded.Security,
|
||||||
onClick = { navController.push(Route.SettingsSecurity) }
|
onClick = { navigator push SettingsSecurityScreen() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
Pref(
|
Pref(
|
||||||
title = stringResource(MR.strings.settings_parental_controls),
|
title = stringResource(MR.strings.settings_parental_controls),
|
||||||
icon = Icons.Rounded.PeopleOutline,
|
icon = Icons.Rounded.PeopleOutline,
|
||||||
onClick = { navController.push(Route.SettingsParentalControls) }
|
onClick = { navigator push SettingsParentalControlsScreen() }
|
||||||
)
|
)
|
||||||
}*/
|
}*/
|
||||||
item {
|
item {
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title = stringResource(MR.strings.settings_advanced),
|
title = stringResource(MR.strings.settings_advanced),
|
||||||
icon = Icons.Rounded.Code,
|
icon = Icons.Rounded.Code,
|
||||||
onClick = { menuController.push(Routes.SettingsAdvanced) }
|
onClick = { navigator push SettingsAdvancedScreen() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,25 @@ 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 ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
|
||||||
|
class SettingsSecurityScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
SettingsSecurityScreenContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsSecurityScreen(menuController: MenuController) {
|
fun SettingsSecurityScreenContent() {
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_security_screen), menuController, true)
|
Toolbar(stringResource(MR.strings.settings_security_screen))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
|
|||||||
@@ -34,23 +34,68 @@ import ca.gosyer.data.server.ServerService
|
|||||||
import ca.gosyer.data.server.model.Auth
|
import ca.gosyer.data.server.model.Auth
|
||||||
import ca.gosyer.data.server.model.Proxy
|
import ca.gosyer.data.server.model.Proxy
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.ChoicePreference
|
import ca.gosyer.ui.base.prefs.ChoicePreference
|
||||||
import ca.gosyer.ui.base.prefs.EditTextPreference
|
import ca.gosyer.ui.base.prefs.EditTextPreference
|
||||||
import ca.gosyer.ui.base.prefs.PreferenceRow
|
import ca.gosyer.ui.base.prefs.PreferenceRow
|
||||||
import ca.gosyer.ui.base.prefs.SwitchPreference
|
import ca.gosyer.ui.base.prefs.SwitchPreference
|
||||||
|
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
|
||||||
import ca.gosyer.uicore.prefs.asStateIn
|
import ca.gosyer.uicore.prefs.asStateIn
|
||||||
import ca.gosyer.uicore.prefs.asStringStateIn
|
import ca.gosyer.uicore.prefs.asStringStateIn
|
||||||
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
|
class SettingsServerScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<SettingsServerViewModel>()
|
||||||
|
SettingsServerScreenContent(
|
||||||
|
hostValue = vm.host.collectAsState().value,
|
||||||
|
basicAuthEnabledValue = vm.basicAuthEnabled.collectAsState().value,
|
||||||
|
proxyValue = vm.proxy.collectAsState().value,
|
||||||
|
authValue = vm.auth.collectAsState().value,
|
||||||
|
restartServer = vm::restartServer,
|
||||||
|
serverSettingChanged = vm::serverSettingChanged,
|
||||||
|
host = vm.host,
|
||||||
|
ip = vm.ip,
|
||||||
|
port = vm.port,
|
||||||
|
socksProxyEnabled = vm.socksProxyEnabled,
|
||||||
|
socksProxyHost = vm.socksProxyHost,
|
||||||
|
socksProxyPort = vm.socksProxyPort,
|
||||||
|
debugLogsEnabled = vm.debugLogsEnabled,
|
||||||
|
systemTrayEnabled = vm.systemTrayEnabled,
|
||||||
|
webUIEnabled = vm.webUIEnabled,
|
||||||
|
openInBrowserEnabled = vm.openInBrowserEnabled,
|
||||||
|
basicAuthEnabled = vm.basicAuthEnabled,
|
||||||
|
basicAuthUsername = vm.basicAuthUsername,
|
||||||
|
basicAuthPassword = vm.basicAuthPassword,
|
||||||
|
serverUrl = vm.serverUrl,
|
||||||
|
serverPort = vm.serverPort,
|
||||||
|
proxy = vm.proxy,
|
||||||
|
proxyChoices = vm.getProxyChoices(),
|
||||||
|
httpHost = vm.httpHost,
|
||||||
|
httpPort = vm.httpPort,
|
||||||
|
socksHost = vm.socksHost,
|
||||||
|
socksPort = vm.socksPort,
|
||||||
|
auth = vm.auth,
|
||||||
|
authChoices = vm.getAuthChoices(),
|
||||||
|
authUsername = vm.authUsername,
|
||||||
|
authPassword = vm.authPassword
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SettingsServerViewModel @Inject constructor(
|
class SettingsServerViewModel @Inject constructor(
|
||||||
serverPreferences: ServerPreferences,
|
serverPreferences: ServerPreferences,
|
||||||
serverHostPreferences: ServerHostPreferences,
|
serverHostPreferences: ServerHostPreferences,
|
||||||
@@ -136,26 +181,53 @@ class SettingsServerViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsServerScreen(menuController: MenuController) {
|
fun SettingsServerScreenContent(
|
||||||
val vm = viewModel<SettingsServerViewModel>()
|
hostValue: Boolean,
|
||||||
val host by vm.host.collectAsState()
|
basicAuthEnabledValue: Boolean,
|
||||||
val basicAuthEnabled by vm.basicAuthEnabled.collectAsState()
|
proxyValue: Proxy,
|
||||||
val proxy by vm.proxy.collectAsState()
|
authValue: Auth,
|
||||||
val auth by vm.auth.collectAsState()
|
restartServer: () -> Unit,
|
||||||
|
serverSettingChanged: () -> Unit,
|
||||||
|
host: PreferenceMutableStateFlow<Boolean>,
|
||||||
|
ip: PreferenceMutableStateFlow<String>,
|
||||||
|
port: PreferenceMutableStateFlow<String>,
|
||||||
|
socksProxyEnabled: PreferenceMutableStateFlow<Boolean>,
|
||||||
|
socksProxyHost: PreferenceMutableStateFlow<String>,
|
||||||
|
socksProxyPort: PreferenceMutableStateFlow<String>,
|
||||||
|
debugLogsEnabled: PreferenceMutableStateFlow<Boolean>,
|
||||||
|
systemTrayEnabled: PreferenceMutableStateFlow<Boolean>,
|
||||||
|
webUIEnabled: PreferenceMutableStateFlow<Boolean>,
|
||||||
|
openInBrowserEnabled: PreferenceMutableStateFlow<Boolean>,
|
||||||
|
basicAuthEnabled: PreferenceMutableStateFlow<Boolean>,
|
||||||
|
basicAuthUsername: PreferenceMutableStateFlow<String>,
|
||||||
|
basicAuthPassword: PreferenceMutableStateFlow<String>,
|
||||||
|
serverUrl: PreferenceMutableStateFlow<String>,
|
||||||
|
serverPort: PreferenceMutableStateFlow<String>,
|
||||||
|
proxy: PreferenceMutableStateFlow<Proxy>,
|
||||||
|
proxyChoices: Map<Proxy, String>,
|
||||||
|
httpHost: PreferenceMutableStateFlow<String>,
|
||||||
|
httpPort: PreferenceMutableStateFlow<String>,
|
||||||
|
socksHost: PreferenceMutableStateFlow<String>,
|
||||||
|
socksPort: PreferenceMutableStateFlow<String>,
|
||||||
|
auth: PreferenceMutableStateFlow<Auth>,
|
||||||
|
authChoices: Map<Auth, String>,
|
||||||
|
authUsername: PreferenceMutableStateFlow<String>,
|
||||||
|
authPassword: PreferenceMutableStateFlow<String>
|
||||||
|
) {
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
vm.restartServer()
|
restartServer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_server_screen), menuController, true)
|
Toolbar(stringResource(MR.strings.settings_server_screen))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
item {
|
item {
|
||||||
SwitchPreference(preference = vm.host, title = stringResource(MR.strings.host_server))
|
SwitchPreference(preference = host, title = stringResource(MR.strings.host_server))
|
||||||
}
|
}
|
||||||
if (host) {
|
if (hostValue) {
|
||||||
item {
|
item {
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
stringResource(MR.strings.host_settings),
|
stringResource(MR.strings.host_settings),
|
||||||
@@ -164,105 +236,105 @@ fun SettingsServerScreen(menuController: MenuController) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
val ip by vm.ip.collectAsState()
|
val ipValue by ip.collectAsState()
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
preference = vm.ip,
|
preference = ip,
|
||||||
title = stringResource(MR.strings.host_ip),
|
title = stringResource(MR.strings.host_ip),
|
||||||
subtitle = stringResource(MR.strings.host_ip_sub, ip),
|
subtitle = stringResource(MR.strings.host_ip_sub, ipValue),
|
||||||
changeListener = vm::serverSettingChanged
|
changeListener = serverSettingChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
val port by vm.port.collectAsState()
|
val portValue by port.collectAsState()
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
preference = vm.port,
|
preference = port,
|
||||||
title = stringResource(MR.strings.host_port),
|
title = stringResource(MR.strings.host_port),
|
||||||
subtitle = stringResource(MR.strings.host_port_sub, port),
|
subtitle = stringResource(MR.strings.host_port_sub, portValue),
|
||||||
changeListener = vm::serverSettingChanged
|
changeListener = serverSettingChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
preference = vm.socksProxyEnabled,
|
preference = socksProxyEnabled,
|
||||||
title = stringResource(MR.strings.host_socks_enabled),
|
title = stringResource(MR.strings.host_socks_enabled),
|
||||||
changeListener = vm::serverSettingChanged
|
changeListener = serverSettingChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
val proxyHost by vm.socksProxyHost.collectAsState()
|
val proxyHost by socksProxyHost.collectAsState()
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
preference = vm.socksProxyHost,
|
preference = socksProxyHost,
|
||||||
title = stringResource(MR.strings.host_socks_host),
|
title = stringResource(MR.strings.host_socks_host),
|
||||||
subtitle = stringResource(MR.strings.host_socks_host_sub, proxyHost),
|
subtitle = stringResource(MR.strings.host_socks_host_sub, proxyHost),
|
||||||
changeListener = vm::serverSettingChanged
|
changeListener = serverSettingChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
val proxyPort by vm.socksProxyPort.collectAsState()
|
val proxyPort by socksProxyPort.collectAsState()
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
preference = vm.socksProxyPort,
|
preference = socksProxyPort,
|
||||||
title = stringResource(MR.strings.host_socks_port),
|
title = stringResource(MR.strings.host_socks_port),
|
||||||
subtitle = stringResource(MR.strings.host_socks_port_sub, proxyPort),
|
subtitle = stringResource(MR.strings.host_socks_port_sub, proxyPort),
|
||||||
changeListener = vm::serverSettingChanged
|
changeListener = serverSettingChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
preference = vm.debugLogsEnabled,
|
preference = debugLogsEnabled,
|
||||||
title = stringResource(MR.strings.host_debug_logging),
|
title = stringResource(MR.strings.host_debug_logging),
|
||||||
subtitle = stringResource(MR.strings.host_debug_logging_sub),
|
subtitle = stringResource(MR.strings.host_debug_logging_sub),
|
||||||
changeListener = vm::serverSettingChanged
|
changeListener = serverSettingChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
preference = vm.systemTrayEnabled,
|
preference = systemTrayEnabled,
|
||||||
title = stringResource(MR.strings.host_system_tray),
|
title = stringResource(MR.strings.host_system_tray),
|
||||||
subtitle = stringResource(MR.strings.host_system_tray_sub),
|
subtitle = stringResource(MR.strings.host_system_tray_sub),
|
||||||
changeListener = vm::serverSettingChanged
|
changeListener = serverSettingChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
preference = vm.webUIEnabled,
|
preference = webUIEnabled,
|
||||||
title = stringResource(MR.strings.host_webui),
|
title = stringResource(MR.strings.host_webui),
|
||||||
subtitle = stringResource(MR.strings.host_webui_sub),
|
subtitle = stringResource(MR.strings.host_webui_sub),
|
||||||
changeListener = vm::serverSettingChanged
|
changeListener = serverSettingChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
val webUIEnabled by vm.webUIEnabled.collectAsState()
|
val webUIEnabledValue by webUIEnabled.collectAsState()
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
preference = vm.openInBrowserEnabled,
|
preference = openInBrowserEnabled,
|
||||||
title = stringResource(MR.strings.host_open_in_browser),
|
title = stringResource(MR.strings.host_open_in_browser),
|
||||||
subtitle = stringResource(MR.strings.host_open_in_browser_sub),
|
subtitle = stringResource(MR.strings.host_open_in_browser_sub),
|
||||||
changeListener = vm::serverSettingChanged,
|
changeListener = serverSettingChanged,
|
||||||
enabled = webUIEnabled
|
enabled = webUIEnabledValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
preference = vm.basicAuthEnabled,
|
preference = basicAuthEnabled,
|
||||||
title = stringResource(MR.strings.basic_auth),
|
title = stringResource(MR.strings.basic_auth),
|
||||||
subtitle = stringResource(MR.strings.host_basic_auth_sub),
|
subtitle = stringResource(MR.strings.host_basic_auth_sub),
|
||||||
changeListener = vm::serverSettingChanged
|
changeListener = serverSettingChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
preference = vm.basicAuthUsername,
|
preference = basicAuthUsername,
|
||||||
title = stringResource(MR.strings.host_basic_auth_username),
|
title = stringResource(MR.strings.host_basic_auth_username),
|
||||||
changeListener = vm::serverSettingChanged,
|
changeListener = serverSettingChanged,
|
||||||
enabled = basicAuthEnabled
|
enabled = basicAuthEnabledValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
preference = vm.basicAuthPassword,
|
preference = basicAuthPassword,
|
||||||
title = stringResource(MR.strings.host_basic_auth_password),
|
title = stringResource(MR.strings.host_basic_auth_password),
|
||||||
changeListener = vm::serverSettingChanged,
|
changeListener = serverSettingChanged,
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
enabled = basicAuthEnabled
|
enabled = basicAuthEnabledValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,16 +343,16 @@ fun SettingsServerScreen(menuController: MenuController) {
|
|||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
vm.serverUrl,
|
serverUrl,
|
||||||
stringResource(MR.strings.server_url),
|
stringResource(MR.strings.server_url),
|
||||||
subtitle = vm.serverUrl.collectAsState().value
|
subtitle = serverUrl.collectAsState().value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
vm.serverPort,
|
serverPort,
|
||||||
stringResource(MR.strings.server_port),
|
stringResource(MR.strings.server_port),
|
||||||
subtitle = vm.serverPort.collectAsState().value
|
subtitle = serverPort.collectAsState().value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,53 +364,53 @@ fun SettingsServerScreen(menuController: MenuController) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ChoicePreference(vm.proxy, vm.getProxyChoices(), stringResource(MR.strings.server_proxy))
|
ChoicePreference(proxy, proxyChoices, stringResource(MR.strings.server_proxy))
|
||||||
}
|
}
|
||||||
when (proxy) {
|
when (proxyValue) {
|
||||||
Proxy.NO_PROXY -> Unit
|
Proxy.NO_PROXY -> Unit
|
||||||
Proxy.HTTP_PROXY -> {
|
Proxy.HTTP_PROXY -> {
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
vm.httpHost,
|
httpHost,
|
||||||
stringResource(MR.strings.http_proxy),
|
stringResource(MR.strings.http_proxy),
|
||||||
vm.httpHost.collectAsState().value
|
httpHost.collectAsState().value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
vm.httpPort,
|
httpPort,
|
||||||
stringResource(MR.strings.http_port),
|
stringResource(MR.strings.http_port),
|
||||||
vm.httpPort.collectAsState().value
|
httpPort.collectAsState().value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Proxy.SOCKS_PROXY -> {
|
Proxy.SOCKS_PROXY -> {
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
vm.socksHost,
|
socksHost,
|
||||||
stringResource(MR.strings.socks_proxy),
|
stringResource(MR.strings.socks_proxy),
|
||||||
vm.socksHost.collectAsState().value
|
socksHost.collectAsState().value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
vm.socksPort,
|
socksPort,
|
||||||
stringResource(MR.strings.socks_port),
|
stringResource(MR.strings.socks_port),
|
||||||
vm.socksPort.collectAsState().value
|
socksPort.collectAsState().value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ChoicePreference(vm.auth, vm.getAuthChoices(), stringResource(MR.strings.authentication))
|
ChoicePreference(auth, authChoices, stringResource(MR.strings.authentication))
|
||||||
}
|
}
|
||||||
if (auth != Auth.NONE) {
|
if (authValue != Auth.NONE) {
|
||||||
item {
|
item {
|
||||||
EditTextPreference(vm.authUsername, stringResource(MR.strings.auth_username))
|
EditTextPreference(authUsername, stringResource(MR.strings.auth_username))
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
vm.authPassword,
|
authPassword,
|
||||||
stringResource(MR.strings.auth_password),
|
stringResource(MR.strings.auth_password),
|
||||||
visualTransformation = PasswordVisualTransformation()
|
visualTransformation = PasswordVisualTransformation()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,14 +20,25 @@ 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 ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
|
||||||
|
class SettingsTrackingScreen : Screen {
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
SettingsTrackingScreenContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsTrackingScreen(menuController: MenuController) {
|
fun SettingsTrackingScreenContent() {
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.settings_tracking_screen), menuController, true)
|
Toolbar(stringResource(MR.strings.settings_tracking_screen))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ca.gosyer.ui.sources
|
|
||||||
|
|
||||||
import ca.gosyer.core.logging.CKLogger
|
|
||||||
import ca.gosyer.data.models.Source
|
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.drop
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import me.tatarka.inject.annotations.Inject
|
|
||||||
|
|
||||||
class SourcesMenuViewModel @Inject constructor(
|
|
||||||
private val bundle: Bundle
|
|
||||||
) : ViewModel() {
|
|
||||||
private val _sourceTabs = MutableStateFlow<List<Source?>>(listOf(null))
|
|
||||||
val sourceTabs = _sourceTabs.asStateFlow()
|
|
||||||
|
|
||||||
private val _selectedSourceTab = MutableStateFlow<Source?>(null)
|
|
||||||
val selectedSourceTab = _selectedSourceTab.asStateFlow()
|
|
||||||
|
|
||||||
init {
|
|
||||||
_sourceTabs.drop(1)
|
|
||||||
.onEach { sources ->
|
|
||||||
bundle.putLongArray(SOURCE_TABS_KEY, sources.mapNotNull { it?.id }.toLongArray())
|
|
||||||
}
|
|
||||||
.launchIn(scope)
|
|
||||||
|
|
||||||
_selectedSourceTab.drop(1)
|
|
||||||
.onEach {
|
|
||||||
if (it != null) {
|
|
||||||
bundle.putLong(SELECTED_SOURCE_TAB, it.id)
|
|
||||||
} else {
|
|
||||||
bundle.remove(SELECTED_SOURCE_TAB)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.launchIn(scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun selectTab(source: Source?) {
|
|
||||||
_selectedSourceTab.value = source
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addTab(source: Source) {
|
|
||||||
if (source !in _sourceTabs.value) {
|
|
||||||
_sourceTabs.value += source
|
|
||||||
}
|
|
||||||
selectTab(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun closeTab(source: Source) {
|
|
||||||
_sourceTabs.value -= source
|
|
||||||
if (selectedSourceTab.value?.id == source.id) {
|
|
||||||
_selectedSourceTab.value = null
|
|
||||||
}
|
|
||||||
bundle.remove(source.id.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setLoadedSources(sources: List<Source>) {
|
|
||||||
val sourceTabs = bundle.getLongArray(SOURCE_TABS_KEY)
|
|
||||||
if (sourceTabs != null) {
|
|
||||||
_sourceTabs.value = listOf(null) + sourceTabs.toList()
|
|
||||||
.mapNotNull { sourceId ->
|
|
||||||
sources.find { it.id == sourceId }
|
|
||||||
}
|
|
||||||
_selectedSourceTab.value = bundle.getLong(SELECTED_SOURCE_TAB, -1).let { id ->
|
|
||||||
if (id != -1L) {
|
|
||||||
sources.find { it.id == id }
|
|
||||||
} else null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object : CKLogger({}) {
|
|
||||||
const val SOURCE_TABS_KEY = "source_tabs"
|
|
||||||
const val SELECTED_SOURCE_TAB = "selected_tab"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.sources
|
||||||
|
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
|
import ca.gosyer.ui.sources.components.SourcesMenu
|
||||||
|
import ca.gosyer.ui.util.compose.ThemedWindow
|
||||||
|
import ca.gosyer.ui.util.lang.launchApplication
|
||||||
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun openSourcesMenu() {
|
||||||
|
launchApplication {
|
||||||
|
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
||||||
|
Surface {
|
||||||
|
Navigator(remember { SourcesScreen() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SourcesScreen : Screen {
|
||||||
|
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<SourcesScreenViewModel>()
|
||||||
|
SourcesMenu(
|
||||||
|
sourceTabs = vm.sourceTabs.collectAsState().value,
|
||||||
|
selectedSourceTab = vm.selectedSourceTab.collectAsState().value,
|
||||||
|
selectTab = vm::selectTab,
|
||||||
|
closeTab = vm::closeTab
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.sources
|
||||||
|
|
||||||
|
import ca.gosyer.data.models.Source
|
||||||
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
|
class SourcesScreenViewModel @Inject constructor() : ViewModel() {
|
||||||
|
private val _sourceTabs = MutableStateFlow<List<Source?>>(listOf(null))
|
||||||
|
val sourceTabs = _sourceTabs.asStateFlow()
|
||||||
|
|
||||||
|
private val _selectedSourceTab = MutableStateFlow<Source?>(null)
|
||||||
|
val selectedSourceTab = _selectedSourceTab.asStateFlow()
|
||||||
|
|
||||||
|
fun selectTab(source: Source?) {
|
||||||
|
if (source !in _sourceTabs.value) {
|
||||||
|
_sourceTabs.value += source
|
||||||
|
}
|
||||||
|
_selectedSourceTab.value = source
|
||||||
|
}
|
||||||
|
|
||||||
|
fun closeTab(source: Source) {
|
||||||
|
_sourceTabs.value -= source
|
||||||
|
if (selectedSourceTab.value?.id == source.id) {
|
||||||
|
_selectedSourceTab.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.sources.browse
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import ca.gosyer.data.models.Source
|
||||||
|
import ca.gosyer.ui.manga.MangaScreen
|
||||||
|
import ca.gosyer.ui.sources.browse.components.SourceScreenContent
|
||||||
|
import ca.gosyer.ui.sources.browse.filter.SourceFiltersViewModel
|
||||||
|
import ca.gosyer.ui.sources.components.LocalSourcesNavigator
|
||||||
|
import ca.gosyer.ui.sources.settings.SourceSettingsScreen
|
||||||
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
|
||||||
|
class SourceScreen(val source: Source) : Screen {
|
||||||
|
|
||||||
|
override val key: ScreenKey = source.id.toString()
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val sourceVM = viewModel {
|
||||||
|
instantiate<SourceScreenViewModel>(SourceScreenViewModel.Params(source))
|
||||||
|
}
|
||||||
|
val filterVM = viewModel {
|
||||||
|
instantiate<SourceFiltersViewModel>(SourceFiltersViewModel.Params(source.id))
|
||||||
|
}
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val sourcesNavigator = LocalSourcesNavigator.current
|
||||||
|
SourceScreenContent(
|
||||||
|
source = source,
|
||||||
|
onMangaClick = { navigator push MangaScreen(it) },
|
||||||
|
onCloseSourceTabClick = sourcesNavigator::remove,
|
||||||
|
onSourceSettingsClick = { navigator push SourceSettingsScreen(it) },
|
||||||
|
mangas = sourceVM.mangas.collectAsState().value,
|
||||||
|
hasNextPage = sourceVM.hasNextPage.collectAsState().value,
|
||||||
|
loading = sourceVM.loading.collectAsState().value,
|
||||||
|
isLatest = sourceVM.isLatest.collectAsState().value,
|
||||||
|
showLatestButton = source.supportsLatest,
|
||||||
|
sourceSearchQuery = sourceVM.sourceSearchQuery.collectAsState().value,
|
||||||
|
enableLatest = sourceVM::enableLatest,
|
||||||
|
search = sourceVM::search,
|
||||||
|
submitSearch = sourceVM::submitSearch,
|
||||||
|
setMode = sourceVM::setMode,
|
||||||
|
loadNextPage = sourceVM::loadNextPage,
|
||||||
|
setUsingFilters = sourceVM::setUsingFilters,
|
||||||
|
// FilterVM
|
||||||
|
filters = filterVM.filters.collectAsState().value,
|
||||||
|
showingFilters = filterVM.showingFilters.collectAsState().value,
|
||||||
|
showFilterButton = filterVM.filterButtonEnabled.collectAsState().value,
|
||||||
|
setShowingFilters = filterVM::showingFilters,
|
||||||
|
resetFiltersClicked = {
|
||||||
|
sourceVM.setUsingFilters(false)
|
||||||
|
filterVM.resetFilters()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.sources.components
|
package ca.gosyer.ui.sources.browse
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
import ca.gosyer.core.lang.throwIfCancellation
|
||||||
import ca.gosyer.data.models.Manga
|
import ca.gosyer.data.models.Manga
|
||||||
@@ -12,11 +12,6 @@ import ca.gosyer.data.models.MangaPage
|
|||||||
import ca.gosyer.data.models.Source
|
import ca.gosyer.data.models.Source
|
||||||
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.util.compose.saveBooleanInBundle
|
|
||||||
import ca.gosyer.util.compose.saveIntInBundle
|
|
||||||
import ca.gosyer.util.compose.saveObjectInBundle
|
|
||||||
import ca.gosyer.util.compose.saveStringInBundle
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -24,7 +19,6 @@ import me.tatarka.inject.annotations.Inject
|
|||||||
|
|
||||||
class SourceScreenViewModel(
|
class SourceScreenViewModel(
|
||||||
private val source: Source,
|
private val source: Source,
|
||||||
private val bundle: Bundle,
|
|
||||||
private val sourceHandler: SourceInteractionHandler
|
private val sourceHandler: SourceInteractionHandler
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
@@ -33,49 +27,40 @@ class SourceScreenViewModel(
|
|||||||
params: Params
|
params: Params
|
||||||
) : this(
|
) : this(
|
||||||
params.source,
|
params.source,
|
||||||
params.bundle,
|
|
||||||
sourceHandler
|
sourceHandler
|
||||||
)
|
)
|
||||||
|
|
||||||
private val _mangas = saveObjectInBundle(scope, bundle, MANGAS_KEY) { emptyList<Manga>() }
|
private val _mangas = MutableStateFlow(emptyList<Manga>())
|
||||||
val mangas = _mangas.asStateFlow()
|
val mangas = _mangas.asStateFlow()
|
||||||
|
|
||||||
private val _hasNextPage = saveBooleanInBundle(scope, bundle, NEXT_PAGE_KEY, false)
|
private val _hasNextPage = MutableStateFlow(false)
|
||||||
val hasNextPage = _hasNextPage.asStateFlow()
|
val hasNextPage = _hasNextPage.asStateFlow()
|
||||||
|
|
||||||
private val _loading = MutableStateFlow(true)
|
private val _loading = MutableStateFlow(true)
|
||||||
val loading = _loading.asStateFlow()
|
val loading = _loading.asStateFlow()
|
||||||
|
|
||||||
private val _isLatest = saveBooleanInBundle(scope, bundle, IS_LATEST_KEY, false)
|
private val _isLatest = MutableStateFlow(false)
|
||||||
val isLatest = _isLatest.asStateFlow()
|
val isLatest = _isLatest.asStateFlow()
|
||||||
|
|
||||||
private val _filterButtonEnabled = saveBooleanInBundle(scope, bundle, SHOW_FILTERS, false)
|
private val _latestButtonEnabled = MutableStateFlow(false)
|
||||||
val filterButtonEnabled = _filterButtonEnabled.asStateFlow()
|
|
||||||
|
|
||||||
private val _latestButtonEnabled = saveBooleanInBundle(scope, bundle, SHOW_LATEST, false)
|
|
||||||
val latestButtonEnabled = _latestButtonEnabled.asStateFlow()
|
val latestButtonEnabled = _latestButtonEnabled.asStateFlow()
|
||||||
|
|
||||||
private val _showingFilters = MutableStateFlow(false)
|
|
||||||
val showingFilters = _showingFilters.asStateFlow()
|
|
||||||
|
|
||||||
private val _usingFilters = MutableStateFlow(false)
|
private val _usingFilters = MutableStateFlow(false)
|
||||||
|
|
||||||
private val _sourceSearchQuery = MutableStateFlow<String?>(null)
|
private val _sourceSearchQuery = MutableStateFlow<String?>(null)
|
||||||
val sourceSearchQuery = _sourceSearchQuery.asStateFlow()
|
val sourceSearchQuery = _sourceSearchQuery.asStateFlow()
|
||||||
|
|
||||||
private val _query = saveStringInBundle(scope, bundle, QUERY_KEY) { null }
|
private val _query = MutableStateFlow<String?>(null)
|
||||||
|
|
||||||
private val _pageNum = saveIntInBundle(scope, bundle, PAGE_NUM_KEY, 1)
|
private val _pageNum = MutableStateFlow(1)
|
||||||
val pageNum = _pageNum.asStateFlow()
|
val pageNum = _pageNum.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
if (bundle[MANGAS_KEY] == null) {
|
val (mangas, hasNextPage) = getPage()
|
||||||
val (mangas, hasNextPage) = getPage()
|
_mangas.value = mangas
|
||||||
_mangas.value = mangas
|
_hasNextPage.value = hasNextPage
|
||||||
_hasNextPage.value = hasNextPage
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.throwIfCancellation()
|
e.throwIfCancellation()
|
||||||
} finally {
|
} finally {
|
||||||
@@ -103,19 +88,8 @@ class SourceScreenViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cleanBundle(removeMode: Boolean = true) {
|
|
||||||
bundle.remove(MANGAS_KEY)
|
|
||||||
bundle.remove(NEXT_PAGE_KEY)
|
|
||||||
bundle.remove(PAGE_NUM_KEY)
|
|
||||||
if (removeMode) {
|
|
||||||
bundle.remove(IS_LATEST_KEY)
|
|
||||||
}
|
|
||||||
bundle.remove(QUERY_KEY)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setMode(toLatest: Boolean) {
|
fun setMode(toLatest: Boolean) {
|
||||||
if (isLatest.value != toLatest) {
|
if (isLatest.value != toLatest) {
|
||||||
cleanBundle()
|
|
||||||
_isLatest.value = toLatest
|
_isLatest.value = toLatest
|
||||||
// [loadNextPage] increments by 1
|
// [loadNextPage] increments by 1
|
||||||
_pageNum.value = 0
|
_pageNum.value = 0
|
||||||
@@ -135,7 +109,6 @@ class SourceScreenViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun startSearch(query: String?) {
|
fun startSearch(query: String?) {
|
||||||
cleanBundle(false)
|
|
||||||
_pageNum.value = 0
|
_pageNum.value = 0
|
||||||
_hasNextPage.value = true
|
_hasNextPage.value = true
|
||||||
_loading.value = true
|
_loading.value = true
|
||||||
@@ -144,15 +117,9 @@ class SourceScreenViewModel(
|
|||||||
loadNextPage()
|
loadNextPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showingFilters(show: Boolean) {
|
|
||||||
_showingFilters.value = show
|
|
||||||
}
|
|
||||||
fun setUsingFilters(usingFilters: Boolean) {
|
fun setUsingFilters(usingFilters: Boolean) {
|
||||||
_usingFilters.value = usingFilters
|
_usingFilters.value = usingFilters
|
||||||
}
|
}
|
||||||
fun enableFilters(enabled: Boolean) {
|
|
||||||
_filterButtonEnabled.value = enabled
|
|
||||||
}
|
|
||||||
fun enableLatest(enabled: Boolean) {
|
fun enableLatest(enabled: Boolean) {
|
||||||
_latestButtonEnabled.value = enabled
|
_latestButtonEnabled.value = enabled
|
||||||
}
|
}
|
||||||
@@ -164,15 +131,5 @@ class SourceScreenViewModel(
|
|||||||
startSearch(sourceSearchQuery.value)
|
startSearch(sourceSearchQuery.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Params(val source: Source, val bundle: Bundle)
|
data class Params(val source: Source)
|
||||||
|
|
||||||
private companion object {
|
|
||||||
const val MANGAS_KEY = "mangas"
|
|
||||||
const val NEXT_PAGE_KEY = "next_page"
|
|
||||||
const val PAGE_NUM_KEY = "page_num"
|
|
||||||
const val IS_LATEST_KEY = "is_latest"
|
|
||||||
const val SHOW_FILTERS = "show_filters"
|
|
||||||
const val SHOW_LATEST = "show_latest"
|
|
||||||
const val QUERY_KEY = "query"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.sources.components
|
package ca.gosyer.ui.sources.browse.components
|
||||||
|
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.GridCells
|
import androidx.compose.foundation.lazy.GridCells
|
||||||
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollbarAdapter
|
import androidx.compose.foundation.rememberScrollbarAdapter
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Explore
|
import androidx.compose.material.icons.rounded.Explore
|
||||||
@@ -22,8 +23,6 @@ import androidx.compose.material.icons.rounded.NewReleases
|
|||||||
import androidx.compose.material.icons.rounded.Settings
|
import androidx.compose.material.icons.rounded.Settings
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.FilterQuality
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
@@ -33,38 +32,40 @@ import ca.gosyer.data.models.Source
|
|||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.TextActionIcon
|
import ca.gosyer.ui.base.navigation.TextActionIcon
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.sources.components.filter.SourceFiltersMenu
|
import ca.gosyer.ui.sources.browse.filter.SourceFiltersMenu
|
||||||
|
import ca.gosyer.ui.sources.browse.filter.model.SourceFiltersView
|
||||||
import ca.gosyer.uicore.components.LoadingScreen
|
import ca.gosyer.uicore.components.LoadingScreen
|
||||||
import ca.gosyer.uicore.components.MangaGridItem
|
import ca.gosyer.uicore.components.MangaGridItem
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
|
||||||
import ca.gosyer.util.compose.persistentLazyListState
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.BundleScope
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import io.kamel.image.lazyPainterResource
|
import io.kamel.image.lazyPainterResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceScreen(
|
fun SourceScreenContent(
|
||||||
bundle: Bundle,
|
|
||||||
source: Source,
|
source: Source,
|
||||||
onMangaClick: (Long) -> Unit,
|
onMangaClick: (Long) -> Unit,
|
||||||
onCloseSourceTabClick: (Source) -> Unit,
|
onCloseSourceTabClick: (Source) -> Unit,
|
||||||
onSourceSettingsClick: (Long) -> Unit
|
onSourceSettingsClick: (Long) -> Unit,
|
||||||
|
mangas: List<Manga>,
|
||||||
|
hasNextPage: Boolean,
|
||||||
|
loading: Boolean,
|
||||||
|
isLatest: Boolean,
|
||||||
|
showLatestButton: Boolean,
|
||||||
|
sourceSearchQuery: String?,
|
||||||
|
enableLatest: (Boolean) -> Unit,
|
||||||
|
search: (String) -> Unit,
|
||||||
|
submitSearch: () -> Unit,
|
||||||
|
setMode: (Boolean) -> Unit,
|
||||||
|
loadNextPage: () -> Unit,
|
||||||
|
setUsingFilters: (Boolean) -> Unit,
|
||||||
|
// filter
|
||||||
|
filters: List<SourceFiltersView<*, *>>,
|
||||||
|
showingFilters: Boolean,
|
||||||
|
showFilterButton: Boolean,
|
||||||
|
setShowingFilters: (Boolean) -> Unit,
|
||||||
|
resetFiltersClicked: () -> Unit
|
||||||
) {
|
) {
|
||||||
val vm = viewModel(source.id) {
|
LaunchedEffect(source) {
|
||||||
instantiate<SourceScreenViewModel>(SourceScreenViewModel.Params(source, bundle))
|
enableLatest(source.supportsLatest)
|
||||||
}
|
|
||||||
val mangas by vm.mangas.collectAsState()
|
|
||||||
val hasNextPage by vm.hasNextPage.collectAsState()
|
|
||||||
val loading by vm.loading.collectAsState()
|
|
||||||
val isLatest by vm.isLatest.collectAsState()
|
|
||||||
val showingFilters by vm.showingFilters.collectAsState()
|
|
||||||
val showFilterButton by vm.filterButtonEnabled.collectAsState()
|
|
||||||
val showLatestButton by vm.latestButtonEnabled.collectAsState()
|
|
||||||
val sourceSearchQuery by vm.sourceSearchQuery.collectAsState()
|
|
||||||
|
|
||||||
LaunchedEffect(vm to source) {
|
|
||||||
vm.enableLatest(source.supportsLatest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@@ -72,44 +73,35 @@ fun SourceScreen(
|
|||||||
source = source,
|
source = source,
|
||||||
onCloseSourceTabClick = onCloseSourceTabClick,
|
onCloseSourceTabClick = onCloseSourceTabClick,
|
||||||
sourceSearchQuery = sourceSearchQuery,
|
sourceSearchQuery = sourceSearchQuery,
|
||||||
onSearch = vm::search,
|
onSearch = search,
|
||||||
onSubmitSearch = vm::submitSearch,
|
onSubmitSearch = submitSearch,
|
||||||
onSourceSettingsClick = onSourceSettingsClick,
|
onSourceSettingsClick = onSourceSettingsClick,
|
||||||
showFilterButton = showFilterButton,
|
showFilterButton = showFilterButton,
|
||||||
showLatestButton = showLatestButton,
|
showLatestButton = showLatestButton,
|
||||||
isLatest = isLatest,
|
isLatest = isLatest,
|
||||||
showingFilters = showingFilters,
|
showingFilters = showingFilters,
|
||||||
onClickMode = vm::setMode,
|
onClickMode = setMode,
|
||||||
onToggleFiltersClick = vm::showingFilters,
|
onToggleFiltersClick = setShowingFilters,
|
||||||
)
|
)
|
||||||
Box {
|
Box {
|
||||||
MangaTable(
|
MangaTable(
|
||||||
bundle = bundle,
|
|
||||||
mangas = mangas,
|
mangas = mangas,
|
||||||
isLoading = loading,
|
isLoading = loading,
|
||||||
hasNextPage = hasNextPage,
|
hasNextPage = hasNextPage,
|
||||||
onLoadNextPage = vm::loadNextPage,
|
onLoadNextPage = loadNextPage,
|
||||||
onMangaClick = onMangaClick,
|
onMangaClick = onMangaClick,
|
||||||
)
|
)
|
||||||
BundleScope("filters", autoDispose = false) {
|
SourceFiltersMenu(
|
||||||
SourceFiltersMenu(
|
modifier = Modifier.align(Alignment.TopEnd),
|
||||||
bundle = bundle,
|
showFilters = showingFilters && !isLatest,
|
||||||
modifier = Modifier.align(Alignment.TopEnd),
|
filters = filters,
|
||||||
sourceId = source.id,
|
onSearchClicked = {
|
||||||
showFilters = showingFilters && !isLatest,
|
setUsingFilters(true)
|
||||||
onSearchClicked = {
|
setShowingFilters(false)
|
||||||
vm.setUsingFilters(true)
|
submitSearch()
|
||||||
vm.showingFilters(false)
|
},
|
||||||
vm.submitSearch()
|
resetFiltersClicked = resetFiltersClicked
|
||||||
},
|
)
|
||||||
onResetClicked = {
|
|
||||||
vm.setUsingFilters(false)
|
|
||||||
vm.showingFilters(false)
|
|
||||||
vm.submitSearch()
|
|
||||||
},
|
|
||||||
showFiltersButton = vm::enableFilters
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +175,6 @@ fun SourceToolbar(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MangaTable(
|
private fun MangaTable(
|
||||||
bundle: Bundle,
|
|
||||||
mangas: List<Manga>,
|
mangas: List<Manga>,
|
||||||
isLoading: Boolean = false,
|
isLoading: Boolean = false,
|
||||||
hasNextPage: Boolean = false,
|
hasNextPage: Boolean = false,
|
||||||
@@ -193,9 +184,9 @@ private fun MangaTable(
|
|||||||
if (isLoading || mangas.isEmpty()) {
|
if (isLoading || mangas.isEmpty()) {
|
||||||
LoadingScreen(isLoading)
|
LoadingScreen(isLoading)
|
||||||
} else {
|
} else {
|
||||||
val persistentState = persistentLazyListState(bundle)
|
val lazyListState = rememberLazyListState()
|
||||||
Box {
|
Box {
|
||||||
LazyVerticalGrid(GridCells.Adaptive(160.dp), state = persistentState) {
|
LazyVerticalGrid(GridCells.Adaptive(160.dp), state = lazyListState) {
|
||||||
itemsIndexed(mangas) { index, manga ->
|
itemsIndexed(mangas) { index, manga ->
|
||||||
if (hasNextPage && index == mangas.lastIndex) {
|
if (hasNextPage && index == mangas.lastIndex) {
|
||||||
LaunchedEffect(Unit) { onLoadNextPage() }
|
LaunchedEffect(Unit) { onLoadNextPage() }
|
||||||
@@ -210,7 +201,7 @@ private fun MangaTable(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
VerticalScrollbar(
|
VerticalScrollbar(
|
||||||
rememberScrollbarAdapter(persistentState),
|
rememberScrollbarAdapter(lazyListState),
|
||||||
Modifier.align(Alignment.CenterEnd)
|
Modifier.align(Alignment.CenterEnd)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.padding(horizontal = 4.dp, vertical = 8.dp)
|
.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.sources.components.filter
|
package ca.gosyer.ui.sources.browse.filter
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.LinearEasing
|
import androidx.compose.animation.core.LinearEasing
|
||||||
@@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollbarAdapter
|
import androidx.compose.foundation.rememberScrollbarAdapter
|
||||||
import androidx.compose.material.Button
|
import androidx.compose.material.Button
|
||||||
import androidx.compose.material.Checkbox
|
import androidx.compose.material.Checkbox
|
||||||
@@ -48,7 +49,6 @@ import androidx.compose.material.TriStateCheckbox
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.ArrowUpward
|
import androidx.compose.material.icons.rounded.ArrowUpward
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -67,41 +67,19 @@ import androidx.compose.ui.util.fastForEach
|
|||||||
import ca.gosyer.data.models.sourcefilters.SortFilter
|
import ca.gosyer.data.models.sourcefilters.SortFilter
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.prefs.ExpandablePreference
|
import ca.gosyer.ui.base.prefs.ExpandablePreference
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.ui.sources.browse.filter.model.SourceFiltersView
|
||||||
import ca.gosyer.ui.sources.components.filter.model.SourceFiltersView
|
|
||||||
import ca.gosyer.uicore.components.Spinner
|
import ca.gosyer.uicore.components.Spinner
|
||||||
import ca.gosyer.util.compose.persistentLazyListState
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import kotlinx.coroutines.flow.filterIsInstance
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceFiltersMenu(
|
fun SourceFiltersMenu(
|
||||||
bundle: Bundle,
|
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
sourceId: Long,
|
|
||||||
showFilters: Boolean,
|
showFilters: Boolean,
|
||||||
|
filters: List<SourceFiltersView<*, *>>,
|
||||||
onSearchClicked: () -> Unit,
|
onSearchClicked: () -> Unit,
|
||||||
onResetClicked: () -> Unit,
|
resetFiltersClicked: () -> Unit
|
||||||
showFiltersButton: (Boolean) -> Unit
|
|
||||||
) {
|
) {
|
||||||
val vm = viewModel(sourceId) {
|
|
||||||
instantiate<SourceFiltersViewModel>(SourceFiltersViewModel.Params(bundle, sourceId))
|
|
||||||
}
|
|
||||||
val filters by vm.filters.collectAsState()
|
|
||||||
DisposableEffect(filters) {
|
|
||||||
showFiltersButton(filters.isNotEmpty())
|
|
||||||
onDispose { showFiltersButton(false) }
|
|
||||||
}
|
|
||||||
LaunchedEffect(vm) {
|
|
||||||
launch {
|
|
||||||
vm.resetFilters.collect {
|
|
||||||
onResetClicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
showFilters,
|
showFilters,
|
||||||
enter = fadeIn() + slideInHorizontally(initialOffsetX = { it * 2 }),
|
enter = fadeIn() + slideInHorizontally(initialOffsetX = { it * 2 }),
|
||||||
@@ -116,7 +94,7 @@ fun SourceFiltersMenu(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
TextButton(vm::resetFilters) {
|
TextButton(resetFiltersClicked) {
|
||||||
Text(stringResource(MR.strings.reset_filters))
|
Text(stringResource(MR.strings.reset_filters))
|
||||||
}
|
}
|
||||||
Button(onSearchClicked) {
|
Button(onSearchClicked) {
|
||||||
@@ -126,7 +104,7 @@ fun SourceFiltersMenu(
|
|||||||
}
|
}
|
||||||
val expandedGroups = remember { mutableStateListOf<Int>() }
|
val expandedGroups = remember { mutableStateListOf<Int>() }
|
||||||
Box {
|
Box {
|
||||||
val lazyListState = persistentLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), lazyListState) {
|
LazyColumn(Modifier.fillMaxSize(), lazyListState) {
|
||||||
items(
|
items(
|
||||||
items = filters,
|
items = filters,
|
||||||
@@ -4,15 +4,14 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.sources.components.filter
|
package ca.gosyer.ui.sources.browse.filter
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
import ca.gosyer.core.lang.throwIfCancellation
|
||||||
import ca.gosyer.core.logging.CKLogger
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.models.sourcefilters.SourceFilter
|
import ca.gosyer.data.models.sourcefilters.SourceFilter
|
||||||
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
||||||
|
import ca.gosyer.ui.sources.browse.filter.model.SourceFiltersView
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import ca.gosyer.ui.sources.components.filter.model.SourceFiltersView
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -27,7 +26,6 @@ import me.tatarka.inject.annotations.Inject
|
|||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
class SourceFiltersViewModel(
|
class SourceFiltersViewModel(
|
||||||
private val bundle: Bundle,
|
|
||||||
private val sourceId: Long,
|
private val sourceId: Long,
|
||||||
private val sourceHandler: SourceInteractionHandler
|
private val sourceHandler: SourceInteractionHandler
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -35,7 +33,6 @@ class SourceFiltersViewModel(
|
|||||||
sourceHandler: SourceInteractionHandler,
|
sourceHandler: SourceInteractionHandler,
|
||||||
params: Params,
|
params: Params,
|
||||||
) : this(
|
) : this(
|
||||||
params.bundle,
|
|
||||||
params.sourceId,
|
params.sourceId,
|
||||||
sourceHandler
|
sourceHandler
|
||||||
)
|
)
|
||||||
@@ -49,14 +46,21 @@ class SourceFiltersViewModel(
|
|||||||
private val _resetFilters = MutableSharedFlow<Unit>()
|
private val _resetFilters = MutableSharedFlow<Unit>()
|
||||||
val resetFilters = _resetFilters.asSharedFlow()
|
val resetFilters = _resetFilters.asSharedFlow()
|
||||||
|
|
||||||
|
private val _showingFilters = MutableStateFlow(false)
|
||||||
|
val showingFilters = _showingFilters.asStateFlow()
|
||||||
|
|
||||||
|
private val _filterButtonEnabled = MutableStateFlow(false)
|
||||||
|
val filterButtonEnabled = _filterButtonEnabled.asStateFlow()
|
||||||
|
|
||||||
private val subscriptions: CopyOnWriteArrayList<Job> = CopyOnWriteArrayList()
|
private val subscriptions: CopyOnWriteArrayList<Job> = CopyOnWriteArrayList()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
getFilters(initialLoad = !bundle.getBoolean(FILTERING, false))
|
getFilters(initialLoad = true)
|
||||||
|
|
||||||
filters.onEach { settings ->
|
filters.onEach { settings ->
|
||||||
subscriptions.forEach { it.cancel() }
|
subscriptions.forEach { it.cancel() }
|
||||||
subscriptions.clear()
|
subscriptions.clear()
|
||||||
|
_filterButtonEnabled.value = settings.isNotEmpty()
|
||||||
subscriptions += settings.flatMap { filter ->
|
subscriptions += settings.flatMap { filter ->
|
||||||
if (filter is SourceFiltersView.Group) {
|
if (filter is SourceFiltersView.Group) {
|
||||||
filter.state.value.map { childFilter ->
|
filter.state.value.map { childFilter ->
|
||||||
@@ -81,15 +85,18 @@ class SourceFiltersViewModel(
|
|||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showingFilters(show: Boolean) {
|
||||||
|
_showingFilters.value = show
|
||||||
|
}
|
||||||
|
fun enableFilters(enabled: Boolean) {
|
||||||
|
_filterButtonEnabled.value = enabled
|
||||||
|
}
|
||||||
|
|
||||||
private fun getFilters(initialLoad: Boolean = false) {
|
private fun getFilters(initialLoad: Boolean = false) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
_filters.value = sourceHandler.getFilterList(sourceId, reset = initialLoad).toView()
|
_filters.value = sourceHandler.getFilterList(sourceId, reset = initialLoad).toView()
|
||||||
if (!initialLoad) {
|
_resetFilters.emit(Unit)
|
||||||
bundle.putBoolean(FILTERING, true)
|
|
||||||
} else {
|
|
||||||
_resetFilters.emit(Unit)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.throwIfCancellation()
|
e.throwIfCancellation()
|
||||||
} finally {
|
} finally {
|
||||||
@@ -100,12 +107,11 @@ class SourceFiltersViewModel(
|
|||||||
|
|
||||||
fun resetFilters() {
|
fun resetFilters() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
bundle.remove(FILTERING)
|
|
||||||
getFilters(initialLoad = true)
|
getFilters(initialLoad = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Params(val bundle: Bundle, val sourceId: Long)
|
data class Params(val sourceId: Long)
|
||||||
|
|
||||||
private fun List<SourceFilter>.toView() = mapIndexed { index, sourcePreference ->
|
private fun List<SourceFilter>.toView() = mapIndexed { index, sourcePreference ->
|
||||||
SourceFiltersView(index, sourcePreference)
|
SourceFiltersView(index, sourcePreference)
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.sources.components.filter.model
|
package ca.gosyer.ui.sources.browse.filter.model
|
||||||
|
|
||||||
import ca.gosyer.data.models.sourcefilters.CheckBoxFilter
|
import ca.gosyer.data.models.sourcefilters.CheckBoxFilter
|
||||||
import ca.gosyer.data.models.sourcefilters.GroupFilter
|
import ca.gosyer.data.models.sourcefilters.GroupFilter
|
||||||
@@ -4,9 +4,8 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.sources
|
package ca.gosyer.ui.sources.components
|
||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
|
||||||
import androidx.compose.foundation.TooltipArea
|
import androidx.compose.foundation.TooltipArea
|
||||||
import androidx.compose.foundation.TooltipPlacement
|
import androidx.compose.foundation.TooltipPlacement
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
@@ -29,9 +28,8 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Home
|
import androidx.compose.material.icons.rounded.Home
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
@@ -40,68 +38,43 @@ import androidx.compose.ui.unit.DpOffset
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ca.gosyer.data.models.Source
|
import ca.gosyer.data.models.Source
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
import ca.gosyer.ui.sources.browse.SourceScreen
|
||||||
import ca.gosyer.ui.manga.openMangaMenu
|
import ca.gosyer.ui.sources.home.SourceHomeScreen
|
||||||
import ca.gosyer.ui.sources.components.SourceHomeScreen
|
|
||||||
import ca.gosyer.ui.sources.components.SourceScreen
|
|
||||||
import ca.gosyer.ui.sources.settings.openSourceSettingsMenu
|
|
||||||
import ca.gosyer.uicore.components.combinedMouseClickable
|
import ca.gosyer.uicore.components.combinedMouseClickable
|
||||||
import ca.gosyer.uicore.image.KamelImage
|
import ca.gosyer.uicore.image.KamelImage
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
|
||||||
import ca.gosyer.util.compose.ThemedWindow
|
|
||||||
import ca.gosyer.util.lang.launchApplication
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.BundleScope
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.LocalSavedInstanceState
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import io.kamel.image.lazyPainterResource
|
import io.kamel.image.lazyPainterResource
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@Composable
|
||||||
fun openSourcesMenu() {
|
fun SourcesMenu(
|
||||||
launchApplication {
|
sourceTabs: List<Source?>,
|
||||||
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
selectedSourceTab: Source?,
|
||||||
Surface {
|
selectTab: (Source?) -> Unit,
|
||||||
CompositionLocalProvider(
|
closeTab: (Source) -> Unit
|
||||||
LocalSavedInstanceState provides Bundle()
|
) {
|
||||||
) {
|
val homeScreen = remember { SourceHomeScreen() }
|
||||||
SourcesMenu(
|
SourcesNavigator(
|
||||||
::openSourceSettingsMenu,
|
homeScreen,
|
||||||
::openMangaMenu
|
removeSource = closeTab,
|
||||||
)
|
selectSource = selectTab
|
||||||
}
|
) { navigator ->
|
||||||
}
|
LaunchedEffect(selectedSourceTab) {
|
||||||
|
navigator.current = if (selectedSourceTab == null) {
|
||||||
|
homeScreen
|
||||||
|
} else SourceScreen(selectedSourceTab)
|
||||||
}
|
}
|
||||||
}
|
Row {
|
||||||
}
|
SourcesSideMenu(
|
||||||
|
sourceTabs = sourceTabs,
|
||||||
|
onSourceTabClick = selectTab,
|
||||||
|
onCloseSourceTabClick = {
|
||||||
|
closeTab(it)
|
||||||
|
navigator.stateHolder.removeState(it.id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
CurrentSource()
|
||||||
fun SourcesMenu(onSourceSettingsClick: (Long) -> Unit, onMangaClick: (Long) -> Unit) {
|
}
|
||||||
SourcesMenu(LocalSavedInstanceState.current, onSourceSettingsClick, onMangaClick)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SourcesMenu(bundle: Bundle, onSourceSettingsClick: (Long) -> Unit, onMangaClick: (Long) -> Unit) {
|
|
||||||
val vm = viewModel {
|
|
||||||
instantiate<SourcesMenuViewModel>(bundle)
|
|
||||||
}
|
|
||||||
val sourceTabs by vm.sourceTabs.collectAsState()
|
|
||||||
val selectedSourceTab by vm.selectedSourceTab.collectAsState()
|
|
||||||
Row {
|
|
||||||
SourcesSideMenu(
|
|
||||||
sourceTabs = sourceTabs,
|
|
||||||
onSourceTabClick = vm::selectTab,
|
|
||||||
onCloseSourceTabClick = vm::closeTab
|
|
||||||
)
|
|
||||||
|
|
||||||
SourceTab(
|
|
||||||
onLoadSources = vm::setLoadedSources,
|
|
||||||
onSourceClicked = vm::addTab,
|
|
||||||
selectedSourceTab = selectedSourceTab,
|
|
||||||
onMangaClick = onMangaClick,
|
|
||||||
onCloseSourceTabClick = vm::closeTab,
|
|
||||||
onSourceSettingsClick = onSourceSettingsClick
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,33 +142,3 @@ fun SourcesSideMenu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SourceTab(
|
|
||||||
onLoadSources: (List<Source>) -> Unit,
|
|
||||||
onSourceClicked: (Source) -> Unit,
|
|
||||||
selectedSourceTab: Source?,
|
|
||||||
onMangaClick: (Long) -> Unit,
|
|
||||||
onCloseSourceTabClick: (Source) -> Unit,
|
|
||||||
onSourceSettingsClick: (Long) -> Unit
|
|
||||||
) {
|
|
||||||
Crossfade(selectedSourceTab) { selectedSource ->
|
|
||||||
BundleScope(selectedSource?.id.toString(), autoDispose = false) {
|
|
||||||
if (selectedSource != null) {
|
|
||||||
SourceScreen(
|
|
||||||
bundle = it,
|
|
||||||
source = selectedSource,
|
|
||||||
onMangaClick = onMangaClick,
|
|
||||||
onCloseSourceTabClick = onCloseSourceTabClick,
|
|
||||||
onSourceSettingsClick = onSourceSettingsClick
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
SourceHomeScreen(
|
|
||||||
bundle = it,
|
|
||||||
onAddSource = onSourceClicked,
|
|
||||||
onLoadSources = onLoadSources
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.sources.components
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.SaveableStateHolder
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import ca.gosyer.data.models.Source
|
||||||
|
import ca.gosyer.ui.sources.browse.SourceScreen
|
||||||
|
import ca.gosyer.ui.sources.home.SourceHomeScreen
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
|
||||||
|
typealias SourcesNavigatorContent = @Composable (sourcesNavigator: SourcesNavigator) -> Unit
|
||||||
|
|
||||||
|
val LocalSourcesNavigator: ProvidableCompositionLocal<SourcesNavigator> =
|
||||||
|
staticCompositionLocalOf { error("SourcesNavigator not initialized") }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SourcesNavigator(
|
||||||
|
homeScreen: SourceHomeScreen,
|
||||||
|
removeSource: (Source) -> Unit,
|
||||||
|
selectSource: (Source) -> Unit,
|
||||||
|
content: SourcesNavigatorContent = { CurrentSource() }
|
||||||
|
) {
|
||||||
|
Navigator(homeScreen, autoDispose = false, onBackPressed = null) { navigator ->
|
||||||
|
val sourcesNavigator = remember(navigator) {
|
||||||
|
SourcesNavigator(navigator, homeScreen, removeSource, selectSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositionLocalProvider(LocalSourcesNavigator provides sourcesNavigator) {
|
||||||
|
content(sourcesNavigator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SourcesNavigator internal constructor(
|
||||||
|
private val navigator: Navigator,
|
||||||
|
private val homeScreen: SourceHomeScreen,
|
||||||
|
private val removeSource: (Source) -> Unit,
|
||||||
|
private val selectSource: (Source) -> Unit,
|
||||||
|
val stateHolder: SaveableStateHolder = navigator.stateHolder
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun remove(source: Source) {
|
||||||
|
removeSource(source)
|
||||||
|
navigator replaceAll homeScreen
|
||||||
|
stateHolder.removeState(source.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun select(source: Source) {
|
||||||
|
selectSource(source)
|
||||||
|
navigator replaceAll SourceScreen(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
var current
|
||||||
|
get() = navigator.lastItem
|
||||||
|
set(value) = navigator replaceAll value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CurrentSource() {
|
||||||
|
val sourcesNavigator = LocalSourcesNavigator.current
|
||||||
|
val currentSource = sourcesNavigator.current
|
||||||
|
|
||||||
|
sourcesNavigator.stateHolder.SaveableStateProvider(currentSource.key) {
|
||||||
|
currentSource.Content()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.sources.home
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import ca.gosyer.ui.sources.components.LocalSourcesNavigator
|
||||||
|
import ca.gosyer.ui.sources.home.components.SourceHomeScreenContent
|
||||||
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
|
||||||
|
class SourceHomeScreen : Screen {
|
||||||
|
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<SourceHomeScreenViewModel>()
|
||||||
|
val sourcesNavigator = LocalSourcesNavigator.current
|
||||||
|
SourceHomeScreenContent(
|
||||||
|
onAddSource = sourcesNavigator::select,
|
||||||
|
isLoading = vm.isLoading.collectAsState().value,
|
||||||
|
sources = vm.sources.collectAsState().value,
|
||||||
|
languages = vm.languages,
|
||||||
|
getSourceLanguages = vm::getSourceLanguages,
|
||||||
|
setEnabledLanguages = vm::setEnabledLanguages
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.sources.components
|
package ca.gosyer.ui.sources.home
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
import ca.gosyer.core.lang.throwIfCancellation
|
||||||
import ca.gosyer.core.logging.CKLogger
|
import ca.gosyer.core.logging.CKLogger
|
||||||
@@ -12,7 +12,6 @@ import ca.gosyer.data.catalog.CatalogPreferences
|
|||||||
import ca.gosyer.data.models.Source
|
import ca.gosyer.data.models.Source
|
||||||
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -20,8 +19,7 @@ import me.tatarka.inject.annotations.Inject
|
|||||||
|
|
||||||
class SourceHomeScreenViewModel @Inject constructor(
|
class SourceHomeScreenViewModel @Inject constructor(
|
||||||
private val sourceHandler: SourceInteractionHandler,
|
private val sourceHandler: SourceInteractionHandler,
|
||||||
catalogPreferences: CatalogPreferences,
|
catalogPreferences: CatalogPreferences
|
||||||
private val bundle: Bundle,
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _isLoading = MutableStateFlow(true)
|
private val _isLoading = MutableStateFlow(true)
|
||||||
val isLoading = _isLoading.asStateFlow()
|
val isLoading = _isLoading.asStateFlow()
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.sources.components
|
package ca.gosyer.ui.sources.home.components
|
||||||
|
|
||||||
import androidx.compose.foundation.TooltipArea
|
import androidx.compose.foundation.TooltipArea
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
@@ -32,9 +32,6 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Translate
|
import androidx.compose.material.icons.rounded.Translate
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
@@ -45,41 +42,31 @@ import ca.gosyer.data.models.Source
|
|||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.navigation.TextActionIcon
|
import ca.gosyer.ui.base.navigation.TextActionIcon
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.ui.extensions.components.LanguageDialog
|
||||||
import ca.gosyer.ui.extensions.LanguageDialog
|
|
||||||
import ca.gosyer.uicore.components.LoadingScreen
|
import ca.gosyer.uicore.components.LoadingScreen
|
||||||
import ca.gosyer.uicore.image.KamelImage
|
import ca.gosyer.uicore.image.KamelImage
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import io.kamel.image.lazyPainterResource
|
import io.kamel.image.lazyPainterResource
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceHomeScreen(
|
fun SourceHomeScreenContent(
|
||||||
bundle: Bundle,
|
|
||||||
onAddSource: (Source) -> Unit,
|
onAddSource: (Source) -> Unit,
|
||||||
onLoadSources: (List<Source>) -> Unit
|
isLoading: Boolean,
|
||||||
|
sources: List<Source>,
|
||||||
|
languages: StateFlow<Set<String>>,
|
||||||
|
getSourceLanguages: () -> Set<String>,
|
||||||
|
setEnabledLanguages: (Set<String>) -> Unit
|
||||||
) {
|
) {
|
||||||
val vm = viewModel {
|
|
||||||
instantiate<SourceHomeScreenViewModel>(bundle)
|
|
||||||
}
|
|
||||||
val sources by vm.sources.collectAsState()
|
|
||||||
val isLoading by vm.isLoading.collectAsState()
|
|
||||||
LaunchedEffect(sources) {
|
|
||||||
if (sources.isNotEmpty()) {
|
|
||||||
onLoadSources(sources)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sources.isEmpty()) {
|
if (sources.isEmpty()) {
|
||||||
LoadingScreen(isLoading)
|
LoadingScreen(isLoading)
|
||||||
} else {
|
} else {
|
||||||
Column {
|
Column {
|
||||||
SourceHomeScreenToolbar(
|
SourceHomeScreenToolbar(
|
||||||
vm.languages,
|
languages,
|
||||||
vm::getSourceLanguages,
|
getSourceLanguages,
|
||||||
vm::setEnabledLanguages
|
setEnabledLanguages
|
||||||
)
|
)
|
||||||
Box(Modifier.fillMaxSize(), Alignment.TopCenter) {
|
Box(Modifier.fillMaxSize(), Alignment.TopCenter) {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.sources.settings
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
|
import ca.gosyer.ui.sources.settings.components.SourceSettingsScreenContent
|
||||||
|
import ca.gosyer.ui.util.compose.ThemedWindow
|
||||||
|
import ca.gosyer.ui.util.lang.launchApplication
|
||||||
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun openSourceSettingsMenu(sourceId: Long) {
|
||||||
|
launchApplication {
|
||||||
|
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
||||||
|
Navigator(remember { SourceSettingsScreen(sourceId) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SourceSettingsScreen(private val sourceId: Long) : Screen {
|
||||||
|
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel {
|
||||||
|
instantiate<SourceSettingsScreenViewModel>(SourceSettingsScreenViewModel.Params(sourceId))
|
||||||
|
}
|
||||||
|
SourceSettingsScreenContent(
|
||||||
|
settings = vm.sourceSettings.collectAsState().value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@ package ca.gosyer.ui.sources.settings
|
|||||||
import ca.gosyer.core.lang.throwIfCancellation
|
import ca.gosyer.core.lang.throwIfCancellation
|
||||||
import ca.gosyer.data.models.sourcepreference.SourcePreference
|
import ca.gosyer.data.models.sourcepreference.SourcePreference
|
||||||
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
|
||||||
import ca.gosyer.ui.sources.settings.model.SourceSettingsView
|
import ca.gosyer.ui.sources.settings.model.SourceSettingsView
|
||||||
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@@ -22,7 +22,7 @@ import kotlinx.coroutines.launch
|
|||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
class SourceSettingsViewModel @Inject constructor(
|
class SourceSettingsScreenViewModel @Inject constructor(
|
||||||
private val sourceHandler: SourceInteractionHandler,
|
private val sourceHandler: SourceInteractionHandler,
|
||||||
private val params: Params
|
private val params: Params
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.sources.settings
|
package ca.gosyer.ui.sources.settings.components
|
||||||
|
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -33,43 +33,27 @@ import androidx.compose.ui.unit.dp
|
|||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
import ca.gosyer.ui.base.WindowDialog
|
||||||
import ca.gosyer.ui.base.navigation.LocalMenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.MenuController
|
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.ChoiceDialog
|
import ca.gosyer.ui.base.prefs.ChoiceDialog
|
||||||
import ca.gosyer.ui.base.prefs.MultiSelectDialog
|
import ca.gosyer.ui.base.prefs.MultiSelectDialog
|
||||||
import ca.gosyer.ui.base.prefs.PreferenceRow
|
import ca.gosyer.ui.base.prefs.PreferenceRow
|
||||||
|
import ca.gosyer.ui.sources.settings.model.SourceSettingsView
|
||||||
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.CheckBox
|
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.CheckBox
|
||||||
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.EditText
|
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.EditText
|
||||||
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.List
|
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.List
|
||||||
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.MultiSelect
|
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.MultiSelect
|
||||||
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.Switch
|
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.Switch
|
||||||
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.TwoState
|
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.TwoState
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
|
||||||
import ca.gosyer.util.compose.ThemedWindow
|
|
||||||
import ca.gosyer.util.lang.launchApplication
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlin.collections.List as KtList
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun openSourceSettingsMenu(sourceId: Long) {
|
|
||||||
launchApplication {
|
|
||||||
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
|
||||||
SourceSettingsMenu(sourceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceSettingsMenu(sourceId: Long, menuController: MenuController? = LocalMenuController.current) {
|
fun SourceSettingsScreenContent(
|
||||||
val vm = viewModel {
|
settings: KtList<SourceSettingsView<*, *>>
|
||||||
instantiate<SourceSettingsViewModel>(SourceSettingsViewModel.Params(sourceId))
|
) {
|
||||||
}
|
|
||||||
val settings by vm.sourceSettings.collectAsState()
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.location_settings), menuController, menuController != null)
|
Toolbar(stringResource(MR.strings.location_settings))
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), state) {
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.updates
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import ca.gosyer.ui.manga.MangaScreen
|
||||||
|
import ca.gosyer.ui.reader.openReaderMenu
|
||||||
|
import ca.gosyer.ui.updates.components.UpdatesScreenContent
|
||||||
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
|
||||||
|
class UpdatesScreen : Screen {
|
||||||
|
|
||||||
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val vm = viewModel<UpdatesScreenViewModel>()
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
UpdatesScreenContent(
|
||||||
|
isLoading = vm.isLoading.collectAsState().value,
|
||||||
|
updates = vm.updates.collectAsState().value,
|
||||||
|
loadNextPage = vm::loadNextPage,
|
||||||
|
openChapter = ::openReaderMenu,
|
||||||
|
openManga = { navigator push MangaScreen(it) },
|
||||||
|
downloadChapter = vm::downloadChapter,
|
||||||
|
deleteDownloadedChapter = vm::deleteDownloadedChapter,
|
||||||
|
stopDownloadingChapter = vm::stopDownloadingChapter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class UpdatesMenuViewModel @Inject constructor(
|
class UpdatesScreenViewModel @Inject constructor(
|
||||||
private val chapterHandler: ChapterInteractionHandler,
|
private val chapterHandler: ChapterInteractionHandler,
|
||||||
private val updatesHandler: UpdatesInteractionHandler,
|
private val updatesHandler: UpdatesInteractionHandler,
|
||||||
private val downloadService: DownloadService
|
private val downloadService: DownloadService
|
||||||
@@ -113,7 +113,7 @@ class UpdatesMenuViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDispose() {
|
||||||
downloadService.removeWatches(mangaIds)
|
downloadService.removeWatches(mangaIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.ui.updates
|
package ca.gosyer.ui.updates.components
|
||||||
|
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -23,8 +23,6 @@ import androidx.compose.foundation.rememberScrollbarAdapter
|
|||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
@@ -44,18 +42,20 @@ import ca.gosyer.uicore.components.MangaListItemImage
|
|||||||
import ca.gosyer.uicore.components.MangaListItemSubtitle
|
import ca.gosyer.uicore.components.MangaListItemSubtitle
|
||||||
import ca.gosyer.uicore.components.MangaListItemTitle
|
import ca.gosyer.uicore.components.MangaListItemTitle
|
||||||
import ca.gosyer.uicore.components.mangaAspectRatio
|
import ca.gosyer.uicore.components.mangaAspectRatio
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import io.kamel.image.lazyPainterResource
|
import io.kamel.image.lazyPainterResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpdatesMenu(
|
fun UpdatesScreenContent(
|
||||||
|
isLoading: Boolean,
|
||||||
|
updates: List<ChapterDownloadItem>,
|
||||||
|
loadNextPage: () -> Unit,
|
||||||
openChapter: (Int, Long) -> Unit,
|
openChapter: (Int, Long) -> Unit,
|
||||||
openManga: (Long) -> Unit
|
openManga: (Long) -> Unit,
|
||||||
|
downloadChapter: (Chapter) -> Unit,
|
||||||
|
deleteDownloadedChapter: (Chapter) -> Unit,
|
||||||
|
stopDownloadingChapter: (Chapter) -> Unit
|
||||||
) {
|
) {
|
||||||
val vm = viewModel<UpdatesMenuViewModel>()
|
|
||||||
val isLoading by vm.isLoading.collectAsState()
|
|
||||||
val updates by vm.updates.collectAsState()
|
|
||||||
Column {
|
Column {
|
||||||
Toolbar(stringResource(MR.strings.location_updates), closable = false)
|
Toolbar(stringResource(MR.strings.location_updates), closable = false)
|
||||||
if (isLoading || updates.isEmpty()) {
|
if (isLoading || updates.isEmpty()) {
|
||||||
@@ -67,7 +67,7 @@ fun UpdatesMenu(
|
|||||||
itemsIndexed(updates) { index, item ->
|
itemsIndexed(updates) { index, item ->
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (index == updates.lastIndex) {
|
if (index == updates.lastIndex) {
|
||||||
vm.loadNextPage()
|
loadNextPage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val manga = item.manga!!
|
val manga = item.manga!!
|
||||||
@@ -76,9 +76,9 @@ fun UpdatesMenu(
|
|||||||
item,
|
item,
|
||||||
onClickItem = { openChapter(chapter.index, chapter.mangaId) },
|
onClickItem = { openChapter(chapter.index, chapter.mangaId) },
|
||||||
onClickCover = { openManga(manga.id) },
|
onClickCover = { openManga(manga.id) },
|
||||||
onClickDownload = vm::downloadChapter,
|
onClickDownload = downloadChapter,
|
||||||
onClickDeleteDownload = vm::deleteDownloadedChapter,
|
onClickDeleteDownload = deleteDownloadedChapter,
|
||||||
onClickStopDownload = vm::stopDownloadingChapter
|
onClickStopDownload = stopDownloadingChapter
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ca.gosyer.util.compose
|
|
||||||
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.drop
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
inline fun <reified T> Bundle.putJsonObject(key: String, item: T) {
|
|
||||||
putString(key, Json.encodeToString(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T> Bundle.getJsonObject(key: String): T? {
|
|
||||||
return getString(key)?.let { Json.decodeFromString(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T> Bundle.getJsonObjectArray(key: String): List<T?>? {
|
|
||||||
return getString(key)?.let { Json.decodeFromString(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> saveAnyInBundle(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
bundle: Bundle,
|
|
||||||
key: String,
|
|
||||||
getValue: Bundle.(String) -> T?,
|
|
||||||
crossinline putValue: Bundle.(itemKey: String, item: T) -> Unit,
|
|
||||||
initialValue: () -> T
|
|
||||||
): MutableStateFlow<T> {
|
|
||||||
val item = bundle.getValue(key)
|
|
||||||
val flow: MutableStateFlow<T> = if (item != null) {
|
|
||||||
MutableStateFlow(item)
|
|
||||||
} else {
|
|
||||||
MutableStateFlow(initialValue())
|
|
||||||
}
|
|
||||||
flow.drop(1)
|
|
||||||
.mapLatest { bundle.putValue(key, it) }
|
|
||||||
.launchIn(scope)
|
|
||||||
|
|
||||||
return flow
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T> saveObjectInBundle(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
bundle: Bundle,
|
|
||||||
key: String,
|
|
||||||
initialValue: () -> T
|
|
||||||
): MutableStateFlow<T> {
|
|
||||||
return saveAnyInBundle(
|
|
||||||
scope,
|
|
||||||
bundle,
|
|
||||||
key,
|
|
||||||
{ getJsonObject<T>(it) },
|
|
||||||
{ itemKey, item ->
|
|
||||||
putJsonObject(itemKey, item)
|
|
||||||
},
|
|
||||||
initialValue
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveIntInBundle(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
bundle: Bundle,
|
|
||||||
key: String,
|
|
||||||
initialValue: Int
|
|
||||||
): MutableStateFlow<Int> {
|
|
||||||
return saveAnyInBundle(
|
|
||||||
scope,
|
|
||||||
bundle,
|
|
||||||
key,
|
|
||||||
{ getInt(key, initialValue) },
|
|
||||||
{ itemKey, item ->
|
|
||||||
putInt(itemKey, item)
|
|
||||||
},
|
|
||||||
{ initialValue }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveBooleanInBundle(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
bundle: Bundle,
|
|
||||||
key: String,
|
|
||||||
initialValue: Boolean
|
|
||||||
): MutableStateFlow<Boolean> {
|
|
||||||
return saveAnyInBundle(
|
|
||||||
scope,
|
|
||||||
bundle,
|
|
||||||
key,
|
|
||||||
{ getBoolean(key, initialValue) },
|
|
||||||
{ itemKey, item ->
|
|
||||||
putBoolean(itemKey, item)
|
|
||||||
},
|
|
||||||
{ initialValue }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveStringInBundle(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
bundle: Bundle,
|
|
||||||
key: String,
|
|
||||||
initialValue: () -> String? = { null }
|
|
||||||
): MutableStateFlow<String?> {
|
|
||||||
return saveAnyInBundle(
|
|
||||||
scope,
|
|
||||||
bundle,
|
|
||||||
key,
|
|
||||||
{ getString(key) ?: initialValue() },
|
|
||||||
{ itemKey, item ->
|
|
||||||
if (item != null) {
|
|
||||||
putString(itemKey, item)
|
|
||||||
} else remove(itemKey)
|
|
||||||
},
|
|
||||||
initialValue
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.util.compose
|
package ca.gosyer.ui.util.compose
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.util.compose
|
package ca.gosyer.ui.util.compose
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.util.compose
|
package ca.gosyer.ui.util.compose
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.graphics.toComposeImageBitmap
|
import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.util.compose
|
package ca.gosyer.ui.util.compose
|
||||||
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ca.gosyer.util.compose
|
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
|
||||||
import com.github.zsoltk.compose.savedinstancestate.LocalSavedInstanceState
|
|
||||||
|
|
||||||
const val LAZY_LIST_ITEM = "lazy_list_item"
|
|
||||||
const val LAZY_LIST_OFFSET = "lazy_list_offset"
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun persistentLazyListState(bundle: Bundle = LocalSavedInstanceState.current): LazyListState {
|
|
||||||
val state = rememberLazyListState(
|
|
||||||
remember { bundle.getInt(LAZY_LIST_ITEM, 0) },
|
|
||||||
remember { bundle.getInt(LAZY_LIST_OFFSET, 0) }
|
|
||||||
)
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
onDispose {
|
|
||||||
bundle.putInt(LAZY_LIST_ITEM, state.firstVisibleItemIndex)
|
|
||||||
bundle.putInt(LAZY_LIST_OFFSET, state.firstVisibleItemScrollOffset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.util.compose
|
package ca.gosyer.ui.util.compose
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.util.compose
|
package ca.gosyer.ui.util.compose
|
||||||
|
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.util.lang
|
package ca.gosyer.ui.util.lang
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.window.ApplicationScope
|
import androidx.compose.ui.window.ApplicationScope
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.util.system
|
package ca.gosyer.ui.util.system
|
||||||
|
|
||||||
import ca.gosyer.core.lang.launchUI
|
import ca.gosyer.core.lang.launchUI
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ca.gosyer.util.system
|
package ca.gosyer.ui.util.system
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ kotlin {
|
|||||||
api(kotlin("stdlib-common"))
|
api(kotlin("stdlib-common"))
|
||||||
api(libs.coroutinesCore)
|
api(libs.coroutinesCore)
|
||||||
api(libs.kamel)
|
api(libs.kamel)
|
||||||
|
api(libs.voyagerCore)
|
||||||
api(project(":core"))
|
api(project(":core"))
|
||||||
api(project(":i18n"))
|
api(project(":i18n"))
|
||||||
api(compose.desktop.currentOs)
|
api(compose.desktop.currentOs)
|
||||||
|
|||||||
@@ -8,36 +8,20 @@ package ca.gosyer.uicore.vm
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisallowComposableCalls
|
import androidx.compose.runtime.DisallowComposableCalls
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import androidx.compose.runtime.remember
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
inline fun <reified VM : ViewModel> viewModel(key: Any? = Unit): VM {
|
inline fun <reified VM : ViewModel> Screen.viewModel(tag: String? = null): VM {
|
||||||
val viewModelFactory = LocalViewModelFactory.current
|
val viewModelFactory = LocalViewModelFactory.current
|
||||||
val viewModel = remember(key) {
|
return rememberScreenModel(tag) { viewModelFactory.instantiate() }
|
||||||
viewModelFactory.instantiate<VM>()
|
|
||||||
}
|
|
||||||
DisposableEffect(viewModel) {
|
|
||||||
onDispose {
|
|
||||||
viewModel.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return viewModel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
inline fun <reified VM : ViewModel> viewModel(
|
inline fun <reified VM : ViewModel> Screen.viewModel(
|
||||||
key: Any? = Unit,
|
tag: String? = null,
|
||||||
crossinline factory: @DisallowComposableCalls ViewModelFactory.() -> VM
|
crossinline factory: @DisallowComposableCalls ViewModelFactory.() -> VM
|
||||||
): VM {
|
): VM {
|
||||||
val viewModelFactory = LocalViewModelFactory.current
|
val viewModelFactory = LocalViewModelFactory.current
|
||||||
val viewModel = remember(key) {
|
return rememberScreenModel(tag) { viewModelFactory.factory() }
|
||||||
viewModelFactory.factory()
|
|
||||||
}
|
|
||||||
DisposableEffect(viewModel) {
|
|
||||||
onDispose {
|
|
||||||
viewModel.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return viewModel
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,23 +8,17 @@ package ca.gosyer.uicore.vm
|
|||||||
|
|
||||||
import ca.gosyer.core.prefs.Preference
|
import ca.gosyer.core.prefs.Preference
|
||||||
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
|
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
|
||||||
import kotlinx.coroutines.MainScope
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import kotlinx.coroutines.cancel
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
abstract class ViewModel {
|
abstract class ViewModel : ScreenModel {
|
||||||
|
|
||||||
protected val scope = MainScope()
|
protected open val scope
|
||||||
|
get() = coroutineScope
|
||||||
fun destroy() {
|
|
||||||
scope.cancel()
|
|
||||||
onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun onDestroy() {}
|
|
||||||
|
|
||||||
fun <T> Preference<T>.asStateFlow() = PreferenceMutableStateFlow(this, scope)
|
fun <T> Preference<T>.asStateFlow() = PreferenceMutableStateFlow(this, scope)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user