mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
Implement configuration of server settings during runtime
This commit is contained in:
@@ -31,6 +31,7 @@ dependencies {
|
|||||||
implementation(libs.imageloader.core)
|
implementation(libs.imageloader.core)
|
||||||
implementation(libs.imageloader.moko)
|
implementation(libs.imageloader.moko)
|
||||||
implementation(libs.materialDialogs.core)
|
implementation(libs.materialDialogs.core)
|
||||||
|
implementation(libs.materialDialogs.datetime)
|
||||||
|
|
||||||
// Android
|
// Android
|
||||||
implementation(libs.androidx.core)
|
implementation(libs.androidx.core)
|
||||||
@@ -111,6 +112,9 @@ android {
|
|||||||
buildTypes {
|
buildTypes {
|
||||||
getByName("debug") {
|
getByName("debug") {
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
|
isMinifyEnabled = true
|
||||||
|
isShrinkResources = true
|
||||||
|
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
|
||||||
}
|
}
|
||||||
getByName("release") {
|
getByName("release") {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
|
|||||||
@@ -1,46 +1,146 @@
|
|||||||
|
fragment SettingsTypeFragment on SettingsType {
|
||||||
|
autoDownloadNewChapters
|
||||||
|
autoDownloadNewChaptersLimit
|
||||||
|
backupInterval
|
||||||
|
backupPath
|
||||||
|
backupTTL
|
||||||
|
backupTime
|
||||||
|
basicAuthEnabled
|
||||||
|
basicAuthPassword
|
||||||
|
basicAuthUsername
|
||||||
|
debugLogsEnabled
|
||||||
|
downloadAsCbz
|
||||||
|
downloadsPath
|
||||||
|
electronPath
|
||||||
|
excludeCompleted
|
||||||
|
excludeEntryWithUnreadChapters
|
||||||
|
excludeNotStarted
|
||||||
|
excludeUnreadChapters
|
||||||
|
extensionRepos
|
||||||
|
flareSolverrEnabled
|
||||||
|
flareSolverrSessionName
|
||||||
|
flareSolverrSessionTtl
|
||||||
|
flareSolverrTimeout
|
||||||
|
flareSolverrUrl
|
||||||
|
globalUpdateInterval
|
||||||
|
gqlDebugLogsEnabled
|
||||||
|
initialOpenInBrowserEnabled
|
||||||
|
ip
|
||||||
|
localSourcePath
|
||||||
|
maxSourcesInParallel
|
||||||
|
port
|
||||||
|
socksProxyEnabled
|
||||||
|
socksProxyHost
|
||||||
|
socksProxyPassword
|
||||||
|
socksProxyPort
|
||||||
|
socksProxyUsername
|
||||||
|
socksProxyVersion
|
||||||
|
systemTrayEnabled
|
||||||
|
updateMangas
|
||||||
|
webUIChannel
|
||||||
|
webUIFlavor
|
||||||
|
webUIInterface
|
||||||
|
webUIUpdateCheckInterval
|
||||||
|
}
|
||||||
|
|
||||||
query AllSettings {
|
query AllSettings {
|
||||||
settings {
|
settings {
|
||||||
autoDownloadNewChapters
|
...SettingsTypeFragment
|
||||||
autoDownloadNewChaptersLimit
|
}
|
||||||
backupInterval
|
}
|
||||||
backupPath
|
|
||||||
backupTTL
|
mutation SetSettings(
|
||||||
backupTime
|
$autoDownloadNewChapters: Boolean = null,
|
||||||
basicAuthEnabled
|
$autoDownloadNewChaptersLimit: Int = null,
|
||||||
basicAuthPassword
|
$backupInterval: Int = null,
|
||||||
basicAuthUsername
|
$backupPath: String = null,
|
||||||
debugLogsEnabled
|
$backupTTL: Int = null,
|
||||||
downloadAsCbz
|
$backupTime: String = null,
|
||||||
downloadsPath
|
$basicAuthEnabled: Boolean = null,
|
||||||
electronPath
|
$basicAuthPassword: String = null,
|
||||||
excludeCompleted
|
$basicAuthUsername: String = null,
|
||||||
excludeEntryWithUnreadChapters
|
$debugLogsEnabled: Boolean = null,
|
||||||
excludeNotStarted
|
$downloadAsCbz: Boolean = null,
|
||||||
excludeUnreadChapters
|
$downloadsPath: String = null,
|
||||||
extensionRepos
|
$electronPath: String = null,
|
||||||
flareSolverrEnabled
|
$excludeCompleted: Boolean = null,
|
||||||
flareSolverrSessionName
|
$excludeEntryWithUnreadChapters: Boolean = null,
|
||||||
flareSolverrSessionTtl
|
$excludeNotStarted: Boolean = null,
|
||||||
flareSolverrTimeout
|
$excludeUnreadChapters: Boolean = null,
|
||||||
flareSolverrUrl
|
$extensionRepos: [String!] = null,
|
||||||
globalUpdateInterval
|
$flareSolverrEnabled: Boolean = null,
|
||||||
gqlDebugLogsEnabled
|
$flareSolverrSessionName: String = null,
|
||||||
initialOpenInBrowserEnabled
|
$flareSolverrSessionTtl: Int = null,
|
||||||
ip
|
$flareSolverrTimeout: Int = null,
|
||||||
localSourcePath
|
$flareSolverrUrl: String = null,
|
||||||
maxSourcesInParallel
|
$globalUpdateInterval: Float = null,
|
||||||
port
|
$gqlDebugLogsEnabled: Boolean = null,
|
||||||
socksProxyEnabled
|
$initialOpenInBrowserEnabled: Boolean = null,
|
||||||
socksProxyHost
|
$ip: String = null,
|
||||||
socksProxyPassword
|
$localSourcePath: String = null,
|
||||||
socksProxyPort
|
$maxSourcesInParallel: Int = null,
|
||||||
socksProxyUsername
|
$port: Int = null,
|
||||||
socksProxyVersion
|
$socksProxyEnabled: Boolean = null,
|
||||||
systemTrayEnabled
|
$socksProxyHost: String = null,
|
||||||
updateMangas
|
$socksProxyPassword: String = null,
|
||||||
webUIChannel
|
$socksProxyPort: String = null,
|
||||||
webUIFlavor
|
$socksProxyUsername: String = null,
|
||||||
webUIInterface
|
$socksProxyVersion: Int = null,
|
||||||
webUIUpdateCheckInterval
|
$systemTrayEnabled: Boolean = null,
|
||||||
|
$updateMangas: Boolean = null,
|
||||||
|
$webUIChannel: WebUIChannel = null,
|
||||||
|
$webUIFlavor: WebUIFlavor = null,
|
||||||
|
$webUIInterface: WebUIInterface = null,
|
||||||
|
$webUIUpdateCheckInterval: Float = null
|
||||||
|
) {
|
||||||
|
setSettings(
|
||||||
|
input: {
|
||||||
|
settings: {
|
||||||
|
autoDownloadNewChapters: $autoDownloadNewChapters,
|
||||||
|
autoDownloadNewChaptersLimit: $autoDownloadNewChaptersLimit,
|
||||||
|
backupInterval: $backupInterval,
|
||||||
|
backupPath: $backupPath,
|
||||||
|
backupTTL: $backupTTL,
|
||||||
|
backupTime: $backupTime,
|
||||||
|
basicAuthEnabled: $basicAuthEnabled,
|
||||||
|
basicAuthPassword: $basicAuthPassword,
|
||||||
|
basicAuthUsername: $basicAuthUsername,
|
||||||
|
debugLogsEnabled: $debugLogsEnabled,
|
||||||
|
downloadAsCbz: $downloadAsCbz,
|
||||||
|
downloadsPath: $downloadsPath,
|
||||||
|
electronPath: $electronPath,
|
||||||
|
excludeCompleted: $excludeCompleted,
|
||||||
|
excludeEntryWithUnreadChapters: $excludeEntryWithUnreadChapters,
|
||||||
|
excludeNotStarted: $excludeNotStarted,
|
||||||
|
excludeUnreadChapters: $excludeUnreadChapters,
|
||||||
|
extensionRepos: $extensionRepos,
|
||||||
|
flareSolverrEnabled: $flareSolverrEnabled,
|
||||||
|
flareSolverrSessionName: $flareSolverrSessionName,
|
||||||
|
flareSolverrSessionTtl: $flareSolverrSessionTtl,
|
||||||
|
flareSolverrTimeout: $flareSolverrTimeout,
|
||||||
|
flareSolverrUrl: $flareSolverrUrl,
|
||||||
|
globalUpdateInterval: $globalUpdateInterval,
|
||||||
|
gqlDebugLogsEnabled: $gqlDebugLogsEnabled,
|
||||||
|
initialOpenInBrowserEnabled: $initialOpenInBrowserEnabled,
|
||||||
|
ip: $ip,
|
||||||
|
localSourcePath: $localSourcePath,
|
||||||
|
maxSourcesInParallel: $maxSourcesInParallel,
|
||||||
|
port: $port,
|
||||||
|
socksProxyEnabled: $socksProxyEnabled,
|
||||||
|
socksProxyHost: $socksProxyHost,
|
||||||
|
socksProxyPassword: $socksProxyPassword,
|
||||||
|
socksProxyPort: $socksProxyPort,
|
||||||
|
socksProxyUsername: $socksProxyUsername,
|
||||||
|
socksProxyVersion: $socksProxyVersion,
|
||||||
|
systemTrayEnabled: $systemTrayEnabled,
|
||||||
|
updateMangas: $updateMangas,
|
||||||
|
webUIChannel: $webUIChannel,
|
||||||
|
webUIFlavor: $webUIFlavor,
|
||||||
|
webUIInterface: $webUIInterface,
|
||||||
|
webUIUpdateCheckInterval: $webUIUpdateCheckInterval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
clientMutationId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
package ca.gosyer.jui.data
|
package ca.gosyer.jui.data
|
||||||
|
|
||||||
import ca.gosyer.jui.core.lang.addSuffix
|
import ca.gosyer.jui.core.lang.addSuffix
|
||||||
|
import ca.gosyer.jui.data.settings.SettingsRepositoryImpl
|
||||||
import ca.gosyer.jui.domain.backup.service.BackupRepository
|
import ca.gosyer.jui.domain.backup.service.BackupRepository
|
||||||
import ca.gosyer.jui.domain.category.service.CategoryRepository
|
import ca.gosyer.jui.domain.category.service.CategoryRepository
|
||||||
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
|
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
|
||||||
@@ -18,6 +19,7 @@ import ca.gosyer.jui.domain.manga.service.MangaRepository
|
|||||||
import ca.gosyer.jui.domain.server.Http
|
import ca.gosyer.jui.domain.server.Http
|
||||||
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
||||||
import ca.gosyer.jui.domain.settings.service.SettingsRepository
|
import ca.gosyer.jui.domain.settings.service.SettingsRepository
|
||||||
|
import ca.gosyer.jui.domain.settings.service.SettingsRepositoryOld
|
||||||
import ca.gosyer.jui.domain.source.service.SourceRepository
|
import ca.gosyer.jui.domain.source.service.SourceRepository
|
||||||
import ca.gosyer.jui.domain.updates.service.UpdatesRepository
|
import ca.gosyer.jui.domain.updates.service.UpdatesRepository
|
||||||
import com.apollographql.apollo3.ApolloClient
|
import com.apollographql.apollo3.ApolloClient
|
||||||
@@ -79,11 +81,15 @@ interface DataComponent {
|
|||||||
fun mangaRepository(ktorfit: Ktorfit) = ktorfit.create<MangaRepository>()
|
fun mangaRepository(ktorfit: Ktorfit) = ktorfit.create<MangaRepository>()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun settingsRepository(ktorfit: Ktorfit) = ktorfit.create<SettingsRepository>()
|
fun settingsRepositoryOld(ktorfit: Ktorfit) = ktorfit.create<SettingsRepositoryOld>()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun sourceRepository(ktorfit: Ktorfit) = ktorfit.create<SourceRepository>()
|
fun sourceRepository(ktorfit: Ktorfit) = ktorfit.create<SourceRepository>()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun updatesRepository(ktorfit: Ktorfit) = ktorfit.create<UpdatesRepository>()
|
fun updatesRepository(ktorfit: Ktorfit) = ktorfit.create<UpdatesRepository>()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun settingsRepository(apolloClient: ApolloClient): SettingsRepository =
|
||||||
|
SettingsRepositoryImpl(apolloClient)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
* 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.jui.data.settings
|
||||||
|
|
||||||
|
import ca.gosyer.jui.data.graphql.AllSettingsQuery
|
||||||
|
import ca.gosyer.jui.data.graphql.SetSettingsMutation
|
||||||
|
import ca.gosyer.jui.data.graphql.fragment.SettingsTypeFragment
|
||||||
|
import ca.gosyer.jui.data.graphql.type.WebUIChannel
|
||||||
|
import ca.gosyer.jui.data.graphql.type.WebUIFlavor
|
||||||
|
import ca.gosyer.jui.data.graphql.type.WebUIInterface
|
||||||
|
import ca.gosyer.jui.data.util.toOptional
|
||||||
|
import ca.gosyer.jui.domain.settings.model.SetSettingsInput
|
||||||
|
import ca.gosyer.jui.domain.settings.model.Settings
|
||||||
|
import ca.gosyer.jui.domain.settings.service.SettingsRepository
|
||||||
|
import com.apollographql.apollo3.ApolloClient
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.IO
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import ca.gosyer.jui.domain.settings.model.WebUIChannel as DomainWebUIChannel
|
||||||
|
import ca.gosyer.jui.domain.settings.model.WebUIFlavor as DomainWebUIFlavor
|
||||||
|
import ca.gosyer.jui.domain.settings.model.WebUIInterface as DomainWebUIInterface
|
||||||
|
|
||||||
|
class SettingsRepositoryImpl(private val apolloClient: ApolloClient) : SettingsRepository {
|
||||||
|
|
||||||
|
private fun SettingsTypeFragment.toSettings() = Settings(
|
||||||
|
autoDownloadNewChapters = autoDownloadNewChapters,
|
||||||
|
autoDownloadNewChaptersLimit = autoDownloadNewChaptersLimit,
|
||||||
|
backupInterval = backupInterval,
|
||||||
|
backupPath = backupPath,
|
||||||
|
backupTTL = backupTTL,
|
||||||
|
backupTime = backupTime,
|
||||||
|
basicAuthEnabled = basicAuthEnabled,
|
||||||
|
basicAuthPassword = basicAuthPassword,
|
||||||
|
basicAuthUsername = basicAuthUsername,
|
||||||
|
debugLogsEnabled = debugLogsEnabled,
|
||||||
|
downloadAsCbz = downloadAsCbz,
|
||||||
|
downloadsPath = downloadsPath,
|
||||||
|
electronPath = electronPath,
|
||||||
|
excludeCompleted = excludeCompleted,
|
||||||
|
excludeEntryWithUnreadChapters = excludeEntryWithUnreadChapters,
|
||||||
|
excludeNotStarted = excludeNotStarted,
|
||||||
|
excludeUnreadChapters = excludeUnreadChapters,
|
||||||
|
extensionRepos = extensionRepos,
|
||||||
|
flareSolverrEnabled = flareSolverrEnabled,
|
||||||
|
flareSolverrSessionName = flareSolverrSessionName,
|
||||||
|
flareSolverrSessionTtl = flareSolverrSessionTtl,
|
||||||
|
flareSolverrTimeout = flareSolverrTimeout,
|
||||||
|
flareSolverrUrl = flareSolverrUrl,
|
||||||
|
globalUpdateInterval = globalUpdateInterval,
|
||||||
|
gqlDebugLogsEnabled = gqlDebugLogsEnabled,
|
||||||
|
initialOpenInBrowserEnabled = initialOpenInBrowserEnabled,
|
||||||
|
ip = ip,
|
||||||
|
localSourcePath = localSourcePath,
|
||||||
|
maxSourcesInParallel = maxSourcesInParallel,
|
||||||
|
port = port,
|
||||||
|
socksProxyEnabled = socksProxyEnabled,
|
||||||
|
socksProxyHost = socksProxyHost,
|
||||||
|
socksProxyPassword = socksProxyPassword,
|
||||||
|
socksProxyPort = socksProxyPort,
|
||||||
|
socksProxyUsername = socksProxyUsername,
|
||||||
|
socksProxyVersion = socksProxyVersion,
|
||||||
|
systemTrayEnabled = systemTrayEnabled,
|
||||||
|
updateMangas = updateMangas,
|
||||||
|
webUIChannel = webUIChannel.toDomain(),
|
||||||
|
webUIFlavor = webUIFlavor.toDomain(),
|
||||||
|
webUIInterface = webUIInterface.toDomain(),
|
||||||
|
webUIUpdateCheckInterval = webUIUpdateCheckInterval
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun WebUIChannel.toDomain() = when (this) {
|
||||||
|
WebUIChannel.BUNDLED -> DomainWebUIChannel.BUNDLED
|
||||||
|
WebUIChannel.STABLE -> DomainWebUIChannel.STABLE
|
||||||
|
WebUIChannel.PREVIEW -> DomainWebUIChannel.PREVIEW
|
||||||
|
WebUIChannel.UNKNOWN__ -> DomainWebUIChannel.UNKNOWN__
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun WebUIFlavor.toDomain() = when (this) {
|
||||||
|
WebUIFlavor.WEBUI -> DomainWebUIFlavor.WEBUI
|
||||||
|
WebUIFlavor.VUI -> DomainWebUIFlavor.VUI
|
||||||
|
WebUIFlavor.CUSTOM -> DomainWebUIFlavor.CUSTOM
|
||||||
|
WebUIFlavor.UNKNOWN__ -> DomainWebUIFlavor.UNKNOWN__
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun WebUIInterface.toDomain() = when (this) {
|
||||||
|
WebUIInterface.BROWSER -> DomainWebUIInterface.BROWSER
|
||||||
|
WebUIInterface.ELECTRON -> DomainWebUIInterface.ELECTRON
|
||||||
|
WebUIInterface.UNKNOWN__ -> DomainWebUIInterface.UNKNOWN__
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DomainWebUIChannel.toGraphQL() = when (this) {
|
||||||
|
DomainWebUIChannel.BUNDLED -> WebUIChannel.BUNDLED
|
||||||
|
DomainWebUIChannel.STABLE -> WebUIChannel.STABLE
|
||||||
|
DomainWebUIChannel.PREVIEW -> WebUIChannel.PREVIEW
|
||||||
|
DomainWebUIChannel.UNKNOWN__ -> WebUIChannel.UNKNOWN__
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DomainWebUIFlavor.toGraphQL() = when (this) {
|
||||||
|
DomainWebUIFlavor.WEBUI -> WebUIFlavor.WEBUI
|
||||||
|
DomainWebUIFlavor.VUI -> WebUIFlavor.VUI
|
||||||
|
DomainWebUIFlavor.CUSTOM -> WebUIFlavor.CUSTOM
|
||||||
|
DomainWebUIFlavor.UNKNOWN__ -> WebUIFlavor.UNKNOWN__
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DomainWebUIInterface.toGraphQL() = when (this) {
|
||||||
|
DomainWebUIInterface.BROWSER -> WebUIInterface.BROWSER
|
||||||
|
DomainWebUIInterface.ELECTRON -> WebUIInterface.ELECTRON
|
||||||
|
DomainWebUIInterface.UNKNOWN__ -> WebUIInterface.UNKNOWN__
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSettings(): Flow<Settings> {
|
||||||
|
return apolloClient.query(AllSettingsQuery()).toFlow()
|
||||||
|
.map {
|
||||||
|
it.dataOrThrow().settings.settingsTypeFragment.toSettings()
|
||||||
|
}
|
||||||
|
.flowOn(Dispatchers.IO)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun SetSettingsInput.toMutation() = SetSettingsMutation(
|
||||||
|
autoDownloadNewChapters = autoDownloadNewChapters.toOptional(),
|
||||||
|
autoDownloadNewChaptersLimit = autoDownloadNewChaptersLimit.toOptional(),
|
||||||
|
backupInterval = backupInterval.toOptional(),
|
||||||
|
backupPath = backupPath.toOptional(),
|
||||||
|
backupTTL = backupTTL.toOptional(),
|
||||||
|
backupTime = backupTime.toOptional(),
|
||||||
|
basicAuthEnabled = basicAuthEnabled.toOptional(),
|
||||||
|
basicAuthPassword = basicAuthPassword.toOptional(),
|
||||||
|
basicAuthUsername = basicAuthUsername.toOptional(),
|
||||||
|
debugLogsEnabled = debugLogsEnabled.toOptional(),
|
||||||
|
downloadAsCbz = downloadAsCbz.toOptional(),
|
||||||
|
downloadsPath = downloadsPath.toOptional(),
|
||||||
|
electronPath = electronPath.toOptional(),
|
||||||
|
excludeCompleted = excludeCompleted.toOptional(),
|
||||||
|
excludeEntryWithUnreadChapters = excludeEntryWithUnreadChapters.toOptional(),
|
||||||
|
excludeNotStarted = excludeNotStarted.toOptional(),
|
||||||
|
excludeUnreadChapters = excludeUnreadChapters.toOptional(),
|
||||||
|
extensionRepos = extensionRepos.toOptional(),
|
||||||
|
flareSolverrEnabled = flareSolverrEnabled.toOptional(),
|
||||||
|
flareSolverrSessionName = flareSolverrSessionName.toOptional(),
|
||||||
|
flareSolverrSessionTtl = flareSolverrSessionTtl.toOptional(),
|
||||||
|
flareSolverrTimeout = flareSolverrTimeout.toOptional(),
|
||||||
|
flareSolverrUrl = flareSolverrUrl.toOptional(),
|
||||||
|
globalUpdateInterval = globalUpdateInterval.toOptional(),
|
||||||
|
gqlDebugLogsEnabled = gqlDebugLogsEnabled.toOptional(),
|
||||||
|
initialOpenInBrowserEnabled = initialOpenInBrowserEnabled.toOptional(),
|
||||||
|
ip = ip.toOptional(),
|
||||||
|
localSourcePath = localSourcePath.toOptional(),
|
||||||
|
maxSourcesInParallel = maxSourcesInParallel.toOptional(),
|
||||||
|
port = port.toOptional(),
|
||||||
|
socksProxyEnabled = socksProxyEnabled.toOptional(),
|
||||||
|
socksProxyHost = socksProxyHost.toOptional(),
|
||||||
|
socksProxyPassword = socksProxyPassword.toOptional(),
|
||||||
|
socksProxyPort = socksProxyPort.toOptional(),
|
||||||
|
socksProxyUsername = socksProxyUsername.toOptional(),
|
||||||
|
socksProxyVersion = socksProxyVersion.toOptional(),
|
||||||
|
systemTrayEnabled = systemTrayEnabled.toOptional(),
|
||||||
|
updateMangas = updateMangas.toOptional(),
|
||||||
|
webUIChannel = webUIChannel?.toGraphQL().toOptional(),
|
||||||
|
webUIFlavor = webUIFlavor?.toGraphQL().toOptional(),
|
||||||
|
webUIInterface = webUIInterface?.toGraphQL().toOptional(),
|
||||||
|
webUIUpdateCheckInterval = webUIUpdateCheckInterval.toOptional(),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun setSettings(input: SetSettingsInput): Flow<Unit> {
|
||||||
|
return apolloClient.mutation(input.toMutation())
|
||||||
|
.toFlow()
|
||||||
|
.map {
|
||||||
|
it.dataOrThrow()
|
||||||
|
Unit
|
||||||
|
}
|
||||||
|
.flowOn(Dispatchers.IO)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* 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.jui.data.util
|
||||||
|
|
||||||
|
import com.apollographql.apollo3.api.Optional
|
||||||
|
|
||||||
|
internal fun <T> T?.toOptional() = Optional.presentIfNotNull(this)
|
||||||
@@ -39,6 +39,7 @@ dependencies {
|
|||||||
implementation(libs.imageloader.core)
|
implementation(libs.imageloader.core)
|
||||||
implementation(libs.imageloader.moko)
|
implementation(libs.imageloader.moko)
|
||||||
implementation(libs.materialDialogs.core)
|
implementation(libs.materialDialogs.core)
|
||||||
|
implementation(libs.materialDialogs.datetime)
|
||||||
|
|
||||||
// UI (Swing)
|
// UI (Swing)
|
||||||
implementation(libs.darklaf)
|
implementation(libs.darklaf)
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ const val filePattern =
|
|||||||
'$' +
|
'$' +
|
||||||
"{LOG_EXCEPTION_CONVERSION_WORD:-%xEx}"
|
"{LOG_EXCEPTION_CONVERSION_WORD:-%xEx}"
|
||||||
|
|
||||||
@Suppress("UPPER_BOUND_VIOLATED_WARNING")
|
|
||||||
fun initializeLogger(loggingLocation: Path) {
|
fun initializeLogger(loggingLocation: Path) {
|
||||||
val ctx = LogManager.getContext(false) as LoggerContext
|
val ctx = LogManager.getContext(false) as LoggerContext
|
||||||
val builder = ConfigurationBuilderFactory.newConfigurationBuilder()
|
val builder = ConfigurationBuilderFactory.newConfigurationBuilder()
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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.jui.domain.server.service
|
||||||
|
|
||||||
|
import ca.gosyer.jui.core.prefs.Preference
|
||||||
|
import ca.gosyer.jui.core.prefs.PreferenceStore
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
actual class ServerHostPreferences actual constructor(
|
||||||
|
@Suppress("unused") private val preferenceStore: PreferenceStore,
|
||||||
|
) {
|
||||||
|
actual fun host(): Preference<Boolean> = object : Preference<Boolean> {
|
||||||
|
override fun key(): String = "host"
|
||||||
|
override fun get(): Boolean = false
|
||||||
|
override fun isSet(): Boolean = false
|
||||||
|
override fun delete() {}
|
||||||
|
override fun defaultValue(): Boolean = false
|
||||||
|
override fun changes(): Flow<Boolean> = MutableStateFlow(false)
|
||||||
|
override fun stateIn(scope: CoroutineScope): StateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
override fun set(value: Boolean) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import ca.gosyer.jui.domain.migration.service.MigrationPreferences
|
|||||||
import ca.gosyer.jui.domain.reader.service.ReaderPreferences
|
import ca.gosyer.jui.domain.reader.service.ReaderPreferences
|
||||||
import ca.gosyer.jui.domain.server.Http
|
import ca.gosyer.jui.domain.server.Http
|
||||||
import ca.gosyer.jui.domain.server.httpClient
|
import ca.gosyer.jui.domain.server.httpClient
|
||||||
|
import ca.gosyer.jui.domain.server.service.ServerHostPreferences
|
||||||
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
||||||
import ca.gosyer.jui.domain.source.service.CatalogPreferences
|
import ca.gosyer.jui.domain.source.service.CatalogPreferences
|
||||||
import ca.gosyer.jui.domain.ui.service.UiPreferences
|
import ca.gosyer.jui.domain.ui.service.UiPreferences
|
||||||
@@ -107,6 +108,11 @@ interface SharedDomainComponent : CoreComponent {
|
|||||||
val updatePreferencesFactory: UpdatePreferences
|
val updatePreferencesFactory: UpdatePreferences
|
||||||
get() = UpdatePreferences(preferenceFactory.create("update"))
|
get() = UpdatePreferences(preferenceFactory.create("update"))
|
||||||
|
|
||||||
|
@get:AppScope
|
||||||
|
@get:Provides
|
||||||
|
val serverHostPreferencesFactory: ServerHostPreferences
|
||||||
|
get() = ServerHostPreferences(preferenceFactory.create("host"))
|
||||||
|
|
||||||
@get:AppScope
|
@get:AppScope
|
||||||
@get:Provides
|
@get:Provides
|
||||||
val libraryUpdateServiceFactory: LibraryUpdateService
|
val libraryUpdateServiceFactory: LibraryUpdateService
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* 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.jui.domain.server.service
|
||||||
|
|
||||||
|
import ca.gosyer.jui.core.prefs.Preference
|
||||||
|
import ca.gosyer.jui.core.prefs.PreferenceStore
|
||||||
|
|
||||||
|
expect class ServerHostPreferences(
|
||||||
|
preferenceStore: PreferenceStore,
|
||||||
|
) {
|
||||||
|
fun host(): Preference<Boolean>
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
package ca.gosyer.jui.domain.settings.interactor
|
package ca.gosyer.jui.domain.settings.interactor
|
||||||
|
|
||||||
import ca.gosyer.jui.domain.settings.service.SettingsRepository
|
import ca.gosyer.jui.domain.settings.service.SettingsRepositoryOld
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.singleOrNull
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
@@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging
|
|||||||
class AboutServer
|
class AboutServer
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepositoryOld: SettingsRepositoryOld,
|
||||||
) {
|
) {
|
||||||
suspend fun await(onError: suspend (Throwable) -> Unit = {}) =
|
suspend fun await(onError: suspend (Throwable) -> Unit = {}) =
|
||||||
asFlow()
|
asFlow()
|
||||||
@@ -25,7 +25,7 @@ class AboutServer
|
|||||||
}
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
fun asFlow() = settingsRepository.aboutServer()
|
fun asFlow() = settingsRepositoryOld.aboutServer()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = logging()
|
private val log = logging()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
package ca.gosyer.jui.domain.settings.interactor
|
package ca.gosyer.jui.domain.settings.interactor
|
||||||
|
|
||||||
import ca.gosyer.jui.domain.settings.service.SettingsRepository
|
import ca.gosyer.jui.domain.settings.service.SettingsRepositoryOld
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.singleOrNull
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
@@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging
|
|||||||
class CheckUpdate
|
class CheckUpdate
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepositoryOld: SettingsRepositoryOld,
|
||||||
) {
|
) {
|
||||||
suspend fun await(onError: suspend (Throwable) -> Unit = {}) =
|
suspend fun await(onError: suspend (Throwable) -> Unit = {}) =
|
||||||
asFlow()
|
asFlow()
|
||||||
@@ -25,7 +25,7 @@ class CheckUpdate
|
|||||||
}
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
fun asFlow() = settingsRepository.checkUpdate()
|
fun asFlow() = settingsRepositoryOld.checkUpdate()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = logging()
|
private val log = logging()
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* 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.jui.domain.settings.interactor
|
||||||
|
|
||||||
|
import ca.gosyer.jui.domain.settings.service.SettingsRepository
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
import org.lighthousegames.logging.logging
|
||||||
|
|
||||||
|
class GetSettings
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val settingsRepository: SettingsRepository,
|
||||||
|
) {
|
||||||
|
suspend fun await(onError: suspend (Throwable) -> Unit = {}) =
|
||||||
|
asFlow()
|
||||||
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to check for server updates" }
|
||||||
|
}
|
||||||
|
.singleOrNull()
|
||||||
|
|
||||||
|
fun asFlow() = settingsRepository.getSettings()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = logging()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.jui.domain.settings.interactor
|
||||||
|
|
||||||
|
import ca.gosyer.jui.domain.settings.model.SetSettingsInput
|
||||||
|
import ca.gosyer.jui.domain.settings.service.SettingsRepository
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
import org.lighthousegames.logging.logging
|
||||||
|
|
||||||
|
class SetSettings
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val settingsRepository: SettingsRepository,
|
||||||
|
) {
|
||||||
|
suspend fun await(input: SetSettingsInput, onError: suspend (Throwable) -> Unit = {}) =
|
||||||
|
asFlow(input)
|
||||||
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to check for server updates" }
|
||||||
|
}
|
||||||
|
.singleOrNull()
|
||||||
|
|
||||||
|
fun asFlow(input: SetSettingsInput) = settingsRepository.setSettings(input)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = logging()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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.jui.domain.settings.model
|
||||||
|
|
||||||
|
class SetSettingsInput(
|
||||||
|
val autoDownloadNewChapters: Boolean? = null,
|
||||||
|
val autoDownloadNewChaptersLimit: Int? = null,
|
||||||
|
val backupInterval: Int? = null,
|
||||||
|
val backupPath: String? = null,
|
||||||
|
val backupTTL: Int? = null,
|
||||||
|
val backupTime: String? = null,
|
||||||
|
val basicAuthEnabled: Boolean? = null,
|
||||||
|
val basicAuthPassword: String? = null,
|
||||||
|
val basicAuthUsername: String? = null,
|
||||||
|
val debugLogsEnabled: Boolean? = null,
|
||||||
|
val downloadAsCbz: Boolean? = null,
|
||||||
|
val downloadsPath: String? = null,
|
||||||
|
val electronPath: String? = null,
|
||||||
|
val excludeCompleted: Boolean? = null,
|
||||||
|
val excludeEntryWithUnreadChapters: Boolean? = null,
|
||||||
|
val excludeNotStarted: Boolean? = null,
|
||||||
|
val excludeUnreadChapters: Boolean? = null,
|
||||||
|
val extensionRepos: List<String>? = null,
|
||||||
|
val flareSolverrEnabled: Boolean? = null,
|
||||||
|
val flareSolverrSessionName: String? = null,
|
||||||
|
val flareSolverrSessionTtl: Int? = null,
|
||||||
|
val flareSolverrTimeout: Int? = null,
|
||||||
|
val flareSolverrUrl: String? = null,
|
||||||
|
val globalUpdateInterval: Double? = null,
|
||||||
|
val gqlDebugLogsEnabled: Boolean? = null,
|
||||||
|
val initialOpenInBrowserEnabled: Boolean? = null,
|
||||||
|
val ip: String? = null,
|
||||||
|
val localSourcePath: String? = null,
|
||||||
|
val maxSourcesInParallel: Int? = null,
|
||||||
|
val port: Int? = null,
|
||||||
|
val socksProxyEnabled: Boolean? = null,
|
||||||
|
val socksProxyHost: String? = null,
|
||||||
|
val socksProxyPassword: String? = null,
|
||||||
|
val socksProxyPort: String? = null,
|
||||||
|
val socksProxyUsername: String? = null,
|
||||||
|
val socksProxyVersion: Int? = null,
|
||||||
|
val systemTrayEnabled: Boolean? = null,
|
||||||
|
val updateMangas: Boolean? = null,
|
||||||
|
val webUIChannel: WebUIChannel? = null,
|
||||||
|
val webUIFlavor: WebUIFlavor? = null,
|
||||||
|
val webUIInterface: WebUIInterface? = null,
|
||||||
|
val webUIUpdateCheckInterval: Double? = null,
|
||||||
|
)
|
||||||
@@ -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.jui.domain.settings.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class Settings(
|
||||||
|
val autoDownloadNewChapters: Boolean,
|
||||||
|
val autoDownloadNewChaptersLimit: Int,
|
||||||
|
val backupInterval: Int,
|
||||||
|
val backupPath: String,
|
||||||
|
val backupTTL: Int,
|
||||||
|
val backupTime: String,
|
||||||
|
val basicAuthEnabled: Boolean,
|
||||||
|
val basicAuthPassword: String,
|
||||||
|
val basicAuthUsername: String,
|
||||||
|
val debugLogsEnabled: Boolean,
|
||||||
|
val downloadAsCbz: Boolean,
|
||||||
|
val downloadsPath: String,
|
||||||
|
val electronPath: String,
|
||||||
|
val excludeCompleted: Boolean,
|
||||||
|
val excludeEntryWithUnreadChapters: Boolean,
|
||||||
|
val excludeNotStarted: Boolean,
|
||||||
|
val excludeUnreadChapters: Boolean,
|
||||||
|
val extensionRepos: List<String>,
|
||||||
|
val flareSolverrEnabled: Boolean,
|
||||||
|
val flareSolverrSessionName: String,
|
||||||
|
val flareSolverrSessionTtl: Int,
|
||||||
|
val flareSolverrTimeout: Int,
|
||||||
|
val flareSolverrUrl: String,
|
||||||
|
val globalUpdateInterval: Double,
|
||||||
|
val gqlDebugLogsEnabled: Boolean,
|
||||||
|
val initialOpenInBrowserEnabled: Boolean,
|
||||||
|
val ip: String,
|
||||||
|
val localSourcePath: String,
|
||||||
|
val maxSourcesInParallel: Int,
|
||||||
|
val port: Int,
|
||||||
|
val socksProxyEnabled: Boolean,
|
||||||
|
val socksProxyHost: String,
|
||||||
|
val socksProxyPassword: String,
|
||||||
|
val socksProxyPort: String,
|
||||||
|
val socksProxyUsername: String,
|
||||||
|
val socksProxyVersion: Int,
|
||||||
|
val systemTrayEnabled: Boolean,
|
||||||
|
val updateMangas: Boolean,
|
||||||
|
val webUIChannel: WebUIChannel,
|
||||||
|
val webUIFlavor: WebUIFlavor,
|
||||||
|
val webUIInterface: WebUIInterface,
|
||||||
|
val webUIUpdateCheckInterval: Double,
|
||||||
|
)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* 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.jui.domain.settings.model
|
||||||
|
|
||||||
|
enum class WebUIChannel {
|
||||||
|
BUNDLED,
|
||||||
|
STABLE,
|
||||||
|
PREVIEW,
|
||||||
|
UNKNOWN__;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* 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.jui.domain.settings.model
|
||||||
|
|
||||||
|
enum class WebUIFlavor {
|
||||||
|
WEBUI,
|
||||||
|
VUI,
|
||||||
|
CUSTOM,
|
||||||
|
UNKNOWN__;
|
||||||
|
}
|
||||||
@@ -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.jui.domain.settings.model
|
||||||
|
|
||||||
|
enum class WebUIInterface {
|
||||||
|
BROWSER,
|
||||||
|
ELECTRON,
|
||||||
|
UNKNOWN__;
|
||||||
|
}
|
||||||
@@ -6,16 +6,13 @@
|
|||||||
|
|
||||||
package ca.gosyer.jui.domain.settings.service
|
package ca.gosyer.jui.domain.settings.service
|
||||||
|
|
||||||
import ca.gosyer.jui.domain.settings.model.About
|
import ca.gosyer.jui.domain.settings.model.SetSettingsInput
|
||||||
import de.jensklingenberg.ktorfit.http.GET
|
import ca.gosyer.jui.domain.settings.model.Settings
|
||||||
import de.jensklingenberg.ktorfit.http.POST
|
|
||||||
import io.ktor.client.statement.HttpResponse
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface SettingsRepository {
|
interface SettingsRepository {
|
||||||
@GET("api/v1/settings/about")
|
|
||||||
fun aboutServer(): Flow<About>
|
|
||||||
|
|
||||||
@POST("api/v1/settings/check-update")
|
fun getSettings(): Flow<Settings>
|
||||||
fun checkUpdate(): Flow<HttpResponse>
|
|
||||||
|
fun setSettings(input: SetSettingsInput): Flow<Unit>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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.jui.domain.settings.service
|
||||||
|
|
||||||
|
import ca.gosyer.jui.domain.settings.model.About
|
||||||
|
import de.jensklingenberg.ktorfit.http.GET
|
||||||
|
import de.jensklingenberg.ktorfit.http.POST
|
||||||
|
import io.ktor.client.statement.HttpResponse
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface SettingsRepositoryOld {
|
||||||
|
@GET("api/v1/settings/about")
|
||||||
|
fun aboutServer(): Flow<About>
|
||||||
|
|
||||||
|
@POST("api/v1/settings/check-update")
|
||||||
|
fun checkUpdate(): Flow<HttpResponse>
|
||||||
|
}
|
||||||
@@ -18,11 +18,6 @@ actual interface DomainComponent : SharedDomainComponent {
|
|||||||
|
|
||||||
val serverHostPreferences: ServerHostPreferences
|
val serverHostPreferences: ServerHostPreferences
|
||||||
|
|
||||||
@get:AppScope
|
|
||||||
@get:Provides
|
|
||||||
val serverHostPreferencesFactory: ServerHostPreferences
|
|
||||||
get() = ServerHostPreferences(preferenceFactory.create("host"))
|
|
||||||
|
|
||||||
@get:AppScope
|
@get:AppScope
|
||||||
@get:Provides
|
@get:Provides
|
||||||
val serverServiceFactory: ServerService
|
val serverServiceFactory: ServerService
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import ca.gosyer.jui.core.prefs.Preference
|
|||||||
import ca.gosyer.jui.core.prefs.PreferenceStore
|
import ca.gosyer.jui.core.prefs.PreferenceStore
|
||||||
import ca.gosyer.jui.domain.server.service.host.ServerHostPreference
|
import ca.gosyer.jui.domain.server.service.host.ServerHostPreference
|
||||||
|
|
||||||
class ServerHostPreferences(
|
actual class ServerHostPreferences actual constructor(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
fun host(): Preference<Boolean> = preferenceStore.getBoolean("host", true)
|
actual fun host(): Preference<Boolean> = preferenceStore.getBoolean("host", true)
|
||||||
|
|
||||||
private val ip = ServerHostPreference.IP(preferenceStore)
|
private val ip = ServerHostPreference.IP(preferenceStore)
|
||||||
|
|
||||||
@@ -23,45 +23,20 @@ class ServerHostPreferences(
|
|||||||
|
|
||||||
fun port(): Preference<Int> = port.preference()
|
fun port(): Preference<Int> = port.preference()
|
||||||
|
|
||||||
// Proxy
|
|
||||||
private val socksProxyEnabled = ServerHostPreference.SocksProxyEnabled(preferenceStore)
|
|
||||||
|
|
||||||
fun socksProxyEnabled(): Preference<Boolean> = socksProxyEnabled.preference()
|
|
||||||
|
|
||||||
private val socksProxyHost = ServerHostPreference.SocksProxyHost(preferenceStore)
|
|
||||||
|
|
||||||
fun socksProxyHost(): Preference<String> = socksProxyHost.preference()
|
|
||||||
|
|
||||||
private val socksProxyPort = ServerHostPreference.SocksProxyPort(preferenceStore)
|
|
||||||
|
|
||||||
fun socksProxyPort(): Preference<Int> = socksProxyPort.preference()
|
|
||||||
|
|
||||||
// Misc
|
|
||||||
private val debugLogsEnabled = ServerHostPreference.DebugLogsEnabled(preferenceStore)
|
|
||||||
|
|
||||||
fun debugLogsEnabled(): Preference<Boolean> = debugLogsEnabled.preference()
|
|
||||||
|
|
||||||
private val systemTrayEnabled = ServerHostPreference.SystemTrayEnabled(preferenceStore)
|
|
||||||
|
|
||||||
fun systemTrayEnabled(): Preference<Boolean> = systemTrayEnabled.preference()
|
|
||||||
|
|
||||||
// Downloader
|
// Downloader
|
||||||
private val downloadPath = ServerHostPreference.DownloadPath(preferenceStore)
|
private val downloadPath = ServerHostPreference.DownloadPath(preferenceStore)
|
||||||
|
|
||||||
fun downloadPath(): Preference<String> = downloadPath.preference()
|
fun downloadPath(): Preference<String> = downloadPath.preference()
|
||||||
|
|
||||||
private val downloadAsCbz = ServerHostPreference.DownloadAsCbz(preferenceStore)
|
// Backup
|
||||||
|
private val backupPath = ServerHostPreference.BackupPath(preferenceStore)
|
||||||
|
|
||||||
fun downloadAsCbz(): Preference<Boolean> = downloadAsCbz.preference()
|
fun backupPath(): Preference<String> = backupPath.preference()
|
||||||
|
|
||||||
// WebUI
|
// LocalSource
|
||||||
private val webUIEnabled = ServerHostPreference.WebUIEnabled(preferenceStore)
|
private val localSourcePath = ServerHostPreference.LocalSourcePath(preferenceStore)
|
||||||
|
|
||||||
fun webUIEnabled(): Preference<Boolean> = webUIEnabled.preference()
|
fun localSourcePath(): Preference<String> = localSourcePath.preference()
|
||||||
|
|
||||||
private val openInBrowserEnabled = ServerHostPreference.OpenInBrowserEnabled(preferenceStore)
|
|
||||||
|
|
||||||
fun openInBrowserEnabled(): Preference<Boolean> = openInBrowserEnabled.preference()
|
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
private val basicAuthEnabled = ServerHostPreference.BasicAuthEnabled(preferenceStore)
|
private val basicAuthEnabled = ServerHostPreference.BasicAuthEnabled(preferenceStore)
|
||||||
@@ -80,19 +55,15 @@ class ServerHostPreferences(
|
|||||||
listOf(
|
listOf(
|
||||||
ip,
|
ip,
|
||||||
port,
|
port,
|
||||||
socksProxyEnabled,
|
|
||||||
socksProxyHost,
|
|
||||||
socksProxyPort,
|
|
||||||
debugLogsEnabled,
|
|
||||||
systemTrayEnabled,
|
|
||||||
downloadPath,
|
downloadPath,
|
||||||
downloadAsCbz,
|
backupPath,
|
||||||
webUIEnabled,
|
localSourcePath,
|
||||||
openInBrowserEnabled,
|
|
||||||
basicAuthEnabled,
|
basicAuthEnabled,
|
||||||
basicAuthUsername,
|
basicAuthUsername,
|
||||||
basicAuthPassword,
|
basicAuthPassword,
|
||||||
).mapNotNull {
|
).mapNotNull {
|
||||||
it.getProperty()
|
it.getProperty()
|
||||||
}.toTypedArray()
|
}.plus(
|
||||||
|
"-Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false"
|
||||||
|
).toTypedArray()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,49 +79,6 @@ sealed class ServerHostPreference<T : Any> {
|
|||||||
4567,
|
4567,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Proxy
|
|
||||||
class SocksProxyEnabled(
|
|
||||||
preferenceStore: PreferenceStore,
|
|
||||||
) : BooleanServerHostPreference(
|
|
||||||
preferenceStore,
|
|
||||||
"socksProxyEnabled",
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
|
|
||||||
class SocksProxyHost(
|
|
||||||
preferenceStore: PreferenceStore,
|
|
||||||
) : StringServerHostPreference(
|
|
||||||
preferenceStore,
|
|
||||||
"socksProxyHost",
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|
||||||
class SocksProxyPort(
|
|
||||||
override val preferenceStore: PreferenceStore,
|
|
||||||
) : IntServerHostPreference(
|
|
||||||
preferenceStore,
|
|
||||||
"socksProxyPort",
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Misc
|
|
||||||
class DebugLogsEnabled(
|
|
||||||
preferenceStore: PreferenceStore,
|
|
||||||
) : BooleanServerHostPreference(
|
|
||||||
preferenceStore,
|
|
||||||
"debugLogsEnabled",
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
|
|
||||||
class SystemTrayEnabled(
|
|
||||||
preferenceStore: PreferenceStore,
|
|
||||||
) : BooleanServerHostPreference(
|
|
||||||
preferenceStore,
|
|
||||||
"systemTrayEnabled",
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Downloader
|
// Downloader
|
||||||
class DownloadPath(
|
class DownloadPath(
|
||||||
preferenceStore: PreferenceStore,
|
preferenceStore: PreferenceStore,
|
||||||
@@ -131,32 +88,23 @@ sealed class ServerHostPreference<T : Any> {
|
|||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
|
|
||||||
class DownloadAsCbz(
|
// Backup
|
||||||
|
class BackupPath(
|
||||||
preferenceStore: PreferenceStore,
|
preferenceStore: PreferenceStore,
|
||||||
) : BooleanServerHostPreference(
|
) : StringServerHostPreference(
|
||||||
preferenceStore,
|
preferenceStore,
|
||||||
"downloadAsCbz",
|
"backupPath",
|
||||||
false,
|
"",
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebUI
|
// LocalSource
|
||||||
class WebUIEnabled(
|
class LocalSourcePath(
|
||||||
preferenceStore: PreferenceStore,
|
preferenceStore: PreferenceStore,
|
||||||
) : BooleanServerHostPreference(
|
) : StringServerHostPreference(
|
||||||
preferenceStore,
|
preferenceStore,
|
||||||
"webUIEnabled",
|
"localSourcePath",
|
||||||
false,
|
"",
|
||||||
true,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
class OpenInBrowserEnabled(
|
|
||||||
preferenceStore: PreferenceStore,
|
|
||||||
) : BooleanServerHostPreference(
|
|
||||||
preferenceStore,
|
|
||||||
"initialOpenInBrowserEnabled",
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
class BasicAuthEnabled(
|
class BasicAuthEnabled(
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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.jui.domain.server.service
|
||||||
|
|
||||||
|
import ca.gosyer.jui.core.prefs.Preference
|
||||||
|
import ca.gosyer.jui.core.prefs.PreferenceStore
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
actual class ServerHostPreferences actual constructor(
|
||||||
|
@Suppress("unused") private val preferenceStore: PreferenceStore,
|
||||||
|
) {
|
||||||
|
actual fun host(): Preference<Boolean> = object : Preference<Boolean> {
|
||||||
|
override fun key(): String = "host"
|
||||||
|
override fun get(): Boolean = false
|
||||||
|
override fun isSet(): Boolean = false
|
||||||
|
override fun delete() {}
|
||||||
|
override fun defaultValue(): Boolean = false
|
||||||
|
override fun changes(): Flow<Boolean> = MutableStateFlow(false)
|
||||||
|
override fun stateIn(scope: CoroutineScope): StateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
override fun set(value: Boolean) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,6 +99,7 @@ accompanist-systemUIController = { module = "com.google.accompanist:accompanist-
|
|||||||
imageloader-core = { module = "io.github.qdsfdhvh:image-loader", version.ref = "imageloader" }
|
imageloader-core = { module = "io.github.qdsfdhvh:image-loader", version.ref = "imageloader" }
|
||||||
imageloader-moko = { module = "io.github.qdsfdhvh:image-loader-extension-moko-resources", version.ref = "imageloader" }
|
imageloader-moko = { module = "io.github.qdsfdhvh:image-loader-extension-moko-resources", version.ref = "imageloader" }
|
||||||
materialDialogs-core = { module = "ca.gosyer:compose-material-dialogs-core", version.ref = "materialDialogs" }
|
materialDialogs-core = { module = "ca.gosyer:compose-material-dialogs-core", version.ref = "materialDialogs" }
|
||||||
|
materialDialogs-datetime = { module = "ca.gosyer:compose-material-dialogs-datetime", version.ref = "materialDialogs" }
|
||||||
|
|
||||||
# Android
|
# Android
|
||||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "core" }
|
androidx-core = { module = "androidx.core:core-ktx", version.ref = "core" }
|
||||||
|
|||||||
@@ -220,9 +220,9 @@
|
|||||||
|
|
||||||
<!-- Backup Settings -->
|
<!-- Backup Settings -->
|
||||||
<string name="backup_restore">Restore Backup</string>
|
<string name="backup_restore">Restore Backup</string>
|
||||||
<string name="backup_restore_sub">Restore a backup into Tachidesk</string>
|
<string name="backup_restore_sub">Restore a backup to your library</string>
|
||||||
<string name="backup_create">Create Backup</string>
|
<string name="backup_create">Create Backup</string>
|
||||||
<string name="backup_create_sub">Create a backup from Tachidesk</string>
|
<string name="backup_create_sub">Create a backup from your library</string>
|
||||||
<string name="missing_sources">Missing sources:</string>
|
<string name="missing_sources">Missing sources:</string>
|
||||||
|
|
||||||
<!-- General Settings -->
|
<!-- General Settings -->
|
||||||
@@ -282,17 +282,21 @@
|
|||||||
|
|
||||||
<!-- Server Settings -->
|
<!-- Server Settings -->
|
||||||
<string name="host_server">Host server inside Tachidesk-JUI</string>
|
<string name="host_server">Host server inside Tachidesk-JUI</string>
|
||||||
<string name="host_settings">Tachidesk-Server settings</string>
|
<string name="host_settings">Suwayomi-Server settings</string>
|
||||||
<string name="host_settings_sub">The below settings configure the internal Tachidesk-Server</string>
|
<string name="host_settings_sub">The below settings configure the internal Suwayomi-Server</string>
|
||||||
|
|
||||||
<string name="host_ip">Server IP</string>
|
<string name="host_ip">Server IP</string>
|
||||||
<string name="host_ip_sub">Current server IP: %1$s</string>
|
<string name="host_ip_sub">Current server IP: %1$s</string>
|
||||||
<string name="host_port">Server PORT</string>
|
<string name="host_port">Server PORT</string>
|
||||||
<string name="host_port_sub">Current server PORT: %1$s</string>
|
<string name="host_port_sub">Current server PORT: %1$s</string>
|
||||||
<string name="host_socks_enabled">Server SOCKS5 Proxy</string>
|
<string name="host_socks_enabled">Server SOCKS Proxy</string>
|
||||||
<string name="host_socks_host">Server SOCKS5 Proxy HOST</string>
|
<string name="host_socks_host">Server SOCKS Proxy HOST</string>
|
||||||
<string name="host_socks_host_sub">Current Proxy HOST: %1$s</string>
|
<string name="host_socks_host_sub">Current Proxy HOST: %1$s</string>
|
||||||
<string name="host_socks_port">Server SOCKS5 Proxy PORT</string>
|
<string name="host_socks_port">Server SOCKS Proxy PORT</string>
|
||||||
<string name="host_socks_port_sub">Current Proxy PORT: %1$s</string>
|
<string name="host_socks_port_sub">Current Proxy PORT: %1$s</string>
|
||||||
|
<string name="host_socks_username">Server SOCKS Proxy username</string>
|
||||||
|
<string name="host_socks_password">Server SOCKS Proxy password</string>
|
||||||
|
<string name="host_socks_version">Server SOCKS Proxy version</string>
|
||||||
<string name="host_debug_logging">Server debug logs</string>
|
<string name="host_debug_logging">Server debug logs</string>
|
||||||
<string name="host_debug_logging_sub">Output debug logs from the server to JUI</string>
|
<string name="host_debug_logging_sub">Output debug logs from the server to JUI</string>
|
||||||
<string name="host_system_tray">Server system tray icon</string>
|
<string name="host_system_tray">Server system tray icon</string>
|
||||||
@@ -300,10 +304,16 @@
|
|||||||
<string name="host_download_path">Download path</string>
|
<string name="host_download_path">Download path</string>
|
||||||
<string name="host_download_path_sub">Current download path: %1$s</string>
|
<string name="host_download_path_sub">Current download path: %1$s</string>
|
||||||
<string name="host_download_path_sub_empty">Using default download path</string>
|
<string name="host_download_path_sub_empty">Using default download path</string>
|
||||||
|
<string name="host_backup_path">Backup path</string>
|
||||||
|
<string name="host_backup_path_sub">Current backup path: %1$s</string>
|
||||||
|
<string name="host_backup_path_sub_empty">Using default backup path</string>
|
||||||
|
<string name="host_local_source_path">Local source path</string>
|
||||||
|
<string name="host_local_source_path_sub">Current local source path: %1$s</string>
|
||||||
|
<string name="host_local_source_path_sub_empty">Using default local source path</string>
|
||||||
<string name="host_download_as_cbz">Download as CBZ</string>
|
<string name="host_download_as_cbz">Download as CBZ</string>
|
||||||
<string name="host_download_as_cbz_sub">Download chapters into CBZ archives</string>
|
<string name="host_download_as_cbz_sub">Download chapters into CBZ archives</string>
|
||||||
<string name="host_webui">Server WebUI</string>
|
<string name="host_webui">Server WebUI</string>
|
||||||
<string name="host_webui_sub">Whether the server\'s default WebUI is enabled, makes you able to use Tachidesk in your browser</string>
|
<string name="host_webui_sub">Whether the server\'s default WebUI is enabled, makes you able to use Suwayomi in your browser</string>
|
||||||
<string name="host_open_in_browser">Open Server WebUI on startup</string>
|
<string name="host_open_in_browser">Open Server WebUI on startup</string>
|
||||||
<string name="host_open_in_browser_sub">Open the WebUI inside your browser on server startup. Requires the WebUI be enabled</string>
|
<string name="host_open_in_browser_sub">Open the WebUI inside your browser on server startup. Requires the WebUI be enabled</string>
|
||||||
<string name="host_basic_auth_sub">Use basic auth to protect your library, requires username and password</string>
|
<string name="host_basic_auth_sub">Use basic auth to protect your library, requires username and password</string>
|
||||||
@@ -327,6 +337,37 @@
|
|||||||
<string name="digest_auth">Digest auth</string>
|
<string name="digest_auth">Digest auth</string>
|
||||||
<string name="auth_username">Auth username</string>
|
<string name="auth_username">Auth username</string>
|
||||||
<string name="auth_password">Auth password</string>
|
<string name="auth_password">Auth password</string>
|
||||||
|
<string name="server_settings_sub">The below settings configure the connected Suwayomi-Server</string>
|
||||||
|
<string name="extension_repos">Extension repos</string>
|
||||||
|
<string name="extension_repos_sub">Configure your extension repos to allow extensions to be found</string>
|
||||||
|
<string name="global_update_interval">Global update interval</string>
|
||||||
|
<string name="global_update_interval_sub">Update interval in hours, 0 to disable, values under 6 hours will be ignored</string>
|
||||||
|
<string name="update_manga_info">Update manga info</string>
|
||||||
|
<string name="update_manga_info_sub">Update manga info alongside manga chapters in the library update</string>
|
||||||
|
<string name="exclude_completed">Exclude completed</string>
|
||||||
|
<string name="exclude_completed_sub">Exclude completed manga from library update</string>
|
||||||
|
<string name="exclude_unread">Exclude unread</string>
|
||||||
|
<string name="exclude_unread_sub">Exclude manga with unread chapters from library update</string>
|
||||||
|
<string name="exclude_not_started">Exclude not started</string>
|
||||||
|
<string name="exclude_not_started_sub">Exclude manga with no read chapters from library update</string>
|
||||||
|
<string name="max_sources_parallel">Max sources in parallel</string>
|
||||||
|
<string name="max_sources_parallel_sub">How many sources can be updated or downloaded in parallel</string>
|
||||||
|
<string name="graphql_debug_logs">Server GraphQL debug logs</string>
|
||||||
|
<string name="graphql_debug_logs_sub">Output GraphQL debug logs from the server to JUI</string>
|
||||||
|
<string name="download_new_chapters">Download new chapters</string>
|
||||||
|
<string name="download_chapter_limit">Chapter download limit</string>
|
||||||
|
<string name="download_chapter_limit_sub">Limit the amount of new chapters that are going to get downloaded</string>
|
||||||
|
<string name="ignore_unread_entries">Ignore entries with unread chapters</string>
|
||||||
|
<string name="backup_interval">Backup interval</string>
|
||||||
|
<string name="backup_interval_sub">Automatic backup interval in days, 0 to disable it</string>
|
||||||
|
<string name="backup_ttl">Backup TTL</string>
|
||||||
|
<string name="backup_ttl_sub">How long to keep a specific backup, in days, 0 to disable</string>
|
||||||
|
<string name="backup_time">Backup time</string>
|
||||||
|
<string name="backup_time_sub">When to run the automatic backup</string>
|
||||||
|
<string name="flaresolverr_url">FlareSolverr server url</string>
|
||||||
|
<string name="flaresolverr_timeout">FlareSolverr request timeout</string>
|
||||||
|
<string name="flaresolverr_session_name">FlareSolverr session name</string>
|
||||||
|
<string name="flaresolverr_session_ttl">FlareSolverr session TTL</string>
|
||||||
|
|
||||||
<!-- Advanced Settings -->
|
<!-- Advanced Settings -->
|
||||||
<string name="update_checker">Check for updates</string>
|
<string name="update_checker">Check for updates</string>
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ kotlin {
|
|||||||
implementation(libs.imageloader.core)
|
implementation(libs.imageloader.core)
|
||||||
implementation(libs.imageloader.moko)
|
implementation(libs.imageloader.moko)
|
||||||
implementation(libs.materialDialogs.core)
|
implementation(libs.materialDialogs.core)
|
||||||
|
implementation(libs.materialDialogs.datetime)
|
||||||
|
|
||||||
// Threading
|
// Threading
|
||||||
implementation(libs.coroutines.core)
|
implementation(libs.coroutines.core)
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ kotlin {
|
|||||||
api(libs.voyager.transitions)
|
api(libs.voyager.transitions)
|
||||||
api(libs.voyager.screenmodel)
|
api(libs.voyager.screenmodel)
|
||||||
api(libs.materialDialogs.core)
|
api(libs.materialDialogs.core)
|
||||||
|
api(libs.materialDialogs.datetime)
|
||||||
api(libs.accompanist.pager)
|
api(libs.accompanist.pager)
|
||||||
api(libs.accompanist.pagerIndicators)
|
api(libs.accompanist.pagerIndicators)
|
||||||
api(libs.accompanist.flowLayout)
|
api(libs.accompanist.flowLayout)
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ import ca.gosyer.jui.uicore.components.VerticalScrollbar
|
|||||||
import ca.gosyer.jui.uicore.components.keyboardHandler
|
import ca.gosyer.jui.uicore.components.keyboardHandler
|
||||||
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
|
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
|
||||||
import ca.gosyer.jui.uicore.components.scrollbarPadding
|
import ca.gosyer.jui.uicore.components.scrollbarPadding
|
||||||
import ca.gosyer.jui.uicore.prefs.PreferenceMutableStateFlow
|
|
||||||
import ca.gosyer.jui.uicore.resources.stringResource
|
import ca.gosyer.jui.uicore.resources.stringResource
|
||||||
import com.vanpra.composematerialdialogs.MaterialDialog
|
import com.vanpra.composematerialdialogs.MaterialDialog
|
||||||
import com.vanpra.composematerialdialogs.MaterialDialogButtons
|
import com.vanpra.composematerialdialogs.MaterialDialogButtons
|
||||||
@@ -85,6 +84,7 @@ import com.vanpra.composematerialdialogs.title
|
|||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.ImmutableMap
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PreferenceRow(
|
fun PreferenceRow(
|
||||||
@@ -151,7 +151,7 @@ fun PreferenceRow(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchPreference(
|
fun SwitchPreference(
|
||||||
preference: PreferenceMutableStateFlow<Boolean>,
|
preference: MutableStateFlow<Boolean>,
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
icon: ImageVector? = null,
|
icon: ImageVector? = null,
|
||||||
@@ -176,7 +176,7 @@ fun SwitchPreference(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EditTextPreference(
|
fun EditTextPreference(
|
||||||
preference: PreferenceMutableStateFlow<String>,
|
preference: MutableStateFlow<String>,
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
icon: ImageVector? = null,
|
icon: ImageVector? = null,
|
||||||
@@ -221,7 +221,7 @@ fun EditTextPreference(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <Key> ChoicePreference(
|
fun <Key> ChoicePreference(
|
||||||
preference: PreferenceMutableStateFlow<Key>,
|
preference: MutableStateFlow<Key>,
|
||||||
choices: ImmutableMap<Key, String>,
|
choices: ImmutableMap<Key, String>,
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
@@ -344,7 +344,7 @@ fun <T> MultiSelectDialog(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ColorPreference(
|
fun ColorPreference(
|
||||||
preference: PreferenceMutableStateFlow<Color>,
|
preference: MutableStateFlow<Color>,
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ class SettingsLibraryViewModel
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun getDisplayModeChoices() =
|
fun getDisplayModeChoices() =
|
||||||
DisplayMode.values()
|
DisplayMode.entries
|
||||||
.associateWith { stringResource(it.res) }
|
.associateWith { stringResource(it.res) }
|
||||||
.toImmutableMap()
|
.toImmutableMap()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,40 +6,72 @@
|
|||||||
|
|
||||||
package ca.gosyer.jui.ui.settings
|
package ca.gosyer.jui.ui.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
import androidx.compose.foundation.layout.add
|
import androidx.compose.foundation.layout.add
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
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.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.only
|
import androidx.compose.foundation.layout.only
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.statusBars
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.CircularProgressIndicator
|
||||||
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.OutlinedTextField
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Add
|
||||||
|
import androidx.compose.material.icons.rounded.Delete
|
||||||
|
import androidx.compose.material.icons.rounded.Info
|
||||||
import androidx.compose.material.icons.rounded.Warning
|
import androidx.compose.material.icons.rounded.Warning
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
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.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ca.gosyer.jui.core.lang.launchIO
|
||||||
import ca.gosyer.jui.domain.server.model.Auth
|
import ca.gosyer.jui.domain.server.model.Auth
|
||||||
import ca.gosyer.jui.domain.server.model.Proxy
|
import ca.gosyer.jui.domain.server.model.Proxy
|
||||||
|
import ca.gosyer.jui.domain.server.service.ServerHostPreferences
|
||||||
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
||||||
|
import ca.gosyer.jui.domain.settings.interactor.GetSettings
|
||||||
|
import ca.gosyer.jui.domain.settings.interactor.SetSettings
|
||||||
|
import ca.gosyer.jui.domain.settings.model.SetSettingsInput
|
||||||
|
import ca.gosyer.jui.domain.settings.model.Settings
|
||||||
import ca.gosyer.jui.i18n.MR
|
import ca.gosyer.jui.i18n.MR
|
||||||
|
import ca.gosyer.jui.ui.base.dialog.getMaterialDialogProperties
|
||||||
import ca.gosyer.jui.ui.base.navigation.Toolbar
|
import ca.gosyer.jui.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.jui.ui.base.prefs.ChoicePreference
|
import ca.gosyer.jui.ui.base.prefs.ChoicePreference
|
||||||
import ca.gosyer.jui.ui.base.prefs.EditTextPreference
|
import ca.gosyer.jui.ui.base.prefs.EditTextPreference
|
||||||
import ca.gosyer.jui.ui.base.prefs.PreferenceRow
|
import ca.gosyer.jui.ui.base.prefs.PreferenceRow
|
||||||
|
import ca.gosyer.jui.ui.base.prefs.SwitchPreference
|
||||||
import ca.gosyer.jui.ui.main.components.bottomNav
|
import ca.gosyer.jui.ui.main.components.bottomNav
|
||||||
import ca.gosyer.jui.ui.viewModel
|
import ca.gosyer.jui.ui.viewModel
|
||||||
import ca.gosyer.jui.uicore.components.VerticalScrollbar
|
import ca.gosyer.jui.uicore.components.VerticalScrollbar
|
||||||
|
import ca.gosyer.jui.uicore.components.keyboardHandler
|
||||||
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
|
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
|
||||||
import ca.gosyer.jui.uicore.components.scrollbarPadding
|
import ca.gosyer.jui.uicore.components.scrollbarPadding
|
||||||
import ca.gosyer.jui.uicore.prefs.PreferenceMutableStateFlow
|
import ca.gosyer.jui.uicore.prefs.PreferenceMutableStateFlow
|
||||||
@@ -51,10 +83,24 @@ import ca.gosyer.jui.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.datetime.time.timepicker
|
||||||
|
import com.vanpra.composematerialdialogs.listItems
|
||||||
|
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
|
||||||
|
import com.vanpra.composematerialdialogs.title
|
||||||
import kotlinx.collections.immutable.ImmutableMap
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.datetime.LocalTime
|
||||||
|
import kotlinx.datetime.format.char
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class SettingsServerScreen : Screen {
|
class SettingsServerScreen : Screen {
|
||||||
@@ -80,6 +126,8 @@ class SettingsServerScreen : Screen {
|
|||||||
authChoices = connectionVM.getAuthChoices(),
|
authChoices = connectionVM.getAuthChoices(),
|
||||||
authUsername = connectionVM.authUsername,
|
authUsername = connectionVM.authUsername,
|
||||||
authPassword = connectionVM.authPassword,
|
authPassword = connectionVM.authPassword,
|
||||||
|
serverSettings = connectionVM.serverSettings.collectAsState().value,
|
||||||
|
hosted = connectionVM.host.collectAsState().value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,10 +137,238 @@ expect class SettingsServerHostViewModel : ViewModel
|
|||||||
@Composable
|
@Composable
|
||||||
expect fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostViewModel): LazyListScope.() -> Unit
|
expect fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostViewModel): LazyListScope.() -> Unit
|
||||||
|
|
||||||
|
private class ServerSettingMutableStateFlow<T>(
|
||||||
|
parent: StateFlow<Settings>,
|
||||||
|
getSetting: (Settings) -> T,
|
||||||
|
private val setSetting: (T) -> Unit,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
private val state: MutableStateFlow<T> = MutableStateFlow(getSetting(parent.value)),
|
||||||
|
) : MutableStateFlow<T> by state {
|
||||||
|
|
||||||
|
init {
|
||||||
|
parent
|
||||||
|
.onEach { state.value = getSetting(it) }
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override var value: T
|
||||||
|
get() = state.value
|
||||||
|
set(value) {
|
||||||
|
setSetting(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class ServerSettings(
|
||||||
|
private val getSettings: GetSettings,
|
||||||
|
private val setSettings: SetSettings,
|
||||||
|
private val scope: CoroutineScope,
|
||||||
|
initial: Settings,
|
||||||
|
private val onError: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val settings = MutableStateFlow(initial)
|
||||||
|
|
||||||
|
val autoDownloadNewChapters = getServerFlow(
|
||||||
|
getSetting = { it.autoDownloadNewChapters },
|
||||||
|
getInput = { SetSettingsInput(autoDownloadNewChapters = it) }
|
||||||
|
)
|
||||||
|
val autoDownloadNewChaptersLimit = getServerFlow(
|
||||||
|
getSetting = { it.autoDownloadNewChaptersLimit.toString() },
|
||||||
|
getInput = { SetSettingsInput(autoDownloadNewChaptersLimit = it.toIntOrNull()) }
|
||||||
|
)
|
||||||
|
val backupInterval = getServerFlow(
|
||||||
|
getSetting = { it.backupInterval.toString() },
|
||||||
|
getInput = { SetSettingsInput(backupInterval = it.toIntOrNull()) }
|
||||||
|
)
|
||||||
|
val backupPath = getServerFlow(
|
||||||
|
getSetting = { it.backupPath },
|
||||||
|
getInput = { SetSettingsInput(backupPath = it) }
|
||||||
|
)
|
||||||
|
val backupTTL = getServerFlow(
|
||||||
|
getSetting = { it.backupTTL.toString() },
|
||||||
|
getInput = { SetSettingsInput(backupTTL = it.toIntOrNull()) }
|
||||||
|
)
|
||||||
|
val backupTime = getServerFlow(
|
||||||
|
getSetting = { it.backupTime },
|
||||||
|
getInput = { SetSettingsInput(backupTime = it) }
|
||||||
|
)
|
||||||
|
val basicAuthEnabled = getServerFlow(
|
||||||
|
getSetting = { it.basicAuthEnabled },
|
||||||
|
getInput = { SetSettingsInput(basicAuthEnabled = it) }
|
||||||
|
)
|
||||||
|
val basicAuthPassword = getServerFlow(
|
||||||
|
getSetting = { it.basicAuthPassword },
|
||||||
|
getInput = { SetSettingsInput(basicAuthPassword = it) }
|
||||||
|
)
|
||||||
|
val basicAuthUsername = getServerFlow(
|
||||||
|
getSetting = { it.basicAuthUsername },
|
||||||
|
getInput = { SetSettingsInput(basicAuthUsername = it) }
|
||||||
|
)
|
||||||
|
val debugLogsEnabled = getServerFlow(
|
||||||
|
getSetting = { it.debugLogsEnabled },
|
||||||
|
getInput = { SetSettingsInput(debugLogsEnabled = it) }
|
||||||
|
)
|
||||||
|
val downloadAsCbz = getServerFlow(
|
||||||
|
getSetting = { it.downloadAsCbz },
|
||||||
|
getInput = { SetSettingsInput(downloadAsCbz = it) }
|
||||||
|
)
|
||||||
|
val downloadsPath = getServerFlow(
|
||||||
|
getSetting = { it.downloadsPath },
|
||||||
|
getInput = { SetSettingsInput(downloadsPath = it) }
|
||||||
|
)
|
||||||
|
val electronPath = getServerFlow(
|
||||||
|
getSetting = { it.electronPath },
|
||||||
|
getInput = { SetSettingsInput(electronPath = it) }
|
||||||
|
)
|
||||||
|
val excludeCompleted = getServerFlow(
|
||||||
|
getSetting = { it.excludeCompleted },
|
||||||
|
getInput = { SetSettingsInput(excludeCompleted = it) }
|
||||||
|
)
|
||||||
|
val excludeEntryWithUnreadChapters = getServerFlow(
|
||||||
|
getSetting = { it.excludeEntryWithUnreadChapters },
|
||||||
|
getInput = { SetSettingsInput(excludeEntryWithUnreadChapters = it) }
|
||||||
|
)
|
||||||
|
val excludeNotStarted = getServerFlow(
|
||||||
|
getSetting = { it.excludeNotStarted },
|
||||||
|
getInput = { SetSettingsInput(excludeNotStarted = it) }
|
||||||
|
)
|
||||||
|
val excludeUnreadChapters = getServerFlow(
|
||||||
|
getSetting = { it.excludeUnreadChapters },
|
||||||
|
getInput = { SetSettingsInput(excludeUnreadChapters = it) }
|
||||||
|
)
|
||||||
|
val extensionRepos = getServerFlow(
|
||||||
|
getSetting = { it.extensionRepos },
|
||||||
|
getInput = { SetSettingsInput(extensionRepos = it) }
|
||||||
|
)
|
||||||
|
val flareSolverrEnabled = getServerFlow(
|
||||||
|
getSetting = { it.flareSolverrEnabled },
|
||||||
|
getInput = { SetSettingsInput(flareSolverrEnabled = it) }
|
||||||
|
)
|
||||||
|
val flareSolverrSessionName = getServerFlow(
|
||||||
|
getSetting = { it.flareSolverrSessionName },
|
||||||
|
getInput = { SetSettingsInput(flareSolverrSessionName = it) }
|
||||||
|
)
|
||||||
|
val flareSolverrSessionTtl = getServerFlow(
|
||||||
|
getSetting = { it.flareSolverrSessionTtl.toString() },
|
||||||
|
getInput = { SetSettingsInput(flareSolverrSessionTtl = it.toIntOrNull()) }
|
||||||
|
)
|
||||||
|
val flareSolverrTimeout = getServerFlow(
|
||||||
|
getSetting = { it.flareSolverrTimeout.toString() },
|
||||||
|
getInput = { SetSettingsInput(flareSolverrTimeout = it.toIntOrNull()) }
|
||||||
|
)
|
||||||
|
val flareSolverrUrl = getServerFlow(
|
||||||
|
getSetting = { it.flareSolverrUrl },
|
||||||
|
getInput = { SetSettingsInput(flareSolverrUrl = it) }
|
||||||
|
)
|
||||||
|
val globalUpdateInterval = getServerFlow(
|
||||||
|
getSetting = { it.globalUpdateInterval.toString() },
|
||||||
|
getInput = { SetSettingsInput(globalUpdateInterval = it.toDoubleOrNull()?.takeIf { it !in 0.01..5.99 }) }
|
||||||
|
)
|
||||||
|
val gqlDebugLogsEnabled = getServerFlow(
|
||||||
|
getSetting = { it.gqlDebugLogsEnabled },
|
||||||
|
getInput = { SetSettingsInput(gqlDebugLogsEnabled = it) }
|
||||||
|
)
|
||||||
|
val initialOpenInBrowserEnabled = getServerFlow(
|
||||||
|
getSetting = { it.initialOpenInBrowserEnabled },
|
||||||
|
getInput = { SetSettingsInput(initialOpenInBrowserEnabled = it) }
|
||||||
|
)
|
||||||
|
val ip = getServerFlow(
|
||||||
|
getSetting = { it.ip },
|
||||||
|
getInput = { SetSettingsInput(ip = it) }
|
||||||
|
)
|
||||||
|
val localSourcePath = getServerFlow(
|
||||||
|
getSetting = { it.localSourcePath },
|
||||||
|
getInput = { SetSettingsInput(localSourcePath = it) }
|
||||||
|
)
|
||||||
|
val maxSourcesInParallel = getServerFlow(
|
||||||
|
getSetting = { it.maxSourcesInParallel.toString() },
|
||||||
|
getInput = { SetSettingsInput(maxSourcesInParallel = it.toIntOrNull()) }
|
||||||
|
)
|
||||||
|
val port = getServerFlow(
|
||||||
|
getSetting = { it.port.toString() },
|
||||||
|
getInput = { SetSettingsInput(port = it.toIntOrNull()) }
|
||||||
|
)
|
||||||
|
val socksProxyEnabled = getServerFlow(
|
||||||
|
getSetting = { it.socksProxyEnabled },
|
||||||
|
getInput = { SetSettingsInput(socksProxyEnabled = it) }
|
||||||
|
)
|
||||||
|
val socksProxyHost = getServerFlow(
|
||||||
|
getSetting = { it.socksProxyHost },
|
||||||
|
getInput = { SetSettingsInput(socksProxyHost = it) }
|
||||||
|
)
|
||||||
|
val socksProxyPassword = getServerFlow(
|
||||||
|
getSetting = { it.socksProxyPassword },
|
||||||
|
getInput = { SetSettingsInput(socksProxyPassword = it) }
|
||||||
|
)
|
||||||
|
val socksProxyPort = getServerFlow(
|
||||||
|
getSetting = { it.socksProxyPort },
|
||||||
|
getInput = { SetSettingsInput(socksProxyPort = it) }
|
||||||
|
)
|
||||||
|
val socksProxyUsername = getServerFlow(
|
||||||
|
getSetting = { it.socksProxyUsername },
|
||||||
|
getInput = { SetSettingsInput(socksProxyUsername = it) }
|
||||||
|
)
|
||||||
|
val socksProxyVersion = getServerFlow(
|
||||||
|
getSetting = { it.socksProxyVersion },
|
||||||
|
getInput = { SetSettingsInput(socksProxyVersion = it) }
|
||||||
|
)
|
||||||
|
val systemTrayEnabled = getServerFlow(
|
||||||
|
getSetting = { it.systemTrayEnabled },
|
||||||
|
getInput = { SetSettingsInput(systemTrayEnabled = it) }
|
||||||
|
)
|
||||||
|
val updateMangas = getServerFlow(
|
||||||
|
getSetting = { it.updateMangas },
|
||||||
|
getInput = { SetSettingsInput(updateMangas = it) }
|
||||||
|
)
|
||||||
|
val webUIChannel = getServerFlow(
|
||||||
|
getSetting = { it.webUIChannel },
|
||||||
|
getInput = { SetSettingsInput(webUIChannel = it) }
|
||||||
|
)
|
||||||
|
val webUIFlavor = getServerFlow(
|
||||||
|
getSetting = { it.webUIFlavor },
|
||||||
|
getInput = { SetSettingsInput(webUIFlavor = it) }
|
||||||
|
)
|
||||||
|
val webUIInterface = getServerFlow(
|
||||||
|
getSetting = { it.webUIInterface },
|
||||||
|
getInput = { SetSettingsInput(webUIInterface = it) }
|
||||||
|
)
|
||||||
|
val webUIUpdateCheckInterval = getServerFlow(
|
||||||
|
getSetting = { it.webUIUpdateCheckInterval },
|
||||||
|
getInput = { SetSettingsInput(webUIUpdateCheckInterval = it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun <T> getServerFlow(
|
||||||
|
getSetting: (Settings) -> T,
|
||||||
|
getInput: (T) -> SetSettingsInput,
|
||||||
|
): MutableStateFlow<T> {
|
||||||
|
return ServerSettingMutableStateFlow(
|
||||||
|
parent = settings,
|
||||||
|
getSetting = getSetting,
|
||||||
|
setSetting = {
|
||||||
|
scope.launch {
|
||||||
|
val input = getInput(it)
|
||||||
|
setSettings.await(
|
||||||
|
input,
|
||||||
|
onError = { onError(it.message.orEmpty()) }
|
||||||
|
)
|
||||||
|
val response = getSettings.await(onError = { onError(it.message.orEmpty()) })
|
||||||
|
if (response != null) {
|
||||||
|
settings.value = response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scope = scope,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SettingsServerViewModel
|
class SettingsServerViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
|
private val getSettings: GetSettings,
|
||||||
|
private val setSettings: SetSettings,
|
||||||
serverPreferences: ServerPreferences,
|
serverPreferences: ServerPreferences,
|
||||||
|
serverHostPreferences: ServerHostPreferences,
|
||||||
contextWrapper: ContextWrapper,
|
contextWrapper: ContextWrapper,
|
||||||
) : ViewModel(contextWrapper) {
|
) : ViewModel(contextWrapper) {
|
||||||
val serverUrl = serverPreferences.server().asStateIn(scope)
|
val serverUrl = serverPreferences.server().asStateIn(scope)
|
||||||
@@ -101,6 +377,8 @@ class SettingsServerViewModel
|
|||||||
|
|
||||||
val proxy = serverPreferences.proxy().asStateIn(scope)
|
val proxy = serverPreferences.proxy().asStateIn(scope)
|
||||||
|
|
||||||
|
val host = serverHostPreferences.host().asStateIn(scope)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun getProxyChoices(): ImmutableMap<Proxy, String> =
|
fun getProxyChoices(): ImmutableMap<Proxy, String> =
|
||||||
persistentMapOf(
|
persistentMapOf(
|
||||||
@@ -127,11 +405,22 @@ class SettingsServerViewModel
|
|||||||
val authUsername = serverPreferences.authUsername().asStateIn(scope)
|
val authUsername = serverPreferences.authUsername().asStateIn(scope)
|
||||||
val authPassword = serverPreferences.authPassword().asStateIn(scope)
|
val authPassword = serverPreferences.authPassword().asStateIn(scope)
|
||||||
|
|
||||||
private val _serverSettingChanged = MutableStateFlow(false)
|
private val _serverSettings = MutableStateFlow<ServerSettings?>(null)
|
||||||
val serverSettingChanged = _serverSettingChanged.asStateFlow()
|
val serverSettings = _serverSettings.asStateFlow()
|
||||||
|
|
||||||
fun serverSettingChanged() {
|
init {
|
||||||
_serverSettingChanged.value = true
|
scope.launchIO {
|
||||||
|
val initialSettings = getSettings.await(onError = { toast(it.message.orEmpty()) })
|
||||||
|
if (initialSettings != null) {
|
||||||
|
_serverSettings.value = ServerSettings(
|
||||||
|
getSettings,
|
||||||
|
setSettings,
|
||||||
|
scope,
|
||||||
|
initialSettings,
|
||||||
|
onError = { toast(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +442,8 @@ fun SettingsServerScreenContent(
|
|||||||
authChoices: ImmutableMap<Auth, String>,
|
authChoices: ImmutableMap<Auth, String>,
|
||||||
authUsername: PreferenceMutableStateFlow<String>,
|
authUsername: PreferenceMutableStateFlow<String>,
|
||||||
authPassword: PreferenceMutableStateFlow<String>,
|
authPassword: PreferenceMutableStateFlow<String>,
|
||||||
|
hosted: Boolean,
|
||||||
|
serverSettings: ServerSettings?,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.windowInsetsPadding(
|
modifier = Modifier.windowInsetsPadding(
|
||||||
@@ -260,6 +551,18 @@ fun SettingsServerScreenContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
item {
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
if (serverSettings != null) {
|
||||||
|
ServerSettingsItems(hosted, serverSettings)
|
||||||
|
} else {
|
||||||
|
item {
|
||||||
|
Box(Modifier.fillMaxWidth().height(48.dp), contentAlignment = Alignment.Center) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
VerticalScrollbar(
|
VerticalScrollbar(
|
||||||
rememberScrollbarAdapter(state),
|
rememberScrollbarAdapter(state),
|
||||||
@@ -277,3 +580,433 @@ fun SettingsServerScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun LazyListScope.ServerSettingsItems(
|
||||||
|
hosted: Boolean,
|
||||||
|
serverSettings: ServerSettings,
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
PreferenceRow(
|
||||||
|
stringResource(MR.strings.host_settings),
|
||||||
|
Icons.Rounded.Info,
|
||||||
|
subtitle = stringResource(MR.strings.server_settings_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val ipValue by serverSettings.ip.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.ip,
|
||||||
|
title = stringResource(MR.strings.host_ip),
|
||||||
|
subtitle = stringResource(MR.strings.host_ip_sub, ipValue),
|
||||||
|
enabled = !hosted
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val portValue by serverSettings.port.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.port,
|
||||||
|
title = stringResource(MR.strings.host_port),
|
||||||
|
subtitle = stringResource(MR.strings.host_port_sub, portValue),
|
||||||
|
enabled = !hosted
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val dialog = rememberMaterialDialogState()
|
||||||
|
PreferenceRow(
|
||||||
|
stringResource(MR.strings.extension_repos),
|
||||||
|
subtitle = stringResource(MR.strings.extension_repos_sub),
|
||||||
|
onClick = dialog::show
|
||||||
|
)
|
||||||
|
val repos by serverSettings.extensionRepos.collectAsState()
|
||||||
|
ExtensionReposDialog(
|
||||||
|
dialog,
|
||||||
|
repos,
|
||||||
|
onSetRepos = {
|
||||||
|
serverSettings.extensionRepos.value = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.socksProxyEnabled,
|
||||||
|
title = stringResource(MR.strings.host_socks_enabled),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val socksProxyEnabled by serverSettings.socksProxyEnabled.collectAsState()
|
||||||
|
val proxyHost by serverSettings.socksProxyHost.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.socksProxyHost,
|
||||||
|
title = stringResource(MR.strings.host_socks_host),
|
||||||
|
subtitle = stringResource(MR.strings.host_socks_host_sub, proxyHost),
|
||||||
|
enabled = socksProxyEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val socksProxyEnabled by serverSettings.socksProxyEnabled.collectAsState()
|
||||||
|
val proxyPort by serverSettings.socksProxyPort.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.socksProxyPort,
|
||||||
|
title = stringResource(MR.strings.host_socks_port),
|
||||||
|
subtitle = stringResource(MR.strings.host_socks_port_sub, proxyPort),
|
||||||
|
enabled = socksProxyEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val socksProxyEnabled by serverSettings.socksProxyEnabled.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.socksProxyUsername,
|
||||||
|
title = stringResource(MR.strings.host_socks_username),
|
||||||
|
enabled = socksProxyEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val socksProxyEnabled by serverSettings.socksProxyEnabled.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.socksProxyPassword,
|
||||||
|
title = stringResource(MR.strings.host_socks_password),
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
|
enabled = socksProxyEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val socksProxyEnabled by serverSettings.socksProxyEnabled.collectAsState()
|
||||||
|
ChoicePreference(
|
||||||
|
preference = serverSettings.socksProxyVersion,
|
||||||
|
choices = mapOf(
|
||||||
|
4 to "SOCKS4",
|
||||||
|
5 to "SOCKS5"
|
||||||
|
).toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.host_socks_version),
|
||||||
|
enabled = socksProxyEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.globalUpdateInterval,
|
||||||
|
title = stringResource(MR.strings.global_update_interval),
|
||||||
|
subtitle = stringResource(MR.strings.global_update_interval_sub)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.updateMangas,
|
||||||
|
title = stringResource(MR.strings.update_manga_info),
|
||||||
|
subtitle = stringResource(MR.strings.update_manga_info_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.excludeCompleted,
|
||||||
|
title = stringResource(MR.strings.exclude_completed),
|
||||||
|
subtitle = stringResource(MR.strings.exclude_completed_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.excludeUnreadChapters,
|
||||||
|
title = stringResource(MR.strings.exclude_unread),
|
||||||
|
subtitle = stringResource(MR.strings.exclude_unread_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.excludeNotStarted,
|
||||||
|
title = stringResource(MR.strings.exclude_not_started),
|
||||||
|
subtitle = stringResource(MR.strings.exclude_not_started_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.maxSourcesInParallel,
|
||||||
|
title = stringResource(MR.strings.max_sources_parallel),
|
||||||
|
subtitle = stringResource(MR.strings.max_sources_parallel_sub)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.downloadAsCbz,
|
||||||
|
title = stringResource(MR.strings.host_download_as_cbz),
|
||||||
|
subtitle = stringResource(MR.strings.host_download_as_cbz_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.autoDownloadNewChapters,
|
||||||
|
title = stringResource(MR.strings.download_new_chapters),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.autoDownloadNewChaptersLimit,
|
||||||
|
title = stringResource(MR.strings.download_chapter_limit),
|
||||||
|
subtitle = stringResource(MR.strings.download_chapter_limit_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.excludeEntryWithUnreadChapters,
|
||||||
|
title = stringResource(MR.strings.ignore_unread_entries),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.debugLogsEnabled,
|
||||||
|
title = stringResource(MR.strings.host_debug_logging),
|
||||||
|
subtitle = stringResource(MR.strings.host_debug_logging_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.gqlDebugLogsEnabled,
|
||||||
|
title = stringResource(MR.strings.graphql_debug_logs),
|
||||||
|
subtitle = stringResource(MR.strings.graphql_debug_logs_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.systemTrayEnabled,
|
||||||
|
title = stringResource(MR.strings.host_system_tray),
|
||||||
|
subtitle = stringResource(MR.strings.host_system_tray_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
// val webUIEnabledValue by serverSettings.webUIEnabled.collectAsState()
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.initialOpenInBrowserEnabled,
|
||||||
|
title = stringResource(MR.strings.host_open_in_browser),
|
||||||
|
subtitle = stringResource(MR.strings.host_open_in_browser_sub),
|
||||||
|
enabled = !hosted, //webUIEnabledValue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.backupInterval,
|
||||||
|
title = stringResource(MR.strings.backup_interval),
|
||||||
|
subtitle = stringResource(MR.strings.backup_interval_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.backupTTL,
|
||||||
|
title = stringResource(MR.strings.backup_ttl),
|
||||||
|
subtitle = stringResource(MR.strings.backup_ttl_sub),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val dialog = rememberMaterialDialogState()
|
||||||
|
val backupTime by serverSettings.backupTime.collectAsState()
|
||||||
|
PreferenceRow(
|
||||||
|
title = stringResource(MR.strings.backup_time),
|
||||||
|
subtitle = stringResource(MR.strings.backup_time_sub),
|
||||||
|
onClick = dialog::show
|
||||||
|
)
|
||||||
|
BackupTimeDialog(
|
||||||
|
dialog,
|
||||||
|
backupTime,
|
||||||
|
onSetTime = {
|
||||||
|
serverSettings.backupTime.value = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.basicAuthEnabled,
|
||||||
|
title = stringResource(MR.strings.basic_auth),
|
||||||
|
subtitle = stringResource(MR.strings.host_basic_auth_sub),
|
||||||
|
enabled = !hosted
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
val basicAuthEnabledValue by serverSettings.basicAuthEnabled.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.basicAuthUsername,
|
||||||
|
title = stringResource(MR.strings.host_basic_auth_username),
|
||||||
|
enabled = basicAuthEnabledValue && !hosted,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val basicAuthEnabledValue by serverSettings.basicAuthEnabled.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.basicAuthPassword,
|
||||||
|
title = stringResource(MR.strings.host_basic_auth_password),
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
|
enabled = basicAuthEnabledValue && !hosted,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SwitchPreference(
|
||||||
|
preference = serverSettings.flareSolverrEnabled,
|
||||||
|
title = "FlareSolverr enabled",
|
||||||
|
subtitle = "Use a FlareSolverr instance to bypass CloudFlare. Manual setup required",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val flareSolverrEnabled by serverSettings.flareSolverrEnabled.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.flareSolverrUrl,
|
||||||
|
title = stringResource(MR.strings.flaresolverr_url),
|
||||||
|
enabled = flareSolverrEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val flareSolverrEnabled by serverSettings.flareSolverrEnabled.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.flareSolverrTimeout,
|
||||||
|
title = stringResource(MR.strings.flaresolverr_timeout),
|
||||||
|
enabled = flareSolverrEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val flareSolverrEnabled by serverSettings.flareSolverrEnabled.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.flareSolverrSessionName,
|
||||||
|
title = stringResource(MR.strings.flaresolverr_session_name),
|
||||||
|
enabled = flareSolverrEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val flareSolverrEnabled by serverSettings.flareSolverrEnabled.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.flareSolverrSessionTtl,
|
||||||
|
title = stringResource(MR.strings.flaresolverr_session_ttl),
|
||||||
|
enabled = flareSolverrEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val repoRegex =
|
||||||
|
(
|
||||||
|
"https:\\/\\/(?>www\\.|raw\\.)?(github|githubusercontent)\\.com" +
|
||||||
|
"\\/([^\\/]+)\\/([^\\/]+)(?>(?>\\/tree|\\/blob)?\\/([^\\/\\n]*))?(?>\\/([^\\/\\n]*\\.json)?)?"
|
||||||
|
).toRegex()
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExtensionReposDialog(
|
||||||
|
state: MaterialDialogState,
|
||||||
|
extensionRepos: List<String>,
|
||||||
|
onSetRepos: (List<String>) -> Unit
|
||||||
|
) {
|
||||||
|
val repos = remember(state.showing) {
|
||||||
|
extensionRepos.toMutableStateList()
|
||||||
|
}
|
||||||
|
var newRepo by remember(state.showing) { mutableStateOf("") }
|
||||||
|
MaterialDialog(
|
||||||
|
state,
|
||||||
|
properties = getMaterialDialogProperties(),
|
||||||
|
buttons = {
|
||||||
|
negativeButton(stringResource(MR.strings.action_cancel))
|
||||||
|
positiveButton(stringResource(MR.strings.action_ok), onClick = { onSetRepos(repos.toList()) })
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
title(stringResource(MR.strings.extension_repos))
|
||||||
|
Row(
|
||||||
|
Modifier.fillMaxWidth().padding(vertical = 12.dp, horizontal = 24.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
val repoMatches by derivedStateOf {
|
||||||
|
newRepo.matches(repoRegex)
|
||||||
|
}
|
||||||
|
OutlinedTextField(
|
||||||
|
value = newRepo,
|
||||||
|
onValueChange = { newRepo = it },
|
||||||
|
modifier = Modifier.weight(4f)
|
||||||
|
.keyboardHandler(
|
||||||
|
singleLine = true,
|
||||||
|
enterAction = {
|
||||||
|
if (repoMatches) {
|
||||||
|
repos.add(newRepo)
|
||||||
|
newRepo = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
isError = newRepo.isNotBlank() && !repoMatches,
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
repos.add(newRepo)
|
||||||
|
newRepo = ""
|
||||||
|
},
|
||||||
|
enabled = repoMatches,
|
||||||
|
modifier = Modifier.weight(1f, fill = false)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Add,
|
||||||
|
contentDescription = stringResource(MR.strings.action_add)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listItems(
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp),
|
||||||
|
list = repos,
|
||||||
|
closeOnClick = false,
|
||||||
|
) { _, item ->
|
||||||
|
Row(
|
||||||
|
Modifier.fillMaxWidth().padding(vertical = 12.dp, horizontal = 24.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
item,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(4f)
|
||||||
|
.wrapContentWidth(Alignment.Start)
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
onClick = { repos.remove(item) },
|
||||||
|
modifier = Modifier.weight(1f, fill = false)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Delete,
|
||||||
|
contentDescription = stringResource(MR.strings.action_delete)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val formatter = LocalTime.Format {
|
||||||
|
hour()
|
||||||
|
char(':')
|
||||||
|
minute()
|
||||||
|
}
|
||||||
|
@Composable
|
||||||
|
fun BackupTimeDialog(
|
||||||
|
state: MaterialDialogState,
|
||||||
|
backupTime: String,
|
||||||
|
onSetTime: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val time = remember(state.showing) {
|
||||||
|
LocalTime.parse(backupTime, formatter)
|
||||||
|
}
|
||||||
|
MaterialDialog(
|
||||||
|
state,
|
||||||
|
properties = getMaterialDialogProperties(),
|
||||||
|
buttons = {
|
||||||
|
negativeButton(stringResource(MR.strings.action_cancel))
|
||||||
|
positiveButton(stringResource(MR.strings.action_ok))
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
timepicker(
|
||||||
|
time,
|
||||||
|
title = stringResource(MR.strings.backup_time),
|
||||||
|
onTimeChange = {
|
||||||
|
onSetTime(formatter.format(it))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ fun SourceSettingsScreenContent(settings: ImmutableList<SourceSettingsView<*, *>
|
|||||||
).asPaddingValues(),
|
).asPaddingValues(),
|
||||||
) {
|
) {
|
||||||
items(settings, { it.props.hashCode() }) {
|
items(settings, { it.props.hashCode() }) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
when (it) {
|
when (it) {
|
||||||
is CheckBox, is Switch -> {
|
is CheckBox, is Switch -> {
|
||||||
TwoStatePreference(it as TwoState, it is CheckBox)
|
TwoStatePreference(it as TwoState, it is CheckBox)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import ca.gosyer.jui.ui.base.prefs.EditTextPreference
|
|||||||
import ca.gosyer.jui.ui.base.prefs.PreferenceRow
|
import ca.gosyer.jui.ui.base.prefs.PreferenceRow
|
||||||
import ca.gosyer.jui.ui.base.prefs.SwitchPreference
|
import ca.gosyer.jui.ui.base.prefs.SwitchPreference
|
||||||
import ca.gosyer.jui.ui.util.system.folderPicker
|
import ca.gosyer.jui.ui.util.system.folderPicker
|
||||||
import ca.gosyer.jui.uicore.prefs.PreferenceMutableStateFlow
|
|
||||||
import ca.gosyer.jui.uicore.prefs.asStateIn
|
import ca.gosyer.jui.uicore.prefs.asStateIn
|
||||||
import ca.gosyer.jui.uicore.prefs.asStringStateIn
|
import ca.gosyer.jui.uicore.prefs.asStringStateIn
|
||||||
import ca.gosyer.jui.uicore.resources.stringResource
|
import ca.gosyer.jui.uicore.resources.stringResource
|
||||||
@@ -56,15 +55,9 @@ actual fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostVie
|
|||||||
host = serverVm.host,
|
host = serverVm.host,
|
||||||
ip = serverVm.ip,
|
ip = serverVm.ip,
|
||||||
port = serverVm.port,
|
port = serverVm.port,
|
||||||
socksProxyEnabled = serverVm.socksProxyEnabled,
|
|
||||||
socksProxyHost = serverVm.socksProxyHost,
|
|
||||||
socksProxyPort = serverVm.socksProxyPort,
|
|
||||||
debugLogsEnabled = serverVm.debugLogsEnabled,
|
|
||||||
systemTrayEnabled = serverVm.systemTrayEnabled,
|
|
||||||
downloadPath = serverVm.downloadPath,
|
downloadPath = serverVm.downloadPath,
|
||||||
downloadAsCbz = serverVm.downloadAsCbz,
|
backupPath = serverVm.backupPath,
|
||||||
webUIEnabled = serverVm.webUIEnabled,
|
localSourcePath = serverVm.localSourcePath,
|
||||||
openInBrowserEnabled = serverVm.openInBrowserEnabled,
|
|
||||||
basicAuthEnabled = serverVm.basicAuthEnabled,
|
basicAuthEnabled = serverVm.basicAuthEnabled,
|
||||||
basicAuthUsername = serverVm.basicAuthUsername,
|
basicAuthUsername = serverVm.basicAuthUsername,
|
||||||
basicAuthPassword = serverVm.basicAuthPassword,
|
basicAuthPassword = serverVm.basicAuthPassword,
|
||||||
@@ -84,22 +77,12 @@ actual class SettingsServerHostViewModel
|
|||||||
val ip = serverHostPreferences.ip().asStateIn(scope)
|
val ip = serverHostPreferences.ip().asStateIn(scope)
|
||||||
val port = serverHostPreferences.port().asStringStateIn(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)
|
|
||||||
|
|
||||||
// Downloader
|
// Downloader
|
||||||
val downloadPath = serverHostPreferences.downloadPath().asStateIn(scope)
|
val downloadPath = serverHostPreferences.downloadPath().asStateIn(scope)
|
||||||
val downloadAsCbz = serverHostPreferences.downloadAsCbz().asStateIn(scope)
|
// Backup
|
||||||
|
val backupPath = serverHostPreferences.backupPath().asStateIn(scope)
|
||||||
// WebUI
|
// LocalSource
|
||||||
val webUIEnabled = serverHostPreferences.webUIEnabled().asStateIn(scope)
|
val localSourcePath = serverHostPreferences.localSourcePath().asStateIn(scope)
|
||||||
val openInBrowserEnabled = serverHostPreferences.openInBrowserEnabled().asStateIn(scope)
|
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
val basicAuthEnabled = serverHostPreferences.basicAuthEnabled().asStateIn(scope)
|
val basicAuthEnabled = serverHostPreferences.basicAuthEnabled().asStateIn(scope)
|
||||||
@@ -145,21 +128,15 @@ fun LazyListScope.ServerHostItems(
|
|||||||
hostValue: Boolean,
|
hostValue: Boolean,
|
||||||
basicAuthEnabledValue: Boolean,
|
basicAuthEnabledValue: Boolean,
|
||||||
serverSettingChanged: () -> Unit,
|
serverSettingChanged: () -> Unit,
|
||||||
host: PreferenceMutableStateFlow<Boolean>,
|
host: MutableStateFlow<Boolean>,
|
||||||
ip: PreferenceMutableStateFlow<String>,
|
ip: MutableStateFlow<String>,
|
||||||
port: PreferenceMutableStateFlow<String>,
|
port: MutableStateFlow<String>,
|
||||||
socksProxyEnabled: PreferenceMutableStateFlow<Boolean>,
|
downloadPath: MutableStateFlow<String>,
|
||||||
socksProxyHost: PreferenceMutableStateFlow<String>,
|
backupPath: MutableStateFlow<String>,
|
||||||
socksProxyPort: PreferenceMutableStateFlow<String>,
|
localSourcePath: MutableStateFlow<String>,
|
||||||
debugLogsEnabled: PreferenceMutableStateFlow<Boolean>,
|
basicAuthEnabled: MutableStateFlow<Boolean>,
|
||||||
systemTrayEnabled: PreferenceMutableStateFlow<Boolean>,
|
basicAuthUsername: MutableStateFlow<String>,
|
||||||
downloadPath: PreferenceMutableStateFlow<String>,
|
basicAuthPassword: MutableStateFlow<String>,
|
||||||
downloadAsCbz: PreferenceMutableStateFlow<Boolean>,
|
|
||||||
webUIEnabled: PreferenceMutableStateFlow<Boolean>,
|
|
||||||
openInBrowserEnabled: PreferenceMutableStateFlow<Boolean>,
|
|
||||||
basicAuthEnabled: PreferenceMutableStateFlow<Boolean>,
|
|
||||||
basicAuthUsername: PreferenceMutableStateFlow<String>,
|
|
||||||
basicAuthPassword: PreferenceMutableStateFlow<String>,
|
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
SwitchPreference(preference = host, title = stringResource(MR.strings.host_server))
|
SwitchPreference(preference = host, title = stringResource(MR.strings.host_server))
|
||||||
@@ -190,47 +167,6 @@ fun LazyListScope.ServerHostItems(
|
|||||||
changeListener = serverSettingChanged,
|
changeListener = serverSettingChanged,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
|
||||||
SwitchPreference(
|
|
||||||
preference = socksProxyEnabled,
|
|
||||||
title = stringResource(MR.strings.host_socks_enabled),
|
|
||||||
changeListener = serverSettingChanged,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
val proxyHost by socksProxyHost.collectAsState()
|
|
||||||
EditTextPreference(
|
|
||||||
preference = socksProxyHost,
|
|
||||||
title = stringResource(MR.strings.host_socks_host),
|
|
||||||
subtitle = stringResource(MR.strings.host_socks_host_sub, proxyHost),
|
|
||||||
changeListener = serverSettingChanged,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
val proxyPort by socksProxyPort.collectAsState()
|
|
||||||
EditTextPreference(
|
|
||||||
preference = socksProxyPort,
|
|
||||||
title = stringResource(MR.strings.host_socks_port),
|
|
||||||
subtitle = stringResource(MR.strings.host_socks_port_sub, proxyPort),
|
|
||||||
changeListener = serverSettingChanged,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
SwitchPreference(
|
|
||||||
preference = debugLogsEnabled,
|
|
||||||
title = stringResource(MR.strings.host_debug_logging),
|
|
||||||
subtitle = stringResource(MR.strings.host_debug_logging_sub),
|
|
||||||
changeListener = serverSettingChanged,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
SwitchPreference(
|
|
||||||
preference = systemTrayEnabled,
|
|
||||||
title = stringResource(MR.strings.host_system_tray),
|
|
||||||
subtitle = stringResource(MR.strings.host_system_tray_sub),
|
|
||||||
changeListener = serverSettingChanged,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
item {
|
||||||
val downloadPathValue by downloadPath.collectAsState()
|
val downloadPathValue by downloadPath.collectAsState()
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
@@ -253,29 +189,45 @@ fun LazyListScope.ServerHostItems(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
val backupPathValue by backupPath.collectAsState()
|
||||||
preference = downloadAsCbz,
|
PreferenceRow(
|
||||||
title = stringResource(MR.strings.host_download_as_cbz),
|
title = stringResource(MR.strings.host_backup_path),
|
||||||
subtitle = stringResource(MR.strings.host_download_as_cbz_sub),
|
subtitle = if (backupPathValue.isEmpty()) {
|
||||||
changeListener = serverSettingChanged,
|
stringResource(MR.strings.host_backup_path_sub_empty)
|
||||||
|
} else {
|
||||||
|
stringResource(MR.strings.host_backup_path_sub, backupPathValue)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
folderPicker {
|
||||||
|
backupPath.value = it.toString()
|
||||||
|
serverSettingChanged()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
backupPath.value = ""
|
||||||
|
serverSettingChanged()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
val localSourcePathValue by localSourcePath.collectAsState()
|
||||||
preference = webUIEnabled,
|
PreferenceRow(
|
||||||
title = stringResource(MR.strings.host_webui),
|
title = stringResource(MR.strings.host_local_source_path),
|
||||||
subtitle = stringResource(MR.strings.host_webui_sub),
|
subtitle = if (localSourcePathValue.isEmpty()) {
|
||||||
changeListener = serverSettingChanged,
|
stringResource(MR.strings.host_local_source_path_sub_empty)
|
||||||
)
|
} else {
|
||||||
}
|
stringResource(MR.strings.host_local_source_path_sub, localSourcePathValue)
|
||||||
item {
|
},
|
||||||
val webUIEnabledValue by webUIEnabled.collectAsState()
|
onClick = {
|
||||||
SwitchPreference(
|
folderPicker {
|
||||||
preference = openInBrowserEnabled,
|
localSourcePath.value = it.toString()
|
||||||
title = stringResource(MR.strings.host_open_in_browser),
|
serverSettingChanged()
|
||||||
subtitle = stringResource(MR.strings.host_open_in_browser_sub),
|
}
|
||||||
changeListener = serverSettingChanged,
|
},
|
||||||
enabled = webUIEnabledValue,
|
onLongClick = {
|
||||||
|
localSourcePath.value = ""
|
||||||
|
serverSettingChanged()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
|
|||||||
Reference in New Issue
Block a user