mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-11 15:22:03 +01:00
Many updates and fixes
- Use multiplatform dialogs library(WIP) - Port accompanist to multiplatform - Use new gradle type-safe project accessors - Cleanup and improve a few viewmodels - Start moving presentation to multiplatform
This commit is contained in:
@@ -8,7 +8,7 @@ plugins {
|
|||||||
kotlin("plugin.serialization") version "1.6.10" apply false
|
kotlin("plugin.serialization") version "1.6.10" apply false
|
||||||
id("com.android.library") version "7.0.4" apply false
|
id("com.android.library") version "7.0.4" apply false
|
||||||
id("com.android.application") version "7.0.4" apply false
|
id("com.android.application") version "7.0.4" apply false
|
||||||
id("org.jetbrains.compose") version "1.0.1" apply false
|
id("org.jetbrains.compose") version "1.1.0-alpha03" apply false
|
||||||
id("com.google.devtools.ksp") version "1.6.10-1.0.2"
|
id("com.google.devtools.ksp") version "1.6.10-1.0.2"
|
||||||
id("com.github.gmazzo.buildconfig") version "3.0.3" apply false
|
id("com.github.gmazzo.buildconfig") version "3.0.3" apply false
|
||||||
id("com.codingfeline.buildkonfig") version "0.11.0" apply false
|
id("com.codingfeline.buildkonfig") version "0.11.0" apply false
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ kotlin {
|
|||||||
api(libs.ktorWebsockets)
|
api(libs.ktorWebsockets)
|
||||||
api(libs.ktorOkHttp)
|
api(libs.ktorOkHttp)
|
||||||
api(libs.okio)
|
api(libs.okio)
|
||||||
api(project(":core"))
|
api(projects.core)
|
||||||
api(project(":i18n"))
|
api(projects.i18n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import ca.gosyer.core.prefs.Preference
|
|||||||
import ca.gosyer.core.prefs.PreferenceStore
|
import ca.gosyer.core.prefs.PreferenceStore
|
||||||
import ca.gosyer.data.server.host.ServerHostPreference
|
import ca.gosyer.data.server.host.ServerHostPreference
|
||||||
|
|
||||||
class ServerHostPreferences(preferenceStore: PreferenceStore) {
|
class ServerHostPreferences(private val preferenceStore: PreferenceStore) {
|
||||||
|
|
||||||
|
fun host(): Preference<Boolean> {
|
||||||
|
return preferenceStore.getBoolean("host", true)
|
||||||
|
}
|
||||||
|
|
||||||
private val ip = ServerHostPreference.IP(preferenceStore)
|
private val ip = ServerHostPreference.IP(preferenceStore)
|
||||||
fun ip(): Preference<String> {
|
fun ip(): Preference<String> {
|
||||||
|
|||||||
@@ -40,11 +40,10 @@ import kotlin.io.path.isExecutable
|
|||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
class ServerService @Inject constructor(
|
class ServerService @Inject constructor(
|
||||||
serverPreferences: ServerPreferences,
|
|
||||||
private val serverHostPreferences: ServerHostPreferences
|
private val serverHostPreferences: ServerHostPreferences
|
||||||
) {
|
) {
|
||||||
private val restartServerFlow = MutableSharedFlow<Unit>()
|
private val restartServerFlow = MutableSharedFlow<Unit>()
|
||||||
private val host = serverPreferences.host().stateIn(GlobalScope)
|
private val host = serverHostPreferences.host().stateIn(GlobalScope)
|
||||||
private val _initialized = MutableStateFlow(
|
private val _initialized = MutableStateFlow(
|
||||||
if (host.value) {
|
if (host.value) {
|
||||||
ServerResult.STARTING
|
ServerResult.STARTING
|
||||||
|
|||||||
@@ -13,10 +13,6 @@ import ca.gosyer.data.server.model.Proxy
|
|||||||
|
|
||||||
class ServerPreferences(private val preferenceStore: PreferenceStore) {
|
class ServerPreferences(private val preferenceStore: PreferenceStore) {
|
||||||
|
|
||||||
fun host(): Preference<Boolean> {
|
|
||||||
return preferenceStore.getBoolean("host", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun server(): Preference<String> {
|
fun server(): Preference<String> {
|
||||||
return preferenceStore.getString("server_url", "http://localhost")
|
return preferenceStore.getString("server_url", "http://localhost")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":core"))
|
implementation(projects.core)
|
||||||
implementation(project(":i18n"))
|
implementation(projects.i18n)
|
||||||
implementation(project(":data"))
|
implementation(projects.data)
|
||||||
implementation(project(":ui-core"))
|
implementation(projects.uiCore)
|
||||||
implementation(project(":presentation"))
|
implementation(projects.presentation)
|
||||||
|
|
||||||
// UI (Compose)
|
// UI (Compose)
|
||||||
implementation(compose.desktop.currentOs)
|
implementation(compose.desktop.currentOs)
|
||||||
@@ -33,6 +33,7 @@ dependencies {
|
|||||||
implementation(libs.accompanistPager)
|
implementation(libs.accompanistPager)
|
||||||
implementation(libs.accompanistFlowLayout)
|
implementation(libs.accompanistFlowLayout)
|
||||||
implementation(libs.kamel)
|
implementation(libs.kamel)
|
||||||
|
implementation(libs.materialDialogsCore)
|
||||||
|
|
||||||
// UI (Swing)
|
// UI (Swing)
|
||||||
implementation(libs.darklaf)
|
implementation(libs.darklaf)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ package ca.gosyer
|
|||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
|
||||||
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.collectAsState
|
||||||
@@ -20,6 +19,8 @@ import androidx.compose.ui.input.key.KeyEventType
|
|||||||
import androidx.compose.ui.input.key.key
|
import androidx.compose.ui.input.key.key
|
||||||
import androidx.compose.ui.input.key.type
|
import androidx.compose.ui.input.key.type
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.awaitApplication
|
import androidx.compose.ui.window.awaitApplication
|
||||||
import androidx.compose.ui.window.rememberWindowState
|
import androidx.compose.ui.window.rememberWindowState
|
||||||
@@ -32,7 +33,7 @@ import ca.gosyer.data.ui.model.ThemeMode
|
|||||||
import ca.gosyer.desktop.build.BuildConfig
|
import ca.gosyer.desktop.build.BuildConfig
|
||||||
import ca.gosyer.i18n.MR
|
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.dialog.getMaterialDialogProperties
|
||||||
import ca.gosyer.ui.base.theme.AppTheme
|
import ca.gosyer.ui.base.theme.AppTheme
|
||||||
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.DebugOverlay
|
||||||
@@ -44,6 +45,10 @@ 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.vanpra.composematerialdialogs.MaterialDialog
|
||||||
|
import com.vanpra.composematerialdialogs.message
|
||||||
|
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.title
|
||||||
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
|
||||||
@@ -130,15 +135,12 @@ suspend fun main() {
|
|||||||
|
|
||||||
Tray(icon)
|
Tray(icon)
|
||||||
|
|
||||||
|
val confirmExitDialogState = rememberMaterialDialogState()
|
||||||
|
|
||||||
Window(
|
Window(
|
||||||
onCloseRequest = {
|
onCloseRequest = {
|
||||||
if (confirmExit.value) {
|
if (confirmExit.value) {
|
||||||
WindowDialog(
|
confirmExitDialogState.show()
|
||||||
title = MR.strings.confirm_exit.localized(),
|
|
||||||
onPositiveButton = ::exitApplication
|
|
||||||
) {
|
|
||||||
Text(stringResource(MR.strings.confirm_exit_message))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
exitApplication()
|
exitApplication()
|
||||||
}
|
}
|
||||||
@@ -186,6 +188,20 @@ suspend fun main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MaterialDialog(
|
||||||
|
confirmExitDialogState,
|
||||||
|
buttons = {
|
||||||
|
positiveButton(stringResource(MR.strings.action_ok), onClick = ::exitApplication)
|
||||||
|
negativeButton(stringResource(MR.strings.action_cancel))
|
||||||
|
},
|
||||||
|
properties = getMaterialDialogProperties(
|
||||||
|
size = DpSize(400.dp, 200.dp)
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
title(stringResource(MR.strings.confirm_exit))
|
||||||
|
message(stringResource(MR.strings.confirm_exit_message))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ xmlUtil = "0.84.0"
|
|||||||
|
|
||||||
# Compose
|
# Compose
|
||||||
voyager = "1.0.0-beta15"
|
voyager = "1.0.0-beta15"
|
||||||
accompanist = "0.18.1"
|
accompanist = "0.20.1"
|
||||||
kamel = "0.3.0"
|
kamel = "0.3.0"
|
||||||
|
materialDialogs = "0.6.4"
|
||||||
|
|
||||||
# Swing
|
# Swing
|
||||||
darklaf = "2.7.3"
|
darklaf = "2.7.3"
|
||||||
@@ -58,6 +59,7 @@ voyagerTransitions = { module = "cafe.adriel.voyager:voyager-transitions", versi
|
|||||||
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" }
|
||||||
|
materialDialogsCore = { module = "ca.gosyer:compose-material-dialogs-core", version.ref = "materialDialogs" }
|
||||||
|
|
||||||
# Swing
|
# Swing
|
||||||
darklaf = { module = "com.github.weisj:darklaf-core", version.ref = "darklaf" }
|
darklaf = { module = "com.github.weisj:darklaf-core", version.ref = "darklaf" }
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
BIN
i18n/src/commonMain/resources/MR/images/icon@1x.png
Normal file
BIN
i18n/src/commonMain/resources/MR/images/icon@1x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -41,6 +41,8 @@
|
|||||||
<string name="action_close">Close</string>
|
<string name="action_close">Close</string>
|
||||||
<string name="action_search">Search</string>
|
<string name="action_search">Search</string>
|
||||||
<string name="action_searching">Search…</string>
|
<string name="action_searching">Search…</string>
|
||||||
|
<string name="action_more_actions">More actions</string>
|
||||||
|
<string name="action_ok">Ok</string>
|
||||||
|
|
||||||
<!-- Locations -->
|
<!-- Locations -->
|
||||||
<string name="location_library">Library</string>
|
<string name="location_library">Library</string>
|
||||||
|
|||||||
@@ -46,10 +46,11 @@ kotlin {
|
|||||||
api(libs.voyagerCore)
|
api(libs.voyagerCore)
|
||||||
api(libs.voyagerNavigation)
|
api(libs.voyagerNavigation)
|
||||||
api(libs.voyagerTransitions)
|
api(libs.voyagerTransitions)
|
||||||
api(project(":core"))
|
api(libs.materialDialogsCore)
|
||||||
api(project(":i18n"))
|
api(projects.core)
|
||||||
api(project(":data"))
|
api(projects.i18n)
|
||||||
api(project(":ui-core"))
|
api(projects.data)
|
||||||
|
api(projects.uiCore)
|
||||||
api(compose.desktop.currentOs)
|
api(compose.desktop.currentOs)
|
||||||
api(compose("org.jetbrains.compose.ui:ui-util"))
|
api(compose("org.jetbrains.compose.ui:ui-util"))
|
||||||
api(compose.materialIconsExtended)
|
api(compose.materialIconsExtended)
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.base.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
actual interface ScrollbarAdapter {
|
||||||
|
fun noop()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual class ScrollbarStyle
|
||||||
|
|
||||||
|
actual val LocalScrollbarStyle: ProvidableCompositionLocal<ScrollbarStyle> = staticCompositionLocalOf { ScrollbarStyle() }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun VerticalScrollbar(
|
||||||
|
adapter: ScrollbarAdapter,
|
||||||
|
modifier: Modifier,
|
||||||
|
reverseLayout: Boolean,
|
||||||
|
style: ScrollbarStyle,
|
||||||
|
interactionSource: MutableInteractionSource
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun rememberScrollbarAdapter(
|
||||||
|
scrollState: ScrollState
|
||||||
|
): ScrollbarAdapter {
|
||||||
|
return remember {
|
||||||
|
object : ScrollbarAdapter {
|
||||||
|
override fun noop() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun rememberScrollbarAdapter(
|
||||||
|
scrollState: LazyListState,
|
||||||
|
): ScrollbarAdapter {
|
||||||
|
return remember {
|
||||||
|
object : ScrollbarAdapter {
|
||||||
|
override fun noop() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* 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.base.navigation
|
||||||
|
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
|
||||||
|
// todo
|
||||||
|
@Composable
|
||||||
|
actual fun ActionIcon(onClick: () -> Unit, contentDescription: String, icon: ImageVector) {
|
||||||
|
IconButton(onClick = onClick) {
|
||||||
|
Icon(icon, contentDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* 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.base.theme
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import ca.gosyer.ui.base.components.ScrollbarStyle
|
||||||
|
|
||||||
|
actual object ThemeScrollbarStyle {
|
||||||
|
@Composable
|
||||||
|
actual fun getScrollbarStyle(): ScrollbarStyle {
|
||||||
|
return ScrollbarStyle()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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.base.vm
|
||||||
|
|
||||||
|
import ca.gosyer.ui.base.theme.AppThemeViewModel
|
||||||
|
import ca.gosyer.ui.updates.UpdatesScreenViewModel
|
||||||
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
|
import ca.gosyer.uicore.vm.ViewModelFactory
|
||||||
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
actual class ViewModelFactoryImpl(
|
||||||
|
private val appThemeFactory: () -> AppThemeViewModel,
|
||||||
|
private val updatesFactory: () -> UpdatesScreenViewModel
|
||||||
|
) : ViewModelFactory() {
|
||||||
|
|
||||||
|
override fun <VM : ViewModel> instantiate(klass: KClass<VM>, arg1: Any?): VM {
|
||||||
|
@Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
|
||||||
|
return when (klass) {
|
||||||
|
AppThemeViewModel::class -> appThemeFactory()
|
||||||
|
UpdatesScreenViewModel::class -> updatesFactory()
|
||||||
|
else -> throw IllegalArgumentException("Unknown ViewModel $klass")
|
||||||
|
} as VM
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,180 +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.base
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraintsScope
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.RowScope
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.OutlinedButton
|
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.input.key.Key
|
|
||||||
import androidx.compose.ui.input.key.KeyEvent
|
|
||||||
import androidx.compose.ui.input.key.key
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.DpSize
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.window.Window
|
|
||||||
import androidx.compose.ui.window.WindowPosition
|
|
||||||
import androidx.compose.ui.window.rememberWindowState
|
|
||||||
import ca.gosyer.ui.AppComponent
|
|
||||||
import ca.gosyer.ui.base.theme.AppTheme
|
|
||||||
import ca.gosyer.ui.util.lang.launchApplication
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
@Suppress("FunctionName")
|
|
||||||
fun WindowDialog(
|
|
||||||
title: String = "Dialog",
|
|
||||||
size: DpSize = DpSize(400.dp, 200.dp),
|
|
||||||
onCloseRequest: (() -> Unit)? = null,
|
|
||||||
forceFocus: Boolean = true,
|
|
||||||
showNegativeButton: Boolean = true,
|
|
||||||
negativeButtonText: String = "Cancel",
|
|
||||||
onNegativeButton: (() -> Unit)? = null,
|
|
||||||
positiveButtonText: String = "OK",
|
|
||||||
onPositiveButton: (() -> Unit)? = null,
|
|
||||||
keyboardShortcuts: Map<Key, (KeyEvent) -> Boolean> = emptyMap(),
|
|
||||||
row: @Composable RowScope.() -> Unit
|
|
||||||
) = launchApplication {
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
onDispose {
|
|
||||||
onCloseRequest?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun (() -> Unit)?.plusClose(): (() -> Unit) = {
|
|
||||||
this?.invoke()
|
|
||||||
exitApplication()
|
|
||||||
}
|
|
||||||
|
|
||||||
val icon = painterResource("icon.png")
|
|
||||||
val hooks = AppComponent.getInstance().uiComponent.getHooks()
|
|
||||||
val windowState = rememberWindowState(size = size, position = WindowPosition(Alignment.Center))
|
|
||||||
|
|
||||||
Window(
|
|
||||||
title = title,
|
|
||||||
icon = icon,
|
|
||||||
state = windowState,
|
|
||||||
onCloseRequest = ::exitApplication,
|
|
||||||
onKeyEvent = {
|
|
||||||
when {
|
|
||||||
it.key == Key.Enter -> {
|
|
||||||
onPositiveButton.plusClose()()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
it.key == Key.Escape -> {
|
|
||||||
onNegativeButton.plusClose()()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
keyboardShortcuts[it.key] != null -> {
|
|
||||||
keyboardShortcuts[it.key]?.invoke(it) ?: false
|
|
||||||
}
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
alwaysOnTop = forceFocus
|
|
||||||
) {
|
|
||||||
CompositionLocalProvider(
|
|
||||||
*hooks
|
|
||||||
) {
|
|
||||||
AppTheme {
|
|
||||||
Surface {
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
Row(
|
|
||||||
content = row,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.Bottom,
|
|
||||||
horizontalArrangement = Arrangement.End,
|
|
||||||
modifier = Modifier.height(70.dp)
|
|
||||||
.align(Alignment.BottomEnd)
|
|
||||||
) {
|
|
||||||
if (showNegativeButton) {
|
|
||||||
OutlinedButton(onNegativeButton.plusClose(), modifier = Modifier.padding(end = 8.dp, bottom = 8.dp)) {
|
|
||||||
Text(negativeButtonText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OutlinedButton(onPositiveButton.plusClose(), modifier = Modifier.padding(end = 8.dp, bottom = 8.dp)) {
|
|
||||||
Text(positiveButtonText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun WindowDialog(
|
|
||||||
title: String = "Dialog",
|
|
||||||
size: DpSize = DpSize(400.dp, 200.dp),
|
|
||||||
onCloseRequest: (() -> Unit)? = null,
|
|
||||||
forceFocus: Boolean = true,
|
|
||||||
keyboardShortcuts: Map<Key, (KeyEvent) -> Boolean> = emptyMap(),
|
|
||||||
buttons: @Composable BoxWithConstraintsScope.(() -> Unit) -> Unit,
|
|
||||||
content: @Composable BoxWithConstraintsScope.(() -> Unit) -> Unit
|
|
||||||
) = launchApplication {
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
onDispose {
|
|
||||||
onCloseRequest?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val icon = painterResource("icon.png")
|
|
||||||
val hooks = AppComponent.getInstance().uiComponent.getHooks()
|
|
||||||
val windowState = rememberWindowState(size = size, position = WindowPosition.Aligned(Alignment.Center))
|
|
||||||
|
|
||||||
Window(
|
|
||||||
title = title,
|
|
||||||
icon = icon,
|
|
||||||
state = windowState,
|
|
||||||
onCloseRequest = ::exitApplication,
|
|
||||||
onKeyEvent = {
|
|
||||||
when {
|
|
||||||
keyboardShortcuts[it.key] != null -> {
|
|
||||||
keyboardShortcuts[it.key]?.invoke(it) ?: false
|
|
||||||
}
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
alwaysOnTop = forceFocus,
|
|
||||||
) {
|
|
||||||
CompositionLocalProvider(
|
|
||||||
*hooks
|
|
||||||
) {
|
|
||||||
AppTheme {
|
|
||||||
Surface {
|
|
||||||
Column {
|
|
||||||
BoxWithConstraints(
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
content(::exitApplication)
|
|
||||||
buttons(::exitApplication)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.base.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
actual typealias ScrollbarAdapter = androidx.compose.foundation.ScrollbarAdapter
|
||||||
|
|
||||||
|
actual typealias ScrollbarStyle = androidx.compose.foundation.ScrollbarStyle
|
||||||
|
|
||||||
|
actual val LocalScrollbarStyle: ProvidableCompositionLocal<ScrollbarStyle>
|
||||||
|
get() = androidx.compose.foundation.LocalScrollbarStyle
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun VerticalScrollbar(
|
||||||
|
adapter: ScrollbarAdapter,
|
||||||
|
modifier: Modifier,
|
||||||
|
reverseLayout: Boolean,
|
||||||
|
style: ScrollbarStyle,
|
||||||
|
interactionSource: MutableInteractionSource
|
||||||
|
) = androidx.compose.foundation.VerticalScrollbar(
|
||||||
|
adapter, modifier, reverseLayout, style, interactionSource
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun rememberScrollbarAdapter(
|
||||||
|
scrollState: ScrollState
|
||||||
|
): ScrollbarAdapter {
|
||||||
|
return androidx.compose.foundation.rememberScrollbarAdapter(scrollState)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun rememberScrollbarAdapter(
|
||||||
|
scrollState: LazyListState,
|
||||||
|
): ScrollbarAdapter {
|
||||||
|
return androidx.compose.foundation.rememberScrollbarAdapter(scrollState)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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.base.navigation
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ca.gosyer.uicore.components.BoxWithTooltipSurface
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun ActionIcon(onClick: () -> Unit, contentDescription: String, icon: ImageVector) {
|
||||||
|
BoxWithTooltipSurface(
|
||||||
|
{
|
||||||
|
Text(contentDescription, modifier = Modifier.padding(10.dp))
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
IconButton(onClick = onClick) {
|
||||||
|
Icon(icon, contentDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.base.theme
|
||||||
|
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ca.gosyer.ui.base.components.ScrollbarStyle
|
||||||
|
|
||||||
|
actual object ThemeScrollbarStyle {
|
||||||
|
@Composable
|
||||||
|
actual fun getScrollbarStyle(): ScrollbarStyle {
|
||||||
|
return androidx.compose.foundation.ScrollbarStyle(
|
||||||
|
minimalHeight = 16.dp,
|
||||||
|
thickness = 8.dp,
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
hoverDurationMillis = 300,
|
||||||
|
unhoverColor = MaterialTheme.colors.onSurface.copy(alpha = 0.30f),
|
||||||
|
hoverColor = MaterialTheme.colors.onSurface.copy(alpha = 0.70f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import ca.gosyer.ui.settings.SettingsBackupViewModel
|
|||||||
import ca.gosyer.ui.settings.SettingsGeneralViewModel
|
import ca.gosyer.ui.settings.SettingsGeneralViewModel
|
||||||
import ca.gosyer.ui.settings.SettingsLibraryViewModel
|
import ca.gosyer.ui.settings.SettingsLibraryViewModel
|
||||||
import ca.gosyer.ui.settings.SettingsReaderViewModel
|
import ca.gosyer.ui.settings.SettingsReaderViewModel
|
||||||
|
import ca.gosyer.ui.settings.SettingsServerHostViewModel
|
||||||
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.SourcesScreenViewModel
|
import ca.gosyer.ui.sources.SourcesScreenViewModel
|
||||||
@@ -35,7 +36,7 @@ import me.tatarka.inject.annotations.Inject
|
|||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class ViewModelFactoryImpl(
|
actual class ViewModelFactoryImpl(
|
||||||
private val appThemeFactory: () -> AppThemeViewModel,
|
private val appThemeFactory: () -> AppThemeViewModel,
|
||||||
private val categoryFactory: () -> CategoriesScreenViewModel,
|
private val categoryFactory: () -> CategoriesScreenViewModel,
|
||||||
private val downloadsFactory: (Boolean) -> DownloadsScreenViewModel,
|
private val downloadsFactory: (Boolean) -> DownloadsScreenViewModel,
|
||||||
@@ -53,6 +54,7 @@ class ViewModelFactoryImpl(
|
|||||||
private val settingsLibraryFactory: () -> SettingsLibraryViewModel,
|
private val settingsLibraryFactory: () -> SettingsLibraryViewModel,
|
||||||
private val settingsReaderFactory: () -> SettingsReaderViewModel,
|
private val settingsReaderFactory: () -> SettingsReaderViewModel,
|
||||||
private val settingsServerFactory: () -> SettingsServerViewModel,
|
private val settingsServerFactory: () -> SettingsServerViewModel,
|
||||||
|
private val settingsServerHostFactory: () -> SettingsServerHostViewModel,
|
||||||
private val sourceFiltersFactory: (params: SourceFiltersViewModel.Params) -> SourceFiltersViewModel,
|
private val sourceFiltersFactory: (params: SourceFiltersViewModel.Params) -> SourceFiltersViewModel,
|
||||||
private val sourceSettingsFactory: (params: SourceSettingsScreenViewModel.Params) -> SourceSettingsScreenViewModel,
|
private val sourceSettingsFactory: (params: SourceSettingsScreenViewModel.Params) -> SourceSettingsScreenViewModel,
|
||||||
private val sourceHomeFactory: () -> SourceHomeScreenViewModel,
|
private val sourceHomeFactory: () -> SourceHomeScreenViewModel,
|
||||||
@@ -81,6 +83,7 @@ class ViewModelFactoryImpl(
|
|||||||
SettingsLibraryViewModel::class -> settingsLibraryFactory()
|
SettingsLibraryViewModel::class -> settingsLibraryFactory()
|
||||||
SettingsReaderViewModel::class -> settingsReaderFactory()
|
SettingsReaderViewModel::class -> settingsReaderFactory()
|
||||||
SettingsServerViewModel::class -> settingsServerFactory()
|
SettingsServerViewModel::class -> settingsServerFactory()
|
||||||
|
SettingsServerHostViewModel::class -> settingsServerHostFactory()
|
||||||
SourceFiltersViewModel::class -> sourceFiltersFactory(arg1 as SourceFiltersViewModel.Params)
|
SourceFiltersViewModel::class -> sourceFiltersFactory(arg1 as SourceFiltersViewModel.Params)
|
||||||
SourceSettingsScreenViewModel::class -> sourceSettingsFactory(arg1 as SourceSettingsScreenViewModel.Params)
|
SourceSettingsScreenViewModel::class -> sourceSettingsFactory(arg1 as SourceSettingsScreenViewModel.Params)
|
||||||
SourceHomeScreenViewModel::class -> sourceHomeFactory()
|
SourceHomeScreenViewModel::class -> sourceHomeFactory()
|
||||||
|
|||||||
@@ -6,81 +6,98 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.categories.components
|
package ca.gosyer.ui.categories.components
|
||||||
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.TextField
|
import androidx.compose.material.TextField
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
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.ui.base.dialog.getMaterialDialogProperties
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
|
||||||
import ca.gosyer.ui.categories.CategoriesScreenViewModel
|
import ca.gosyer.ui.categories.CategoriesScreenViewModel
|
||||||
import ca.gosyer.uicore.components.keyboardHandler
|
import ca.gosyer.uicore.components.keyboardHandler
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import com.vanpra.composematerialdialogs.MaterialDialog
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.message
|
||||||
|
import com.vanpra.composematerialdialogs.title
|
||||||
|
|
||||||
fun openRenameDialog(
|
@Composable
|
||||||
|
fun RenameDialog(
|
||||||
|
state: MaterialDialogState,
|
||||||
category: CategoriesScreenViewModel.MenuCategory,
|
category: CategoriesScreenViewModel.MenuCategory,
|
||||||
onRename: (String) -> Unit
|
onRename: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val newName = MutableStateFlow(TextFieldValue(category.name))
|
var newName by remember { mutableStateOf(TextFieldValue(category.name)) }
|
||||||
|
|
||||||
WindowDialog(
|
MaterialDialog(
|
||||||
title = "${BuildKonfig.NAME} - Categories - Rename Dialog",
|
state,
|
||||||
positiveButtonText = "Rename",
|
buttons = {
|
||||||
onPositiveButton = {
|
positiveButton(stringResource(MR.strings.action_rename)) {
|
||||||
if (newName.value.text != category.name) {
|
if (newName.text != category.name) {
|
||||||
onRename(newName.value.text)
|
onRename(newName.text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
negativeButton(stringResource(MR.strings.action_cancel))
|
||||||
|
},
|
||||||
|
properties = getMaterialDialogProperties(),
|
||||||
) {
|
) {
|
||||||
val newNameState by newName.collectAsState()
|
title("Rename Category")
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
newNameState,
|
newName,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
newName.value = it
|
newName = it
|
||||||
},
|
},
|
||||||
modifier = Modifier.keyboardHandler(singleLine = true)
|
modifier = Modifier.keyboardHandler(singleLine = true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openDeleteDialog(
|
@Composable
|
||||||
|
fun DeleteDialog(
|
||||||
|
state: MaterialDialogState,
|
||||||
category: CategoriesScreenViewModel.MenuCategory,
|
category: CategoriesScreenViewModel.MenuCategory,
|
||||||
onDelete: (CategoriesScreenViewModel.MenuCategory) -> Unit
|
onDelete: (CategoriesScreenViewModel.MenuCategory) -> Unit
|
||||||
) {
|
) {
|
||||||
WindowDialog(
|
MaterialDialog(
|
||||||
title = "${BuildKonfig.NAME} - Categories - Delete Dialog",
|
state,
|
||||||
positiveButtonText = "Yes",
|
buttons = {
|
||||||
onPositiveButton = {
|
positiveButton(stringResource(MR.strings.action_yes)) {
|
||||||
onDelete(category)
|
onDelete(category)
|
||||||
|
}
|
||||||
|
negativeButton(stringResource(MR.strings.action_no))
|
||||||
},
|
},
|
||||||
negativeButtonText = "No"
|
properties = getMaterialDialogProperties(),
|
||||||
) {
|
) {
|
||||||
Text(stringResource(MR.strings.categories_delete_confirm, category.name))
|
title("Delete Category")
|
||||||
|
message(stringResource(MR.strings.categories_delete_confirm, category.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openCreateDialog(
|
@Composable
|
||||||
|
fun CreateDialog(
|
||||||
|
state: MaterialDialogState,
|
||||||
onCreate: (String) -> Unit
|
onCreate: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val name = MutableStateFlow(TextFieldValue(""))
|
var name by remember { mutableStateOf(TextFieldValue("")) }
|
||||||
|
|
||||||
WindowDialog(
|
MaterialDialog(
|
||||||
title = "${BuildKonfig.NAME} - Categories - Create Dialog",
|
state,
|
||||||
positiveButtonText = "Create",
|
buttons = {
|
||||||
onPositiveButton = {
|
positiveButton(stringResource(MR.strings.action_create)) {
|
||||||
onCreate(name.value.text)
|
onCreate(name.text)
|
||||||
}
|
}
|
||||||
|
negativeButton(stringResource(MR.strings.action_cancel))
|
||||||
|
},
|
||||||
|
properties = getMaterialDialogProperties(),
|
||||||
) {
|
) {
|
||||||
val nameState by name.collectAsState()
|
title("Create Category")
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
nameState,
|
name,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
name.value = it
|
name = it
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.keyboardHandler(singleLine = true)
|
modifier = Modifier.keyboardHandler(singleLine = true)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.categories.CategoriesScreenViewModel.MenuCategory
|
import ca.gosyer.ui.categories.CategoriesScreenViewModel.MenuCategory
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
@@ -77,11 +78,16 @@ fun CategoriesScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val createDialogState = rememberMaterialDialogState()
|
||||||
|
|
||||||
Surface {
|
Surface {
|
||||||
Box {
|
Box {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
LazyColumn(modifier = Modifier.fillMaxSize(), state = state,) {
|
LazyColumn(modifier = Modifier.fillMaxSize(), state = state,) {
|
||||||
itemsIndexed(categories) { i, category ->
|
itemsIndexed(categories) { i, category ->
|
||||||
|
val renameDialogState = rememberMaterialDialogState()
|
||||||
|
val deleteDialogState = rememberMaterialDialogState()
|
||||||
CategoryRow(
|
CategoryRow(
|
||||||
category = category,
|
category = category,
|
||||||
moveUpEnabled = i != 0,
|
moveUpEnabled = i != 0,
|
||||||
@@ -89,16 +95,18 @@ fun CategoriesScreenContent(
|
|||||||
onMoveUp = { moveCategoryUp(category) },
|
onMoveUp = { moveCategoryUp(category) },
|
||||||
onMoveDown = { moveCategoryDown(category) },
|
onMoveDown = { moveCategoryDown(category) },
|
||||||
onRename = {
|
onRename = {
|
||||||
openRenameDialog(category) {
|
renameDialogState.show()
|
||||||
renameCategory(category, it)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onDelete = {
|
onDelete = {
|
||||||
openDeleteDialog(category) {
|
deleteDialogState.show()
|
||||||
deleteCategory(category)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
RenameDialog(renameDialogState, category) {
|
||||||
|
renameCategory(category, it)
|
||||||
|
}
|
||||||
|
DeleteDialog(deleteDialogState, category) {
|
||||||
|
deleteCategory(category)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
Spacer(Modifier.height(80.dp).fillMaxWidth())
|
Spacer(Modifier.height(80.dp).fillMaxWidth())
|
||||||
@@ -109,9 +117,7 @@ fun CategoriesScreenContent(
|
|||||||
icon = { Icon(imageVector = Icons.Rounded.Add, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Rounded.Add, contentDescription = null) },
|
||||||
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
|
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
openCreateDialog {
|
createDialogState.show()
|
||||||
createCategory(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
VerticalScrollbar(
|
VerticalScrollbar(
|
||||||
@@ -122,6 +128,7 @@ fun CategoriesScreenContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CreateDialog(createDialogState, createCategory)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -6,38 +6,16 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.downloads
|
package ca.gosyer.ui.downloads
|
||||||
|
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
|
||||||
import ca.gosyer.ui.AppComponent
|
|
||||||
import ca.gosyer.ui.downloads.components.DownloadsScreenContent
|
import ca.gosyer.ui.downloads.components.DownloadsScreenContent
|
||||||
import ca.gosyer.ui.manga.MangaScreen
|
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 ca.gosyer.uicore.vm.viewModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun openDownloadsMenu() {
|
|
||||||
launchApplication {
|
|
||||||
CompositionLocalProvider(*remember { AppComponent.getInstance().uiComponent.getHooks() }) {
|
|
||||||
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
|
||||||
Surface {
|
|
||||||
Navigator(remember { DownloadsScreen() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DownloadsScreen : Screen {
|
class DownloadsScreen : Screen {
|
||||||
override val key: ScreenKey = uniqueScreenKey
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
|
|||||||
@@ -6,39 +6,13 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.extensions
|
package ca.gosyer.ui.extensions
|
||||||
|
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.collectAsState
|
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.AppComponent
|
|
||||||
import ca.gosyer.ui.extensions.components.ExtensionsScreenContent
|
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 ca.gosyer.uicore.vm.viewModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun openExtensionsMenu() {
|
|
||||||
launchApplication {
|
|
||||||
CompositionLocalProvider(*remember { AppComponent.getInstance().uiComponent.getHooks() }) {
|
|
||||||
val state = rememberWindowState(size = DpSize(550.dp, 700.dp))
|
|
||||||
ThemedWindow(::exitApplication, state, title = BuildKonfig.NAME) {
|
|
||||||
Surface {
|
|
||||||
Navigator(remember { ExtensionsScreen() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExtensionsScreen : Screen {
|
class ExtensionsScreen : Screen {
|
||||||
|
|
||||||
@@ -52,9 +26,9 @@ class ExtensionsScreen : Screen {
|
|||||||
extensions = vm.extensions.collectAsState().value,
|
extensions = vm.extensions.collectAsState().value,
|
||||||
isLoading = vm.isLoading.collectAsState().value,
|
isLoading = vm.isLoading.collectAsState().value,
|
||||||
query = vm.searchQuery.collectAsState().value,
|
query = vm.searchQuery.collectAsState().value,
|
||||||
setQuery = vm::search,
|
setQuery = vm::setQuery,
|
||||||
enabledLangs = vm.enabledLangs,
|
enabledLangs = vm.enabledLangs.collectAsState().value,
|
||||||
getSourceLanguages = vm::getSourceLanguages,
|
availableLangs = vm.availableLangs.collectAsState().value,
|
||||||
setEnabledLanguages = vm::setEnabledLanguages,
|
setEnabledLanguages = vm::setEnabledLanguages,
|
||||||
installExtension = vm::install,
|
installExtension = vm::install,
|
||||||
updateExtension = vm::update,
|
updateExtension = vm::update,
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ import ca.gosyer.data.server.interactions.ExtensionInteractionHandler
|
|||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -26,36 +28,43 @@ class ExtensionsScreenViewModel @Inject constructor(
|
|||||||
private val extensionHandler: ExtensionInteractionHandler,
|
private val extensionHandler: ExtensionInteractionHandler,
|
||||||
extensionPreferences: ExtensionPreferences
|
extensionPreferences: ExtensionPreferences
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
private val extensionList = MutableStateFlow<List<Extension>?>(null)
|
||||||
|
|
||||||
private val _enabledLangs = extensionPreferences.languages().asStateFlow()
|
private val _enabledLangs = extensionPreferences.languages().asStateFlow()
|
||||||
val enabledLangs = _enabledLangs.asStateFlow()
|
val enabledLangs = _enabledLangs.asStateFlow()
|
||||||
|
|
||||||
private var extensionList: List<Extension>? = null
|
private val _searchQuery = MutableStateFlow<String?>(null)
|
||||||
|
val searchQuery = _searchQuery.asStateFlow()
|
||||||
|
|
||||||
private val _extensions = MutableStateFlow(emptyMap<String, List<Extension>>())
|
val extensions = combine(
|
||||||
val extensions = _extensions.asStateFlow()
|
searchQuery,
|
||||||
|
extensionList,
|
||||||
|
enabledLangs
|
||||||
|
) { searchQuery, extensions, enabledLangs ->
|
||||||
|
search(searchQuery, extensions, enabledLangs)
|
||||||
|
}.stateIn(scope, SharingStarted.Eagerly, emptyMap())
|
||||||
|
|
||||||
|
val availableLangs = extensionList.filterNotNull().map { langs ->
|
||||||
|
langs.map { it.lang }.toSet()
|
||||||
|
}.stateIn(scope, SharingStarted.Eagerly, emptySet())
|
||||||
|
|
||||||
private val _isLoading = MutableStateFlow(true)
|
private val _isLoading = MutableStateFlow(true)
|
||||||
val isLoading = _isLoading.asStateFlow()
|
val isLoading = _isLoading.asStateFlow()
|
||||||
val searchQuery = MutableStateFlow<String?>(null)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
getExtensions()
|
getExtensions()
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledLangs.drop(1).onEach {
|
|
||||||
search(searchQuery.value.orEmpty())
|
|
||||||
}.launchIn(scope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getExtensions() {
|
private suspend fun getExtensions() {
|
||||||
try {
|
try {
|
||||||
_isLoading.value = true
|
_isLoading.value = true
|
||||||
extensionList = extensionHandler.getExtensionList()
|
extensionList.value = extensionHandler.getExtensionList()
|
||||||
search(searchQuery.value.orEmpty())
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.throwIfCancellation()
|
e.throwIfCancellation()
|
||||||
extensionList = emptyList()
|
extensionList.value = emptyList()
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -97,26 +106,26 @@ class ExtensionsScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSourceLanguages() = extensionList?.map { it.lang }?.toSet().orEmpty()
|
|
||||||
|
|
||||||
fun setEnabledLanguages(langs: Set<String>) {
|
fun setEnabledLanguages(langs: Set<String>) {
|
||||||
info { langs }
|
|
||||||
_enabledLangs.value = langs
|
_enabledLangs.value = langs
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search(searchQuery: String) {
|
fun setQuery(query: String) {
|
||||||
this.searchQuery.value = searchQuery.takeUnless { it.isBlank() }
|
_searchQuery.value = query
|
||||||
val extensionList = extensionList?.filter { it.lang in enabledLangs.value }
|
}
|
||||||
|
|
||||||
|
private fun search(searchQuery: String?, extensionList: List<Extension>?, enabledLangs: Set<String>): Map<String, List<Extension>> {
|
||||||
|
val extensions = extensionList?.filter { it.lang in enabledLangs }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
if (searchQuery.isBlank()) {
|
return if (searchQuery.isNullOrBlank()) {
|
||||||
_extensions.value = extensionList.splitSort()
|
extensions.splitSort()
|
||||||
} else {
|
} else {
|
||||||
val queries = searchQuery.split(" ")
|
val queries = searchQuery.split(" ")
|
||||||
val extensions = extensionList.toMutableList()
|
val filteredExtensions = extensions.toMutableList()
|
||||||
queries.forEach { query ->
|
queries.forEach { query ->
|
||||||
extensions.removeIf { !it.name.contains(query, true) }
|
filteredExtensions.removeIf { !it.name.contains(query, true) }
|
||||||
}
|
}
|
||||||
_extensions.value = extensions.toList().splitSort()
|
filteredExtensions.toList().splitSort()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,8 @@ 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.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.toMutableStateList
|
||||||
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
|
||||||
@@ -48,15 +47,17 @@ import androidx.compose.ui.unit.sp
|
|||||||
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
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
import ca.gosyer.ui.base.dialog.getMaterialDialogProperties
|
||||||
import ca.gosyer.ui.base.navigation.ActionItem
|
import ca.gosyer.ui.base.navigation.ActionItem
|
||||||
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.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialog
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.title
|
||||||
import io.kamel.image.lazyPainterResource
|
import io.kamel.image.lazyPainterResource
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -65,26 +66,25 @@ fun ExtensionsScreenContent(
|
|||||||
isLoading: Boolean,
|
isLoading: Boolean,
|
||||||
query: String?,
|
query: String?,
|
||||||
setQuery: (String) -> Unit,
|
setQuery: (String) -> Unit,
|
||||||
enabledLangs: StateFlow<Set<String>>,
|
enabledLangs: Set<String>,
|
||||||
getSourceLanguages: () -> Set<String>,
|
availableLangs: Set<String>,
|
||||||
setEnabledLanguages: (Set<String>) -> Unit,
|
setEnabledLanguages: (Set<String>) -> Unit,
|
||||||
installExtension: (Extension) -> Unit,
|
installExtension: (Extension) -> Unit,
|
||||||
updateExtension: (Extension) -> Unit,
|
updateExtension: (Extension) -> Unit,
|
||||||
uninstallExtension: (Extension) -> Unit
|
uninstallExtension: (Extension) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val languageDialogState = rememberMaterialDialogState()
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
ExtensionsToolbar(
|
ExtensionsToolbar(
|
||||||
query,
|
query,
|
||||||
setQuery,
|
setQuery,
|
||||||
enabledLangs,
|
languageDialogState::show
|
||||||
getSourceLanguages,
|
|
||||||
setEnabledLanguages
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
LoadingScreen(isLoading)
|
LoadingScreen()
|
||||||
} else {
|
} else {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
|
|
||||||
@@ -118,26 +118,21 @@ fun ExtensionsScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LanguageDialog(languageDialogState, enabledLangs, availableLangs, setEnabledLanguages)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionsToolbar(
|
fun ExtensionsToolbar(
|
||||||
searchText: String?,
|
searchText: String?,
|
||||||
search: (String) -> Unit,
|
search: (String) -> Unit,
|
||||||
currentEnabledLangs: StateFlow<Set<String>>,
|
openLanguageDialog: () -> Unit
|
||||||
getSourceLanguages: () -> Set<String>,
|
|
||||||
setEnabledLanguages: (Set<String>) -> Unit
|
|
||||||
) {
|
) {
|
||||||
Toolbar(
|
Toolbar(
|
||||||
stringResource(MR.strings.location_extensions),
|
stringResource(MR.strings.location_extensions),
|
||||||
searchText = searchText,
|
searchText = searchText,
|
||||||
search = search,
|
search = search,
|
||||||
actions = {
|
actions = {
|
||||||
getActionItems(
|
getActionItems(openLanguageDialog)
|
||||||
currentEnabledLangs = currentEnabledLangs,
|
|
||||||
getSourceLanguages = getSourceLanguages,
|
|
||||||
setEnabledLanguages = setEnabledLanguages
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -195,26 +190,42 @@ fun ExtensionItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LanguageDialog(enabledLangsFlow: MutableStateFlow<Set<String>>, availableLangs: List<String>, setLangs: () -> Unit) {
|
@Composable
|
||||||
WindowDialog(BuildKonfig.NAME, onPositiveButton = setLangs) {
|
fun LanguageDialog(
|
||||||
val locale = Locale.getDefault()
|
state: MaterialDialogState,
|
||||||
val enabledLangs by enabledLangsFlow.collectAsState()
|
enabledLangs: Set<String>,
|
||||||
val state = rememberLazyListState()
|
availableLangs: Set<String>,
|
||||||
|
setLangs: (Set<String>) -> Unit
|
||||||
|
) {
|
||||||
|
val modifiedLangs = remember(enabledLangs) { enabledLangs.toMutableStateList() }
|
||||||
|
MaterialDialog(
|
||||||
|
state,
|
||||||
|
buttons = {
|
||||||
|
positiveButton(stringResource(MR.strings.action_ok)) {
|
||||||
|
setLangs(modifiedLangs.toSet())
|
||||||
|
}
|
||||||
|
negativeButton(stringResource(MR.strings.action_cancel))
|
||||||
|
},
|
||||||
|
properties = getMaterialDialogProperties(),
|
||||||
|
) {
|
||||||
|
title(BuildKonfig.NAME)
|
||||||
Box {
|
Box {
|
||||||
LazyColumn(Modifier.fillMaxWidth(), state) {
|
val locale = remember { Locale.getDefault() }
|
||||||
items(availableLangs) { lang ->
|
val listState = rememberLazyListState()
|
||||||
|
LazyColumn(Modifier.fillMaxWidth(), listState) {
|
||||||
|
items(availableLangs.toList()) { lang ->
|
||||||
Row {
|
Row {
|
||||||
val langName = remember(lang) {
|
val langName = remember(lang) {
|
||||||
Locale.forLanguageTag(lang)?.getDisplayName(locale) ?: lang
|
Locale.forLanguageTag(lang)?.getDisplayName(locale) ?: lang
|
||||||
}
|
}
|
||||||
Text(langName)
|
Text(langName)
|
||||||
Switch(
|
Switch(
|
||||||
lang in enabledLangs,
|
checked = lang in modifiedLangs,
|
||||||
{
|
onCheckedChange = {
|
||||||
if (it) {
|
if (it) {
|
||||||
enabledLangsFlow.value += lang
|
modifiedLangs += lang
|
||||||
} else {
|
} else {
|
||||||
enabledLangsFlow.value -= lang
|
modifiedLangs -= lang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -223,7 +234,7 @@ fun LanguageDialog(enabledLangsFlow: MutableStateFlow<Set<String>>, availableLan
|
|||||||
item { Spacer(Modifier.height(70.dp)) }
|
item { Spacer(Modifier.height(70.dp)) }
|
||||||
}
|
}
|
||||||
VerticalScrollbar(
|
VerticalScrollbar(
|
||||||
rememberScrollbarAdapter(state),
|
rememberScrollbarAdapter(listState),
|
||||||
Modifier.align(Alignment.CenterEnd)
|
Modifier.align(Alignment.CenterEnd)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.padding(horizontal = 4.dp, vertical = 8.dp)
|
.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||||
@@ -235,19 +246,13 @@ fun LanguageDialog(enabledLangsFlow: MutableStateFlow<Set<String>>, availableLan
|
|||||||
@Stable
|
@Stable
|
||||||
@Composable
|
@Composable
|
||||||
private fun getActionItems(
|
private fun getActionItems(
|
||||||
currentEnabledLangs: StateFlow<Set<String>>,
|
openLanguageDialog: () -> Unit
|
||||||
getSourceLanguages: () -> Set<String>,
|
|
||||||
setEnabledLanguages: (Set<String>) -> Unit
|
|
||||||
): List<ActionItem> {
|
): List<ActionItem> {
|
||||||
return listOf(
|
return listOf(
|
||||||
ActionItem(
|
ActionItem(
|
||||||
stringResource(MR.strings.enabled_languages),
|
stringResource(MR.strings.enabled_languages),
|
||||||
Icons.Rounded.Translate
|
Icons.Rounded.Translate,
|
||||||
) {
|
doAction = openLanguageDialog
|
||||||
val enabledLangs = MutableStateFlow(currentEnabledLangs.value)
|
)
|
||||||
LanguageDialog(enabledLangs, getSourceLanguages().toList()) {
|
|
||||||
setEnabledLanguages(enabledLangs.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,38 +6,16 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.library
|
package ca.gosyer.ui.library
|
||||||
|
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
|
||||||
import ca.gosyer.ui.AppComponent
|
|
||||||
import ca.gosyer.ui.library.components.LibraryScreenContent
|
import ca.gosyer.ui.library.components.LibraryScreenContent
|
||||||
import ca.gosyer.ui.manga.MangaScreen
|
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 ca.gosyer.uicore.vm.viewModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun openLibraryMenu() {
|
|
||||||
launchApplication {
|
|
||||||
CompositionLocalProvider(*remember { AppComponent.getInstance().uiComponent.getHooks() }) {
|
|
||||||
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
|
||||||
Surface {
|
|
||||||
Navigator(remember { LibraryScreen() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LibraryScreen : Screen {
|
class LibraryScreen : Screen {
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ fun LibraryPager(
|
|||||||
) {
|
) {
|
||||||
if (categories.isEmpty()) return
|
if (categories.isEmpty()) return
|
||||||
|
|
||||||
val state = rememberPagerState(categories.size, selectedPage)
|
val state = rememberPagerState(selectedPage)
|
||||||
LaunchedEffect(state.currentPage) {
|
LaunchedEffect(state.currentPage) {
|
||||||
if (state.currentPage != selectedPage) {
|
if (state.currentPage != selectedPage) {
|
||||||
onPageChanged(state.currentPage)
|
onPageChanged(state.currentPage)
|
||||||
@@ -40,7 +40,7 @@ fun LibraryPager(
|
|||||||
state.animateScrollToPage(selectedPage)
|
state.animateScrollToPage(selectedPage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HorizontalPager(state = state) {
|
HorizontalPager(categories.size, state = state) {
|
||||||
val library by getLibraryForPage(categories[it].id)
|
val library by getLibraryForPage(categories[it].id)
|
||||||
when (displayMode) {
|
when (displayMode) {
|
||||||
DisplayMode.CompactGrid -> LibraryMangaCompactGrid(
|
DisplayMode.CompactGrid -> LibraryMangaCompactGrid(
|
||||||
|
|||||||
@@ -26,14 +26,11 @@ 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.downloads.DownloadsScreen
|
||||||
import ca.gosyer.ui.extensions.ExtensionsScreen
|
import ca.gosyer.ui.extensions.ExtensionsScreen
|
||||||
import ca.gosyer.ui.extensions.openExtensionsMenu
|
|
||||||
import ca.gosyer.ui.library.LibraryScreen
|
import ca.gosyer.ui.library.LibraryScreen
|
||||||
import ca.gosyer.ui.library.openLibraryMenu
|
|
||||||
import ca.gosyer.ui.main.components.DownloadsExtraInfo
|
import ca.gosyer.ui.main.components.DownloadsExtraInfo
|
||||||
import ca.gosyer.ui.main.more.MoreScreen
|
import ca.gosyer.ui.main.more.MoreScreen
|
||||||
import ca.gosyer.ui.settings.SettingsScreen
|
import ca.gosyer.ui.settings.SettingsScreen
|
||||||
import ca.gosyer.ui.sources.SourcesScreen
|
import ca.gosyer.ui.sources.SourcesScreen
|
||||||
import ca.gosyer.ui.sources.openSourcesMenu
|
|
||||||
import ca.gosyer.ui.updates.UpdatesScreen
|
import ca.gosyer.ui.updates.UpdatesScreen
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
@@ -46,7 +43,6 @@ interface Menu {
|
|||||||
val selectedIcon: ImageVector
|
val selectedIcon: ImageVector
|
||||||
val screen: KClass<*>
|
val screen: KClass<*>
|
||||||
val createScreen: () -> Screen
|
val createScreen: () -> Screen
|
||||||
val openInNewWindow: (() -> Unit)?
|
|
||||||
val extraInfo: (@Composable () -> Unit)?
|
val extraInfo: (@Composable () -> Unit)?
|
||||||
|
|
||||||
fun isSelected(navigator: Navigator) = navigator.items.first()::class == screen
|
fun isSelected(navigator: Navigator) = navigator.items.first()::class == screen
|
||||||
@@ -58,13 +54,12 @@ enum class TopLevelMenus(
|
|||||||
override val selectedIcon: ImageVector,
|
override val selectedIcon: ImageVector,
|
||||||
override val screen: KClass<*>,
|
override val screen: KClass<*>,
|
||||||
override val createScreen: () -> Screen,
|
override val createScreen: () -> Screen,
|
||||||
override val openInNewWindow: (() -> Unit)? = null,
|
|
||||||
override val extraInfo: (@Composable () -> Unit)? = null
|
override val extraInfo: (@Composable () -> Unit)? = null
|
||||||
) : Menu {
|
) : Menu {
|
||||||
Library(MR.strings.location_library, Icons.Outlined.Book, Icons.Rounded.Book, LibraryScreen::class, { LibraryScreen() }, ::openLibraryMenu),
|
Library(MR.strings.location_library, Icons.Outlined.Book, Icons.Rounded.Book, LibraryScreen::class, { LibraryScreen() }),
|
||||||
Updates(MR.strings.location_updates, Icons.Outlined.NewReleases, Icons.Rounded.NewReleases, UpdatesScreen::class, { UpdatesScreen() }, ::openLibraryMenu),
|
Updates(MR.strings.location_updates, Icons.Outlined.NewReleases, Icons.Rounded.NewReleases, UpdatesScreen::class, { UpdatesScreen() }),
|
||||||
Sources(MR.strings.location_sources, Icons.Outlined.Explore, Icons.Rounded.Explore, SourcesScreen::class, { SourcesScreen() }, ::openSourcesMenu),
|
Sources(MR.strings.location_sources, Icons.Outlined.Explore, Icons.Rounded.Explore, SourcesScreen::class, { SourcesScreen() }),
|
||||||
Extensions(MR.strings.location_extensions, Icons.Outlined.Store, Icons.Rounded.Store, ExtensionsScreen::class, { ExtensionsScreen() }, ::openExtensionsMenu),
|
Extensions(MR.strings.location_extensions, Icons.Outlined.Store, Icons.Rounded.Store, ExtensionsScreen::class, { ExtensionsScreen() }),
|
||||||
More(MR.strings.location_more, Icons.Outlined.MoreHoriz, Icons.Rounded.MoreHoriz, MoreScreen::class, { MoreScreen() });
|
More(MR.strings.location_more, Icons.Outlined.MoreHoriz, Icons.Rounded.MoreHoriz, MoreScreen::class, { MoreScreen() });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,9 +69,8 @@ enum class MoreMenus(
|
|||||||
override val selectedIcon: ImageVector,
|
override val selectedIcon: ImageVector,
|
||||||
override val screen: KClass<*>,
|
override val screen: KClass<*>,
|
||||||
override val createScreen: () -> Screen,
|
override val createScreen: () -> Screen,
|
||||||
override val openInNewWindow: (() -> Unit)? = null,
|
|
||||||
override val extraInfo: (@Composable () -> Unit)? = null
|
override val extraInfo: (@Composable () -> Unit)? = null
|
||||||
) : Menu {
|
) : Menu {
|
||||||
Downloads(MR.strings.location_downloads, Icons.Outlined.Download, Icons.Rounded.Download, DownloadsScreen::class, { DownloadsScreen() }, extraInfo = { DownloadsExtraInfo() }),
|
Downloads(MR.strings.location_downloads, Icons.Outlined.Download, Icons.Rounded.Download, DownloadsScreen::class, { DownloadsScreen() }, extraInfo = { DownloadsExtraInfo() }),
|
||||||
Settings(MR.strings.location_settings, Icons.Outlined.Settings, Icons.Rounded.Settings, SettingsScreen::class, { SettingsScreen() });
|
Settings(MR.strings.location_settings, Icons.Outlined.Settings, Icons.Rounded.Settings, SettingsScreen::class, { SettingsScreen() });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.main.components
|
package ca.gosyer.ui.main.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -25,7 +26,6 @@ import androidx.compose.ui.graphics.Color
|
|||||||
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.Menu
|
import ca.gosyer.ui.main.Menu
|
||||||
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
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
|
||||||
@@ -37,7 +37,6 @@ fun SideMenuItem(selected: Boolean, topLevelMenu: Menu, newRoot: (Screen) -> Uni
|
|||||||
topLevelMenu.createScreen,
|
topLevelMenu.createScreen,
|
||||||
topLevelMenu.selectedIcon,
|
topLevelMenu.selectedIcon,
|
||||||
topLevelMenu.unselectedIcon,
|
topLevelMenu.unselectedIcon,
|
||||||
topLevelMenu.openInNewWindow,
|
|
||||||
topLevelMenu.extraInfo,
|
topLevelMenu.extraInfo,
|
||||||
newRoot
|
newRoot
|
||||||
)
|
)
|
||||||
@@ -50,7 +49,6 @@ private fun SideMenuItem(
|
|||||||
createScreen: () -> Screen,
|
createScreen: () -> Screen,
|
||||||
selectedIcon: ImageVector,
|
selectedIcon: ImageVector,
|
||||||
unselectedIcon: ImageVector,
|
unselectedIcon: ImageVector,
|
||||||
onMiddleClick: (() -> Unit)?,
|
|
||||||
extraInfo: (@Composable () -> Unit)? = null,
|
extraInfo: (@Composable () -> Unit)? = null,
|
||||||
onClick: (Screen) -> Unit
|
onClick: (Screen) -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -68,9 +66,9 @@ private fun SideMenuItem(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
.defaultMinSize(minHeight = 40.dp)
|
.defaultMinSize(minHeight = 40.dp)
|
||||||
.combinedMouseClickable(
|
.clickable(
|
||||||
onClick = { onClick(createScreen()) },
|
onClick = { onClick(createScreen()) },
|
||||||
onMiddleClick = { onMiddleClick?.invoke() }
|
// onMiddleClick = { onMiddleClick?.invoke() } todo
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Spacer(Modifier.width(16.dp))
|
Spacer(Modifier.width(16.dp))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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 kotlinx.coroutines.MainScope
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class TrayViewModel @Inject constructor(
|
class TrayViewModel @Inject constructor(
|
||||||
@@ -21,4 +22,9 @@ class TrayViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
val updateFound
|
val updateFound
|
||||||
get() = updateChecker.updateFound
|
get() = updateChecker.updateFound
|
||||||
|
|
||||||
|
override fun onDispose() {
|
||||||
|
super.onDispose()
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,35 +6,13 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.manga
|
package ca.gosyer.ui.manga
|
||||||
|
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
|
||||||
import ca.gosyer.ui.AppComponent
|
|
||||||
import ca.gosyer.ui.manga.components.MangaScreenContent
|
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 ca.gosyer.uicore.vm.viewModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
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 {
|
|
||||||
CompositionLocalProvider(*remember { AppComponent.getInstance().uiComponent.getHooks() }) {
|
|
||||||
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
|
||||||
Surface {
|
|
||||||
Navigator(remember { MangaScreen(mangaId) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MangaScreen(private val mangaId: Long) : Screen {
|
class MangaScreen(private val mangaId: Long) : Screen {
|
||||||
|
|
||||||
@@ -53,6 +31,8 @@ class MangaScreen(private val mangaId: Long) : Screen {
|
|||||||
dateTimeFormatter = vm.dateTimeFormatter.collectAsState().value,
|
dateTimeFormatter = vm.dateTimeFormatter.collectAsState().value,
|
||||||
categoriesExist = vm.categoriesExist.collectAsState().value,
|
categoriesExist = vm.categoriesExist.collectAsState().value,
|
||||||
chooseCategoriesFlow = vm.chooseCategoriesFlow,
|
chooseCategoriesFlow = vm.chooseCategoriesFlow,
|
||||||
|
availableCategories = vm.categories.collectAsState().value,
|
||||||
|
mangaCategories = vm.mangaCategories.collectAsState().value,
|
||||||
addFavorite = vm::addFavorite,
|
addFavorite = vm::addFavorite,
|
||||||
setCategories = vm::setCategories,
|
setCategories = vm::setCategories,
|
||||||
toggleFavorite = vm::toggleFavorite,
|
toggleFavorite = vm::toggleFavorite,
|
||||||
|
|||||||
@@ -22,10 +22,12 @@ import ca.gosyer.uicore.vm.ViewModel
|
|||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
@@ -53,10 +55,16 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
private val _isLoading = MutableStateFlow(true)
|
private val _isLoading = MutableStateFlow(true)
|
||||||
val isLoading = _isLoading.asStateFlow()
|
val isLoading = _isLoading.asStateFlow()
|
||||||
|
|
||||||
private val _categoriesExist = MutableStateFlow(true)
|
private val _categories = MutableStateFlow(emptyList<Category>())
|
||||||
val categoriesExist = _categoriesExist.asStateFlow()
|
val categories = _categories.asStateFlow()
|
||||||
|
|
||||||
val chooseCategoriesFlow = MutableSharedFlow<Pair<List<Category>, List<Category>>>()
|
private val _mangaCategories = MutableStateFlow(emptyList<Category>())
|
||||||
|
val mangaCategories = _mangaCategories.asStateFlow()
|
||||||
|
|
||||||
|
val categoriesExist = categories.map { it.isNotEmpty() }
|
||||||
|
.stateIn(scope, SharingStarted.Eagerly, true)
|
||||||
|
|
||||||
|
val chooseCategoriesFlow = MutableSharedFlow<Unit>()
|
||||||
|
|
||||||
val dateTimeFormatter = uiPreferences.dateFormat().changes()
|
val dateTimeFormatter = uiPreferences.dateFormat().changes()
|
||||||
.map {
|
.map {
|
||||||
@@ -77,7 +85,7 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
_categoriesExist.value = categoryHandler.getCategories(true).isNotEmpty()
|
_categories.value = categoryHandler.getCategories(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,9 +116,7 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
fun setCategories() {
|
fun setCategories() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
manga.value?.let { manga ->
|
manga.value?.let { manga ->
|
||||||
val categories = async { categoryHandler.getCategories(true) }
|
chooseCategoriesFlow.emit(Unit)
|
||||||
val oldCategories = async { categoryHandler.getMangaCategories(manga) }
|
|
||||||
chooseCategoriesFlow.emit(categories.await() to oldCategories.await())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,6 +125,7 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
async {
|
async {
|
||||||
try {
|
try {
|
||||||
_manga.value = mangaHandler.getManga(mangaId, refresh)
|
_manga.value = mangaHandler.getManga(mangaId, refresh)
|
||||||
|
_mangaCategories.value = categoryHandler.getMangaCategories(mangaId)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.throwIfCancellation()
|
e.throwIfCancellation()
|
||||||
}
|
}
|
||||||
@@ -142,11 +149,10 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
libraryHandler.removeMangaFromLibrary(manga)
|
libraryHandler.removeMangaFromLibrary(manga)
|
||||||
refreshMangaAsync(manga.id).await()
|
refreshMangaAsync(manga.id).await()
|
||||||
} else {
|
} else {
|
||||||
val categories = categoryHandler.getCategories(true)
|
if (categories.value.isEmpty()) {
|
||||||
if (categories.isEmpty()) {
|
|
||||||
addFavorite(emptyList(), emptyList())
|
addFavorite(emptyList(), emptyList())
|
||||||
} else {
|
} else {
|
||||||
chooseCategoriesFlow.emit(categories to emptyList())
|
chooseCategoriesFlow.emit(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ 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.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.toMutableStateList
|
||||||
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
|
||||||
@@ -43,11 +43,15 @@ 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.ui.base.WindowDialog
|
import ca.gosyer.i18n.MR
|
||||||
|
import ca.gosyer.ui.base.dialog.getMaterialDialogProperties
|
||||||
import ca.gosyer.uicore.image.KamelImage
|
import ca.gosyer.uicore.image.KamelImage
|
||||||
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialog
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.title
|
||||||
import io.kamel.image.lazyPainterResource
|
import io.kamel.image.lazyPainterResource
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaItem(manga: Manga) {
|
fun MangaItem(manga: Manga) {
|
||||||
@@ -121,28 +125,36 @@ private fun Chip(text: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openCategorySelectDialog(
|
@Composable
|
||||||
|
fun CategorySelectDialog(
|
||||||
|
state: MaterialDialogState,
|
||||||
categories: List<Category>,
|
categories: List<Category>,
|
||||||
oldCategories: List<Category>,
|
oldCategories: List<Category>,
|
||||||
onPositiveClick: (List<Category>, List<Category>) -> Unit
|
onPositiveClick: (List<Category>, List<Category>) -> Unit
|
||||||
) {
|
) {
|
||||||
val enabledCategoriesFlow = MutableStateFlow(oldCategories)
|
val enabledCategories = remember(oldCategories) { oldCategories.toMutableStateList() }
|
||||||
WindowDialog(
|
MaterialDialog(
|
||||||
"Select Categories",
|
state,
|
||||||
onPositiveButton = { onPositiveClick(enabledCategoriesFlow.value, oldCategories) }
|
buttons = {
|
||||||
|
positiveButton(stringResource(MR.strings.action_ok)) {
|
||||||
|
onPositiveClick(enabledCategories.toList(), oldCategories)
|
||||||
|
}
|
||||||
|
negativeButton(stringResource(MR.strings.action_cancel))
|
||||||
|
},
|
||||||
|
properties = getMaterialDialogProperties(),
|
||||||
) {
|
) {
|
||||||
val enabledCategories by enabledCategoriesFlow.collectAsState()
|
title("Select Categories")
|
||||||
val state = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
Box {
|
Box {
|
||||||
LazyColumn(state = state) {
|
LazyColumn(state = listState) {
|
||||||
items(categories) { category ->
|
items(categories) { category ->
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth().padding(8.dp)
|
Modifier.fillMaxWidth().padding(8.dp)
|
||||||
.clickable {
|
.clickable {
|
||||||
if (category in enabledCategories) {
|
if (category in enabledCategories) {
|
||||||
enabledCategoriesFlow.value -= category
|
enabledCategories -= category
|
||||||
} else {
|
} else {
|
||||||
enabledCategoriesFlow.value += category
|
enabledCategories += category
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
@@ -159,7 +171,7 @@ fun openCategorySelectDialog(
|
|||||||
modifier = Modifier.align(Alignment.CenterEnd)
|
modifier = Modifier.align(Alignment.CenterEnd)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.padding(horizontal = 4.dp, vertical = 8.dp),
|
.padding(horizontal = 4.dp, vertical = 8.dp),
|
||||||
adapter = rememberScrollbarAdapter(state)
|
adapter = rememberScrollbarAdapter(listState)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import ca.gosyer.ui.reader.openReaderMenu
|
|||||||
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.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@@ -48,7 +49,9 @@ fun MangaScreenContent(
|
|||||||
chapters: List<ChapterDownloadItem>,
|
chapters: List<ChapterDownloadItem>,
|
||||||
dateTimeFormatter: DateTimeFormatter,
|
dateTimeFormatter: DateTimeFormatter,
|
||||||
categoriesExist: Boolean,
|
categoriesExist: Boolean,
|
||||||
chooseCategoriesFlow: SharedFlow<Pair<List<Category>, List<Category>>>,
|
chooseCategoriesFlow: SharedFlow<Unit>,
|
||||||
|
availableCategories: List<Category>,
|
||||||
|
mangaCategories: List<Category>,
|
||||||
addFavorite: (List<Category>, List<Category>) -> Unit,
|
addFavorite: (List<Category>, List<Category>) -> Unit,
|
||||||
setCategories: () -> Unit,
|
setCategories: () -> Unit,
|
||||||
toggleFavorite: () -> Unit,
|
toggleFavorite: () -> Unit,
|
||||||
@@ -62,9 +65,10 @@ fun MangaScreenContent(
|
|||||||
loadChapters: () -> Unit,
|
loadChapters: () -> Unit,
|
||||||
loadManga: () -> Unit
|
loadManga: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val categoryDialogState = rememberMaterialDialogState()
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
chooseCategoriesFlow.collect { (availableCategories, usedCategories) ->
|
chooseCategoriesFlow.collect {
|
||||||
openCategorySelectDialog(availableCategories, usedCategories, addFavorite)
|
categoryDialogState.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +139,7 @@ fun MangaScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CategorySelectDialog(categoryDialogState, availableCategories, mangaCategories, addFavorite)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ fun PagerReader(
|
|||||||
retry: (ReaderPage) -> Unit,
|
retry: (ReaderPage) -> Unit,
|
||||||
progress: (Int) -> Unit
|
progress: (Int) -> Unit
|
||||||
) {
|
) {
|
||||||
val state = rememberPagerState(pages.size + 2, initialPage = currentPage)
|
val state = rememberPagerState(initialPage = currentPage)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
val pageRange = 0..(pages.size + 1)
|
val pageRange = 0..(pages.size + 1)
|
||||||
@@ -77,7 +77,12 @@ fun PagerReader(
|
|||||||
val modifier = parentModifier then Modifier.fillMaxSize()
|
val modifier = parentModifier then Modifier.fillMaxSize()
|
||||||
|
|
||||||
if (direction == Direction.Down || direction == Direction.Up) {
|
if (direction == Direction.Down || direction == Direction.Up) {
|
||||||
VerticalPager(state, reverseLayout = direction == Direction.Up, modifier = modifier) {
|
VerticalPager(
|
||||||
|
count = pages.size + 2,
|
||||||
|
state = state,
|
||||||
|
reverseLayout = direction == Direction.Up,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
HandlePager(
|
HandlePager(
|
||||||
pages,
|
pages,
|
||||||
it,
|
it,
|
||||||
@@ -90,7 +95,12 @@ fun PagerReader(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
HorizontalPager(state, reverseLayout = direction == Direction.Left, modifier = modifier) {
|
HorizontalPager(
|
||||||
|
count = pages.size + 2,
|
||||||
|
state = state,
|
||||||
|
reverseLayout = direction == Direction.Left,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
HandlePager(
|
HandlePager(
|
||||||
pages,
|
pages,
|
||||||
it,
|
it,
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ 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.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
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
|
||||||
@@ -36,7 +40,7 @@ import ca.gosyer.core.lang.throwIfCancellation
|
|||||||
import ca.gosyer.core.logging.CKLogger
|
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.dialog.getMaterialDialogProperties
|
||||||
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.filePicker
|
||||||
@@ -47,6 +51,10 @@ import ca.gosyer.uicore.vm.viewModel
|
|||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialog
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.title
|
||||||
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
|
||||||
@@ -225,10 +233,15 @@ private fun SettingsBackupScreenContent(
|
|||||||
stopRestore: () -> Unit,
|
stopRestore: () -> Unit,
|
||||||
exportBackup: () -> Unit
|
exportBackup: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
var backupFile by remember { mutableStateOf<Path?>(null) }
|
||||||
|
var missingSources by remember { mutableStateOf(emptyList<String>()) }
|
||||||
|
val dialogState = rememberMaterialDialogState()
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
launch {
|
launch {
|
||||||
missingSourceFlow.collect { (backup, sources) ->
|
missingSourceFlow.collect { (backup, sources) ->
|
||||||
openMissingSourcesDialog(sources, { restoreBackup(backup) }, stopRestore)
|
backupFile = backup
|
||||||
|
missingSources = sources
|
||||||
|
dialogState.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
@@ -278,21 +291,48 @@ private fun SettingsBackupScreenContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MissingSourcesDialog(
|
||||||
|
dialogState,
|
||||||
|
missingSources,
|
||||||
|
onPositiveClick = {
|
||||||
|
restoreBackup(backupFile ?: return@MissingSourcesDialog)
|
||||||
|
},
|
||||||
|
onNegativeClick = stopRestore
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openMissingSourcesDialog(missingSources: List<String>, onPositiveClick: () -> Unit, onNegativeClick: () -> Unit) {
|
@Composable
|
||||||
WindowDialog(
|
private fun MissingSourcesDialog(
|
||||||
"Missing Sources",
|
state: MaterialDialogState,
|
||||||
onPositiveButton = onPositiveClick,
|
missingSources: List<String>,
|
||||||
onNegativeButton = onNegativeClick
|
onPositiveClick: () -> Unit,
|
||||||
|
onNegativeClick: () -> Unit
|
||||||
|
) {
|
||||||
|
MaterialDialog(
|
||||||
|
state,
|
||||||
|
buttons = {
|
||||||
|
positiveButton(stringResource(MR.strings.action_ok), onClick = onPositiveClick)
|
||||||
|
negativeButton(stringResource(MR.strings.action_cancel), onClick = onNegativeClick)
|
||||||
|
},
|
||||||
|
properties = getMaterialDialogProperties(),
|
||||||
) {
|
) {
|
||||||
LazyColumn {
|
title("Missing Sources")
|
||||||
item {
|
Box {
|
||||||
Text(stringResource(MR.strings.missing_sources), style = MaterialTheme.typography.subtitle2)
|
val listState = rememberLazyListState()
|
||||||
}
|
LazyColumn(Modifier.fillMaxSize(), state = listState) {
|
||||||
items(missingSources) {
|
item {
|
||||||
Text(it)
|
Text(stringResource(MR.strings.missing_sources), style = MaterialTheme.typography.subtitle2)
|
||||||
|
}
|
||||||
|
items(missingSources) {
|
||||||
|
Text(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
VerticalScrollbar(
|
||||||
|
rememberScrollbarAdapter(listState),
|
||||||
|
Modifier.align(Alignment.CenterEnd)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,71 +59,47 @@ class SettingsServerScreen : Screen {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val vm = viewModel<SettingsServerViewModel>()
|
val connectionVM = viewModel<SettingsServerViewModel>()
|
||||||
|
val serverVm = viewModel<SettingsServerHostViewModel>()
|
||||||
SettingsServerScreenContent(
|
SettingsServerScreenContent(
|
||||||
hostValue = vm.host.collectAsState().value,
|
hostValue = serverVm.host.collectAsState().value,
|
||||||
basicAuthEnabledValue = vm.basicAuthEnabled.collectAsState().value,
|
basicAuthEnabledValue = serverVm.basicAuthEnabled.collectAsState().value,
|
||||||
proxyValue = vm.proxy.collectAsState().value,
|
proxyValue = connectionVM.proxy.collectAsState().value,
|
||||||
authValue = vm.auth.collectAsState().value,
|
authValue = connectionVM.auth.collectAsState().value,
|
||||||
restartServer = vm::restartServer,
|
restartServer = serverVm::restartServer,
|
||||||
serverSettingChanged = vm::serverSettingChanged,
|
serverSettingChanged = serverVm::serverSettingChanged,
|
||||||
host = vm.host,
|
host = serverVm.host,
|
||||||
ip = vm.ip,
|
ip = serverVm.ip,
|
||||||
port = vm.port,
|
port = serverVm.port,
|
||||||
socksProxyEnabled = vm.socksProxyEnabled,
|
socksProxyEnabled = serverVm.socksProxyEnabled,
|
||||||
socksProxyHost = vm.socksProxyHost,
|
socksProxyHost = serverVm.socksProxyHost,
|
||||||
socksProxyPort = vm.socksProxyPort,
|
socksProxyPort = serverVm.socksProxyPort,
|
||||||
debugLogsEnabled = vm.debugLogsEnabled,
|
debugLogsEnabled = serverVm.debugLogsEnabled,
|
||||||
systemTrayEnabled = vm.systemTrayEnabled,
|
systemTrayEnabled = serverVm.systemTrayEnabled,
|
||||||
webUIEnabled = vm.webUIEnabled,
|
webUIEnabled = serverVm.webUIEnabled,
|
||||||
openInBrowserEnabled = vm.openInBrowserEnabled,
|
openInBrowserEnabled = serverVm.openInBrowserEnabled,
|
||||||
basicAuthEnabled = vm.basicAuthEnabled,
|
basicAuthEnabled = serverVm.basicAuthEnabled,
|
||||||
basicAuthUsername = vm.basicAuthUsername,
|
basicAuthUsername = serverVm.basicAuthUsername,
|
||||||
basicAuthPassword = vm.basicAuthPassword,
|
basicAuthPassword = serverVm.basicAuthPassword,
|
||||||
serverUrl = vm.serverUrl,
|
serverUrl = connectionVM.serverUrl,
|
||||||
serverPort = vm.serverPort,
|
serverPort = connectionVM.serverPort,
|
||||||
proxy = vm.proxy,
|
proxy = connectionVM.proxy,
|
||||||
proxyChoices = vm.getProxyChoices(),
|
proxyChoices = connectionVM.getProxyChoices(),
|
||||||
httpHost = vm.httpHost,
|
httpHost = connectionVM.httpHost,
|
||||||
httpPort = vm.httpPort,
|
httpPort = connectionVM.httpPort,
|
||||||
socksHost = vm.socksHost,
|
socksHost = connectionVM.socksHost,
|
||||||
socksPort = vm.socksPort,
|
socksPort = connectionVM.socksPort,
|
||||||
auth = vm.auth,
|
auth = connectionVM.auth,
|
||||||
authChoices = vm.getAuthChoices(),
|
authChoices = connectionVM.getAuthChoices(),
|
||||||
authUsername = vm.authUsername,
|
authUsername = connectionVM.authUsername,
|
||||||
authPassword = vm.authPassword
|
authPassword = connectionVM.authPassword
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsServerViewModel @Inject constructor(
|
class SettingsServerViewModel @Inject constructor(
|
||||||
serverPreferences: ServerPreferences,
|
serverPreferences: ServerPreferences
|
||||||
serverHostPreferences: ServerHostPreferences,
|
|
||||||
private val serverService: ServerService
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val host = serverPreferences.host().asStateIn(scope)
|
|
||||||
val ip = serverHostPreferences.ip().asStateIn(scope)
|
|
||||||
val port = serverHostPreferences.port().asStringStateIn(scope)
|
|
||||||
|
|
||||||
// Proxy
|
|
||||||
val socksProxyEnabled = serverHostPreferences.socksProxyEnabled().asStateIn(scope)
|
|
||||||
val socksProxyHost = serverHostPreferences.socksProxyHost().asStateIn(scope)
|
|
||||||
val socksProxyPort = serverHostPreferences.socksProxyPort().asStringStateIn(scope)
|
|
||||||
|
|
||||||
// Misc
|
|
||||||
val debugLogsEnabled = serverHostPreferences.debugLogsEnabled().asStateIn(scope)
|
|
||||||
val systemTrayEnabled = serverHostPreferences.systemTrayEnabled().asStateIn(scope)
|
|
||||||
|
|
||||||
// WebUI
|
|
||||||
val webUIEnabled = serverHostPreferences.webUIEnabled().asStateIn(scope)
|
|
||||||
val openInBrowserEnabled = serverHostPreferences.openInBrowserEnabled().asStateIn(scope)
|
|
||||||
|
|
||||||
// Authentication
|
|
||||||
val basicAuthEnabled = serverHostPreferences.basicAuthEnabled().asStateIn(scope)
|
|
||||||
val basicAuthUsername = serverHostPreferences.basicAuthUsername().asStateIn(scope)
|
|
||||||
val basicAuthPassword = serverHostPreferences.basicAuthPassword().asStateIn(scope)
|
|
||||||
|
|
||||||
// JUI connection
|
|
||||||
val serverUrl = serverPreferences.server().asStateIn(scope)
|
val serverUrl = serverPreferences.server().asStateIn(scope)
|
||||||
val serverPort = serverPreferences.port().asStringStateIn(scope)
|
val serverPort = serverPreferences.port().asStringStateIn(scope)
|
||||||
|
|
||||||
@@ -158,12 +134,53 @@ class SettingsServerViewModel @Inject constructor(
|
|||||||
_serverSettingChanged.value = true
|
_serverSettingChanged.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object : CKLogger({})
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsServerHostViewModel @Inject constructor(
|
||||||
|
serverPreferences: ServerPreferences,
|
||||||
|
serverHostPreferences: ServerHostPreferences,
|
||||||
|
private val serverService: ServerService
|
||||||
|
) : ViewModel() {
|
||||||
|
val host = serverHostPreferences.host().asStateIn(scope)
|
||||||
|
val ip = serverHostPreferences.ip().asStateIn(scope)
|
||||||
|
val port = serverHostPreferences.port().asStringStateIn(scope)
|
||||||
|
|
||||||
|
// Proxy
|
||||||
|
val socksProxyEnabled = serverHostPreferences.socksProxyEnabled().asStateIn(scope)
|
||||||
|
val socksProxyHost = serverHostPreferences.socksProxyHost().asStateIn(scope)
|
||||||
|
val socksProxyPort = serverHostPreferences.socksProxyPort().asStringStateIn(scope)
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
val debugLogsEnabled = serverHostPreferences.debugLogsEnabled().asStateIn(scope)
|
||||||
|
val systemTrayEnabled = serverHostPreferences.systemTrayEnabled().asStateIn(scope)
|
||||||
|
|
||||||
|
// WebUI
|
||||||
|
val webUIEnabled = serverHostPreferences.webUIEnabled().asStateIn(scope)
|
||||||
|
val openInBrowserEnabled = serverHostPreferences.openInBrowserEnabled().asStateIn(scope)
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
val basicAuthEnabled = serverHostPreferences.basicAuthEnabled().asStateIn(scope)
|
||||||
|
val basicAuthUsername = serverHostPreferences.basicAuthUsername().asStateIn(scope)
|
||||||
|
val basicAuthPassword = serverHostPreferences.basicAuthPassword().asStateIn(scope)
|
||||||
|
|
||||||
|
private val _serverSettingChanged = MutableStateFlow(false)
|
||||||
|
val serverSettingChanged = _serverSettingChanged.asStateFlow()
|
||||||
|
fun serverSettingChanged() {
|
||||||
|
_serverSettingChanged.value = true
|
||||||
|
}
|
||||||
|
|
||||||
fun restartServer() {
|
fun restartServer() {
|
||||||
if (serverSettingChanged.value) {
|
if (serverSettingChanged.value) {
|
||||||
serverService.restartServer()
|
serverService.restartServer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle password connection to hosted server
|
||||||
|
val auth = serverPreferences.auth().asStateIn(scope)
|
||||||
|
val authUsername = serverPreferences.authUsername().asStateIn(scope)
|
||||||
|
val authPassword = serverPreferences.authPassword().asStateIn(scope)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
combine(basicAuthEnabled, basicAuthUsername, basicAuthPassword) { enabled, username, password ->
|
combine(basicAuthEnabled, basicAuthUsername, basicAuthPassword) { enabled, username, password ->
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
|
|||||||
@@ -6,35 +6,13 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.sources
|
package ca.gosyer.ui.sources
|
||||||
|
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
|
||||||
import ca.gosyer.ui.AppComponent
|
|
||||||
import ca.gosyer.ui.sources.components.SourcesMenu
|
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 ca.gosyer.uicore.vm.viewModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun openSourcesMenu() {
|
|
||||||
launchApplication {
|
|
||||||
CompositionLocalProvider(*remember { AppComponent.getInstance().uiComponent.getHooks() }) {
|
|
||||||
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
|
||||||
Surface {
|
|
||||||
Navigator(remember { SourcesScreen() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SourcesScreen : Screen {
|
class SourcesScreen : Screen {
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ class SourceHomeScreen : Screen {
|
|||||||
onAddSource = sourcesNavigator::select,
|
onAddSource = sourcesNavigator::select,
|
||||||
isLoading = vm.isLoading.collectAsState().value,
|
isLoading = vm.isLoading.collectAsState().value,
|
||||||
sources = vm.sources.collectAsState().value,
|
sources = vm.sources.collectAsState().value,
|
||||||
languages = vm.languages,
|
languages = vm.languages.collectAsState().value,
|
||||||
getSourceLanguages = vm::getSourceLanguages,
|
sourceLanguages = vm.sourceLanguages.collectAsState().value,
|
||||||
setEnabledLanguages = vm::setEnabledLanguages
|
setEnabledLanguages = vm::setEnabledLanguages
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ 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 kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
@@ -24,13 +28,21 @@ class SourceHomeScreenViewModel @Inject constructor(
|
|||||||
private val _isLoading = MutableStateFlow(true)
|
private val _isLoading = MutableStateFlow(true)
|
||||||
val isLoading = _isLoading.asStateFlow()
|
val isLoading = _isLoading.asStateFlow()
|
||||||
|
|
||||||
|
private val installedSources = MutableStateFlow(emptyList<Source>())
|
||||||
|
|
||||||
private val _languages = catalogPreferences.languages().asStateFlow()
|
private val _languages = catalogPreferences.languages().asStateFlow()
|
||||||
val languages = _languages.asStateFlow()
|
val languages = _languages.asStateFlow()
|
||||||
|
|
||||||
private val _sources = MutableStateFlow(emptyList<Source>())
|
val sources = combine(installedSources, languages) { installedSources, languages ->
|
||||||
val sources = _sources.asStateFlow()
|
installedSources.filter {
|
||||||
|
it.lang in languages || it.lang == Source.LOCAL_SOURCE_LANG
|
||||||
|
}
|
||||||
|
}.stateIn(scope, SharingStarted.Eagerly, emptyList())
|
||||||
|
|
||||||
|
val sourceLanguages = installedSources.map { sources ->
|
||||||
|
sources.map { it.lang }.toSet() - setOf(Source.LOCAL_SOURCE_LANG)
|
||||||
|
}.stateIn(scope, SharingStarted.Eagerly, emptySet())
|
||||||
|
|
||||||
private var installedSources = emptyList<Source>()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
getSources()
|
getSources()
|
||||||
@@ -39,9 +51,7 @@ class SourceHomeScreenViewModel @Inject constructor(
|
|||||||
private fun getSources() {
|
private fun getSources() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
installedSources = sourceHandler.getSourceList()
|
installedSources.value = sourceHandler.getSourceList()
|
||||||
setSources(_languages.value)
|
|
||||||
info { _sources.value }
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.throwIfCancellation()
|
e.throwIfCancellation()
|
||||||
} finally {
|
} finally {
|
||||||
@@ -50,18 +60,9 @@ class SourceHomeScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setSources(langs: Set<String>) {
|
|
||||||
_sources.value = installedSources.filter { it.lang in langs || it.lang == Source.LOCAL_SOURCE_LANG }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSourceLanguages(): Set<String> {
|
|
||||||
return installedSources.map { it.lang }.toSet() - setOf(Source.LOCAL_SOURCE_LANG)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setEnabledLanguages(langs: Set<String>) {
|
fun setEnabledLanguages(langs: Set<String>) {
|
||||||
info { langs }
|
info { langs }
|
||||||
_languages.value = langs
|
_languages.value = langs
|
||||||
setSources(langs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object : CKLogger({})
|
private companion object : CKLogger({})
|
||||||
|
|||||||
@@ -48,25 +48,23 @@ import ca.gosyer.ui.extensions.components.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 ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
|
||||||
import io.kamel.image.lazyPainterResource
|
import io.kamel.image.lazyPainterResource
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceHomeScreenContent(
|
fun SourceHomeScreenContent(
|
||||||
onAddSource: (Source) -> Unit,
|
onAddSource: (Source) -> Unit,
|
||||||
isLoading: Boolean,
|
isLoading: Boolean,
|
||||||
sources: List<Source>,
|
sources: List<Source>,
|
||||||
languages: StateFlow<Set<String>>,
|
languages: Set<String>,
|
||||||
getSourceLanguages: () -> Set<String>,
|
sourceLanguages: Set<String>,
|
||||||
setEnabledLanguages: (Set<String>) -> Unit
|
setEnabledLanguages: (Set<String>) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val languageDialogState = rememberMaterialDialogState()
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
SourceHomeScreenToolbar(
|
SourceHomeScreenToolbar(
|
||||||
languages,
|
languageDialogState::show
|
||||||
getSourceLanguages,
|
|
||||||
setEnabledLanguages
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@@ -97,24 +95,18 @@ fun SourceHomeScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LanguageDialog(languageDialogState, languages, sourceLanguages, setEnabledLanguages)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceHomeScreenToolbar(
|
fun SourceHomeScreenToolbar(
|
||||||
sourceLanguages: StateFlow<Set<String>>,
|
openEnabledLanguagesClick: () -> Unit
|
||||||
onGetEnabledLanguages: () -> Set<String>,
|
|
||||||
onSetEnabledLanguages: (Set<String>) -> Unit
|
|
||||||
) {
|
) {
|
||||||
Toolbar(
|
Toolbar(
|
||||||
stringResource(MR.strings.location_sources),
|
stringResource(MR.strings.location_sources),
|
||||||
actions = {
|
actions = {
|
||||||
getActionItems(
|
getActionItems(
|
||||||
onEnabledLanguagesClick = {
|
openEnabledLanguagesClick = openEnabledLanguagesClick
|
||||||
val enabledLangs = MutableStateFlow(sourceLanguages.value)
|
|
||||||
LanguageDialog(enabledLangs, onGetEnabledLanguages().toList()) {
|
|
||||||
onSetEnabledLanguages(enabledLangs.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -177,13 +169,13 @@ fun SourceItem(
|
|||||||
@Composable
|
@Composable
|
||||||
@Stable
|
@Stable
|
||||||
private fun getActionItems(
|
private fun getActionItems(
|
||||||
onEnabledLanguagesClick: () -> Unit
|
openEnabledLanguagesClick: () -> Unit
|
||||||
): List<ActionItem> {
|
): List<ActionItem> {
|
||||||
return listOf(
|
return listOf(
|
||||||
ActionItem(
|
ActionItem(
|
||||||
stringResource(MR.strings.enabled_languages),
|
stringResource(MR.strings.enabled_languages),
|
||||||
Icons.Rounded.Translate,
|
Icons.Rounded.Translate,
|
||||||
doAction = onEnabledLanguagesClick
|
doAction = openEnabledLanguagesClick
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,31 +7,12 @@
|
|||||||
package ca.gosyer.ui.sources.settings
|
package ca.gosyer.ui.sources.settings
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
|
||||||
import ca.gosyer.ui.AppComponent
|
|
||||||
import ca.gosyer.ui.sources.settings.components.SourceSettingsScreenContent
|
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 ca.gosyer.uicore.vm.viewModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
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 {
|
|
||||||
CompositionLocalProvider(*remember { AppComponent.getInstance().uiComponent.getHooks() }) {
|
|
||||||
ThemedWindow(::exitApplication, title = BuildKonfig.NAME) {
|
|
||||||
Navigator(remember { SourceSettingsScreen(sourceId) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SourceSettingsScreen(private val sourceId: Long) : Screen {
|
class SourceSettingsScreen(private val sourceId: Long) : Screen {
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ 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
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@@ -21,18 +19,19 @@ import androidx.compose.material.Checkbox
|
|||||||
import androidx.compose.material.OutlinedTextField
|
import androidx.compose.material.OutlinedTextField
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.material.Switch
|
import androidx.compose.material.Switch
|
||||||
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.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
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.presentation.build.BuildKonfig
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
import ca.gosyer.ui.base.dialog.getMaterialDialogProperties
|
||||||
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
|
||||||
@@ -46,7 +45,10 @@ 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.components.keyboardHandler
|
import ca.gosyer.uicore.components.keyboardHandler
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import com.vanpra.composematerialdialogs.MaterialDialog
|
||||||
|
import com.vanpra.composematerialdialogs.message
|
||||||
|
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.title
|
||||||
import kotlin.collections.List as KtList
|
import kotlin.collections.List as KtList
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -124,18 +126,21 @@ private fun ListPreference(list: List) {
|
|||||||
list.summary
|
list.summary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val dialogState = rememberMaterialDialogState()
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title,
|
title,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
onClick = {
|
onClick = {
|
||||||
ChoiceDialog(
|
dialogState.show()
|
||||||
list.getOptions(),
|
|
||||||
state,
|
|
||||||
onSelected = list::updateState,
|
|
||||||
title = title
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
ChoiceDialog(
|
||||||
|
dialogState,
|
||||||
|
list.getOptions(),
|
||||||
|
state,
|
||||||
|
onSelected = list::updateState,
|
||||||
|
title = title
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -150,18 +155,21 @@ private fun MultiSelectPreference(multiSelect: MultiSelect) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val dialogTitle = remember(state) { multiSelect.props.dialogTitle ?: multiSelect.title ?: multiSelect.summary ?: "No title" }
|
val dialogTitle = remember(state) { multiSelect.props.dialogTitle ?: multiSelect.title ?: multiSelect.summary ?: "No title" }
|
||||||
|
val dialogState = rememberMaterialDialogState()
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title,
|
title,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
onClick = {
|
onClick = {
|
||||||
MultiSelectDialog(
|
dialogState.show()
|
||||||
multiSelect.getOptions(),
|
|
||||||
state,
|
|
||||||
onFinished = multiSelect::updateState,
|
|
||||||
title = dialogTitle
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
MultiSelectDialog(
|
||||||
|
dialogState,
|
||||||
|
multiSelect.getOptions(),
|
||||||
|
state,
|
||||||
|
onFinished = multiSelect::updateState,
|
||||||
|
title = dialogTitle
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -175,31 +183,33 @@ private fun EditTextPreference(editText: EditText) {
|
|||||||
editText.summary
|
editText.summary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val dialogState = rememberMaterialDialogState()
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title,
|
title,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
onClick = {
|
onClick = dialogState::show
|
||||||
val editTextFlow = MutableStateFlow(TextFieldValue(state))
|
|
||||||
WindowDialog(
|
|
||||||
editText.dialogTitle ?: BuildKonfig.NAME,
|
|
||||||
onPositiveButton = {
|
|
||||||
editText.updateState(editTextFlow.value.text)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
if (editText.dialogMessage != null) {
|
|
||||||
Text(editText.dialogMessage)
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
}
|
|
||||||
|
|
||||||
val text by editTextFlow.collectAsState()
|
|
||||||
OutlinedTextField(
|
|
||||||
text,
|
|
||||||
onValueChange = {
|
|
||||||
editTextFlow.value = it
|
|
||||||
},
|
|
||||||
modifier = Modifier.keyboardHandler(singleLine = true)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
var text by remember(state) { mutableStateOf(TextFieldValue(state)) }
|
||||||
|
MaterialDialog(
|
||||||
|
dialogState,
|
||||||
|
buttons = {
|
||||||
|
positiveButton(stringResource(MR.strings.action_ok)) {
|
||||||
|
editText.updateState(text.text)
|
||||||
|
}
|
||||||
|
negativeButton(stringResource(MR.strings.action_cancel))
|
||||||
|
},
|
||||||
|
properties = getMaterialDialogProperties(),
|
||||||
|
) {
|
||||||
|
title(editText.dialogTitle ?: BuildKonfig.NAME)
|
||||||
|
if (editText.dialogMessage != null) {
|
||||||
|
message(editText.dialogMessage)
|
||||||
|
}
|
||||||
|
OutlinedTextField(
|
||||||
|
text,
|
||||||
|
onValueChange = {
|
||||||
|
text = it
|
||||||
|
},
|
||||||
|
modifier = Modifier.keyboardHandler(singleLine = true)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +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.util.compose
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
operator fun <T> StateFlow<T>.getValue(thisObj: Any?, property: KProperty<*>): T {
|
|
||||||
val item by collectAsState()
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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.base.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VerticalScrollbar(
|
||||||
|
adapter: ScrollbarAdapter,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
reverseLayout: Boolean = false,
|
||||||
|
style: ScrollbarStyle = LocalScrollbarStyle.current,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
real: Boolean = false
|
||||||
|
) = VerticalScrollbar(adapter, modifier, reverseLayout, style, interactionSource)
|
||||||
@@ -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.base.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
expect interface ScrollbarAdapter
|
||||||
|
|
||||||
|
expect class ScrollbarStyle
|
||||||
|
|
||||||
|
expect val LocalScrollbarStyle: ProvidableCompositionLocal<ScrollbarStyle>
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
expect fun VerticalScrollbar(
|
||||||
|
adapter: ScrollbarAdapter,
|
||||||
|
modifier: Modifier,
|
||||||
|
reverseLayout: Boolean,
|
||||||
|
style: ScrollbarStyle,
|
||||||
|
interactionSource: MutableInteractionSource,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
expect fun rememberScrollbarAdapter(
|
||||||
|
scrollState: ScrollState
|
||||||
|
): ScrollbarAdapter
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
expect fun rememberScrollbarAdapter(
|
||||||
|
scrollState: LazyListState,
|
||||||
|
): ScrollbarAdapter
|
||||||
@@ -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.base.dialog
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.graphics.toPainter
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ca.gosyer.i18n.MR
|
||||||
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
|
import com.vanpra.composematerialdialogs.DesktopWindowPosition
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialogProperties
|
||||||
|
import com.vanpra.composematerialdialogs.SecurePolicy
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getMaterialDialogProperties(
|
||||||
|
dismissOnBackPress: Boolean = true,
|
||||||
|
dismissOnClickOutside: Boolean = true,
|
||||||
|
securePolicy: SecurePolicy = SecurePolicy.Inherit,
|
||||||
|
usePlatformDefaultWidth : Boolean = false,
|
||||||
|
position: DesktopWindowPosition = DesktopWindowPosition(Alignment.Center),
|
||||||
|
size: DpSize = DpSize(400.dp, 300.dp),
|
||||||
|
title: String = BuildKonfig.NAME,
|
||||||
|
icon: Painter = remember { MR.images.icon.image.toPainter() },
|
||||||
|
resizable: Boolean = true
|
||||||
|
): MaterialDialogProperties {
|
||||||
|
return MaterialDialogProperties(
|
||||||
|
dismissOnBackPress = dismissOnBackPress,
|
||||||
|
dismissOnClickOutside = dismissOnClickOutside,
|
||||||
|
securePolicy = securePolicy,
|
||||||
|
usePlatformDefaultWidth = usePlatformDefaultWidth,
|
||||||
|
position = position,
|
||||||
|
size = size,
|
||||||
|
title = title,
|
||||||
|
icon = icon,
|
||||||
|
resizable = resizable
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.base.navigation
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
expect fun ActionIcon(onClick: () -> Unit, contentDescription: String, icon: ImageVector)
|
||||||
@@ -8,8 +8,6 @@ package ca.gosyer.ui.base.navigation
|
|||||||
|
|
||||||
import androidx.compose.material.DropdownMenu
|
import androidx.compose.material.DropdownMenu
|
||||||
import androidx.compose.material.DropdownMenuItem
|
import androidx.compose.material.DropdownMenuItem
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.IconButton
|
|
||||||
import androidx.compose.material.LocalContentAlpha
|
import androidx.compose.material.LocalContentAlpha
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
@@ -25,6 +23,8 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
|
import ca.gosyer.i18n.MR
|
||||||
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
|
||||||
// Originally from https://gist.github.com/MachFour/369ebb56a66e2f583ebfb988dda2decf
|
// Originally from https://gist.github.com/MachFour/369ebb56a66e2f583ebfb988dda2decf
|
||||||
|
|
||||||
@@ -82,9 +82,12 @@ fun ActionMenu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (overflowActions.isNotEmpty()) {
|
if (overflowActions.isNotEmpty()) {
|
||||||
IconButton(onClick = { menuVisible.value = true }) {
|
iconItem(
|
||||||
Icon(Icons.Default.MoreVert, "More actions")
|
{ menuVisible.value = true },
|
||||||
}
|
stringResource(MR.strings.action_more_actions),
|
||||||
|
Icons.Default.MoreVert,
|
||||||
|
true
|
||||||
|
)
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = menuVisible.value,
|
expanded = menuVisible.value,
|
||||||
onDismissRequest = { menuVisible.value = false },
|
onDismissRequest = { menuVisible.value = false },
|
||||||
@@ -98,7 +101,7 @@ fun ActionMenu(
|
|||||||
},
|
},
|
||||||
enabled = item.enabled
|
enabled = item.enabled
|
||||||
) {
|
) {
|
||||||
//Icon(item.icon, item.name) just have text in the overflow menu
|
// Icon(item.icon, item.name) just have text in the overflow menu
|
||||||
Text(item.name)
|
Text(item.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,4 +153,4 @@ private fun separateIntoIconAndOverflow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return iconActions to overflowActions
|
return iconActions to overflowActions
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,6 @@ import androidx.compose.ui.unit.Dp
|
|||||||
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.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.uicore.components.BoxWithTooltipSurface
|
|
||||||
import ca.gosyer.uicore.components.keyboardHandler
|
import ca.gosyer.uicore.components.keyboardHandler
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
@@ -192,8 +191,6 @@ private fun WideToolbar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ThinToolbar(
|
private fun ThinToolbar(
|
||||||
name: String,
|
name: String,
|
||||||
@@ -358,19 +355,6 @@ private fun SearchBox(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ActionIcon(onClick: () -> Unit, contentDescription: String, icon: ImageVector) {
|
|
||||||
BoxWithTooltipSurface(
|
|
||||||
{
|
|
||||||
Text(contentDescription, modifier = Modifier.padding(10.dp))
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
IconButton(onClick = onClick) {
|
|
||||||
Icon(icon, contentDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TextActionIcon(
|
fun TextActionIcon(
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
@@ -34,11 +34,9 @@ import androidx.compose.material.Icon
|
|||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.OutlinedTextField
|
import androidx.compose.material.OutlinedTextField
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.TextButton
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Check
|
import androidx.compose.material.icons.rounded.Check
|
||||||
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.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -66,62 +64,56 @@ import androidx.compose.ui.unit.DpSize
|
|||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastForEachIndexed
|
import androidx.compose.ui.util.fastForEachIndexed
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
import ca.gosyer.ui.base.dialog.getMaterialDialogProperties
|
||||||
import ca.gosyer.uicore.components.keyboardHandler
|
import ca.gosyer.uicore.components.keyboardHandler
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import com.vanpra.composematerialdialogs.MaterialDialog
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.title
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
|
@Composable
|
||||||
fun ColorPickerDialog(
|
fun ColorPickerDialog(
|
||||||
|
state: MaterialDialogState,
|
||||||
title: String,
|
title: String,
|
||||||
onCloseRequest: () -> Unit = {},
|
onCloseRequest: () -> Unit = {},
|
||||||
onSelected: (Color) -> Unit,
|
onSelected: (Color) -> Unit,
|
||||||
initialColor: Color = Color.Unspecified,
|
initialColor: Color = Color.Unspecified,
|
||||||
) {
|
) {
|
||||||
val currentColor = MutableStateFlow(initialColor)
|
var currentColor by remember(initialColor) { mutableStateOf(initialColor) }
|
||||||
val showPresets = MutableStateFlow(true)
|
var showPresets by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
WindowDialog(
|
MaterialDialog(
|
||||||
onCloseRequest = onCloseRequest,
|
state,
|
||||||
size = DpSize(300.dp, 520.dp),
|
|
||||||
title = title,
|
|
||||||
content = {
|
|
||||||
val showPresetsState by showPresets.collectAsState()
|
|
||||||
val currentColorState by currentColor.collectAsState()
|
|
||||||
if (showPresetsState) {
|
|
||||||
ColorPresets(
|
|
||||||
initialColor = currentColorState,
|
|
||||||
onColorChanged = { currentColor.value = it }
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ColorPalette(
|
|
||||||
initialColor = currentColorState,
|
|
||||||
onColorChanged = { currentColor.value = it }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
buttons = {
|
buttons = {
|
||||||
val showPresetsState by showPresets.collectAsState()
|
positiveButton("Select", onClick = { onSelected(currentColor) })
|
||||||
val currentColorState by currentColor.collectAsState()
|
button(
|
||||||
Row(Modifier.fillMaxWidth().padding(8.dp).align(Alignment.BottomCenter)) {
|
if (showPresets) "Custom" else "Presets",
|
||||||
TextButton(
|
onClick = {
|
||||||
onClick = {
|
showPresets = !showPresets
|
||||||
showPresets.value = !showPresetsState
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(if (showPresetsState) "Custom" else "Presets")
|
|
||||||
}
|
}
|
||||||
Spacer(Modifier.weight(1f))
|
)
|
||||||
TextButton(
|
},
|
||||||
onClick = {
|
properties = getMaterialDialogProperties(
|
||||||
onSelected(currentColorState)
|
size = DpSize(300.dp, 520.dp)
|
||||||
it()
|
),
|
||||||
}
|
onCloseRequest = {
|
||||||
) {
|
it.hide()
|
||||||
Text("Select")
|
onCloseRequest()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
) {
|
||||||
|
title(title)
|
||||||
|
if (showPresets) {
|
||||||
|
ColorPresets(
|
||||||
|
initialColor = currentColor,
|
||||||
|
onColorChanged = { currentColor = it }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ColorPalette(
|
||||||
|
initialColor = currentColor,
|
||||||
|
onColorChanged = { currentColor = it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@@ -20,14 +20,12 @@ import androidx.compose.animation.fadeIn
|
|||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraintsScope
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -44,7 +42,6 @@ import androidx.compose.foundation.layout.widthIn
|
|||||||
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.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollbarAdapter
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.Checkbox
|
import androidx.compose.material.Checkbox
|
||||||
import androidx.compose.material.ContentAlpha
|
import androidx.compose.material.ContentAlpha
|
||||||
@@ -65,6 +62,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.toMutableStateList
|
||||||
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.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@@ -77,10 +75,18 @@ import androidx.compose.ui.text.input.VisualTransformation
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
import ca.gosyer.i18n.MR
|
||||||
|
import ca.gosyer.ui.base.components.VerticalScrollbar
|
||||||
|
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
|
||||||
|
import ca.gosyer.ui.base.dialog.getMaterialDialogProperties
|
||||||
import ca.gosyer.uicore.components.keyboardHandler
|
import ca.gosyer.uicore.components.keyboardHandler
|
||||||
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
|
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialog
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialogButtons
|
||||||
|
import com.vanpra.composematerialdialogs.MaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.title
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PreferenceRow(
|
fun PreferenceRow(
|
||||||
@@ -180,31 +186,39 @@ fun EditTextPreference(
|
|||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
visualTransformation: VisualTransformation = VisualTransformation.None
|
visualTransformation: VisualTransformation = VisualTransformation.None
|
||||||
) {
|
) {
|
||||||
|
val dialogState = rememberMaterialDialogState()
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
onClick = {
|
onClick = {
|
||||||
var editText by mutableStateOf(TextFieldValue(preference.value))
|
dialogState.show()
|
||||||
WindowDialog(
|
|
||||||
title,
|
|
||||||
onPositiveButton = {
|
|
||||||
preference.value = editText.text
|
|
||||||
changeListener()
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
editText,
|
|
||||||
onValueChange = {
|
|
||||||
editText = it
|
|
||||||
},
|
|
||||||
visualTransformation = visualTransformation,
|
|
||||||
modifier = Modifier.keyboardHandler()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
enabled = enabled
|
enabled = enabled
|
||||||
)
|
)
|
||||||
|
val value by preference.collectAsState()
|
||||||
|
var editText by remember(value) { mutableStateOf(TextFieldValue(preference.value)) }
|
||||||
|
MaterialDialog(
|
||||||
|
dialogState,
|
||||||
|
buttons = {
|
||||||
|
positiveButton(stringResource(MR.strings.action_ok)) {
|
||||||
|
preference.value = editText.text
|
||||||
|
changeListener()
|
||||||
|
}
|
||||||
|
negativeButton(stringResource(MR.strings.action_cancel))
|
||||||
|
},
|
||||||
|
properties = getMaterialDialogProperties(),
|
||||||
|
) {
|
||||||
|
title(title)
|
||||||
|
OutlinedTextField(
|
||||||
|
editText,
|
||||||
|
onValueChange = {
|
||||||
|
editText = it
|
||||||
|
},
|
||||||
|
visualTransformation = visualTransformation,
|
||||||
|
modifier = Modifier.keyboardHandler()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -217,96 +231,117 @@ fun <Key> ChoicePreference(
|
|||||||
enabled: Boolean = true
|
enabled: Boolean = true
|
||||||
) {
|
) {
|
||||||
val prefValue by preference.collectAsState()
|
val prefValue by preference.collectAsState()
|
||||||
|
val dialogState = rememberMaterialDialogState()
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle ?: choices[prefValue],
|
subtitle = subtitle ?: choices[prefValue],
|
||||||
onClick = {
|
onClick = {
|
||||||
ChoiceDialog(
|
dialogState.show()
|
||||||
items = choices.toList(),
|
|
||||||
selected = prefValue,
|
|
||||||
title = title,
|
|
||||||
onSelected = { selected ->
|
|
||||||
preference.value = selected
|
|
||||||
changeListener()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
enabled = enabled
|
enabled = enabled
|
||||||
)
|
)
|
||||||
|
ChoiceDialog(
|
||||||
|
state = dialogState,
|
||||||
|
items = choices.toList(),
|
||||||
|
selected = prefValue,
|
||||||
|
title = title,
|
||||||
|
onSelected = { selected ->
|
||||||
|
preference.value = selected
|
||||||
|
changeListener()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
fun <T> ChoiceDialog(
|
fun <T> ChoiceDialog(
|
||||||
|
state: MaterialDialogState,
|
||||||
items: List<Pair<T, String>>,
|
items: List<Pair<T, String>>,
|
||||||
selected: T?,
|
selected: T?,
|
||||||
onCloseRequest: () -> Unit = {},
|
onCloseRequest: () -> Unit = {},
|
||||||
onSelected: (T) -> Unit,
|
onSelected: (T) -> Unit,
|
||||||
title: String,
|
title: String,
|
||||||
buttons: @Composable BoxWithConstraintsScope.(() -> Unit) -> Unit = { }
|
buttons: @Composable MaterialDialogButtons.() -> Unit = { }
|
||||||
) {
|
) {
|
||||||
WindowDialog(
|
MaterialDialog(
|
||||||
onCloseRequest = onCloseRequest,
|
state,
|
||||||
buttons = buttons,
|
buttons = buttons,
|
||||||
title = title
|
properties = getMaterialDialogProperties(),
|
||||||
|
onCloseRequest = {
|
||||||
|
state.hide()
|
||||||
|
onCloseRequest()
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
val state = rememberLazyListState()
|
title(title)
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
Box {
|
||||||
items(items) { (value, text) ->
|
val listState = rememberLazyListState()
|
||||||
Row(
|
LazyColumn(Modifier.fillMaxSize(), listState) {
|
||||||
modifier = Modifier.requiredHeight(48.dp).fillMaxWidth().clickable(
|
items(items) { (value, text) ->
|
||||||
onClick = {
|
Row(
|
||||||
onSelected(value)
|
modifier = Modifier.requiredHeight(48.dp).fillMaxWidth().clickable(
|
||||||
it()
|
onClick = {
|
||||||
}
|
onSelected(value)
|
||||||
),
|
state.hide()
|
||||||
verticalAlignment = Alignment.CenterVertically
|
}
|
||||||
) {
|
),
|
||||||
RadioButton(
|
verticalAlignment = Alignment.CenterVertically
|
||||||
selected = value == selected,
|
) {
|
||||||
onClick = {
|
RadioButton(
|
||||||
onSelected(value)
|
selected = value == selected,
|
||||||
it()
|
onClick = {
|
||||||
},
|
onSelected(value)
|
||||||
)
|
state.hide()
|
||||||
Text(text = text, modifier = Modifier.padding(start = 24.dp))
|
},
|
||||||
|
)
|
||||||
|
Text(text = text, modifier = Modifier.padding(start = 24.dp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
VerticalScrollbar(
|
||||||
|
rememberScrollbarAdapter(listState),
|
||||||
|
Modifier.align(Alignment.CenterEnd)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
VerticalScrollbar(
|
|
||||||
rememberScrollbarAdapter(state),
|
|
||||||
Modifier.align(Alignment.CenterEnd)
|
|
||||||
.fillMaxHeight()
|
|
||||||
.padding(horizontal = 4.dp, vertical = 8.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
fun <T> MultiSelectDialog(
|
fun <T> MultiSelectDialog(
|
||||||
|
state: MaterialDialogState,
|
||||||
items: List<Pair<T, String>>,
|
items: List<Pair<T, String>>,
|
||||||
selected: List<T>?,
|
selected: List<T>?,
|
||||||
onCloseRequest: () -> Unit = {},
|
onCloseRequest: () -> Unit = {},
|
||||||
onFinished: (List<T>) -> Unit,
|
onFinished: (List<T>) -> Unit,
|
||||||
title: String,
|
title: String,
|
||||||
) {
|
) {
|
||||||
val checkedFlow = MutableStateFlow(selected.orEmpty())
|
val checked = remember(selected) { selected.orEmpty().toMutableStateList() }
|
||||||
WindowDialog(
|
MaterialDialog(
|
||||||
onCloseRequest = onCloseRequest,
|
state,
|
||||||
title = title,
|
buttons = {
|
||||||
onPositiveButton = {
|
positiveButton(stringResource(MR.strings.action_ok)) {
|
||||||
onFinished(checkedFlow.value)
|
onFinished(checked)
|
||||||
|
}
|
||||||
|
negativeButton(stringResource(MR.strings.action_cancel))
|
||||||
|
},
|
||||||
|
properties = getMaterialDialogProperties(),
|
||||||
|
onCloseRequest = {
|
||||||
|
state.hide()
|
||||||
|
onCloseRequest()
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
val checked by checkedFlow.collectAsState()
|
title(title)
|
||||||
val state = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
Box {
|
Box {
|
||||||
LazyColumn(Modifier.fillMaxSize(), state) {
|
LazyColumn(Modifier.fillMaxSize(), listState) {
|
||||||
items(items) { (value, text) ->
|
items(items) { (value, text) ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.requiredHeight(48.dp).fillMaxWidth().clickable(
|
modifier = Modifier.requiredHeight(48.dp).fillMaxWidth().clickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (value in checked) {
|
if (value in checked) {
|
||||||
checkedFlow.value -= value
|
checked -= value
|
||||||
} else {
|
} else {
|
||||||
checkedFlow.value += value
|
checked += value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@@ -322,7 +357,7 @@ fun <T> MultiSelectDialog(
|
|||||||
item { Spacer(Modifier.height(80.dp)) }
|
item { Spacer(Modifier.height(80.dp)) }
|
||||||
}
|
}
|
||||||
VerticalScrollbar(
|
VerticalScrollbar(
|
||||||
rememberScrollbarAdapter(state),
|
rememberScrollbarAdapter(listState),
|
||||||
Modifier.align(Alignment.CenterEnd)
|
Modifier.align(Alignment.CenterEnd)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.padding(horizontal = 4.dp, vertical = 8.dp)
|
.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||||
@@ -340,17 +375,12 @@ fun ColorPreference(
|
|||||||
unsetColor: Color = Color.Unspecified
|
unsetColor: Color = Color.Unspecified
|
||||||
) {
|
) {
|
||||||
val initialColor = preference.value.takeOrElse { unsetColor }
|
val initialColor = preference.value.takeOrElse { unsetColor }
|
||||||
|
val dialogState = rememberMaterialDialogState()
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
onClick = {
|
onClick = {
|
||||||
ColorPickerDialog(
|
dialogState.show()
|
||||||
title = title,
|
|
||||||
onSelected = {
|
|
||||||
preference.value = it
|
|
||||||
},
|
|
||||||
initialColor = initialColor
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
onLongClick = { preference.value = Color.Unspecified },
|
onLongClick = { preference.value = Color.Unspecified },
|
||||||
action = {
|
action = {
|
||||||
@@ -369,6 +399,14 @@ fun ColorPreference(
|
|||||||
},
|
},
|
||||||
enabled = enabled
|
enabled = enabled
|
||||||
)
|
)
|
||||||
|
ColorPickerDialog(
|
||||||
|
state = dialogState,
|
||||||
|
title = title,
|
||||||
|
onSelected = {
|
||||||
|
preference.value = it
|
||||||
|
},
|
||||||
|
initialColor = initialColor
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const val EXPAND_ANIMATION_DURATION = 300
|
const val EXPAND_ANIMATION_DURATION = 300
|
||||||
@@ -4,11 +4,10 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:JvmName("ThemeScrollbarStyleKt")
|
||||||
|
|
||||||
package ca.gosyer.ui.base.theme
|
package ca.gosyer.ui.base.theme
|
||||||
|
|
||||||
import androidx.compose.desktop.DesktopMaterialTheme
|
|
||||||
import androidx.compose.foundation.LocalScrollbarStyle
|
|
||||||
import androidx.compose.foundation.ScrollbarStyle
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material.Colors
|
import androidx.compose.material.Colors
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
@@ -20,9 +19,10 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.ui.graphics.luminance
|
||||||
import androidx.compose.ui.graphics.takeOrElse
|
import androidx.compose.ui.graphics.takeOrElse
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
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.ui.base.components.LocalScrollbarStyle
|
||||||
|
import ca.gosyer.ui.base.theme.ThemeScrollbarStyle.getScrollbarStyle
|
||||||
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.LocalViewModelFactory
|
||||||
@@ -47,14 +47,7 @@ fun AppTheme(content: @Composable () -> Unit) {
|
|||||||
|
|
||||||
MaterialTheme(colors = colors) {
|
MaterialTheme(colors = colors) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalScrollbarStyle provides ScrollbarStyle(
|
LocalScrollbarStyle provides getScrollbarStyle(),
|
||||||
minimalHeight = 16.dp,
|
|
||||||
thickness = 8.dp,
|
|
||||||
shape = MaterialTheme.shapes.small,
|
|
||||||
hoverDurationMillis = 300,
|
|
||||||
unhoverColor = MaterialTheme.colors.onSurface.copy(alpha = 0.30f),
|
|
||||||
hoverColor = MaterialTheme.colors.onSurface.copy(alpha = 0.70f)
|
|
||||||
),
|
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* 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.base.theme
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import ca.gosyer.ui.base.components.ScrollbarStyle
|
||||||
|
|
||||||
|
expect object ThemeScrollbarStyle {
|
||||||
|
@Composable
|
||||||
|
fun getScrollbarStyle(): ScrollbarStyle
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.base.vm
|
||||||
|
|
||||||
|
import ca.gosyer.uicore.vm.ViewModelFactory
|
||||||
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
expect class ViewModelFactoryImpl : ViewModelFactory
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.updates.components
|
package ca.gosyer.ui.updates.components
|
||||||
|
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
@@ -18,7 +17,6 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollbarAdapter
|
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -34,6 +32,8 @@ import ca.gosyer.data.models.Chapter
|
|||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.chapter.ChapterDownloadIcon
|
import ca.gosyer.ui.base.chapter.ChapterDownloadIcon
|
||||||
import ca.gosyer.ui.base.chapter.ChapterDownloadItem
|
import ca.gosyer.ui.base.chapter.ChapterDownloadItem
|
||||||
|
import ca.gosyer.ui.base.components.VerticalScrollbar
|
||||||
|
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
|
||||||
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.components.MangaListItem
|
import ca.gosyer.uicore.components.MangaListItem
|
||||||
@@ -13,7 +13,8 @@ include("desktop")
|
|||||||
include("core")
|
include("core")
|
||||||
include("i18n")
|
include("i18n")
|
||||||
include("data")
|
include("data")
|
||||||
|
|
||||||
enableFeaturePreview("VERSION_CATALOGS")
|
|
||||||
include("ui-core")
|
include("ui-core")
|
||||||
include("presentation")
|
include("presentation")
|
||||||
|
|
||||||
|
enableFeaturePreview("VERSION_CATALOGS")
|
||||||
|
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ kotlin {
|
|||||||
api(libs.coroutinesCore)
|
api(libs.coroutinesCore)
|
||||||
api(libs.kamel)
|
api(libs.kamel)
|
||||||
api(libs.voyagerCore)
|
api(libs.voyagerCore)
|
||||||
api(project(":core"))
|
api(projects.core)
|
||||||
api(project(":i18n"))
|
api(projects.i18n)
|
||||||
api(compose.desktop.currentOs)
|
api(compose.desktop.currentOs)
|
||||||
api(compose.materialIconsExtended)
|
api(compose.materialIconsExtended)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user