chore: review pointers.

This commit is contained in:
kaiserbh
2024-01-20 17:10:23 +11:00
parent 9bf1b37331
commit 7b3a586f05
6 changed files with 239 additions and 187 deletions

View File

@@ -1,5 +1,7 @@
package tachiyomi.domain.sync package eu.kanade.domain.sync
import eu.kanade.domain.sync.models.SyncSettings
import eu.kanade.tachiyomi.data.sync.models.SyncTriggerOptions
import tachiyomi.core.preference.Preference import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.PreferenceStore
import java.util.UUID import java.util.UUID
@@ -7,28 +9,8 @@ import java.util.UUID
class SyncPreferences( class SyncPreferences(
private val preferenceStore: PreferenceStore, private val preferenceStore: PreferenceStore,
) { ) {
object Flags { fun clientHost() = preferenceStore.getString("sync_client_host", "https://sync.tachiyomi.org")
const val NONE = 0x0 fun clientAPIKey() = preferenceStore.getString("sync_client_api_key", "")
const val SYNC_ON_CHAPTER_READ = 0x1
const val SYNC_ON_CHAPTER_OPEN = 0x2
const val SYNC_ON_APP_START = 0x4
const val SYNC_ON_APP_RESUME = 0x8
const val SYNC_ON_LIBRARY_UPDATE = 0x10
const val Defaults = NONE
fun values() = listOf(
NONE,
SYNC_ON_CHAPTER_READ,
SYNC_ON_CHAPTER_OPEN,
SYNC_ON_APP_START,
SYNC_ON_APP_RESUME,
SYNC_ON_LIBRARY_UPDATE,
)
}
fun syncHost() = preferenceStore.getString("sync_host", "https://sync.tachiyomi.org")
fun syncAPIKey() = preferenceStore.getString("sync_api_key", "")
fun lastSyncTimestamp() = preferenceStore.getLong(Preference.appStateKey("last_sync_timestamp"), 0L) fun lastSyncTimestamp() = preferenceStore.getLong(Preference.appStateKey("last_sync_timestamp"), 0L)
fun syncInterval() = preferenceStore.getInt("sync_interval", 0) fun syncInterval() = preferenceStore.getInt("sync_interval", 0)
@@ -57,14 +39,12 @@ class SyncPreferences(
return uniqueID return uniqueID
} }
fun syncFlags() = preferenceStore.getInt("sync_flags", Flags.Defaults)
fun isSyncEnabled(): Boolean { fun isSyncEnabled(): Boolean {
return syncService().get() != 0 return syncService().get() != 0
} }
fun getSyncOptions(): SyncOptions { fun getSyncSettings(): SyncSettings {
return SyncOptions( return SyncSettings(
libraryEntries = preferenceStore.getBoolean("library_entries", true).get(), libraryEntries = preferenceStore.getBoolean("library_entries", true).get(),
categories = preferenceStore.getBoolean("categories", true).get(), categories = preferenceStore.getBoolean("categories", true).get(),
chapters = preferenceStore.getBoolean("chapters", true).get(), chapters = preferenceStore.getBoolean("chapters", true).get(),
@@ -76,25 +56,37 @@ class SyncPreferences(
) )
} }
fun setSyncOptions(syncOptions: SyncOptions) { fun setSyncSettings(syncSettings: SyncSettings) {
preferenceStore.getBoolean("library_entries", true).set(syncOptions.libraryEntries) preferenceStore.getBoolean("library_entries", true).set(syncSettings.libraryEntries)
preferenceStore.getBoolean("categories", true).set(syncOptions.categories) preferenceStore.getBoolean("categories", true).set(syncSettings.categories)
preferenceStore.getBoolean("chapters", true).set(syncOptions.chapters) preferenceStore.getBoolean("chapters", true).set(syncSettings.chapters)
preferenceStore.getBoolean("tracking", true).set(syncOptions.tracking) preferenceStore.getBoolean("tracking", true).set(syncSettings.tracking)
preferenceStore.getBoolean("history", true).set(syncOptions.history) preferenceStore.getBoolean("history", true).set(syncSettings.history)
preferenceStore.getBoolean("appSettings", true).set(syncOptions.appSettings) preferenceStore.getBoolean("appSettings", true).set(syncSettings.appSettings)
preferenceStore.getBoolean("sourceSettings", true).set(syncOptions.sourceSettings) preferenceStore.getBoolean("sourceSettings", true).set(syncSettings.sourceSettings)
preferenceStore.getBoolean("privateSettings", true).set(syncOptions.privateSettings) preferenceStore.getBoolean("privateSettings", true).set(syncSettings.privateSettings)
}
fun getSyncTriggerOptions(): SyncTriggerOptions {
return SyncTriggerOptions(
syncOnChapterRead = preferenceStore.getBoolean("sync_on_chapter_read", false).get(),
syncOnChapterOpen = preferenceStore.getBoolean("sync_on_chapter_open", false).get(),
syncOnAppStart = preferenceStore.getBoolean("sync_on_app_start", false).get(),
syncOnAppResume = preferenceStore.getBoolean("sync_on_app_resume", false).get(),
syncOnLibraryUpdate = preferenceStore.getBoolean("sync_on_library_update", false).get(),
)
}
fun setSyncTriggerOptions(syncTriggerOptions: SyncTriggerOptions) {
preferenceStore.getBoolean("sync_on_chapter_read", false)
.set(syncTriggerOptions.syncOnChapterRead)
preferenceStore.getBoolean("sync_on_chapter_open", false)
.set(syncTriggerOptions.syncOnChapterOpen)
preferenceStore.getBoolean("sync_on_app_start", false)
.set(syncTriggerOptions.syncOnAppStart)
preferenceStore.getBoolean("sync_on_app_resume", false)
.set(syncTriggerOptions.syncOnAppResume)
preferenceStore.getBoolean("sync_on_library_update", false)
.set(syncTriggerOptions.syncOnLibraryUpdate)
} }
} }
data class SyncOptions(
val libraryEntries: Boolean = true,
val categories: Boolean = true,
val chapters: Boolean = true,
val tracking: Boolean = true,
val history: Boolean = true,
val appSettings: Boolean = true,
val sourceSettings: Boolean = true,
val privateSettings: Boolean = false,
)

View File

@@ -0,0 +1,12 @@
package eu.kanade.domain.sync.models
data class SyncSettings(
val libraryEntries: Boolean = true,
val categories: Boolean = true,
val chapters: Boolean = true,
val tracking: Boolean = true,
val history: Boolean = true,
val appSettings: Boolean = true,
val sourceSettings: Boolean = true,
val privateSettings: Boolean = false,
)

View File

@@ -1,125 +0,0 @@
package eu.kanade.presentation.more.settings.screen.data
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen
import kotlinx.collections.immutable.PersistentSet
import kotlinx.collections.immutable.minus
import kotlinx.collections.immutable.plus
import kotlinx.collections.immutable.toPersistentSet
import kotlinx.coroutines.flow.update
import tachiyomi.domain.sync.SyncPreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class SyncOptionsScreen : Screen() {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val model = rememberScreenModel { SyncOptionsScreenModel() }
val state by model.state.collectAsState()
Scaffold(
topBar = {
AppBar(
title = stringResource(MR.strings.pref_sync_options),
navigateUp = navigator::pop,
scrollBehavior = it,
)
},
) { contentPadding ->
Column(
modifier = Modifier
.padding(contentPadding)
.fillMaxSize(),
) {
LazyColumn(
modifier = Modifier
.weight(1f)
.padding(horizontal = MaterialTheme.padding.medium),
) {
SyncChoices.forEach { (k, v) ->
item {
LabeledCheckbox(
label = stringResource(v),
checked = state.flags.contains(k),
onCheckedChange = {
model.toggleOptionFlag(k)
},
)
}
}
}
HorizontalDivider()
}
}
}
}
private class SyncOptionsScreenModel : StateScreenModel<SyncOptionsScreenModel.State>(State()) {
private val syncPreferences = Injekt.get<SyncPreferences>()
init {
loadInitialFlags()
}
private fun loadInitialFlags() {
val savedFlags = syncPreferences.syncFlags().get()
val flagSet = SyncPreferences.Flags.values().filter { flag ->
savedFlags and flag > 0
}.toSet().toPersistentSet()
mutableState.update { State(flags = flagSet) }
}
fun toggleOptionFlag(option: Int) {
mutableState.update { currentState ->
val newFlags = if (currentState.flags.contains(option)) {
currentState.flags - option
} else {
currentState.flags + option
}
saveFlags(newFlags)
currentState.copy(flags = newFlags)
}
}
private fun saveFlags(flags: PersistentSet<Int>) {
val flagsInt = flags.fold(0) { acc, flag -> acc or flag }
syncPreferences.syncFlags().set(flagsInt)
}
@Immutable
data class State(
val flags: PersistentSet<Int> = SyncChoices.keys.toPersistentSet(),
)
}
private val SyncChoices = mapOf(
SyncPreferences.Flags.SYNC_ON_CHAPTER_READ to MR.strings.sync_on_chapter_read,
SyncPreferences.Flags.SYNC_ON_CHAPTER_OPEN to MR.strings.sync_on_chapter_open,
SyncPreferences.Flags.SYNC_ON_APP_START to MR.strings.sync_on_app_start,
SyncPreferences.Flags.SYNC_ON_APP_RESUME to MR.strings.sync_on_app_resume,
SyncPreferences.Flags.SYNC_ON_LIBRARY_UPDATE to MR.strings.sync_on_library_update,
)

View File

@@ -10,6 +10,8 @@ import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.domain.sync.models.SyncSettings
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.data.backup.create.BackupOptions import eu.kanade.tachiyomi.data.backup.create.BackupOptions
@@ -17,8 +19,6 @@ import eu.kanade.tachiyomi.data.sync.SyncDataJob
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import tachiyomi.domain.sync.SyncOptions
import tachiyomi.domain.sync.SyncPreferences
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.LabeledCheckbox import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.LazyColumnWithAction import tachiyomi.presentation.core.components.LazyColumnWithAction
@@ -95,12 +95,12 @@ class SyncSettingsSelector : Screen() {
private class SyncSettingsSelectorModel( private class SyncSettingsSelectorModel(
val syncPreferences: SyncPreferences = Injekt.get(), val syncPreferences: SyncPreferences = Injekt.get(),
) : StateScreenModel<SyncSettingsSelectorModel.State>( ) : StateScreenModel<SyncSettingsSelectorModel.State>(
State(syncOptionsToBackupOptions(syncPreferences.getSyncOptions())), State(syncOptionsToBackupOptions(syncPreferences.getSyncSettings())),
) { ) {
fun toggle(setter: (BackupOptions, Boolean) -> BackupOptions, enabled: Boolean) { fun toggle(setter: (BackupOptions, Boolean) -> BackupOptions, enabled: Boolean) {
mutableState.update { mutableState.update {
val updatedOptions = setter(it.options, enabled) val updatedOptions = setter(it.options, enabled)
syncPreferences.setSyncOptions(backupOptionsToSyncOptions(updatedOptions)) syncPreferences.setSyncSettings(backupOptionsToSyncOptions(updatedOptions))
it.copy(options = updatedOptions) it.copy(options = updatedOptions)
} }
} }
@@ -113,21 +113,21 @@ private class SyncSettingsSelectorModel(
data class State( data class State(
val options: BackupOptions = BackupOptions(), val options: BackupOptions = BackupOptions(),
) companion object { ) companion object {
private fun syncOptionsToBackupOptions(syncOptions: SyncOptions): BackupOptions { private fun syncOptionsToBackupOptions(syncSettings: SyncSettings): BackupOptions {
return BackupOptions( return BackupOptions(
libraryEntries = syncOptions.libraryEntries, libraryEntries = syncSettings.libraryEntries,
categories = syncOptions.categories, categories = syncSettings.categories,
chapters = syncOptions.chapters, chapters = syncSettings.chapters,
tracking = syncOptions.tracking, tracking = syncSettings.tracking,
history = syncOptions.history, history = syncSettings.history,
appSettings = syncOptions.appSettings, appSettings = syncSettings.appSettings,
sourceSettings = syncOptions.sourceSettings, sourceSettings = syncSettings.sourceSettings,
privateSettings = syncOptions.privateSettings, privateSettings = syncSettings.privateSettings,
) )
} }
private fun backupOptionsToSyncOptions(backupOptions: BackupOptions): SyncOptions { private fun backupOptionsToSyncOptions(backupOptions: BackupOptions): SyncSettings {
return SyncOptions( return SyncSettings(
libraryEntries = backupOptions.libraryEntries, libraryEntries = backupOptions.libraryEntries,
categories = backupOptions.categories, categories = backupOptions.categories,
chapters = backupOptions.chapters, chapters = backupOptions.chapters,

View File

@@ -0,0 +1,101 @@
package eu.kanade.presentation.more.settings.screen.data
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.data.sync.models.SyncTriggerOptions
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.update
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.LazyColumnWithAction
import tachiyomi.presentation.core.components.SectionCard
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class SyncTriggerOptionsScreen : Screen() {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val model = rememberScreenModel { SyncOptionsScreenModel() }
val state by model.state.collectAsState()
Scaffold(
topBar = {
AppBar(
title = stringResource(MR.strings.pref_sync_options),
navigateUp = navigator::pop,
scrollBehavior = it,
)
},
) { contentPadding ->
LazyColumnWithAction(
contentPadding = contentPadding,
actionLabel = stringResource(MR.strings.action_save),
actionEnabled = state.options.anyEnabled(),
onClickAction = {
navigator.pop()
},
) {
item {
SectionCard(MR.strings.label_triggers) {
Options(SyncTriggerOptions.mainOptions, state, model)
}
}
}
}
}
@Composable
private fun Options(
options: ImmutableList<SyncTriggerOptions.Entry>,
state: SyncOptionsScreenModel.State,
model: SyncOptionsScreenModel,
) {
options.forEach { option ->
LabeledCheckbox(
label = stringResource(option.label),
checked = option.getter(state.options),
onCheckedChange = {
model.toggle(option.setter, it)
},
enabled = option.enabled(state.options),
)
}
}
}
private class SyncOptionsScreenModel(
val syncPreferences: SyncPreferences = Injekt.get(),
) : StateScreenModel<SyncOptionsScreenModel.State>(
State(
syncPreferences.getSyncTriggerOptions(),
),
) {
fun toggle(setter: (SyncTriggerOptions, Boolean) -> SyncTriggerOptions, enabled: Boolean) {
mutableState.update {
val updatedTriggerOptions = setter(it.options, enabled)
syncPreferences.setSyncTriggerOptions(updatedTriggerOptions)
it.copy(
options = updatedTriggerOptions,
)
}
}
@Immutable
data class State(
val options: SyncTriggerOptions = SyncTriggerOptions(),
)
}

View File

@@ -0,0 +1,72 @@
package eu.kanade.tachiyomi.data.sync.models
import dev.icerock.moko.resources.StringResource
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
data class SyncTriggerOptions(
val syncOnChapterRead: Boolean = false,
val syncOnChapterOpen: Boolean = false,
val syncOnAppStart: Boolean = false,
val syncOnAppResume: Boolean = false,
val syncOnLibraryUpdate: Boolean = false,
) {
fun asBooleanArray() = booleanArrayOf(
syncOnChapterRead,
syncOnChapterOpen,
syncOnAppStart,
syncOnAppResume,
syncOnLibraryUpdate,
)
fun anyEnabled() = syncOnChapterRead ||
syncOnChapterOpen ||
syncOnAppStart ||
syncOnAppResume ||
syncOnLibraryUpdate
companion object {
val mainOptions = persistentListOf(
Entry(
label = MR.strings.sync_on_chapter_read,
getter = SyncTriggerOptions::syncOnChapterRead,
setter = { options, enabled -> options.copy(syncOnChapterRead = enabled) },
),
Entry(
label = MR.strings.sync_on_chapter_open,
getter = SyncTriggerOptions::syncOnChapterOpen,
setter = { options, enabled -> options.copy(syncOnChapterOpen = enabled) },
),
Entry(
label = MR.strings.sync_on_app_start,
getter = SyncTriggerOptions::syncOnAppStart,
setter = { options, enabled -> options.copy(syncOnAppStart = enabled) },
),
Entry(
label = MR.strings.sync_on_app_resume,
getter = SyncTriggerOptions::syncOnAppResume,
setter = { options, enabled -> options.copy(syncOnAppResume = enabled) },
),
Entry(
label = MR.strings.sync_on_library_update,
getter = SyncTriggerOptions::syncOnLibraryUpdate,
setter = { options, enabled -> options.copy(syncOnLibraryUpdate = enabled) },
),
)
fun fromBooleanArray(array: BooleanArray) = SyncTriggerOptions(
syncOnChapterRead = array[0],
syncOnChapterOpen = array[1],
syncOnAppStart = array[2],
syncOnAppResume = array[3],
syncOnLibraryUpdate = array[4],
)
}
data class Entry(
val label: StringResource,
val getter: (SyncTriggerOptions) -> Boolean,
val setter: (SyncTriggerOptions, Boolean) -> SyncTriggerOptions,
val enabled: (SyncTriggerOptions) -> Boolean = { true },
)
}