From 106f6f52c01385c0de40714eda87b03576ed4781 Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Mon, 9 Oct 2023 14:08:36 -0700 Subject: [PATCH] Backup App and Source settings Also added PreferenceStore/TrackPreferences and made the track username/password private Updated kotlinSerialization to 1.6.0 Co-Authored-By: jmir1 <43830312+jmir1@users.noreply.github.com> Co-Authored-By: arkon <4098258+arkon@users.noreply.github.com> --- app/build.gradle.kts | 2 +- .../java/eu/kanade/tachiyomi/AppModule.kt | 9 + .../java/eu/kanade/tachiyomi/Migrations.kt | 21 +- .../tachiyomi/data/backup/BackupConst.kt | 31 +-- .../tachiyomi/data/backup/BackupManager.kt | 57 +++++ .../tachiyomi/data/backup/BackupRestorer.kt | 71 ++++++- .../tachiyomi/data/backup/models/Backup.kt | 4 +- .../data/backup/models/BackupPreference.kt | 37 ++++ .../data/preference/AndroidPreference.kt | 194 ++++++++++++++++++ .../data/preference/AndroidPreferenceStore.kt | 79 +++++++ .../tachiyomi/data/preference/Preference.kt | 59 ++++++ .../data/preference/PreferenceKeys.kt | 6 - .../data/preference/PreferenceStore.kt | 43 ++++ .../data/preference/PreferencesHelper.kt | 14 -- .../tachiyomi/data/track/TrackPreferences.kt | 34 +++ .../tachiyomi/data/track/TrackService.kt | 11 +- .../tachiyomi/data/track/anilist/Anilist.kt | 8 +- .../tachiyomi/data/track/bangumi/Bangumi.kt | 6 +- .../tachiyomi/data/track/kitsu/Kitsu.kt | 4 +- .../data/track/mangaupdates/MangaUpdates.kt | 2 +- .../data/track/myanimelist/MyAnimeList.kt | 6 +- .../data/track/shikimori/Shikimori.kt | 6 +- .../tachiyomi/source/ConfigurableSource.kt | 3 + .../kanade/tachiyomi/ui/main/MainActivity.kt | 4 +- .../ui/manga/track/TrackingBottomSheet.kt | 4 +- .../ui/setting/SettingsBackupController.kt | 8 +- .../ui/setting/SettingsTrackingController.kt | 26 +-- app/src/main/res/values/strings.xml | 2 + buildSrc/src/main/kotlin/Dependencies.kt | 2 +- 29 files changed, 675 insertions(+), 78 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/preference/AndroidPreference.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/preference/AndroidPreferenceStore.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/preference/Preference.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceStore.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/TrackPreferences.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8b11fab44b..fbbc8afa90 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -203,7 +203,7 @@ dependencies { implementation(kotlin("reflect", version = AndroidVersions.kotlin)) // JSON - val kotlinSerialization = "1.5.1" + val kotlinSerialization = "1.6.0" implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${kotlinSerialization}") implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:${kotlinSerialization}") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:${kotlinSerialization}") diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt index 9cd4d22792..8fd7670b83 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt @@ -7,8 +7,11 @@ import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.library.CustomMangaManager +import eu.kanade.tachiyomi.data.preference.AndroidPreferenceStore +import eu.kanade.tachiyomi.data.preference.PreferenceStore import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.track.TrackManager +import eu.kanade.tachiyomi.data.track.TrackPreferences import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.network.JavaScriptEngine import eu.kanade.tachiyomi.network.NetworkHelper @@ -27,8 +30,14 @@ class AppModule(val app: Application) : InjektModule { override fun InjektRegistrar.registerInjectables() { addSingleton(app) + addSingletonFactory { + AndroidPreferenceStore(app) + } + addSingletonFactory { PreferencesHelper(app) } + addSingletonFactory { TrackPreferences(get()) } + addSingletonFactory { DatabaseHelper(app) } addSingletonFactory { ChapterCache(app) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 6011c8b68d..94494e169a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -5,7 +5,9 @@ import androidx.preference.PreferenceManager import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.data.library.LibraryUpdateJob +import eu.kanade.tachiyomi.data.preference.Preference import eu.kanade.tachiyomi.data.preference.PreferenceKeys +import eu.kanade.tachiyomi.data.preference.PreferenceStore import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.plusAssign @@ -33,7 +35,11 @@ object Migrations { * @param preferences Preferences of the application. * @return true if a migration is performed, false otherwise. */ - fun upgrade(preferences: PreferencesHelper, scope: CoroutineScope): Boolean { + fun upgrade( + preferences: PreferencesHelper, + preferenceStore: PreferenceStore, + scope: CoroutineScope, + ): Boolean { val context = preferences.context val prefs = PreferenceManager.getDefaultSharedPreferences(context) prefs.edit { @@ -220,6 +226,19 @@ object Migrations { LibraryUpdateJob.cancelAllWorks(context) LibraryUpdateJob.setupTask(context) } + if (oldVersion < 108) { + preferenceStore.getAll() + .filter { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") } + .forEach { (key, value) -> + if (value is String) { + preferenceStore + .getString(Preference.privateKey(key)) + .set(value) + + preferenceStore.getString(key).delete() + } + } + } return true } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt index a4a5585296..7512d5293b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt @@ -8,17 +8,22 @@ object BackupConst { const val EXTRA_URI = "$ID.$NAME.EXTRA_URI" // Filter options - internal const val BACKUP_CATEGORY = 0x1 - internal const val BACKUP_CATEGORY_MASK = 0x1 - internal const val BACKUP_CHAPTER = 0x2 - internal const val BACKUP_CHAPTER_MASK = 0x2 - internal const val BACKUP_HISTORY = 0x4 - internal const val BACKUP_HISTORY_MASK = 0x4 - internal const val BACKUP_TRACK = 0x8 - internal const val BACKUP_TRACK_MASK = 0x8 - internal const val BACKUP_CUSTOM_INFO = 0x10 - internal const val BACKUP_CUSTOM_INFO_MASK = 0x10 - internal const val BACKUP_READ_MANGA = 0x20 - internal const val BACKUP_READ_MANGA_MASK = 0x20 - internal const val BACKUP_ALL = 0x1F + const val BACKUP_CATEGORY = 0x1 + const val BACKUP_CATEGORY_MASK = 0x1 + const val BACKUP_CHAPTER = 0x2 + const val BACKUP_CHAPTER_MASK = 0x2 + const val BACKUP_HISTORY = 0x4 + const val BACKUP_HISTORY_MASK = 0x4 + const val BACKUP_TRACK = 0x8 + const val BACKUP_TRACK_MASK = 0x8 + const val BACKUP_APP_PREFS = 0x10 + const val BACKUP_APP_PREFS_MASK = 0x10 + const val BACKUP_SOURCE_PREFS = 0x20 + const val BACKUP_SOURCE_PREFS_MASK = 0x20 + const val BACKUP_CUSTOM_INFO = 0x40 + const val BACKUP_CUSTOM_INFO_MASK = 0x40 + const val BACKUP_READ_MANGA = 0x80 + const val BACKUP_READ_MANGA_MASK = 0x80 + + const val BACKUP_ALL = 0x7F } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt index e52ecde30b..80e0ac935e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt @@ -4,6 +4,8 @@ import android.content.Context import android.net.Uri import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_APP_PREFS +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_APP_PREFS_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER @@ -14,6 +16,8 @@ import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_READ_MANGA import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_READ_MANGA_MASK +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_SOURCE_PREFS +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_SOURCE_PREFS_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK import eu.kanade.tachiyomi.data.backup.models.Backup @@ -21,25 +25,41 @@ import eu.kanade.tachiyomi.data.backup.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupChapter import eu.kanade.tachiyomi.data.backup.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupManga +import eu.kanade.tachiyomi.data.backup.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupSerializer import eu.kanade.tachiyomi.data.backup.models.BackupSource +import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import eu.kanade.tachiyomi.data.backup.models.BackupTracking +import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.Track +import eu.kanade.tachiyomi.data.preference.Preference +import eu.kanade.tachiyomi.data.preference.PreferenceStore +import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.preferenceKey +import eu.kanade.tachiyomi.source.sourcePreferences import kotlinx.serialization.protobuf.ProtoBuf import okio.buffer import okio.gzip import okio.sink import timber.log.Timber +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.io.FileOutputStream import kotlin.math.max class BackupManager(context: Context) : AbstractBackupManager(context) { + private val preferenceStore: PreferenceStore = Injekt.get() val parser = ProtoBuf /** @@ -64,6 +84,8 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { backupCategories(), emptyList(), backupExtensionInfo(databaseManga), + backupAppPreferences(flags), + backupSourcePreferences(flags), ) } @@ -198,6 +220,41 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { return mangaObject } + private fun backupAppPreferences(flags: Int): List { + if (flags and BACKUP_APP_PREFS_MASK != BACKUP_APP_PREFS) return emptyList() + return preferenceStore.getAll().toBackupPreferences() + } + + private fun backupSourcePreferences(flags: Int): List { + if (flags and BACKUP_SOURCE_PREFS_MASK != BACKUP_SOURCE_PREFS) return emptyList() + return sourceManager.getOnlineSources() + .filterIsInstance() + .map { + BackupSourcePreferences( + it.preferenceKey(), + it.sourcePreferences().all.toBackupPreferences(), + ) + } + } + + @Suppress("UNCHECKED_CAST") + private fun Map.toBackupPreferences(): List { + return this.filterKeys { !Preference.isPrivate(it) } + .mapNotNull { (key, value) -> + when (value) { + is Int -> BackupPreference(key, IntPreferenceValue(value)) + is Long -> BackupPreference(key, LongPreferenceValue(value)) + is Float -> BackupPreference(key, FloatPreferenceValue(value)) + is String -> BackupPreference(key, StringPreferenceValue(value)) + is Boolean -> BackupPreference(key, BooleanPreferenceValue(value)) + is Set<*> -> (value as? Set)?.let { + BackupPreference(key, StringSetPreferenceValue(it)) + } + else -> null + } + } + } + fun restoreExistingManga(manga: Manga, dbManga: Manga) { manga.id = dbManga.id manga.copyFrom(dbManga) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt index 2f51822bbf..d1c6a4fd45 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt @@ -7,24 +7,38 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupManga +import eu.kanade.tachiyomi.data.backup.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupSerializer import eu.kanade.tachiyomi.data.backup.models.BackupSource +import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences +import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.LibraryUpdateJob +import eu.kanade.tachiyomi.data.preference.AndroidPreferenceStore +import eu.kanade.tachiyomi.data.preference.PreferenceStore +import eu.kanade.tachiyomi.source.sourcePreferences import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.isActive import okio.buffer import okio.gzip import okio.source +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.util.Date class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBackupRestore(context, notifier) { + private val preferenceStore: PreferenceStore = Injekt.get() + @SuppressLint("Recycle") - @Suppress("BlockingMethodInNonBlockingContext") override suspend fun performRestore(uri: Uri): Boolean { backupManager = BackupManager(context) @@ -44,6 +58,9 @@ class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBacku sourceMapping = backupMaps.associate { it.sourceId to it.name } return coroutineScope { + restoreAppPreferences(backup.backupPreferences) + restoreSourcePreferences(backup.backupSourcePreferences) + // Restore individual manga backup.backupManga.forEach { if (!isActive) { @@ -147,4 +164,56 @@ class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBacku customManga?.id = manga.id!! customManga?.let { customMangaManager.saveMangaInfo(it) } } + + private fun restoreAppPreferences(preferences: List) { + restorePreferences(preferences, preferenceStore) + } + + private fun restoreSourcePreferences(preferences: List) { + preferences.forEach { + val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey)) + restorePreferences(it.prefs, sourcePrefs) + } + } + + private fun restorePreferences( + toRestore: List, + preferenceStore: PreferenceStore, + ) { + val prefs = preferenceStore.getAll() + toRestore.forEach { (key, value) -> + when (value) { + is IntPreferenceValue -> { + if (prefs[key] is Int?) { + preferenceStore.getInt(key).set(value.value) + } + } + is LongPreferenceValue -> { + if (prefs[key] is Long?) { + preferenceStore.getLong(key).set(value.value) + } + } + is FloatPreferenceValue -> { + if (prefs[key] is Float?) { + preferenceStore.getFloat(key).set(value.value) + } + } + is StringPreferenceValue -> { + if (prefs[key] is String?) { + preferenceStore.getString(key).set(value.value) + } + } + is BooleanPreferenceValue -> { + if (prefs[key] is Boolean?) { + preferenceStore.getBoolean(key).set(value.value) + } + } + is StringSetPreferenceValue -> { + if (prefs[key] is Set<*>?) { + preferenceStore.getStringSet(key).set(value.value) + } + } + } + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt index 9c790cae82..87a7fa44fd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt @@ -11,10 +11,12 @@ import java.util.Locale data class Backup( @ProtoNumber(1) val backupManga: List, @ProtoNumber(2) var backupCategories: List = emptyList(), - // Bump by 100 to specify this is a 0.x value @ProtoNumber(100) var backupBrokenSources: List = emptyList(), @ProtoNumber(101) var backupSources: List = emptyList(), + @ProtoNumber(104) var backupPreferences: List = emptyList(), + @ProtoNumber(105) var backupSourcePreferences: List = emptyList(), ) { + companion object { val filenameRegex = """(${BuildConfig.APPLICATION_ID}|tachiyomi)?_\d+-\d+-\d+_\d+-\d+\.(tachibk|proto\.gz)""".toRegex() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt new file mode 100644 index 0000000000..3884f37e3f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt @@ -0,0 +1,37 @@ +package eu.kanade.tachiyomi.data.backup.models + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber + +@Serializable +data class BackupPreference( + @ProtoNumber(1) val key: String, + @ProtoNumber(2) val value: PreferenceValue, +) + +@Serializable +data class BackupSourcePreferences( + @ProtoNumber(1) val sourceKey: String, + @ProtoNumber(2) val prefs: List, +) + +@Serializable +sealed class PreferenceValue + +@Serializable +data class IntPreferenceValue(val value: Int) : PreferenceValue() + +@Serializable +data class LongPreferenceValue(val value: Long) : PreferenceValue() + +@Serializable +data class FloatPreferenceValue(val value: Float) : PreferenceValue() + +@Serializable +data class StringPreferenceValue(val value: String) : PreferenceValue() + +@Serializable +data class BooleanPreferenceValue(val value: Boolean) : PreferenceValue() + +@Serializable +data class StringSetPreferenceValue(val value: Set) : PreferenceValue() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/AndroidPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/AndroidPreference.kt new file mode 100644 index 0000000000..7ef2c88af0 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/AndroidPreference.kt @@ -0,0 +1,194 @@ +package eu.kanade.tachiyomi.data.preference + +import android.content.SharedPreferences +import android.content.SharedPreferences.Editor +import androidx.core.content.edit +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn +import timber.log.Timber + +sealed class AndroidPreference( + private val preferences: SharedPreferences, + private val keyFlow: Flow, + private val key: String, + private val defaultValue: T, +) : Preference { + + abstract fun read(preferences: SharedPreferences, key: String, defaultValue: T): T + + abstract fun write(key: String, value: T): Editor.() -> Unit + + override fun key(): String { + return key + } + + override fun get(): T { + return try { + read(preferences, key, defaultValue) + } catch (e: ClassCastException) { + Timber.d("Invalid value for $key; deleting") + delete() + defaultValue + } + } + + override fun set(value: T) { + preferences.edit(action = write(key, value)) + } + + override fun isSet(): Boolean { + return preferences.contains(key) + } + + override fun delete() { + preferences.edit { + remove(key) + } + } + + override fun defaultValue(): T { + return defaultValue + } + + override fun changes(): Flow { + return keyFlow + .filter { it == key || it == null } + .onStart { emit("ignition") } + .map { get() } + .conflate() + } + + override fun stateIn(scope: CoroutineScope): StateFlow { + return changes().stateIn(scope, SharingStarted.Eagerly, get()) + } + + class StringPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: String, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read( + preferences: SharedPreferences, + key: String, + defaultValue: String, + ): String { + return preferences.getString(key, defaultValue) ?: defaultValue + } + + override fun write(key: String, value: String): Editor.() -> Unit = { + putString(key, value) + } + } + + class LongPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Long, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: Long): Long { + return preferences.getLong(key, defaultValue) + } + + override fun write(key: String, value: Long): Editor.() -> Unit = { + putLong(key, value) + } + } + + class IntPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Int, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: Int): Int { + return preferences.getInt(key, defaultValue) + } + + override fun write(key: String, value: Int): Editor.() -> Unit = { + putInt(key, value) + } + } + + class FloatPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Float, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: Float): Float { + return preferences.getFloat(key, defaultValue) + } + + override fun write(key: String, value: Float): Editor.() -> Unit = { + putFloat(key, value) + } + } + + class BooleanPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Boolean, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read( + preferences: SharedPreferences, + key: String, + defaultValue: Boolean, + ): Boolean { + return preferences.getBoolean(key, defaultValue) + } + + override fun write(key: String, value: Boolean): Editor.() -> Unit = { + putBoolean(key, value) + } + } + + class StringSetPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Set, + ) : AndroidPreference>(preferences, keyFlow, key, defaultValue) { + override fun read( + preferences: SharedPreferences, + key: String, + defaultValue: Set, + ): Set { + return preferences.getStringSet(key, defaultValue) ?: defaultValue + } + + override fun write(key: String, value: Set): Editor.() -> Unit = { + putStringSet(key, value) + } + } + + class Object( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: T, + val serializer: (T) -> String, + val deserializer: (String) -> T, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T { + return try { + preferences.getString(key, null)?.let(deserializer) ?: defaultValue + } catch (e: Exception) { + defaultValue + } + } + + override fun write(key: String, value: T): Editor.() -> Unit = { + putString(key, serializer(value)) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/AndroidPreferenceStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/AndroidPreferenceStore.kt new file mode 100644 index 0000000000..917da39452 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/AndroidPreferenceStore.kt @@ -0,0 +1,79 @@ +package eu.kanade.tachiyomi.data.preference + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.data.preference.AndroidPreference.BooleanPrimitive +import eu.kanade.tachiyomi.data.preference.AndroidPreference.FloatPrimitive +import eu.kanade.tachiyomi.data.preference.AndroidPreference.IntPrimitive +import eu.kanade.tachiyomi.data.preference.AndroidPreference.LongPrimitive +import eu.kanade.tachiyomi.data.preference.AndroidPreference.Object +import eu.kanade.tachiyomi.data.preference.AndroidPreference.StringPrimitive +import eu.kanade.tachiyomi.data.preference.AndroidPreference.StringSetPrimitive +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow + +class AndroidPreferenceStore( + context: Context, + private val sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context), +) : PreferenceStore { + + private val keyFlow = sharedPreferences.keyFlow + + override fun getString(key: String, defaultValue: String): Preference { + return StringPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getLong(key: String, defaultValue: Long): Preference { + return LongPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getInt(key: String, defaultValue: Int): Preference { + return IntPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getFloat(key: String, defaultValue: Float): Preference { + return FloatPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getBoolean(key: String, defaultValue: Boolean): Preference { + return BooleanPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getStringSet(key: String, defaultValue: Set): Preference> { + return StringSetPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getObject( + key: String, + defaultValue: T, + serializer: (T) -> String, + deserializer: (String) -> T, + ): Preference { + return Object( + preferences = sharedPreferences, + keyFlow = keyFlow, + key = key, + defaultValue = defaultValue, + serializer = serializer, + deserializer = deserializer, + ) + } + + override fun getAll(): Map { + return sharedPreferences.all ?: emptyMap() + } +} + +private val SharedPreferences.keyFlow + get() = callbackFlow { + val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key: String? -> + trySend( + key, + ) + } + registerOnSharedPreferenceChangeListener(listener) + awaitClose { + unregisterOnSharedPreferenceChangeListener(listener) + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/Preference.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/Preference.kt new file mode 100644 index 0000000000..23f0904bca --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/Preference.kt @@ -0,0 +1,59 @@ +package eu.kanade.tachiyomi.data.preference + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +interface Preference { + + fun key(): String + + fun get(): T + + fun set(value: T) + + fun isSet(): Boolean + + fun delete() + + fun defaultValue(): T + + fun changes(): Flow + + fun stateIn(scope: CoroutineScope): StateFlow + + val isPrivate: Boolean + get() = key().startsWith(PRIVATE_PREFIX) + + companion object { + /** + * A preference that should not be exposed in places like backups. + */ + fun isPrivate(key: String): Boolean { + return key.startsWith(PRIVATE_PREFIX) + } + + fun privateKey(key: String): String { + return "$PRIVATE_PREFIX$key" + } + + private const val PRIVATE_PREFIX = "__PRIVATE_" + } +} + +inline fun Preference.getAndSet(crossinline block: (T) -> R) = set( + block(get()), +) + +operator fun Preference>.plusAssign(item: T) { + set(get() + item) +} + +operator fun Preference>.minusAssign(item: T) { + set(get() - item) +} + +fun Preference.toggle(): Boolean { + set(!get()) + return get() +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index a79236ae39..03b00a2472 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -241,10 +241,4 @@ object PreferenceKeys { const val hideChapterTitles = "hide_chapter_titles" const val chaptersDescAsDefault = "chapters_desc_as_default" - - fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId" - - fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId" - - fun trackToken(syncId: Int) = "track_token_$syncId" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceStore.kt new file mode 100644 index 0000000000..88aaada5f9 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceStore.kt @@ -0,0 +1,43 @@ +package eu.kanade.tachiyomi.data.preference + +interface PreferenceStore { + + fun getString(key: String, defaultValue: String = ""): Preference + + fun getLong(key: String, defaultValue: Long = 0): Preference + + fun getInt(key: String, defaultValue: Int = 0): Preference + + fun getFloat(key: String, defaultValue: Float = 0f): Preference + + fun getBoolean(key: String, defaultValue: Boolean = false): Preference + + fun getStringSet(key: String, defaultValue: Set = emptySet()): Preference> + + fun getObject( + key: String, + defaultValue: T, + serializer: (T) -> String, + deserializer: (String) -> T, + ): Preference + + fun getAll(): Map +} + +inline fun > PreferenceStore.getEnum( + key: String, + defaultValue: T, +): Preference { + return getObject( + key = key, + defaultValue = defaultValue, + serializer = { it.name }, + deserializer = { + try { + enumValueOf(it) + } catch (e: IllegalArgumentException) { + defaultValue + } + }, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index d4335e0689..62531e21db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -11,7 +11,6 @@ import com.google.android.material.color.DynamicColors import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder import eu.kanade.tachiyomi.extension.util.ExtensionInstaller @@ -223,19 +222,6 @@ class PreferencesHelper(val context: Context) { fun sourceSorting() = flowPrefs.getInt(Keys.sourcesSort, 0) - fun trackUsername(sync: TrackService) = prefs.getString(Keys.trackUsername(sync.id), "") - - fun trackPassword(sync: TrackService) = prefs.getString(Keys.trackPassword(sync.id), "") - - fun setTrackCredentials(sync: TrackService, username: String, password: String) { - prefs.edit() - .putString(Keys.trackUsername(sync.id), username) - .putString(Keys.trackPassword(sync.id), password) - .apply() - } - - fun trackToken(sync: TrackService) = flowPrefs.getString(Keys.trackToken(sync.id), "") - fun anilistScoreType() = flowPrefs.getString("anilist_score_type", "POINT_10") fun backupsDirectory() = flowPrefs.getString(Keys.backupDirectory, defaultBackupDir.toString()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackPreferences.kt new file mode 100644 index 0000000000..b105b89452 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackPreferences.kt @@ -0,0 +1,34 @@ +package eu.kanade.tachiyomi.data.track + +import eu.kanade.tachiyomi.data.preference.Preference +import eu.kanade.tachiyomi.data.preference.PreferenceStore +import eu.kanade.tachiyomi.data.track.anilist.Anilist + +class TrackPreferences( + private val preferenceStore: PreferenceStore, +) { + + fun trackUsername(sync: TrackService) = preferenceStore.getString(trackUsername(sync.id), "") + + fun trackPassword(sync: TrackService) = preferenceStore.getString(trackPassword(sync.id), "") + + fun setCredentials(sync: TrackService, username: String, password: String) { + trackUsername(sync).set(username) + trackPassword(sync).set(password) + } + + fun trackToken(sync: TrackService) = preferenceStore.getString(trackToken(sync.id), "") + + fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10) + + fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true) + + companion object { + fun trackUsername(syncId: Int) = Preference.privateKey("pref_mangasync_username_$syncId") + + private fun trackPassword(syncId: Int) = + Preference.privateKey("pref_mangasync_password_$syncId") + + private fun trackToken(syncId: Int) = Preference.privateKey("track_token_$syncId") + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt index 1c042c8a85..a6636d4bd9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt @@ -5,7 +5,6 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.util.system.executeOnIO @@ -14,7 +13,7 @@ import uy.kohesive.injekt.injectLazy abstract class TrackService(val id: Int) { - val preferences: PreferencesHelper by injectLazy() + val trackPreferences: TrackPreferences by injectLazy() val networkService: NetworkHelper by injectLazy() val db: DatabaseHelper by injectLazy() open fun canRemoveFromService() = false @@ -93,19 +92,19 @@ abstract class TrackService(val id: Int) { @CallSuper open fun logout() { - preferences.setTrackCredentials(this, "", "") + trackPreferences.setCredentials(this, "", "") } open val isLogged: Boolean get() = getUsername().isNotEmpty() && getPassword().isNotEmpty() - fun getUsername() = preferences.trackUsername(this)!! + fun getUsername() = trackPreferences.trackUsername(this).get() - fun getPassword() = preferences.trackPassword(this)!! + fun getPassword() = trackPreferences.trackPassword(this).get() fun saveCredentials(username: String, password: String) { - preferences.setTrackCredentials(this, username, password) + trackPreferences.setCredentials(this, username, password) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt index 3870b4d1fb..e5ac105260 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt @@ -41,7 +41,7 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) { override val supportsReadingDates: Boolean = true - private val scorePreference = preferences.anilistScoreType() + private val scorePreference = trackPreferences.anilistScoreType() init { // If the preference is an int from APIv1, logout user to force using APIv2 @@ -229,17 +229,17 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) { override fun logout() { super.logout() - preferences.trackToken(this).delete() + trackPreferences.trackToken(this).delete() interceptor.setAuth(null) } fun saveOAuth(oAuth: OAuth?) { - preferences.trackToken(this).set(json.encodeToString(oAuth)) + trackPreferences.trackToken(this).set(json.encodeToString(oAuth)) } fun loadOAuth(): OAuth? { return try { - json.decodeFromString(preferences.trackToken(this).get()) + json.decodeFromString(trackPreferences.trackToken(this).get()) } catch (e: Exception) { Timber.e(e) null diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt index 26cb4978d9..28287a54e2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt @@ -129,12 +129,12 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) { } fun saveToken(oauth: OAuth?) { - preferences.trackToken(this).set(json.encodeToString(oauth)) + trackPreferences.trackToken(this).set(json.encodeToString(oauth)) } fun restoreToken(): OAuth? { return try { - json.decodeFromString(preferences.trackToken(this).get()) + json.decodeFromString(trackPreferences.trackToken(this).get()) } catch (e: Exception) { null } @@ -142,7 +142,7 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) { override fun logout() { super.logout() - preferences.trackToken(this).delete() + trackPreferences.trackToken(this).delete() interceptor.newAuth(null) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt index f92482b2a6..36d4a62e4c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt @@ -154,12 +154,12 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { } fun saveToken(oauth: OAuth?) { - preferences.trackToken(this).set(json.encodeToString(oauth)) + trackPreferences.trackToken(this).set(json.encodeToString(oauth)) } fun restoreToken(): OAuth? { return try { - json.decodeFromString(preferences.trackToken(this).get()) + json.decodeFromString(trackPreferences.trackToken(this).get()) } catch (e: Exception) { null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt index 0d32360339..de17e09945 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt @@ -130,6 +130,6 @@ class MangaUpdates(private val context: Context, id: Int) : TrackService(id) { } fun restoreSession(): String? { - return preferences.trackPassword(this) + return getPassword() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt index 7d53237354..667da422f0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt @@ -138,17 +138,17 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) { override fun logout() { super.logout() - preferences.trackToken(this).delete() + trackPreferences.trackToken(this).delete() interceptor.setAuth(null) } fun saveOAuth(oAuth: OAuth?) { - preferences.trackToken(this).set(json.encodeToString(oAuth)) + trackPreferences.trackToken(this).set(json.encodeToString(oAuth)) } fun loadOAuth(): OAuth? { return try { - json.decodeFromString(preferences.trackToken(this).get()) + json.decodeFromString(trackPreferences.trackToken(this).get()) } catch (e: Exception) { null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt index f3907e1da8..fb99016ed9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt @@ -129,12 +129,12 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) { } fun saveToken(oauth: OAuth?) { - preferences.trackToken(this).set(json.encodeToString(oauth)) + trackPreferences.trackToken(this).set(json.encodeToString(oauth)) } fun restoreToken(): OAuth? { return try { - json.decodeFromString(preferences.trackToken(this).get()) + json.decodeFromString(trackPreferences.trackToken(this).get()) } catch (e: Exception) { null } @@ -142,7 +142,7 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) { override fun logout() { super.logout() - preferences.trackToken(this).delete() + trackPreferences.trackToken(this).delete() interceptor.newAuth(null) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt index b74c96af7e..6b72d3648a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt @@ -23,3 +23,6 @@ interface ConfigurableSource : Source { // TODO: use getSourcePreferences once all extensions are on ext-lib 1.5 fun ConfigurableSource.sourcePreferences(): SharedPreferences = Injekt.get().getSharedPreferences(preferenceKey(), Context.MODE_PRIVATE) + +fun sourcePreferences(key: String): SharedPreferences = + Injekt.get().getSharedPreferences(key, Context.MODE_PRIVATE) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 9e1a6489e0..e75c95b0b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -132,6 +132,8 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.util.Date import java.util.concurrent.TimeUnit @@ -541,7 +543,7 @@ open class MainActivity : BaseActivity() { preferences.incognitoMode().set(false) // Show changelog if needed - if (Migrations.upgrade(preferences, lifecycleScope)) { + if (Migrations.upgrade(preferences, Injekt.get(), lifecycleScope)) { if (!BuildConfig.DEBUG) { content.post { whatsNewSheet().show() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt index 23f4f6989a..e8c5bb6293 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt @@ -200,9 +200,7 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : return } - if (track.tracking_url.isBlank()) { - activity.toast(R.string.url_not_set_click_again) - } else { + if (track.tracking_url.isNotBlank()) { activity.openInBrowser(track.tracking_url.toUri()) controller.refreshTracker = position } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt index d1192d6c99..b568aa09d9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt @@ -218,6 +218,8 @@ class SettingsBackupController : SettingsController() { R.string.chapters, R.string.tracking, R.string.history, + R.string.app_settings, + R.string.source_settings, R.string.custom_manga_info, R.string.all_read_manga, ) @@ -244,8 +246,10 @@ class SettingsBackupController : SettingsController() { 2 -> flags = flags or BackupConst.BACKUP_CHAPTER 3 -> flags = flags or BackupConst.BACKUP_TRACK 4 -> flags = flags or BackupConst.BACKUP_HISTORY - 5 -> flags = flags or BackupConst.BACKUP_CUSTOM_INFO - 6 -> flags = flags or BackupConst.BACKUP_READ_MANGA + 5 -> flags = flags or BackupConst.BACKUP_APP_PREFS + 6 -> flags = flags or BackupConst.BACKUP_SOURCE_PREFS + 7 -> flags = flags or BackupConst.BACKUP_CUSTOM_INFO + 8 -> flags = flags or BackupConst.BACKUP_READ_MANGA } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt index 24c4773c3f..fbb1303f34 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt @@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.asImmediateFlowIn import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.TrackManager +import eu.kanade.tachiyomi.data.track.TrackPreferences import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.anilist.AnilistApi import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi @@ -30,6 +31,7 @@ class SettingsTrackingController : TrackLogoutDialog.Listener { private val trackManager: TrackManager by injectLazy() + val trackPreferences: TrackPreferences by injectLazy() override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { titleRes = R.string.tracking @@ -59,7 +61,7 @@ class SettingsTrackingController : isIconSpaceReserved = true title = context.getString(R.string.update_tracking_scoring_type, context.getString(R.string.anilist)) - preferences.getStringPref(Keys.trackUsername(trackManager.aniList.id)) + preferences.getStringPref(trackManager.aniList.getUsername()) .asImmediateFlowIn(viewScope) { isVisible = it.isNotEmpty() } @@ -117,7 +119,7 @@ class SettingsTrackingController : ): TrackerPreference { return add( TrackerPreference(context).apply { - key = Keys.trackUsername(service.id) + key = trackPreferences.trackUsername(service).key() title = context.getString(service.nameRes()) iconRes = service.getLogo() iconColor = service.getLogoColor() @@ -125,7 +127,7 @@ class SettingsTrackingController : if (service.isLogged) { if (service is EnhancedTrackService) { service.logout() - updatePreference(service.id) + updatePreference(service) } else { val dialog = TrackLogoutDialog(service) dialog.targetController = this@SettingsTrackingController @@ -134,7 +136,7 @@ class SettingsTrackingController : } else { if (service is EnhancedTrackService) { service.loginNoop() - updatePreference(service.id) + updatePreference(service) } else { login() } @@ -146,22 +148,22 @@ class SettingsTrackingController : override fun onActivityResumed(activity: Activity) { super.onActivityResumed(activity) - updatePreference(trackManager.myAnimeList.id) - updatePreference(trackManager.aniList.id) - updatePreference(trackManager.shikimori.id) - updatePreference(trackManager.bangumi.id) + updatePreference(trackManager.myAnimeList) + updatePreference(trackManager.aniList) + updatePreference(trackManager.shikimori) + updatePreference(trackManager.bangumi) } - private fun updatePreference(id: Int) { - val pref = findPreference(Keys.trackUsername(id)) as? TrackerPreference + private fun updatePreference(service: TrackService) { + val pref = findPreference(trackPreferences.trackUsername(service).key()) as? TrackerPreference pref?.notifyChanged() } override fun trackLoginDialogClosed(service: TrackService) { - updatePreference(service.id) + updatePreference(service) } override fun trackLogoutDialogClosed(service: TrackService) { - updatePreference(service.id) + updatePreference(service) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9b265f3423..c7d33e7d6d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -576,6 +576,8 @@ Error sharing cover Custom manga info All read manga + App settings + Source settings Set as default Filter scanlator groups diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index bc5f8a6c0b..62652770ae 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -4,7 +4,7 @@ object AndroidVersions { const val compileSdk = 34 const val minSdk = 23 const val targetSdk = 34 - const val versionCode = 107 + const val versionCode = 108 const val versionName = "1.7.2" const val ndk = "23.1.7779620" const val kotlin = "1.9.10"