mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
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:
parent
cbd6003aba
commit
106f6f52c0
29 changed files with 675 additions and 78 deletions
|
@ -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}")
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
|
@ -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())
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue