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>
This commit is contained in:
Jays2Kings 2023-10-09 14:08:36 -07:00
parent cbd6003aba
commit 106f6f52c0
29 changed files with 675 additions and 78 deletions

View file

@ -203,7 +203,7 @@ dependencies {
implementation(kotlin("reflect", version = AndroidVersions.kotlin)) implementation(kotlin("reflect", version = AndroidVersions.kotlin))
// JSON // 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-json:${kotlinSerialization}")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:${kotlinSerialization}") implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:${kotlinSerialization}")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:${kotlinSerialization}") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:${kotlinSerialization}")

View file

@ -7,8 +7,11 @@ import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.CustomMangaManager 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.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackPreferences
import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.network.JavaScriptEngine import eu.kanade.tachiyomi.network.JavaScriptEngine
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
@ -27,8 +30,14 @@ class AppModule(val app: Application) : InjektModule {
override fun InjektRegistrar.registerInjectables() { override fun InjektRegistrar.registerInjectables() {
addSingleton(app) addSingleton(app)
addSingletonFactory<PreferenceStore> {
AndroidPreferenceStore(app)
}
addSingletonFactory { PreferencesHelper(app) } addSingletonFactory { PreferencesHelper(app) }
addSingletonFactory { TrackPreferences(get()) }
addSingletonFactory { DatabaseHelper(app) } addSingletonFactory { DatabaseHelper(app) }
addSingletonFactory { ChapterCache(app) } addSingletonFactory { ChapterCache(app) }

View file

@ -5,7 +5,9 @@ import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.data.download.DownloadProvider
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob 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.PreferenceKeys
import eu.kanade.tachiyomi.data.preference.PreferenceStore
import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.data.preference.PreferenceValues
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.plusAssign import eu.kanade.tachiyomi.data.preference.plusAssign
@ -33,7 +35,11 @@ object Migrations {
* @param preferences Preferences of the application. * @param preferences Preferences of the application.
* @return true if a migration is performed, false otherwise. * @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 context = preferences.context
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit { prefs.edit {
@ -220,6 +226,19 @@ object Migrations {
LibraryUpdateJob.cancelAllWorks(context) LibraryUpdateJob.cancelAllWorks(context)
LibraryUpdateJob.setupTask(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 return true
} }

View file

@ -8,17 +8,22 @@ object BackupConst {
const val EXTRA_URI = "$ID.$NAME.EXTRA_URI" const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
// Filter options // Filter options
internal const val BACKUP_CATEGORY = 0x1 const val BACKUP_CATEGORY = 0x1
internal const val BACKUP_CATEGORY_MASK = 0x1 const val BACKUP_CATEGORY_MASK = 0x1
internal const val BACKUP_CHAPTER = 0x2 const val BACKUP_CHAPTER = 0x2
internal const val BACKUP_CHAPTER_MASK = 0x2 const val BACKUP_CHAPTER_MASK = 0x2
internal const val BACKUP_HISTORY = 0x4 const val BACKUP_HISTORY = 0x4
internal const val BACKUP_HISTORY_MASK = 0x4 const val BACKUP_HISTORY_MASK = 0x4
internal const val BACKUP_TRACK = 0x8 const val BACKUP_TRACK = 0x8
internal const val BACKUP_TRACK_MASK = 0x8 const val BACKUP_TRACK_MASK = 0x8
internal const val BACKUP_CUSTOM_INFO = 0x10 const val BACKUP_APP_PREFS = 0x10
internal const val BACKUP_CUSTOM_INFO_MASK = 0x10 const val BACKUP_APP_PREFS_MASK = 0x10
internal const val BACKUP_READ_MANGA = 0x20 const val BACKUP_SOURCE_PREFS = 0x20
internal const val BACKUP_READ_MANGA_MASK = 0x20 const val BACKUP_SOURCE_PREFS_MASK = 0x20
internal const val BACKUP_ALL = 0x1F 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
} }

View file

@ -4,6 +4,8 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R 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
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER 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_HISTORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_READ_MANGA 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_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
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK
import eu.kanade.tachiyomi.data.backup.models.Backup 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.BackupChapter
import eu.kanade.tachiyomi.data.backup.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.models.BackupManga 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.BackupSerializer
import eu.kanade.tachiyomi.data.backup.models.BackupSource 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.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.Chapter
import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track 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.model.SChapter
import eu.kanade.tachiyomi.source.preferenceKey
import eu.kanade.tachiyomi.source.sourcePreferences
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import okio.buffer import okio.buffer
import okio.gzip import okio.gzip
import okio.sink import okio.sink
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.FileOutputStream import java.io.FileOutputStream
import kotlin.math.max import kotlin.math.max
class BackupManager(context: Context) : AbstractBackupManager(context) { class BackupManager(context: Context) : AbstractBackupManager(context) {
private val preferenceStore: PreferenceStore = Injekt.get()
val parser = ProtoBuf val parser = ProtoBuf
/** /**
@ -64,6 +84,8 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
backupCategories(), backupCategories(),
emptyList(), emptyList(),
backupExtensionInfo(databaseManga), backupExtensionInfo(databaseManga),
backupAppPreferences(flags),
backupSourcePreferences(flags),
) )
} }
@ -198,6 +220,41 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
return mangaObject return mangaObject
} }
private fun backupAppPreferences(flags: Int): List<BackupPreference> {
if (flags and BACKUP_APP_PREFS_MASK != BACKUP_APP_PREFS) return emptyList()
return preferenceStore.getAll().toBackupPreferences()
}
private fun backupSourcePreferences(flags: Int): List<BackupSourcePreferences> {
if (flags and BACKUP_SOURCE_PREFS_MASK != BACKUP_SOURCE_PREFS) return emptyList()
return sourceManager.getOnlineSources()
.filterIsInstance<ConfigurableSource>()
.map {
BackupSourcePreferences(
it.preferenceKey(),
it.sourcePreferences().all.toBackupPreferences(),
)
}
}
@Suppress("UNCHECKED_CAST")
private fun Map<String, *>.toBackupPreferences(): List<BackupPreference> {
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<String>)?.let {
BackupPreference(key, StringSetPreferenceValue(it))
}
else -> null
}
}
}
fun restoreExistingManga(manga: Manga, dbManga: Manga) { fun restoreExistingManga(manga: Manga, dbManga: Manga) {
manga.id = dbManga.id manga.id = dbManga.id
manga.copyFrom(dbManga) manga.copyFrom(dbManga)

View file

@ -7,24 +7,38 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.models.BackupManga 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.BackupSerializer
import eu.kanade.tachiyomi.data.backup.models.BackupSource 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob 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.coroutineScope
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import okio.buffer import okio.buffer
import okio.gzip import okio.gzip
import okio.source import okio.source
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Date import java.util.Date
class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<BackupManager>(context, notifier) { class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<BackupManager>(context, notifier) {
private val preferenceStore: PreferenceStore = Injekt.get()
@SuppressLint("Recycle") @SuppressLint("Recycle")
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun performRestore(uri: Uri): Boolean { override suspend fun performRestore(uri: Uri): Boolean {
backupManager = BackupManager(context) backupManager = BackupManager(context)
@ -44,6 +58,9 @@ class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBacku
sourceMapping = backupMaps.associate { it.sourceId to it.name } sourceMapping = backupMaps.associate { it.sourceId to it.name }
return coroutineScope { return coroutineScope {
restoreAppPreferences(backup.backupPreferences)
restoreSourcePreferences(backup.backupSourcePreferences)
// Restore individual manga // Restore individual manga
backup.backupManga.forEach { backup.backupManga.forEach {
if (!isActive) { if (!isActive) {
@ -147,4 +164,56 @@ class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBacku
customManga?.id = manga.id!! customManga?.id = manga.id!!
customManga?.let { customMangaManager.saveMangaInfo(it) } customManga?.let { customMangaManager.saveMangaInfo(it) }
} }
private fun restoreAppPreferences(preferences: List<BackupPreference>) {
restorePreferences(preferences, preferenceStore)
}
private fun restoreSourcePreferences(preferences: List<BackupSourcePreferences>) {
preferences.forEach {
val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey))
restorePreferences(it.prefs, sourcePrefs)
}
}
private fun restorePreferences(
toRestore: List<BackupPreference>,
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)
}
}
}
}
}
} }

View file

@ -11,10 +11,12 @@ import java.util.Locale
data class Backup( data class Backup(
@ProtoNumber(1) val backupManga: List<BackupManga>, @ProtoNumber(1) val backupManga: List<BackupManga>,
@ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(), @ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(),
// Bump by 100 to specify this is a 0.x value
@ProtoNumber(100) var backupBrokenSources: List<BrokenBackupSource> = emptyList(), @ProtoNumber(100) var backupBrokenSources: List<BrokenBackupSource> = emptyList(),
@ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(), @ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(),
@ProtoNumber(104) var backupPreferences: List<BackupPreference> = emptyList(),
@ProtoNumber(105) var backupSourcePreferences: List<BackupSourcePreferences> = emptyList(),
) { ) {
companion object { companion object {
val filenameRegex = """(${BuildConfig.APPLICATION_ID}|tachiyomi)?_\d+-\d+-\d+_\d+-\d+\.(tachibk|proto\.gz)""".toRegex() val filenameRegex = """(${BuildConfig.APPLICATION_ID}|tachiyomi)?_\d+-\d+-\d+_\d+-\d+\.(tachibk|proto\.gz)""".toRegex()

View file

@ -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<BackupPreference>,
)
@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<String>) : PreferenceValue()

View file

@ -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<T>(
private val preferences: SharedPreferences,
private val keyFlow: Flow<String?>,
private val key: String,
private val defaultValue: T,
) : Preference<T> {
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<T> {
return keyFlow
.filter { it == key || it == null }
.onStart { emit("ignition") }
.map { get() }
.conflate()
}
override fun stateIn(scope: CoroutineScope): StateFlow<T> {
return changes().stateIn(scope, SharingStarted.Eagerly, get())
}
class StringPrimitive(
preferences: SharedPreferences,
keyFlow: Flow<String?>,
key: String,
defaultValue: String,
) : AndroidPreference<String>(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<String?>,
key: String,
defaultValue: Long,
) : AndroidPreference<Long>(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<String?>,
key: String,
defaultValue: Int,
) : AndroidPreference<Int>(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<String?>,
key: String,
defaultValue: Float,
) : AndroidPreference<Float>(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<String?>,
key: String,
defaultValue: Boolean,
) : AndroidPreference<Boolean>(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<String?>,
key: String,
defaultValue: Set<String>,
) : AndroidPreference<Set<String>>(preferences, keyFlow, key, defaultValue) {
override fun read(
preferences: SharedPreferences,
key: String,
defaultValue: Set<String>,
): Set<String> {
return preferences.getStringSet(key, defaultValue) ?: defaultValue
}
override fun write(key: String, value: Set<String>): Editor.() -> Unit = {
putStringSet(key, value)
}
}
class Object<T>(
preferences: SharedPreferences,
keyFlow: Flow<String?>,
key: String,
defaultValue: T,
val serializer: (T) -> String,
val deserializer: (String) -> T,
) : AndroidPreference<T>(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))
}
}
}

View file

@ -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<String> {
return StringPrimitive(sharedPreferences, keyFlow, key, defaultValue)
}
override fun getLong(key: String, defaultValue: Long): Preference<Long> {
return LongPrimitive(sharedPreferences, keyFlow, key, defaultValue)
}
override fun getInt(key: String, defaultValue: Int): Preference<Int> {
return IntPrimitive(sharedPreferences, keyFlow, key, defaultValue)
}
override fun getFloat(key: String, defaultValue: Float): Preference<Float> {
return FloatPrimitive(sharedPreferences, keyFlow, key, defaultValue)
}
override fun getBoolean(key: String, defaultValue: Boolean): Preference<Boolean> {
return BooleanPrimitive(sharedPreferences, keyFlow, key, defaultValue)
}
override fun getStringSet(key: String, defaultValue: Set<String>): Preference<Set<String>> {
return StringSetPrimitive(sharedPreferences, keyFlow, key, defaultValue)
}
override fun <T> getObject(
key: String,
defaultValue: T,
serializer: (T) -> String,
deserializer: (String) -> T,
): Preference<T> {
return Object(
preferences = sharedPreferences,
keyFlow = keyFlow,
key = key,
defaultValue = defaultValue,
serializer = serializer,
deserializer = deserializer,
)
}
override fun getAll(): Map<String, *> {
return sharedPreferences.all ?: emptyMap<String, Any>()
}
}
private val SharedPreferences.keyFlow
get() = callbackFlow {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key: String? ->
trySend(
key,
)
}
registerOnSharedPreferenceChangeListener(listener)
awaitClose {
unregisterOnSharedPreferenceChangeListener(listener)
}
}

View file

@ -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<T> {
fun key(): String
fun get(): T
fun set(value: T)
fun isSet(): Boolean
fun delete()
fun defaultValue(): T
fun changes(): Flow<T>
fun stateIn(scope: CoroutineScope): StateFlow<T>
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 <reified T, R : T> Preference<T>.getAndSet(crossinline block: (T) -> R) = set(
block(get()),
)
operator fun <T> Preference<Set<T>>.plusAssign(item: T) {
set(get() + item)
}
operator fun <T> Preference<Set<T>>.minusAssign(item: T) {
set(get() - item)
}
fun Preference<Boolean>.toggle(): Boolean {
set(!get())
return get()
}

View file

@ -241,10 +241,4 @@ object PreferenceKeys {
const val hideChapterTitles = "hide_chapter_titles" const val hideChapterTitles = "hide_chapter_titles"
const val chaptersDescAsDefault = "chapters_desc_as_default" 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"
} }

View file

@ -0,0 +1,43 @@
package eu.kanade.tachiyomi.data.preference
interface PreferenceStore {
fun getString(key: String, defaultValue: String = ""): Preference<String>
fun getLong(key: String, defaultValue: Long = 0): Preference<Long>
fun getInt(key: String, defaultValue: Int = 0): Preference<Int>
fun getFloat(key: String, defaultValue: Float = 0f): Preference<Float>
fun getBoolean(key: String, defaultValue: Boolean = false): Preference<Boolean>
fun getStringSet(key: String, defaultValue: Set<String> = emptySet()): Preference<Set<String>>
fun <T> getObject(
key: String,
defaultValue: T,
serializer: (T) -> String,
deserializer: (String) -> T,
): Preference<T>
fun getAll(): Map<String, *>
}
inline fun <reified T : Enum<T>> PreferenceStore.getEnum(
key: String,
defaultValue: T,
): Preference<T> {
return getObject(
key = key,
defaultValue = defaultValue,
serializer = { it.name },
deserializer = {
try {
enumValueOf(it)
} catch (e: IllegalArgumentException) {
defaultValue
}
},
)
}

View file

@ -11,7 +11,6 @@ import com.google.android.material.color.DynamicColors
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga 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.data.updater.AppDownloadInstallJob
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
@ -223,19 +222,6 @@ class PreferencesHelper(val context: Context) {
fun sourceSorting() = flowPrefs.getInt(Keys.sourcesSort, 0) 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 anilistScoreType() = flowPrefs.getString("anilist_score_type", "POINT_10")
fun backupsDirectory() = flowPrefs.getString(Keys.backupDirectory, defaultBackupDir.toString()) fun backupsDirectory() = flowPrefs.getString(Keys.backupDirectory, defaultBackupDir.toString())

View file

@ -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")
}
}

View file

@ -5,7 +5,6 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Track 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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.util.system.executeOnIO import eu.kanade.tachiyomi.util.system.executeOnIO
@ -14,7 +13,7 @@ import uy.kohesive.injekt.injectLazy
abstract class TrackService(val id: Int) { abstract class TrackService(val id: Int) {
val preferences: PreferencesHelper by injectLazy() val trackPreferences: TrackPreferences by injectLazy()
val networkService: NetworkHelper by injectLazy() val networkService: NetworkHelper by injectLazy()
val db: DatabaseHelper by injectLazy() val db: DatabaseHelper by injectLazy()
open fun canRemoveFromService() = false open fun canRemoveFromService() = false
@ -93,19 +92,19 @@ abstract class TrackService(val id: Int) {
@CallSuper @CallSuper
open fun logout() { open fun logout() {
preferences.setTrackCredentials(this, "", "") trackPreferences.setCredentials(this, "", "")
} }
open val isLogged: Boolean open val isLogged: Boolean
get() = getUsername().isNotEmpty() && get() = getUsername().isNotEmpty() &&
getPassword().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) { fun saveCredentials(username: String, password: String) {
preferences.setTrackCredentials(this, username, password) trackPreferences.setCredentials(this, username, password)
} }
} }

View file

@ -41,7 +41,7 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
override val supportsReadingDates: Boolean = true override val supportsReadingDates: Boolean = true
private val scorePreference = preferences.anilistScoreType() private val scorePreference = trackPreferences.anilistScoreType()
init { init {
// If the preference is an int from APIv1, logout user to force using APIv2 // 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() { override fun logout() {
super.logout() super.logout()
preferences.trackToken(this).delete() trackPreferences.trackToken(this).delete()
interceptor.setAuth(null) interceptor.setAuth(null)
} }
fun saveOAuth(oAuth: OAuth?) { fun saveOAuth(oAuth: OAuth?) {
preferences.trackToken(this).set(json.encodeToString(oAuth)) trackPreferences.trackToken(this).set(json.encodeToString(oAuth))
} }
fun loadOAuth(): OAuth? { fun loadOAuth(): OAuth? {
return try { return try {
json.decodeFromString<OAuth>(preferences.trackToken(this).get()) json.decodeFromString<OAuth>(trackPreferences.trackToken(this).get())
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
null null

View file

@ -129,12 +129,12 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
} }
fun saveToken(oauth: OAuth?) { fun saveToken(oauth: OAuth?) {
preferences.trackToken(this).set(json.encodeToString(oauth)) trackPreferences.trackToken(this).set(json.encodeToString(oauth))
} }
fun restoreToken(): OAuth? { fun restoreToken(): OAuth? {
return try { return try {
json.decodeFromString<OAuth>(preferences.trackToken(this).get()) json.decodeFromString<OAuth>(trackPreferences.trackToken(this).get())
} catch (e: Exception) { } catch (e: Exception) {
null null
} }
@ -142,7 +142,7 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
override fun logout() { override fun logout() {
super.logout() super.logout()
preferences.trackToken(this).delete() trackPreferences.trackToken(this).delete()
interceptor.newAuth(null) interceptor.newAuth(null)
} }

View file

@ -154,12 +154,12 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
} }
fun saveToken(oauth: OAuth?) { fun saveToken(oauth: OAuth?) {
preferences.trackToken(this).set(json.encodeToString(oauth)) trackPreferences.trackToken(this).set(json.encodeToString(oauth))
} }
fun restoreToken(): OAuth? { fun restoreToken(): OAuth? {
return try { return try {
json.decodeFromString<OAuth>(preferences.trackToken(this).get()) json.decodeFromString<OAuth>(trackPreferences.trackToken(this).get())
} catch (e: Exception) { } catch (e: Exception) {
null null
} }

View file

@ -130,6 +130,6 @@ class MangaUpdates(private val context: Context, id: Int) : TrackService(id) {
} }
fun restoreSession(): String? { fun restoreSession(): String? {
return preferences.trackPassword(this) return getPassword()
} }
} }

View file

@ -138,17 +138,17 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
override fun logout() { override fun logout() {
super.logout() super.logout()
preferences.trackToken(this).delete() trackPreferences.trackToken(this).delete()
interceptor.setAuth(null) interceptor.setAuth(null)
} }
fun saveOAuth(oAuth: OAuth?) { fun saveOAuth(oAuth: OAuth?) {
preferences.trackToken(this).set(json.encodeToString(oAuth)) trackPreferences.trackToken(this).set(json.encodeToString(oAuth))
} }
fun loadOAuth(): OAuth? { fun loadOAuth(): OAuth? {
return try { return try {
json.decodeFromString<OAuth>(preferences.trackToken(this).get()) json.decodeFromString<OAuth>(trackPreferences.trackToken(this).get())
} catch (e: Exception) { } catch (e: Exception) {
null null
} }

View file

@ -129,12 +129,12 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
} }
fun saveToken(oauth: OAuth?) { fun saveToken(oauth: OAuth?) {
preferences.trackToken(this).set(json.encodeToString(oauth)) trackPreferences.trackToken(this).set(json.encodeToString(oauth))
} }
fun restoreToken(): OAuth? { fun restoreToken(): OAuth? {
return try { return try {
json.decodeFromString<OAuth>(preferences.trackToken(this).get()) json.decodeFromString<OAuth>(trackPreferences.trackToken(this).get())
} catch (e: Exception) { } catch (e: Exception) {
null null
} }
@ -142,7 +142,7 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
override fun logout() { override fun logout() {
super.logout() super.logout()
preferences.trackToken(this).delete() trackPreferences.trackToken(this).delete()
interceptor.newAuth(null) interceptor.newAuth(null)
} }
} }

View file

@ -23,3 +23,6 @@ interface ConfigurableSource : Source {
// TODO: use getSourcePreferences once all extensions are on ext-lib 1.5 // TODO: use getSourcePreferences once all extensions are on ext-lib 1.5
fun ConfigurableSource.sourcePreferences(): SharedPreferences = fun ConfigurableSource.sourcePreferences(): SharedPreferences =
Injekt.get<Application>().getSharedPreferences(preferenceKey(), Context.MODE_PRIVATE) Injekt.get<Application>().getSharedPreferences(preferenceKey(), Context.MODE_PRIVATE)
fun sourcePreferences(key: String): SharedPreferences =
Injekt.get<Application>().getSharedPreferences(key, Context.MODE_PRIVATE)

View file

@ -132,6 +132,8 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Date import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -541,7 +543,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
preferences.incognitoMode().set(false) preferences.incognitoMode().set(false)
// Show changelog if needed // Show changelog if needed
if (Migrations.upgrade(preferences, lifecycleScope)) { if (Migrations.upgrade(preferences, Injekt.get(), lifecycleScope)) {
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
content.post { content.post {
whatsNewSheet().show() whatsNewSheet().show()

View file

@ -200,9 +200,7 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) :
return return
} }
if (track.tracking_url.isBlank()) { if (track.tracking_url.isNotBlank()) {
activity.toast(R.string.url_not_set_click_again)
} else {
activity.openInBrowser(track.tracking_url.toUri()) activity.openInBrowser(track.tracking_url.toUri())
controller.refreshTracker = position controller.refreshTracker = position
} }

View file

@ -218,6 +218,8 @@ class SettingsBackupController : SettingsController() {
R.string.chapters, R.string.chapters,
R.string.tracking, R.string.tracking,
R.string.history, R.string.history,
R.string.app_settings,
R.string.source_settings,
R.string.custom_manga_info, R.string.custom_manga_info,
R.string.all_read_manga, R.string.all_read_manga,
) )
@ -244,8 +246,10 @@ class SettingsBackupController : SettingsController() {
2 -> flags = flags or BackupConst.BACKUP_CHAPTER 2 -> flags = flags or BackupConst.BACKUP_CHAPTER
3 -> flags = flags or BackupConst.BACKUP_TRACK 3 -> flags = flags or BackupConst.BACKUP_TRACK
4 -> flags = flags or BackupConst.BACKUP_HISTORY 4 -> flags = flags or BackupConst.BACKUP_HISTORY
5 -> flags = flags or BackupConst.BACKUP_CUSTOM_INFO 5 -> flags = flags or BackupConst.BACKUP_APP_PREFS
6 -> flags = flags or BackupConst.BACKUP_READ_MANGA 6 -> flags = flags or BackupConst.BACKUP_SOURCE_PREFS
7 -> flags = flags or BackupConst.BACKUP_CUSTOM_INFO
8 -> flags = flags or BackupConst.BACKUP_READ_MANGA
} }
} }
} }

View file

@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.asImmediateFlowIn import eu.kanade.tachiyomi.data.preference.asImmediateFlowIn
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackManager 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.TrackService
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi
@ -30,6 +31,7 @@ class SettingsTrackingController :
TrackLogoutDialog.Listener { TrackLogoutDialog.Listener {
private val trackManager: TrackManager by injectLazy() private val trackManager: TrackManager by injectLazy()
val trackPreferences: TrackPreferences by injectLazy()
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.tracking titleRes = R.string.tracking
@ -59,7 +61,7 @@ class SettingsTrackingController :
isIconSpaceReserved = true isIconSpaceReserved = true
title = context.getString(R.string.update_tracking_scoring_type, context.getString(R.string.anilist)) 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) { .asImmediateFlowIn(viewScope) {
isVisible = it.isNotEmpty() isVisible = it.isNotEmpty()
} }
@ -117,7 +119,7 @@ class SettingsTrackingController :
): TrackerPreference { ): TrackerPreference {
return add( return add(
TrackerPreference(context).apply { TrackerPreference(context).apply {
key = Keys.trackUsername(service.id) key = trackPreferences.trackUsername(service).key()
title = context.getString(service.nameRes()) title = context.getString(service.nameRes())
iconRes = service.getLogo() iconRes = service.getLogo()
iconColor = service.getLogoColor() iconColor = service.getLogoColor()
@ -125,7 +127,7 @@ class SettingsTrackingController :
if (service.isLogged) { if (service.isLogged) {
if (service is EnhancedTrackService) { if (service is EnhancedTrackService) {
service.logout() service.logout()
updatePreference(service.id) updatePreference(service)
} else { } else {
val dialog = TrackLogoutDialog(service) val dialog = TrackLogoutDialog(service)
dialog.targetController = this@SettingsTrackingController dialog.targetController = this@SettingsTrackingController
@ -134,7 +136,7 @@ class SettingsTrackingController :
} else { } else {
if (service is EnhancedTrackService) { if (service is EnhancedTrackService) {
service.loginNoop() service.loginNoop()
updatePreference(service.id) updatePreference(service)
} else { } else {
login() login()
} }
@ -146,22 +148,22 @@ class SettingsTrackingController :
override fun onActivityResumed(activity: Activity) { override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity) super.onActivityResumed(activity)
updatePreference(trackManager.myAnimeList.id) updatePreference(trackManager.myAnimeList)
updatePreference(trackManager.aniList.id) updatePreference(trackManager.aniList)
updatePreference(trackManager.shikimori.id) updatePreference(trackManager.shikimori)
updatePreference(trackManager.bangumi.id) updatePreference(trackManager.bangumi)
} }
private fun updatePreference(id: Int) { private fun updatePreference(service: TrackService) {
val pref = findPreference(Keys.trackUsername(id)) as? TrackerPreference val pref = findPreference(trackPreferences.trackUsername(service).key()) as? TrackerPreference
pref?.notifyChanged() pref?.notifyChanged()
} }
override fun trackLoginDialogClosed(service: TrackService) { override fun trackLoginDialogClosed(service: TrackService) {
updatePreference(service.id) updatePreference(service)
} }
override fun trackLogoutDialogClosed(service: TrackService) { override fun trackLogoutDialogClosed(service: TrackService) {
updatePreference(service.id) updatePreference(service)
} }
} }

View file

@ -576,6 +576,8 @@
<string name="error_sharing_cover">Error sharing cover</string> <string name="error_sharing_cover">Error sharing cover</string>
<string name="custom_manga_info">Custom manga info</string> <string name="custom_manga_info">Custom manga info</string>
<string name="all_read_manga">All read manga</string> <string name="all_read_manga">All read manga</string>
<string name="app_settings">App settings</string>
<string name="source_settings">Source settings</string>
<string name="set_as_default">Set as default</string> <string name="set_as_default">Set as default</string>
<string name="filter_groups">Filter scanlator groups</string> <string name="filter_groups">Filter scanlator groups</string>
<plurals name="deleted_chapters"> <plurals name="deleted_chapters">

View file

@ -4,7 +4,7 @@ object AndroidVersions {
const val compileSdk = 34 const val compileSdk = 34
const val minSdk = 23 const val minSdk = 23
const val targetSdk = 34 const val targetSdk = 34
const val versionCode = 107 const val versionCode = 108
const val versionName = "1.7.2" const val versionName = "1.7.2"
const val ndk = "23.1.7779620" const val ndk = "23.1.7779620"
const val kotlin = "1.9.10" const val kotlin = "1.9.10"