From b9fd416fc665fdb07c11ebeb13e3dbf918dfe479 Mon Sep 17 00:00:00 2001 From: arkon Date: Thu, 21 Dec 2023 09:49:03 -0500 Subject: [PATCH 01/30] Use smaller window to calculate fetch interval if there's less total chapters This is sort of a workaround for sources that tend to only give you the first few and most recent few chapters, which would have been 28 day intervals before due to the big gap in the middle. --- .../domain/manga/interactor/FetchInterval.kt | 6 ++++-- .../domain/manga/interactor/FetchIntervalTest.kt | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/domain/src/main/java/tachiyomi/domain/manga/interactor/FetchInterval.kt b/domain/src/main/java/tachiyomi/domain/manga/interactor/FetchInterval.kt index 0a9124d16..7570b2a11 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/interactor/FetchInterval.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/interactor/FetchInterval.kt @@ -46,6 +46,8 @@ class FetchInterval( } internal fun calculateInterval(chapters: List, zone: ZoneId): Int { + val chapterWindow = if (chapters.size <= 8) 3 else 10 + val uploadDates = chapters.asSequence() .filter { it.dateUpload > 0L } .sortedByDescending { it.dateUpload } @@ -55,7 +57,7 @@ class FetchInterval( .atStartOfDay() } .distinct() - .take(10) + .take(chapterWindow) .toList() val fetchDates = chapters.asSequence() @@ -66,7 +68,7 @@ class FetchInterval( .atStartOfDay() } .distinct() - .take(10) + .take(chapterWindow) .toList() val interval = when { diff --git a/domain/src/test/java/tachiyomi/domain/manga/interactor/FetchIntervalTest.kt b/domain/src/test/java/tachiyomi/domain/manga/interactor/FetchIntervalTest.kt index 468d7eb2d..ccaaf24da 100644 --- a/domain/src/test/java/tachiyomi/domain/manga/interactor/FetchIntervalTest.kt +++ b/domain/src/test/java/tachiyomi/domain/manga/interactor/FetchIntervalTest.kt @@ -54,6 +54,21 @@ class FetchIntervalTest { fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1 } + @Test + fun `returns interval based on smaller subset of recent chapters if very few chapters`() { + val oldChapters = (1..3).map { + chapterWithTime(chapter, (it * 7).days) + } + // Significant gap between chapters + val newChapters = (1..3).map { + chapterWithTime(chapter, oldChapters.lastUploadDate() + 365.days + (it * 7).days) + } + + val chapters = oldChapters + newChapters + + fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 7 + } + @Test fun `returns interval of 7 days when multiple chapters in 1 day`() { val chapters = (1..10).map { From a51108cbe8d86976ecf8f7e6c7fcf7f5b6f65380 Mon Sep 17 00:00:00 2001 From: arkon Date: Thu, 21 Dec 2023 09:49:46 -0500 Subject: [PATCH 02/30] Update Compose compiler --- gradle/compose.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 1fbf9f375..28d9ca5a7 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,5 +1,5 @@ [versions] -compiler = "1.5.6" +compiler = "1.5.7" compose-bom = "2023.12.00-alpha04" accompanist = "0.33.2-alpha" From 83a67feb48c4e40994a334520c907f71d2fbf75e Mon Sep 17 00:00:00 2001 From: arkon Date: Thu, 21 Dec 2023 22:16:42 -0500 Subject: [PATCH 03/30] Foundations for partial restores Related to #3136 --- .../settings/screen/SettingsDataScreen.kt | 23 ++++++++- .../data/backup/restore/BackupRestoreJob.kt | 19 +++++-- .../data/backup/restore/BackupRestorer.kt | 50 +++++++++++++++---- 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index 1a5c93132..210ce9296 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -40,6 +40,7 @@ import eu.kanade.presentation.util.relativeTimeSpanString import eu.kanade.tachiyomi.data.backup.BackupFileValidator import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob +import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.system.DeviceUtil @@ -249,7 +250,16 @@ object SettingsDataScreen : SearchableSettings { confirmButton = { TextButton( onClick = { - BackupRestoreJob.start(context, err.uri) + BackupRestoreJob.start( + context = context, + uri = err.uri, + // TODO: allow user-selectable restore options + options = RestoreOptions( + appSettings = true, + sourceSettings = true, + library = true, + ), + ) onDismissRequest() }, ) { @@ -283,7 +293,16 @@ object SettingsDataScreen : SearchableSettings { } if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) { - BackupRestoreJob.start(context, it) + BackupRestoreJob.start( + context = context, + uri = it, + // TODO: allow user-selectable restore options + options = RestoreOptions( + appSettings = true, + sourceSettings = true, + library = true, + ), + ) return@rememberLauncherForActivityResult } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt index 9883eb023..f1b3a28c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt @@ -30,13 +30,19 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet override suspend fun doWork(): Result { val uri = inputData.getString(LOCATION_URI_KEY)?.toUri() - ?: return Result.failure() + val options = inputData.getBooleanArray(OPTIONS_KEY) + ?.let { RestoreOptions.fromBooleanArray(it) } + + if (uri == null || options == null) { + return Result.failure() + } + val isSync = inputData.getBoolean(SYNC_KEY, false) setForegroundSafely() return try { - BackupRestorer(context, notifier, isSync).restore(uri) + BackupRestorer(context, notifier, isSync).restore(uri, options) Result.success() } catch (e: Exception) { if (e is CancellationException) { @@ -69,10 +75,16 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet return context.workManager.isRunning(TAG) } - fun start(context: Context, uri: Uri, sync: Boolean = false) { + fun start( + context: Context, + uri: Uri, + options: RestoreOptions, + sync: Boolean = false, + ) { val inputData = workDataOf( LOCATION_URI_KEY to uri.toString(), SYNC_KEY to sync, + OPTIONS_KEY to options.toBooleanArray(), ) val request = OneTimeWorkRequestBuilder() .addTag(TAG) @@ -91,3 +103,4 @@ private const val TAG = "BackupRestore" private const val LOCATION_URI_KEY = "location_uri" // String private const val SYNC_KEY = "sync" // Boolean +private const val OPTIONS_KEY = "options" // BooleanArray diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt index a1e262844..063f592de 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt @@ -39,10 +39,10 @@ class BackupRestorer( */ private var sourceMapping: Map = emptyMap() - suspend fun restore(uri: Uri) { + suspend fun restore(uri: Uri, options: RestoreOptions) { val startTime = System.currentTimeMillis() - restoreFromFile(uri) + restoreFromFile(uri, options) val time = System.currentTimeMillis() - startTime @@ -57,20 +57,36 @@ class BackupRestorer( ) } - private suspend fun restoreFromFile(uri: Uri) { + private suspend fun restoreFromFile(uri: Uri, options: RestoreOptions) { val backup = BackupUtil.decodeBackup(context, uri) - restoreAmount = backup.backupManga.size + 3 // +3 for categories, app prefs, source prefs - // Store source mapping for error messages val backupMaps = backup.backupSources + backup.backupBrokenSources.map { it.toBackupSource() } sourceMapping = backupMaps.associate { it.sourceId to it.name } + if (options.library) { + restoreAmount += backup.backupManga.size + 1 // +1 for categories + } + if (options.appSettings) { + restoreAmount += 1 + } + if (options.sourceSettings) { + restoreAmount += 1 + } + coroutineScope { - restoreCategories(backup.backupCategories) - restoreAppPreferences(backup.backupPreferences) - restoreSourcePreferences(backup.backupSourcePreferences) - restoreManga(backup.backupManga, backup.backupCategories) + if (options.library) { + restoreCategories(backup.backupCategories) + } + if (options.appSettings) { + restoreAppPreferences(backup.backupPreferences) + } + if (options.sourceSettings) { + restoreSourcePreferences(backup.backupSourcePreferences) + } + if (options.library) { + restoreManga(backup.backupManga, backup.backupCategories) + } // TODO: optionally trigger online library + tracker update } @@ -154,3 +170,19 @@ class BackupRestorer( return File("") } } + +data class RestoreOptions( + val appSettings: Boolean, + val sourceSettings: Boolean, + val library: Boolean, +) { + fun toBooleanArray() = booleanArrayOf(appSettings, sourceSettings, library) + + companion object { + fun fromBooleanArray(booleanArray: BooleanArray) = RestoreOptions( + appSettings = booleanArray[0], + sourceSettings = booleanArray[1], + library = booleanArray[2], + ) + } +} From 565317d99c1a7c9b0d3bd650adeb420fd8cd9cf4 Mon Sep 17 00:00:00 2001 From: arkon Date: Thu, 21 Dec 2023 22:41:48 -0500 Subject: [PATCH 04/30] Show MIUI warning more prominently in CreateBackupScreen --- .../screen/data/CreateBackupScreen.kt | 97 +++++++++---------- .../commonMain/resources/MR/base/strings.xml | 2 +- 2 files changed, 48 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt index 261a1b20a..7fd166802 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt @@ -4,10 +4,8 @@ import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.net.Uri -import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -28,6 +26,7 @@ import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.WarningBanner import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob @@ -77,63 +76,61 @@ class CreateBackupScreen : Screen() { ) }, ) { contentPadding -> - Column( + LazyColumn( modifier = Modifier .padding(contentPadding) .fillMaxSize(), ) { - LazyColumn( - modifier = Modifier - .weight(1f) - .padding(horizontal = MaterialTheme.padding.medium), - ) { + if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) { item { - LabeledCheckbox( - label = stringResource(MR.strings.manga), - checked = true, - onCheckedChange = {}, - enabled = false, - ) - } - BackupChoices.forEach { (k, v) -> - item { - LabeledCheckbox( - label = stringResource(v), - checked = state.flags.contains(k), - onCheckedChange = { - model.toggleFlag(k) - }, - ) - } + WarningBanner(MR.strings.restore_miui_warning) } } - - HorizontalDivider() - - Button( - modifier = Modifier - .padding(horizontal = 16.dp, vertical = 8.dp) - .fillMaxWidth(), - onClick = { - if (!BackupCreateJob.isManualJobRunning(context)) { - if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) { - context.toast(MR.strings.restore_miui_warning, Toast.LENGTH_LONG) - } - try { - chooseBackupDir.launch(Backup.getFilename()) - } catch (e: ActivityNotFoundException) { - context.toast(MR.strings.file_picker_error) - } - } else { - context.toast(MR.strings.backup_in_progress) - } - }, - ) { - Text( - text = stringResource(MR.strings.action_create), - color = MaterialTheme.colorScheme.onPrimary, + item { + LabeledCheckbox( + label = stringResource(MR.strings.manga), + checked = true, + onCheckedChange = {}, + enabled = false, + modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium), ) } + BackupChoices.forEach { (k, v) -> + item { + LabeledCheckbox( + label = stringResource(v), + checked = state.flags.contains(k), + onCheckedChange = { + model.toggleFlag(k) + }, + modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium), + ) + } + } + } + + HorizontalDivider() + + Button( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 8.dp) + .fillMaxWidth(), + onClick = { + if (!BackupCreateJob.isManualJobRunning(context)) { + try { + chooseBackupDir.launch(Backup.getFilename()) + } catch (e: ActivityNotFoundException) { + context.toast(MR.strings.file_picker_error) + } + } else { + context.toast(MR.strings.backup_in_progress) + } + }, + ) { + Text( + text = stringResource(MR.strings.action_create), + color = MaterialTheme.colorScheme.onPrimary, + ) } } } diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml index 5711a53cc..0961e4706 100644 --- a/i18n/src/commonMain/resources/MR/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/base/strings.xml @@ -521,7 +521,7 @@ Storage usage Clear chapter cache Used: %1$s - Cache cleared. %1$d files have been deleted + Cache cleared, %1$d files deleted Error occurred while clearing Clear chapter cache on app launch From 9f90ee358b8bee6713ef679aef7893f44fcc8f28 Mon Sep 17 00:00:00 2001 From: arkon Date: Thu, 21 Dec 2023 22:47:23 -0500 Subject: [PATCH 05/30] Initial move of restore backup into a separate screen --- .../settings/screen/SettingsDataScreen.kt | 211 +++------------ .../screen/data/RestoreBackupScreen.kt | 242 ++++++++++++++++++ .../data/backup/restore/BackupRestorer.kt | 6 +- 3 files changed, 280 insertions(+), 179 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/more/settings/screen/data/RestoreBackupScreen.kt diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index 210ce9296..8ffc0e076 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -1,28 +1,24 @@ package eu.kanade.presentation.more.settings.screen import android.content.ActivityNotFoundException -import android.content.Context import android.content.Intent import android.net.Uri import android.os.Environment import android.text.format.Formatter -import android.widget.Toast import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MultiChoiceSegmentedButtonRow +import androidx.compose.material3.SegmentedButton +import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue @@ -34,17 +30,13 @@ import cafe.adriel.voyager.navigator.currentOrThrow import com.hippo.unifile.UniFile import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen +import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding import eu.kanade.presentation.util.relativeTimeSpanString -import eu.kanade.tachiyomi.data.backup.BackupFileValidator import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob -import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob -import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.util.storage.DiskUtil -import eu.kanade.tachiyomi.util.system.DeviceUtil -import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.toast import logcat.LogPriority import tachiyomi.core.i18n.stringResource @@ -142,14 +134,42 @@ object SettingsDataScreen : SearchableSettings { @Composable private fun getBackupAndRestoreGroup(backupPreferences: BackupPreferences): Preference.PreferenceGroup { val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp().collectAsState() return Preference.PreferenceGroup( title = stringResource(MR.strings.label_backup), preferenceItems = listOf( // Manual actions - getCreateBackupPref(), - getRestoreBackupPref(), + Preference.PreferenceItem.CustomPreference( + title = stringResource(MR.strings.label_backup), + ) { + BasePreferenceWidget( + subcomponent = { + MultiChoiceSegmentedButtonRow( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = PrefsHorizontalPadding), + ) { + SegmentedButton( + checked = false, + onCheckedChange = { navigator.push(CreateBackupScreen()) }, + shape = SegmentedButtonDefaults.itemShape(0, 2), + ) { + Text(stringResource(MR.strings.pref_create_backup)) + } + SegmentedButton( + checked = false, + onCheckedChange = { navigator.push(RestoreBackupScreen()) }, + shape = SegmentedButtonDefaults.itemShape(1, 2), + ) { + Text(stringResource(MR.strings.pref_restore_backup)) + } + } + }, + ) + }, // Automatic backups Preference.PreferenceItem.ListPreference( @@ -176,156 +196,6 @@ object SettingsDataScreen : SearchableSettings { ) } - @Composable - private fun getCreateBackupPref(): Preference.PreferenceItem.TextPreference { - val navigator = LocalNavigator.currentOrThrow - return Preference.PreferenceItem.TextPreference( - title = stringResource(MR.strings.pref_create_backup), - subtitle = stringResource(MR.strings.pref_create_backup_summ), - onClick = { navigator.push(CreateBackupScreen()) }, - ) - } - - @Composable - private fun getRestoreBackupPref(): Preference.PreferenceItem.TextPreference { - val context = LocalContext.current - var error by remember { mutableStateOf(null) } - if (error != null) { - val onDismissRequest = { error = null } - when (val err = error) { - is InvalidRestore -> { - AlertDialog( - onDismissRequest = onDismissRequest, - title = { Text(text = stringResource(MR.strings.invalid_backup_file)) }, - text = { Text(text = listOfNotNull(err.uri, err.message).joinToString("\n\n")) }, - dismissButton = { - TextButton( - onClick = { - context.copyToClipboard(err.message, err.message) - onDismissRequest() - }, - ) { - Text(text = stringResource(MR.strings.action_copy_to_clipboard)) - } - }, - confirmButton = { - TextButton(onClick = onDismissRequest) { - Text(text = stringResource(MR.strings.action_ok)) - } - }, - ) - } - is MissingRestoreComponents -> { - AlertDialog( - onDismissRequest = onDismissRequest, - title = { Text(text = stringResource(MR.strings.pref_restore_backup)) }, - text = { - Column( - modifier = Modifier.verticalScroll(rememberScrollState()), - ) { - val msg = buildString { - append(stringResource(MR.strings.backup_restore_content_full)) - if (err.sources.isNotEmpty()) { - append("\n\n").append(stringResource(MR.strings.backup_restore_missing_sources)) - err.sources.joinTo( - this, - separator = "\n- ", - prefix = "\n- ", - ) - } - if (err.trackers.isNotEmpty()) { - append( - "\n\n", - ).append(stringResource(MR.strings.backup_restore_missing_trackers)) - err.trackers.joinTo( - this, - separator = "\n- ", - prefix = "\n- ", - ) - } - } - Text(text = msg) - } - }, - confirmButton = { - TextButton( - onClick = { - BackupRestoreJob.start( - context = context, - uri = err.uri, - // TODO: allow user-selectable restore options - options = RestoreOptions( - appSettings = true, - sourceSettings = true, - library = true, - ), - ) - onDismissRequest() - }, - ) { - Text(text = stringResource(MR.strings.action_restore)) - } - }, - ) - } - else -> error = null // Unknown - } - } - - val chooseBackup = rememberLauncherForActivityResult( - object : ActivityResultContracts.GetContent() { - override fun createIntent(context: Context, input: String): Intent { - val intent = super.createIntent(context, input) - return Intent.createChooser(intent, context.stringResource(MR.strings.file_select_backup)) - } - }, - ) { - if (it == null) { - context.toast(MR.strings.file_null_uri_error) - return@rememberLauncherForActivityResult - } - - val results = try { - BackupFileValidator().validate(context, it) - } catch (e: Exception) { - error = InvalidRestore(it, e.message.toString()) - return@rememberLauncherForActivityResult - } - - if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) { - BackupRestoreJob.start( - context = context, - uri = it, - // TODO: allow user-selectable restore options - options = RestoreOptions( - appSettings = true, - sourceSettings = true, - library = true, - ), - ) - return@rememberLauncherForActivityResult - } - - error = MissingRestoreComponents(it, results.missingSources, results.missingTrackers) - } - - return Preference.PreferenceItem.TextPreference( - title = stringResource(MR.strings.pref_restore_backup), - subtitle = stringResource(MR.strings.pref_restore_backup_summ), - onClick = { - if (!BackupRestoreJob.isRunning(context)) { - if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) { - context.toast(MR.strings.restore_miui_warning, Toast.LENGTH_LONG) - } - // no need to catch because it's wrapped with a chooser - chooseBackup.launch("*/*") - } else { - context.toast(MR.strings.restore_in_progress) - } - }, - ) - } - @Composable private fun getDataGroup(): Preference.PreferenceGroup { val scope = rememberCoroutineScope() @@ -394,14 +264,3 @@ object SettingsDataScreen : SearchableSettings { } } } - -private data class MissingRestoreComponents( - val uri: Uri, - val sources: List, - val trackers: List, -) - -private data class InvalidRestore( - val uri: Uri? = null, - val message: String, -) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/RestoreBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/RestoreBackupScreen.kt new file mode 100644 index 000000000..f1f3be877 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/RestoreBackupScreen.kt @@ -0,0 +1,242 @@ +package eu.kanade.presentation.more.settings.screen.data + +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.WarningBanner +import eu.kanade.presentation.util.Screen +import eu.kanade.tachiyomi.data.backup.BackupFileValidator +import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob +import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions +import eu.kanade.tachiyomi.util.system.DeviceUtil +import eu.kanade.tachiyomi.util.system.copyToClipboard +import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.flow.update +import tachiyomi.core.i18n.stringResource +import tachiyomi.i18n.MR +import tachiyomi.presentation.core.components.material.Scaffold +import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.i18n.stringResource + +class RestoreBackupScreen : Screen() { + + @Composable + override fun Content() { + val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + val model = rememberScreenModel { RestoreBackupScreenModel() } + val state by model.state.collectAsState() + + Scaffold( + topBar = { + AppBar( + title = stringResource(MR.strings.pref_restore_backup), + navigateUp = navigator::pop, + scrollBehavior = it, + ) + }, + ) { contentPadding -> + if (state.error != null) { + val onDismissRequest = model::clearError + when (val err = state.error) { + is InvalidRestore -> { + AlertDialog( + onDismissRequest = onDismissRequest, + title = { Text(text = stringResource(MR.strings.invalid_backup_file)) }, + text = { Text(text = listOfNotNull(err.uri, err.message).joinToString("\n\n")) }, + dismissButton = { + TextButton( + onClick = { + context.copyToClipboard(err.message, err.message) + onDismissRequest() + }, + ) { + Text(text = stringResource(MR.strings.action_copy_to_clipboard)) + } + }, + confirmButton = { + TextButton(onClick = onDismissRequest) { + Text(text = stringResource(MR.strings.action_ok)) + } + }, + ) + } + is MissingRestoreComponents -> { + AlertDialog( + onDismissRequest = onDismissRequest, + title = { Text(text = stringResource(MR.strings.pref_restore_backup)) }, + text = { + Column( + modifier = Modifier.verticalScroll(rememberScrollState()), + ) { + val msg = buildString { + append(stringResource(MR.strings.backup_restore_content_full)) + if (err.sources.isNotEmpty()) { + append( + "\n\n", + ).append(stringResource(MR.strings.backup_restore_missing_sources)) + err.sources.joinTo( + this, + separator = "\n- ", + prefix = "\n- ", + ) + } + if (err.trackers.isNotEmpty()) { + append( + "\n\n", + ).append(stringResource(MR.strings.backup_restore_missing_trackers)) + err.trackers.joinTo( + this, + separator = "\n- ", + prefix = "\n- ", + ) + } + } + Text(text = msg) + } + }, + confirmButton = { + TextButton( + onClick = { + BackupRestoreJob.start( + context = context, + uri = err.uri, + options = state.options, + ) + onDismissRequest() + }, + ) { + Text(text = stringResource(MR.strings.action_restore)) + } + }, + ) + } + else -> onDismissRequest() // Unknown + } + } + + val chooseBackup = rememberLauncherForActivityResult( + object : ActivityResultContracts.GetContent() { + override fun createIntent(context: Context, input: String): Intent { + val intent = super.createIntent(context, input) + return Intent.createChooser(intent, context.stringResource(MR.strings.file_select_backup)) + } + }, + ) { + if (it == null) { + context.toast(MR.strings.file_null_uri_error) + return@rememberLauncherForActivityResult + } + + val results = try { + BackupFileValidator().validate(context, it) + } catch (e: Exception) { + model.setError(InvalidRestore(it, e.message.toString())) + return@rememberLauncherForActivityResult + } + + if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) { + BackupRestoreJob.start( + context = context, + uri = it, + options = state.options, + ) + return@rememberLauncherForActivityResult + } + + model.setError(MissingRestoreComponents(it, results.missingSources, results.missingTrackers)) + } + + LazyColumn( + modifier = Modifier + .padding(contentPadding) + .fillMaxSize(), + ) { + if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) { + item { + WarningBanner(MR.strings.restore_miui_warning) + } + } + + item { + Button( + modifier = Modifier + .padding(horizontal = MaterialTheme.padding.medium) + .fillMaxWidth(), + onClick = { + if (!BackupRestoreJob.isRunning(context)) { + // no need to catch because it's wrapped with a chooser + chooseBackup.launch("*/*") + } else { + context.toast(MR.strings.restore_in_progress) + } + }, + ) { + Text(stringResource(MR.strings.pref_restore_backup)) + } + } + + // TODO: show validation errors inline + // TODO: show options for what to restore + } + } + } +} + +private class RestoreBackupScreenModel : StateScreenModel(State()) { + + fun setError(error: Any) { + mutableState.update { + it.copy(error = error) + } + } + + fun clearError() { + mutableState.update { + it.copy(error = null) + } + } + + @Immutable + data class State( + val error: Any? = null, + // TODO: allow user-selectable restore options + val options: RestoreOptions = RestoreOptions(), + ) +} + +private data class MissingRestoreComponents( + val uri: Uri, + val sources: List, + val trackers: List, +) + +private data class InvalidRestore( + val uri: Uri? = null, + val message: String, +) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt index 063f592de..30133fbbd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt @@ -172,9 +172,9 @@ class BackupRestorer( } data class RestoreOptions( - val appSettings: Boolean, - val sourceSettings: Boolean, - val library: Boolean, + val appSettings: Boolean = true, + val sourceSettings: Boolean = true, + val library: Boolean = true, ) { fun toBooleanArray() = booleanArrayOf(appSettings, sourceSettings, library) From dcf03794965cc29029e7783ee95cc56540c76ab5 Mon Sep 17 00:00:00 2001 From: arkon Date: Fri, 22 Dec 2023 09:22:30 -0500 Subject: [PATCH 06/30] Janky workaround for Moko escaped quotes issue Related: https://github.com/icerockdev/moko-resources/issues/337 --- core/src/main/java/tachiyomi/core/i18n/Localize.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/tachiyomi/core/i18n/Localize.kt b/core/src/main/java/tachiyomi/core/i18n/Localize.kt index 3c0c7e216..d6f8f3c16 100644 --- a/core/src/main/java/tachiyomi/core/i18n/Localize.kt +++ b/core/src/main/java/tachiyomi/core/i18n/Localize.kt @@ -10,17 +10,21 @@ import dev.icerock.moko.resources.desc.ResourceFormatted import dev.icerock.moko.resources.desc.StringDesc fun Context.stringResource(resource: StringResource): String { - return StringDesc.Resource(resource).toString(this) + return StringDesc.Resource(resource).toString(this).fixed() } fun Context.stringResource(resource: StringResource, vararg args: Any): String { - return StringDesc.ResourceFormatted(resource, *args).toString(this) + return StringDesc.ResourceFormatted(resource, *args).toString(this).fixed() } fun Context.pluralStringResource(resource: PluralsResource, count: Int): String { - return StringDesc.Plural(resource, count).toString(this) + return StringDesc.Plural(resource, count).toString(this).fixed() } fun Context.pluralStringResource(resource: PluralsResource, count: Int, vararg args: Any): String { - return StringDesc.PluralFormatted(resource, count, *args).toString(this) + return StringDesc.PluralFormatted(resource, count, *args).toString(this).fixed() } + +// TODO: janky workaround for https://github.com/icerockdev/moko-resources/issues/337 +private fun String.fixed() = + this.replace("""\""", """"""") From bf3899d04a50b77ad05e79b3317ac23ca2581183 Mon Sep 17 00:00:00 2001 From: arkon Date: Fri, 22 Dec 2023 09:23:00 -0500 Subject: [PATCH 07/30] Whoops, accidentally made the create backup button unusable before --- .../more/onboarding/GuidesStep.kt | 1 + .../screen/data/CreateBackupScreen.kt | 92 ++++++++++--------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/onboarding/GuidesStep.kt b/app/src/main/java/eu/kanade/presentation/more/onboarding/GuidesStep.kt index 77e0e7b88..d1ac967fa 100644 --- a/app/src/main/java/eu/kanade/presentation/more/onboarding/GuidesStep.kt +++ b/app/src/main/java/eu/kanade/presentation/more/onboarding/GuidesStep.kt @@ -20,6 +20,7 @@ import tachiyomi.presentation.core.i18n.stringResource internal class GuidesStep( private val onRestoreBackup: () -> Unit, ) : OnboardingStep { + override val isComplete: Boolean = true @Composable diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt index 7fd166802..8ec5dc362 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -76,61 +77,66 @@ class CreateBackupScreen : Screen() { ) }, ) { contentPadding -> - LazyColumn( + Column( modifier = Modifier .padding(contentPadding) .fillMaxSize(), ) { - if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) { - item { - WarningBanner(MR.strings.restore_miui_warning) + LazyColumn( + modifier = Modifier.weight(1f), + ) { + if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) { + item { + WarningBanner(MR.strings.restore_miui_warning) + } } - } - item { - LabeledCheckbox( - label = stringResource(MR.strings.manga), - checked = true, - onCheckedChange = {}, - enabled = false, - modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium), - ) - } - BackupChoices.forEach { (k, v) -> + item { LabeledCheckbox( - label = stringResource(v), - checked = state.flags.contains(k), - onCheckedChange = { - model.toggleFlag(k) - }, + label = stringResource(MR.strings.manga), + checked = true, + onCheckedChange = {}, + enabled = false, modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium), ) } - } - } - - HorizontalDivider() - - Button( - modifier = Modifier - .padding(horizontal = 16.dp, vertical = 8.dp) - .fillMaxWidth(), - onClick = { - if (!BackupCreateJob.isManualJobRunning(context)) { - try { - chooseBackupDir.launch(Backup.getFilename()) - } catch (e: ActivityNotFoundException) { - context.toast(MR.strings.file_picker_error) + BackupChoices.forEach { (k, v) -> + item { + LabeledCheckbox( + label = stringResource(v), + checked = state.flags.contains(k), + onCheckedChange = { + model.toggleFlag(k) + }, + modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium), + ) } - } else { - context.toast(MR.strings.backup_in_progress) } - }, - ) { - Text( - text = stringResource(MR.strings.action_create), - color = MaterialTheme.colorScheme.onPrimary, - ) + } + + HorizontalDivider() + + Button( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 8.dp) + .fillMaxWidth(), + onClick = { + if (!BackupCreateJob.isManualJobRunning(context)) { + try { + chooseBackupDir.launch(Backup.getFilename()) + } catch (e: ActivityNotFoundException) { + context.toast(MR.strings.file_picker_error) + } + } else { + context.toast(MR.strings.backup_in_progress) + } + }, + ) { + Text( + text = stringResource(MR.strings.action_create), + color = MaterialTheme.colorScheme.onPrimary, + ) + } } } } From 19f0175a56b00afd772d5d0c4909c49371a59bbe Mon Sep 17 00:00:00 2001 From: arkon Date: Fri, 22 Dec 2023 19:13:06 -0500 Subject: [PATCH 08/30] Don't use localized numbers for downloaded image filenames Probably fixes #10258 --- .../presentation/more/stats/StatsScreenContent.kt | 2 +- .../eu/kanade/tachiyomi/data/download/Downloader.kt | 11 ++++------- .../main/java/tachiyomi/core/util/system/ImageUtil.kt | 2 ++ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenContent.kt b/app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenContent.kt index 64f59280c..a24b4cb64 100644 --- a/app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenContent.kt +++ b/app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenContent.kt @@ -140,7 +140,7 @@ private fun TrackerStats( val meanScoreStr = remember(data.trackedTitleCount, data.meanScore) { if (data.trackedTitleCount > 0 && !data.meanScore.isNaN()) { // All other numbers are localized in English - String.format(Locale.ENGLISH, "%.2f ★", data.meanScore) + "%.2f ★".format(Locale.ENGLISH, data.meanScore) } else { notApplicable } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 30f11c4d1..8b54cfabd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -59,6 +59,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.BufferedOutputStream import java.io.File +import java.util.Locale import java.util.zip.CRC32 import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream @@ -422,7 +423,7 @@ class Downloader( } val digitCount = (download.pages?.size ?: 0).toString().length.coerceAtLeast(3) - val filename = String.format("%0${digitCount}d", page.number) + val filename = "%0${digitCount}d".format(Locale.ENGLISH, page.number) val tmpFile = tmpDir.findFile("$filename.tmp") // Delete temp file if it exists @@ -537,7 +538,7 @@ class Downloader( if (!downloadPreferences.splitTallImages().get()) return try { - val filenamePrefix = String.format("%03d", page.number) + val filenamePrefix = "%03d".format(Locale.ENGLISH, page.number) val imageFile = tmpDir.listFiles()?.firstOrNull { it.name.orEmpty().startsWith(filenamePrefix) } ?: error(context.stringResource(MR.strings.download_notifier_split_page_not_found, page.number)) @@ -579,11 +580,7 @@ class Downloader( else -> true } } - if (downloadedImagesCount != downloadPageCount) { - return false - } - - return true + return downloadedImagesCount == downloadPageCount } /** diff --git a/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt b/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt index 0aa7f9f59..68da52505 100644 --- a/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt +++ b/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt @@ -31,6 +31,7 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream import java.net.URLConnection +import java.util.Locale import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -274,6 +275,7 @@ object ImageUtil { } private fun splitImageName(filenamePrefix: String, index: Int) = "${filenamePrefix}__${"%03d".format( + Locale.ENGLISH, index + 1, )}.jpg" From 93cbeca5c0dbcf919bca84d7881888883e16b744 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Dec 2023 16:35:18 -0500 Subject: [PATCH 09/30] Highlight restore backup setting when navigating from onboarding step --- .../presentation/more/settings/screen/SettingsDataScreen.kt | 4 +++- .../java/eu/kanade/tachiyomi/ui/more/OnboardingScreen.kt | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index 8ffc0e076..25b5ca763 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -54,6 +54,8 @@ import uy.kohesive.injekt.api.get object SettingsDataScreen : SearchableSettings { + val restorePreferenceKeyString = MR.strings.label_backup + @ReadOnlyComposable @Composable override fun getTitleRes() = MR.strings.label_data_storage @@ -143,7 +145,7 @@ object SettingsDataScreen : SearchableSettings { preferenceItems = listOf( // Manual actions Preference.PreferenceItem.CustomPreference( - title = stringResource(MR.strings.label_backup), + title = stringResource(restorePreferenceKeyString), ) { BasePreferenceWidget( subcomponent = { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/OnboardingScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/OnboardingScreen.kt index bc211445e..a624c4730 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/OnboardingScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/OnboardingScreen.kt @@ -8,8 +8,11 @@ import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.domain.base.BasePreferences import eu.kanade.presentation.more.onboarding.OnboardingScreen +import eu.kanade.presentation.more.settings.screen.SearchableSettings +import eu.kanade.presentation.more.settings.screen.SettingsDataScreen import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.ui.setting.SettingsScreen +import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.collectAsState import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -28,6 +31,8 @@ class OnboardingScreen : Screen() { navigator.pop() } + val restoreSettingKey = stringResource(SettingsDataScreen.restorePreferenceKeyString) + BackHandler( enabled = !shownOnboardingFlow, onBack = { @@ -39,6 +44,7 @@ class OnboardingScreen : Screen() { onComplete = finishOnboarding, onRestoreBackup = { finishOnboarding() + SearchableSettings.highlightKey = restoreSettingKey navigator.push(SettingsScreen(SettingsScreen.Destination.DataAndStorage)) }, ) From 54ba1d719e0f03db34e0d0df6c136c638c083d4f Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Dec 2023 16:45:07 -0500 Subject: [PATCH 10/30] Don't include settings as defaults when manually creating backup --- .../more/settings/screen/data/CreateBackupScreen.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt index 8ec5dc362..cb758be40 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt @@ -36,8 +36,8 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.toast import kotlinx.collections.immutable.PersistentSet import kotlinx.collections.immutable.minus +import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.plus -import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.flow.update import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.LabeledCheckbox @@ -161,7 +161,12 @@ private class CreateBackupScreenModel : StateScreenModel = BackupChoices.keys.toPersistentSet(), + val flags: PersistentSet = persistentSetOf( + BackupCreateFlags.BACKUP_CATEGORY, + BackupCreateFlags.BACKUP_CHAPTER, + BackupCreateFlags.BACKUP_TRACK, + BackupCreateFlags.BACKUP_HISTORY, + ), ) } From 1a559124eb9e029a6146bf2660cbd508941c20d4 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Dec 2023 16:54:22 -0500 Subject: [PATCH 11/30] Split up BackupCreator into smaller classes --- .../data/backup/create/BackupCreator.kt | 179 +++--------------- .../creators/CategoriesBackupCreator.kt | 19 ++ .../create/creators/MangaBackupCreator.kt | 79 ++++++++ .../creators/PreferenceBackupCreator.kt | 59 ++++++ .../create/creators/SourcesBackupCreator.kt | 22 +++ .../data/backup/models/BackupSource.kt | 16 +- .../data/backup/restore/BackupRestorer.kt | 19 +- .../data/backup/restore/RestoreOptions.kt | 17 ++ .../{ => restorers}/CategoriesRestorer.kt | 2 +- .../restore/{ => restorers}/MangaRestorer.kt | 2 +- .../{ => restorers}/PreferenceRestorer.kt | 2 +- 11 files changed, 236 insertions(+), 180 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/RestoreOptions.kt rename app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/{ => restorers}/CategoriesRestorer.kt (95%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/{ => restorers}/MangaRestorer.kt (99%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/{ => restorers}/PreferenceRestorer.kt (98%) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt index 66dd19bbd..4dc2585cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt @@ -6,47 +6,27 @@ import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.data.backup.BackupFileValidator import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_APP_PREFS import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_CATEGORY -import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_CHAPTER -import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_HISTORY import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_SOURCE_PREFS -import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_TRACK +import eu.kanade.tachiyomi.data.backup.create.creators.CategoriesBackupCreator +import eu.kanade.tachiyomi.data.backup.create.creators.MangaBackupCreator +import eu.kanade.tachiyomi.data.backup.create.creators.PreferenceBackupCreator +import eu.kanade.tachiyomi.data.backup.create.creators.SourcesBackupCreator import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.BackupCategory -import eu.kanade.tachiyomi.data.backup.models.BackupChapter -import eu.kanade.tachiyomi.data.backup.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupSerializer import eu.kanade.tachiyomi.data.backup.models.BackupSource import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences -import eu.kanade.tachiyomi.data.backup.models.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.backup.models.backupCategoryMapper -import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper -import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.preferenceKey -import eu.kanade.tachiyomi.source.sourcePreferences import kotlinx.serialization.protobuf.ProtoBuf import logcat.LogPriority import okio.buffer import okio.gzip import okio.sink import tachiyomi.core.i18n.stringResource -import tachiyomi.core.preference.Preference -import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.util.system.logcat -import tachiyomi.data.DatabaseHandler -import tachiyomi.domain.category.interactor.GetCategories -import tachiyomi.domain.category.model.Category -import tachiyomi.domain.history.interactor.GetHistory import tachiyomi.domain.manga.interactor.GetFavorites import tachiyomi.domain.manga.model.Manga -import tachiyomi.domain.source.service.SourceManager import tachiyomi.i18n.MR import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -54,15 +34,13 @@ import java.io.FileOutputStream class BackupCreator( private val context: Context, + private val categoriesBackupCreator: CategoriesBackupCreator = CategoriesBackupCreator(), + private val mangaBackupCreator: MangaBackupCreator = MangaBackupCreator(), + private val preferenceBackupCreator: PreferenceBackupCreator = PreferenceBackupCreator(), + private val sourcesBackupCreator: SourcesBackupCreator = SourcesBackupCreator(), + private val getFavorites: GetFavorites = Injekt.get(), ) { - private val handler: DatabaseHandler = Injekt.get() - private val sourceManager: SourceManager = Injekt.get() - private val getCategories: GetCategories = Injekt.get() - private val getFavorites: GetFavorites = Injekt.get() - private val getHistory: GetHistory = Injekt.get() - private val preferenceStore: PreferenceStore = Injekt.get() - internal val parser = ProtoBuf /** @@ -72,16 +50,6 @@ class BackupCreator( * @param isAutoBackup backup called from scheduled backup job */ suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String { - val databaseManga = getFavorites.await() - val backup = Backup( - backupMangas(databaseManga, flags), - backupCategories(flags), - emptyList(), - prepExtensionInfoForSync(databaseManga), - backupAppPreferences(flags), - backupSourcePreferences(flags), - ) - var file: UniFile? = null try { file = ( @@ -108,6 +76,15 @@ class BackupCreator( throw IllegalStateException("Failed to get handle on a backup file") } + val databaseManga = getFavorites.await() + val backup = Backup( + backupManga = backupMangas(databaseManga, flags), + backupCategories = backupCategories(flags), + backupSources = backupSources(databaseManga), + backupPreferences = backupAppPreferences(flags), + backupSourcePreferences = backupSourcePreferences(flags), + ) + val byteArray = parser.encodeToByteArray(BackupSerializer, backup) if (byteArray.isEmpty()) { throw IllegalStateException(context.stringResource(MR.strings.empty_backup_error)) @@ -130,134 +107,30 @@ class BackupCreator( } } - private fun prepExtensionInfoForSync(mangas: List): List { - return mangas - .asSequence() - .map(Manga::source) - .distinct() - .map(sourceManager::getOrStub) - .map(BackupSource::copyFrom) - .toList() - } - - /** - * Backup the categories of library - * - * @return list of [BackupCategory] to be backed up - */ private suspend fun backupCategories(options: Int): List { - // Check if user wants category information in backup - return if (options and BACKUP_CATEGORY == BACKUP_CATEGORY) { - getCategories.await() - .filterNot(Category::isSystemCategory) - .map(backupCategoryMapper) - } else { - emptyList() - } + if (options and BACKUP_CATEGORY != BACKUP_CATEGORY) return emptyList() + + return categoriesBackupCreator.backupCategories() } private suspend fun backupMangas(mangas: List, flags: Int): List { - return mangas.map { - backupManga(it, flags) - } + return mangaBackupCreator.backupMangas(mangas, flags) } - /** - * Convert a manga to Json - * - * @param manga manga that gets converted - * @param options options for the backup - * @return [BackupManga] containing manga in a serializable form - */ - private suspend fun backupManga(manga: Manga, options: Int): BackupManga { - // Entry for this manga - val mangaObject = BackupManga.copyFrom(manga) - - // Check if user wants chapter information in backup - if (options and BACKUP_CHAPTER == BACKUP_CHAPTER) { - // Backup all the chapters - handler.awaitList { - chaptersQueries.getChaptersByMangaId( - mangaId = manga.id, - applyScanlatorFilter = 0, // false - mapper = backupChapterMapper, - ) - } - .takeUnless(List::isEmpty) - ?.let { mangaObject.chapters = it } - } - - // Check if user wants category information in backup - if (options and BACKUP_CATEGORY == BACKUP_CATEGORY) { - // Backup categories for this manga - val categoriesForManga = getCategories.await(manga.id) - if (categoriesForManga.isNotEmpty()) { - mangaObject.categories = categoriesForManga.map { it.order } - } - } - - // Check if user wants track information in backup - if (options and BACKUP_TRACK == BACKUP_TRACK) { - val tracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id, backupTrackMapper) } - if (tracks.isNotEmpty()) { - mangaObject.tracking = tracks - } - } - - // Check if user wants history information in backup - if (options and BACKUP_HISTORY == BACKUP_HISTORY) { - val historyByMangaId = getHistory.await(manga.id) - if (historyByMangaId.isNotEmpty()) { - val history = historyByMangaId.map { history -> - val chapter = handler.awaitOne { chaptersQueries.getChapterById(history.chapterId) } - BackupHistory(chapter.url, history.readAt?.time ?: 0L, history.readDuration) - } - if (history.isNotEmpty()) { - mangaObject.history = history - } - } - } - - return mangaObject + private fun backupSources(mangas: List): List { + return sourcesBackupCreator.backupSources(mangas) } private fun backupAppPreferences(flags: Int): List { if (flags and BACKUP_APP_PREFS != BACKUP_APP_PREFS) return emptyList() - return preferenceStore.getAll().toBackupPreferences() + return preferenceBackupCreator.backupAppPreferences() } private fun backupSourcePreferences(flags: Int): List { if (flags and BACKUP_SOURCE_PREFS != BACKUP_SOURCE_PREFS) return emptyList() - return sourceManager.getCatalogueSources() - .filterIsInstance() - .map { - BackupSourcePreferences( - it.preferenceKey(), - it.sourcePreferences().all.toBackupPreferences(), - ) - } - } - - @Suppress("UNCHECKED_CAST") - private fun Map.toBackupPreferences(): List { - return this.filterKeys { - !Preference.isPrivate(it) && !Preference.isAppState(it) - } - .mapNotNull { (key, value) -> - when (value) { - is Int -> BackupPreference(key, IntPreferenceValue(value)) - is Long -> BackupPreference(key, LongPreferenceValue(value)) - is Float -> BackupPreference(key, FloatPreferenceValue(value)) - is String -> BackupPreference(key, StringPreferenceValue(value)) - is Boolean -> BackupPreference(key, BooleanPreferenceValue(value)) - is Set<*> -> (value as? Set)?.let { - BackupPreference(key, StringSetPreferenceValue(it)) - } - else -> null - } - } + return preferenceBackupCreator.backupSourcePreferences() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt new file mode 100644 index 000000000..e1ed56ee1 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.data.backup.create.creators + +import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.backup.models.backupCategoryMapper +import tachiyomi.domain.category.interactor.GetCategories +import tachiyomi.domain.category.model.Category +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class CategoriesBackupCreator( + private val getCategories: GetCategories = Injekt.get(), +) { + + suspend fun backupCategories(): List { + return getCategories.await() + .filterNot(Category::isSystemCategory) + .map(backupCategoryMapper) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt new file mode 100644 index 000000000..7a176dec2 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt @@ -0,0 +1,79 @@ +package eu.kanade.tachiyomi.data.backup.create.creators + +import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags +import eu.kanade.tachiyomi.data.backup.models.BackupChapter +import eu.kanade.tachiyomi.data.backup.models.BackupHistory +import eu.kanade.tachiyomi.data.backup.models.BackupManga +import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper +import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper +import tachiyomi.data.DatabaseHandler +import tachiyomi.domain.category.interactor.GetCategories +import tachiyomi.domain.history.interactor.GetHistory +import tachiyomi.domain.manga.model.Manga +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class MangaBackupCreator( + private val handler: DatabaseHandler = Injekt.get(), + private val getCategories: GetCategories = Injekt.get(), + private val getHistory: GetHistory = Injekt.get(), +) { + + suspend fun backupMangas(mangas: List, flags: Int): List { + return mangas.map { + backupManga(it, flags) + } + } + + private suspend fun backupManga(manga: Manga, options: Int): BackupManga { + // Entry for this manga + val mangaObject = BackupManga.copyFrom(manga) + + // Check if user wants chapter information in backup + if (options and BackupCreateFlags.BACKUP_CHAPTER == BackupCreateFlags.BACKUP_CHAPTER) { + // Backup all the chapters + handler.awaitList { + chaptersQueries.getChaptersByMangaId( + mangaId = manga.id, + applyScanlatorFilter = 0, // false + mapper = backupChapterMapper, + ) + } + .takeUnless(List::isEmpty) + ?.let { mangaObject.chapters = it } + } + + // Check if user wants category information in backup + if (options and BackupCreateFlags.BACKUP_CATEGORY == BackupCreateFlags.BACKUP_CATEGORY) { + // Backup categories for this manga + val categoriesForManga = getCategories.await(manga.id) + if (categoriesForManga.isNotEmpty()) { + mangaObject.categories = categoriesForManga.map { it.order } + } + } + + // Check if user wants track information in backup + if (options and BackupCreateFlags.BACKUP_TRACK == BackupCreateFlags.BACKUP_TRACK) { + val tracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id, backupTrackMapper) } + if (tracks.isNotEmpty()) { + mangaObject.tracking = tracks + } + } + + // Check if user wants history information in backup + if (options and BackupCreateFlags.BACKUP_HISTORY == BackupCreateFlags.BACKUP_HISTORY) { + val historyByMangaId = getHistory.await(manga.id) + if (historyByMangaId.isNotEmpty()) { + val history = historyByMangaId.map { history -> + val chapter = handler.awaitOne { chaptersQueries.getChapterById(history.chapterId) } + BackupHistory(chapter.url, history.readAt?.time ?: 0L, history.readDuration) + } + if (history.isNotEmpty()) { + mangaObject.history = history + } + } + } + + return mangaObject + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt new file mode 100644 index 000000000..c75612de9 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt @@ -0,0 +1,59 @@ +package eu.kanade.tachiyomi.data.backup.create.creators + +import eu.kanade.tachiyomi.data.backup.models.BackupPreference +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.source.ConfigurableSource +import eu.kanade.tachiyomi.source.preferenceKey +import eu.kanade.tachiyomi.source.sourcePreferences +import tachiyomi.core.preference.Preference +import tachiyomi.core.preference.PreferenceStore +import tachiyomi.domain.source.service.SourceManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class PreferenceBackupCreator( + private val sourceManager: SourceManager = Injekt.get(), + private val preferenceStore: PreferenceStore = Injekt.get(), +) { + + fun backupAppPreferences(): List { + return preferenceStore.getAll().toBackupPreferences() + } + + fun backupSourcePreferences(): List { + return sourceManager.getCatalogueSources() + .filterIsInstance() + .map { + BackupSourcePreferences( + it.preferenceKey(), + it.sourcePreferences().all.toBackupPreferences(), + ) + } + } + + @Suppress("UNCHECKED_CAST") + private fun Map.toBackupPreferences(): List { + return this.filterKeys { + !Preference.isPrivate(it) && !Preference.isAppState(it) + } + .mapNotNull { (key, value) -> + when (value) { + is Int -> BackupPreference(key, IntPreferenceValue(value)) + is Long -> BackupPreference(key, LongPreferenceValue(value)) + is Float -> BackupPreference(key, FloatPreferenceValue(value)) + is String -> BackupPreference(key, StringPreferenceValue(value)) + is Boolean -> BackupPreference(key, BooleanPreferenceValue(value)) + is Set<*> -> (value as? Set)?.let { + BackupPreference(key, StringSetPreferenceValue(it)) + } + else -> null + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt new file mode 100644 index 000000000..8b964029c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.data.backup.create.creators + +import eu.kanade.tachiyomi.data.backup.models.BackupSource +import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.source.service.SourceManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class SourcesBackupCreator( + private val sourceManager: SourceManager = Injekt.get(), +) { + + fun backupSources(mangas: List): List { + return mangas + .asSequence() + .map(Manga::source) + .distinct() + .map(sourceManager::getOrStub) + .map(BackupSource::copyFrom) + .toList() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt index 34e4cac31..cb75805ae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt @@ -4,14 +4,6 @@ import eu.kanade.tachiyomi.source.Source import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber -@Serializable -data class BrokenBackupSource( - @ProtoNumber(0) var name: String = "", - @ProtoNumber(1) var sourceId: Long, -) { - fun toBackupSource() = BackupSource(name, sourceId) -} - @Serializable data class BackupSource( @ProtoNumber(1) var name: String = "", @@ -26,3 +18,11 @@ data class BackupSource( } } } + +@Serializable +data class BrokenBackupSource( + @ProtoNumber(0) var name: String = "", + @ProtoNumber(1) var sourceId: Long, +) { + fun toBackupSource() = BackupSource(name, sourceId) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt index 30133fbbd..b99421baa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt @@ -7,6 +7,9 @@ import eu.kanade.tachiyomi.data.backup.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences +import eu.kanade.tachiyomi.data.backup.restore.restorers.CategoriesRestorer +import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaRestorer +import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceRestorer import eu.kanade.tachiyomi.util.BackupUtil import eu.kanade.tachiyomi.util.system.createFileInCacheDir import kotlinx.coroutines.CoroutineScope @@ -170,19 +173,3 @@ class BackupRestorer( return File("") } } - -data class RestoreOptions( - val appSettings: Boolean = true, - val sourceSettings: Boolean = true, - val library: Boolean = true, -) { - fun toBooleanArray() = booleanArrayOf(appSettings, sourceSettings, library) - - companion object { - fun fromBooleanArray(booleanArray: BooleanArray) = RestoreOptions( - appSettings = booleanArray[0], - sourceSettings = booleanArray[1], - library = booleanArray[2], - ) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/RestoreOptions.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/RestoreOptions.kt new file mode 100644 index 000000000..d9e124a81 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/RestoreOptions.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.data.backup.restore + +data class RestoreOptions( + val appSettings: Boolean = true, + val sourceSettings: Boolean = true, + val library: Boolean = true, +) { + fun toBooleanArray() = booleanArrayOf(appSettings, sourceSettings, library) + + companion object { + fun fromBooleanArray(booleanArray: BooleanArray) = RestoreOptions( + appSettings = booleanArray[0], + sourceSettings = booleanArray[1], + library = booleanArray[2], + ) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/CategoriesRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/CategoriesRestorer.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt index 5557bb59f..f98af1045 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/CategoriesRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.restore +package eu.kanade.tachiyomi.data.backup.restore.restorers import eu.kanade.tachiyomi.data.backup.models.BackupCategory import tachiyomi.data.DatabaseHandler diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/MangaRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt similarity index 99% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/MangaRestorer.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt index a07f846f2..aba2f1722 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/MangaRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.restore +package eu.kanade.tachiyomi.data.backup.restore.restorers import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.tachiyomi.data.backup.models.BackupCategory diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/PreferenceRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/PreferenceRestorer.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt index 69622d60b..1062937d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/PreferenceRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.restore +package eu.kanade.tachiyomi.data.backup.restore.restorers import android.content.Context import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob From 5908bd19305b3461165fcfe2da5d2217115562b9 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Dec 2023 17:38:01 -0500 Subject: [PATCH 12/30] Move backup models to domain module --- .../screen/data/CreateBackupScreen.kt | 4 +- .../screen/data/RestoreBackupScreen.kt | 2 +- .../screen/debug/BackupSchemaScreen.kt | 2 +- .../backup/BackupDecoder.kt} | 22 +++--- .../data/backup/BackupFileValidator.kt | 7 +- .../data/backup/create/BackupCreateJob.kt | 8 +-- .../data/backup/create/BackupCreator.kt | 71 ++++++++++++------- .../creators/CategoriesBackupCreator.kt | 4 +- .../create/creators/MangaBackupCreator.kt | 34 +++++++-- .../creators/PreferenceBackupCreator.kt | 16 ++--- .../create/creators/SourcesBackupCreator.kt | 11 ++- .../data/backup/models/BackupSerializer.kt | 6 -- .../data/backup/restore/BackupRestorer.kt | 12 ++-- .../restore/restorers/CategoriesRestorer.kt | 2 +- .../backup/restore/restorers/MangaRestorer.kt | 10 +-- .../restore/restorers/PreferenceRestorer.kt | 16 ++--- .../java/eu/kanade/tachiyomi/di/AppModule.kt | 4 ++ domain/build.gradle.kts | 2 + .../tachiyomi/domain/backup/model}/Backup.kt | 22 ++---- .../domain/backup/model}/BackupCategory.kt | 2 +- .../domain/backup/model}/BackupChapter.kt | 2 +- .../domain/backup/model}/BackupHistory.kt | 2 +- .../domain/backup/model}/BackupManga.kt | 27 +------ .../domain/backup/model}/BackupPreference.kt | 2 +- .../domain/backup/model}/BackupSource.kt | 14 +--- .../domain/backup/model}/BackupTracking.kt | 2 +- 26 files changed, 155 insertions(+), 151 deletions(-) rename app/src/main/java/eu/kanade/tachiyomi/{util/BackupUtil.kt => data/backup/BackupDecoder.kt} (56%) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSerializer.kt rename {app/src/main/java/eu/kanade/tachiyomi/data/backup/models => domain/src/main/java/tachiyomi/domain/backup/model}/Backup.kt (54%) rename {app/src/main/java/eu/kanade/tachiyomi/data/backup/models => domain/src/main/java/tachiyomi/domain/backup/model}/BackupCategory.kt (94%) rename {app/src/main/java/eu/kanade/tachiyomi/data/backup/models => domain/src/main/java/tachiyomi/domain/backup/model}/BackupChapter.kt (97%) rename {app/src/main/java/eu/kanade/tachiyomi/data/backup/models => domain/src/main/java/tachiyomi/domain/backup/model}/BackupHistory.kt (94%) rename {app/src/main/java/eu/kanade/tachiyomi/data/backup/models => domain/src/main/java/tachiyomi/domain/backup/model}/BackupManga.kt (73%) rename {app/src/main/java/eu/kanade/tachiyomi/data/backup/models => domain/src/main/java/tachiyomi/domain/backup/model}/BackupPreference.kt (95%) rename {app/src/main/java/eu/kanade/tachiyomi/data/backup/models => domain/src/main/java/tachiyomi/domain/backup/model}/BackupSource.kt (57%) rename {app/src/main/java/eu/kanade/tachiyomi/data/backup/models => domain/src/main/java/tachiyomi/domain/backup/model}/BackupTracking.kt (98%) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt index cb758be40..1d1dfd1b5 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt @@ -31,7 +31,7 @@ import eu.kanade.presentation.components.WarningBanner import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob -import eu.kanade.tachiyomi.data.backup.models.Backup +import eu.kanade.tachiyomi.data.backup.create.BackupCreator import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.toast import kotlinx.collections.immutable.PersistentSet @@ -123,7 +123,7 @@ class CreateBackupScreen : Screen() { onClick = { if (!BackupCreateJob.isManualJobRunning(context)) { try { - chooseBackupDir.launch(Backup.getFilename()) + chooseBackupDir.launch(BackupCreator.getFilename()) } catch (e: ActivityNotFoundException) { context.toast(MR.strings.file_picker_error) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/RestoreBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/RestoreBackupScreen.kt index f1f3be877..2d6aebced 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/RestoreBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/RestoreBackupScreen.kt @@ -154,7 +154,7 @@ class RestoreBackupScreen : Screen() { } val results = try { - BackupFileValidator().validate(context, it) + BackupFileValidator(context).validate(it) } catch (e: Exception) { model.setError(InvalidRestore(it, e.message.toString())) return@rememberLauncherForActivityResult diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/BackupSchemaScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/BackupSchemaScreen.kt index d5652b16a..7d42ebb42 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/BackupSchemaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/BackupSchemaScreen.kt @@ -17,10 +17,10 @@ import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.util.Screen -import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.util.system.copyToClipboard import kotlinx.collections.immutable.persistentListOf import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator +import tachiyomi.domain.backup.model.Backup import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/BackupUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt similarity index 56% rename from app/src/main/java/eu/kanade/tachiyomi/util/BackupUtil.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt index 05401603f..e7778aad5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/BackupUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt @@ -1,21 +1,25 @@ -package eu.kanade.tachiyomi.util +package eu.kanade.tachiyomi.data.backup import android.content.Context import android.net.Uri -import eu.kanade.tachiyomi.data.backup.create.BackupCreator -import eu.kanade.tachiyomi.data.backup.models.Backup -import eu.kanade.tachiyomi.data.backup.models.BackupSerializer +import kotlinx.serialization.protobuf.ProtoBuf import okio.buffer import okio.gzip import okio.source +import tachiyomi.domain.backup.model.Backup +import tachiyomi.domain.backup.model.BackupSerializer +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class BackupDecoder( + private val context: Context, + private val parser: ProtoBuf = Injekt.get(), +) { -object BackupUtil { /** * Decode a potentially-gzipped backup. */ - fun decodeBackup(context: Context, uri: Uri): Backup { - val backupCreator = BackupCreator(context) - + fun decode(uri: Uri): Backup { val backupStringSource = context.contentResolver.openInputStream(uri)!!.source().buffer() val peeked = backupStringSource.peek() @@ -27,6 +31,6 @@ object BackupUtil { backupStringSource }.use { it.readByteArray() } - return backupCreator.parser.decodeFromByteArray(BackupSerializer, backupString) + return parser.decodeFromByteArray(BackupSerializer, backupString) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt index fdf3d68d9..acc768e5a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.backup import android.content.Context import android.net.Uri import eu.kanade.tachiyomi.data.track.TrackerManager -import eu.kanade.tachiyomi.util.BackupUtil import tachiyomi.core.i18n.stringResource import tachiyomi.domain.source.service.SourceManager import tachiyomi.i18n.MR @@ -11,6 +10,8 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class BackupFileValidator( + private val context: Context, + private val sourceManager: SourceManager = Injekt.get(), private val trackerManager: TrackerManager = Injekt.get(), ) { @@ -21,9 +22,9 @@ class BackupFileValidator( * @throws Exception if manga cannot be found. * @return List of missing sources or missing trackers. */ - fun validate(context: Context, uri: Uri): Results { + fun validate(uri: Uri): Results { val backup = try { - BackupUtil.decodeBackup(context, uri) + BackupDecoder(context).decode(uri) } catch (e: Exception) { throw IllegalStateException(e) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreateJob.kt index 4ca6b056b..c2f1e5ded 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreateJob.kt @@ -29,7 +29,6 @@ import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.storage.service.StorageManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.time.Instant import java.util.concurrent.TimeUnit class BackupCreateJob(private val context: Context, workerParams: WorkerParameters) : @@ -49,13 +48,10 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete setForegroundSafely() val flags = inputData.getInt(BACKUP_FLAGS_KEY, BackupCreateFlags.AutomaticDefaults) - val backupPreferences = Injekt.get() return try { - val location = BackupCreator(context).createBackup(uri, flags, isAutoBackup) - if (isAutoBackup) { - backupPreferences.lastAutoBackupTimestamp().set(Instant.now().toEpochMilli()) - } else { + val location = BackupCreator(context, isAutoBackup).backup(uri, flags) + if (!isAutoBackup) { notifier.showBackupComplete(UniFile.fromUri(context, location.toUri())!!) } Result.success() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt index 4dc2585cb..ef2a5328d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.backup.create import android.content.Context import android.net.Uri import com.hippo.unifile.UniFile +import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.data.backup.BackupFileValidator import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_APP_PREFS import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_CATEGORY @@ -11,13 +12,6 @@ import eu.kanade.tachiyomi.data.backup.create.creators.CategoriesBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.MangaBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.PreferenceBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.SourcesBackupCreator -import eu.kanade.tachiyomi.data.backup.models.Backup -import eu.kanade.tachiyomi.data.backup.models.BackupCategory -import eu.kanade.tachiyomi.data.backup.models.BackupManga -import eu.kanade.tachiyomi.data.backup.models.BackupPreference -import eu.kanade.tachiyomi.data.backup.models.BackupSerializer -import eu.kanade.tachiyomi.data.backup.models.BackupSource -import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import kotlinx.serialization.protobuf.ProtoBuf import logcat.LogPriority import okio.buffer @@ -25,31 +19,40 @@ import okio.gzip import okio.sink import tachiyomi.core.i18n.stringResource import tachiyomi.core.util.system.logcat +import tachiyomi.domain.backup.model.Backup +import tachiyomi.domain.backup.model.BackupCategory +import tachiyomi.domain.backup.model.BackupManga +import tachiyomi.domain.backup.model.BackupPreference +import tachiyomi.domain.backup.model.BackupSerializer +import tachiyomi.domain.backup.model.BackupSource +import tachiyomi.domain.backup.model.BackupSourcePreferences +import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.manga.interactor.GetFavorites import tachiyomi.domain.manga.model.Manga import tachiyomi.i18n.MR import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.FileOutputStream +import java.text.SimpleDateFormat +import java.time.Instant +import java.util.Date +import java.util.Locale class BackupCreator( private val context: Context, + private val isAutoBackup: Boolean, + + private val parser: ProtoBuf = Injekt.get(), + private val getFavorites: GetFavorites = Injekt.get(), + private val backupPreferences: BackupPreferences = Injekt.get(), + private val categoriesBackupCreator: CategoriesBackupCreator = CategoriesBackupCreator(), private val mangaBackupCreator: MangaBackupCreator = MangaBackupCreator(), private val preferenceBackupCreator: PreferenceBackupCreator = PreferenceBackupCreator(), private val sourcesBackupCreator: SourcesBackupCreator = SourcesBackupCreator(), - private val getFavorites: GetFavorites = Injekt.get(), ) { - internal val parser = ProtoBuf - - /** - * Create backup file. - * - * @param uri path of Uri - * @param isAutoBackup backup called from scheduled backup job - */ - suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String { + suspend fun backup(uri: Uri, flags: Int): String { var file: UniFile? = null try { file = ( @@ -58,14 +61,14 @@ class BackupCreator( val dir = UniFile.fromUri(context, uri) // Delete older backups - dir?.listFiles { _, filename -> Backup.filenameRegex.matches(filename) } + dir?.listFiles { _, filename -> FILENAME_REGEX.matches(filename) } .orEmpty() .sortedByDescending { it.name } .drop(MAX_AUTO_BACKUPS - 1) .forEach { it.delete() } // Create new file to place backup - dir?.createFile(Backup.getFilename()) + dir?.createFile(BackupCreator.getFilename()) } else { UniFile.fromUri(context, uri) } @@ -90,14 +93,22 @@ class BackupCreator( throw IllegalStateException(context.stringResource(MR.strings.empty_backup_error)) } - file.openOutputStream().also { - // Force overwrite old file - (it as? FileOutputStream)?.channel?.truncate(0) - }.sink().gzip().buffer().use { it.write(byteArray) } + file.openOutputStream() + .also { + // Force overwrite old file + (it as? FileOutputStream)?.channel?.truncate(0) + } + .sink().gzip().buffer().use { + it.write(byteArray) + } val fileUri = file.uri // Make sure it's a valid backup file - BackupFileValidator().validate(context, fileUri) + BackupFileValidator(context).validate(fileUri) + + if (isAutoBackup) { + backupPreferences.lastAutoBackupTimestamp().set(Instant.now().toEpochMilli()) + } return fileUri.toString() } catch (e: Exception) { @@ -132,6 +143,14 @@ class BackupCreator( return preferenceBackupCreator.backupSourcePreferences() } -} -private val MAX_AUTO_BACKUPS: Int = 4 + companion object { + private const val MAX_AUTO_BACKUPS: Int = 4 + private val FILENAME_REGEX = """${BuildConfig.APPLICATION_ID}_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}.tachibk""".toRegex() + + fun getFilename(): String { + val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.ENGLISH).format(Date()) + return "${BuildConfig.APPLICATION_ID}_$date.tachibk" + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt index e1ed56ee1..164957768 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.backup.create.creators -import eu.kanade.tachiyomi.data.backup.models.BackupCategory -import eu.kanade.tachiyomi.data.backup.models.backupCategoryMapper +import tachiyomi.domain.backup.model.BackupCategory +import tachiyomi.domain.backup.model.backupCategoryMapper import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.model.Category import uy.kohesive.injekt.Injekt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt index 7a176dec2..94a550e31 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt @@ -1,12 +1,13 @@ package eu.kanade.tachiyomi.data.backup.create.creators import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags -import eu.kanade.tachiyomi.data.backup.models.BackupChapter -import eu.kanade.tachiyomi.data.backup.models.BackupHistory -import eu.kanade.tachiyomi.data.backup.models.BackupManga -import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper -import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper +import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import tachiyomi.data.DatabaseHandler +import tachiyomi.domain.backup.model.BackupChapter +import tachiyomi.domain.backup.model.BackupHistory +import tachiyomi.domain.backup.model.BackupManga +import tachiyomi.domain.backup.model.backupChapterMapper +import tachiyomi.domain.backup.model.backupTrackMapper import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.history.interactor.GetHistory import tachiyomi.domain.manga.model.Manga @@ -27,7 +28,7 @@ class MangaBackupCreator( private suspend fun backupManga(manga: Manga, options: Int): BackupManga { // Entry for this manga - val mangaObject = BackupManga.copyFrom(manga) + val mangaObject = manga.toBackupManga() // Check if user wants chapter information in backup if (options and BackupCreateFlags.BACKUP_CHAPTER == BackupCreateFlags.BACKUP_CHAPTER) { @@ -77,3 +78,24 @@ class MangaBackupCreator( return mangaObject } } + +private fun Manga.toBackupManga() = + BackupManga( + url = this.url, + title = this.title, + artist = this.artist, + author = this.author, + description = this.description, + genre = this.genre.orEmpty(), + status = this.status.toInt(), + thumbnailUrl = this.thumbnailUrl, + favorite = this.favorite, + source = this.source, + dateAdded = this.dateAdded, + viewer = (this.viewerFlags.toInt() and ReadingMode.MASK), + viewer_flags = this.viewerFlags.toInt(), + chapterFlags = this.chapterFlags.toInt(), + updateStrategy = this.updateStrategy, + lastModifiedAt = this.lastModifiedAt, + favoriteModifiedAt = this.favoriteModifiedAt, + ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt index c75612de9..74e36da19 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt @@ -1,18 +1,18 @@ package eu.kanade.tachiyomi.data.backup.create.creators -import eu.kanade.tachiyomi.data.backup.models.BackupPreference -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.source.ConfigurableSource import eu.kanade.tachiyomi.source.preferenceKey import eu.kanade.tachiyomi.source.sourcePreferences import tachiyomi.core.preference.Preference import tachiyomi.core.preference.PreferenceStore +import tachiyomi.domain.backup.model.BackupPreference +import tachiyomi.domain.backup.model.BackupSourcePreferences +import tachiyomi.domain.backup.model.BooleanPreferenceValue +import tachiyomi.domain.backup.model.FloatPreferenceValue +import tachiyomi.domain.backup.model.IntPreferenceValue +import tachiyomi.domain.backup.model.LongPreferenceValue +import tachiyomi.domain.backup.model.StringPreferenceValue +import tachiyomi.domain.backup.model.StringSetPreferenceValue import tachiyomi.domain.source.service.SourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt index 8b964029c..bf1c7ba36 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.data.backup.create.creators -import eu.kanade.tachiyomi.data.backup.models.BackupSource +import eu.kanade.tachiyomi.source.Source +import tachiyomi.domain.backup.model.BackupSource import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.source.service.SourceManager import uy.kohesive.injekt.Injekt @@ -16,7 +17,13 @@ class SourcesBackupCreator( .map(Manga::source) .distinct() .map(sourceManager::getOrStub) - .map(BackupSource::copyFrom) + .map { it.toBackupSource() } .toList() } } + +private fun Source.toBackupSource() = + BackupSource( + name = this.name, + sourceId = this.id, + ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSerializer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSerializer.kt deleted file mode 100644 index 2e79ebecd..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSerializer.kt +++ /dev/null @@ -1,6 +0,0 @@ -package eu.kanade.tachiyomi.data.backup.models - -import kotlinx.serialization.Serializer - -@Serializer(forClass = Backup::class) -object BackupSerializer diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt index b99421baa..5d9fd8f99 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt @@ -2,21 +2,21 @@ package eu.kanade.tachiyomi.data.backup.restore import android.content.Context import android.net.Uri +import eu.kanade.tachiyomi.data.backup.BackupDecoder import eu.kanade.tachiyomi.data.backup.BackupNotifier -import eu.kanade.tachiyomi.data.backup.models.BackupCategory -import eu.kanade.tachiyomi.data.backup.models.BackupManga -import eu.kanade.tachiyomi.data.backup.models.BackupPreference -import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import eu.kanade.tachiyomi.data.backup.restore.restorers.CategoriesRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceRestorer -import eu.kanade.tachiyomi.util.BackupUtil import eu.kanade.tachiyomi.util.system.createFileInCacheDir import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch import tachiyomi.core.i18n.stringResource +import tachiyomi.domain.backup.model.BackupCategory +import tachiyomi.domain.backup.model.BackupManga +import tachiyomi.domain.backup.model.BackupPreference +import tachiyomi.domain.backup.model.BackupSourcePreferences import tachiyomi.i18n.MR import java.io.File import java.text.SimpleDateFormat @@ -61,7 +61,7 @@ class BackupRestorer( } private suspend fun restoreFromFile(uri: Uri, options: RestoreOptions) { - val backup = BackupUtil.decodeBackup(context, uri) + val backup = BackupDecoder(context).decode(uri) // Store source mapping for error messages val backupMaps = backup.backupSources + backup.backupBrokenSources.map { it.toBackupSource() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt index f98af1045..00f7fe96c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers -import eu.kanade.tachiyomi.data.backup.models.BackupCategory import tachiyomi.data.DatabaseHandler +import tachiyomi.domain.backup.model.BackupCategory import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.library.service.LibraryPreferences import uy.kohesive.injekt.Injekt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt index aba2f1722..deb4e5553 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt @@ -1,13 +1,13 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers import eu.kanade.domain.manga.interactor.UpdateManga -import eu.kanade.tachiyomi.data.backup.models.BackupCategory -import eu.kanade.tachiyomi.data.backup.models.BackupChapter -import eu.kanade.tachiyomi.data.backup.models.BackupHistory -import eu.kanade.tachiyomi.data.backup.models.BackupManga -import eu.kanade.tachiyomi.data.backup.models.BackupTracking import tachiyomi.data.DatabaseHandler import tachiyomi.data.UpdateStrategyColumnAdapter +import tachiyomi.domain.backup.model.BackupCategory +import tachiyomi.domain.backup.model.BackupChapter +import tachiyomi.domain.backup.model.BackupHistory +import tachiyomi.domain.backup.model.BackupManga +import tachiyomi.domain.backup.model.BackupTracking import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.chapter.model.Chapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt index 1062937d4..ac215fbb6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt @@ -2,18 +2,18 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers import android.content.Context import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob -import eu.kanade.tachiyomi.data.backup.models.BackupPreference -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.library.LibraryUpdateJob import eu.kanade.tachiyomi.source.sourcePreferences import tachiyomi.core.preference.AndroidPreferenceStore import tachiyomi.core.preference.PreferenceStore +import tachiyomi.domain.backup.model.BackupPreference +import tachiyomi.domain.backup.model.BackupSourcePreferences +import tachiyomi.domain.backup.model.BooleanPreferenceValue +import tachiyomi.domain.backup.model.FloatPreferenceValue +import tachiyomi.domain.backup.model.IntPreferenceValue +import tachiyomi.domain.backup.model.LongPreferenceValue +import tachiyomi.domain.backup.model.StringPreferenceValue +import tachiyomi.domain.backup.model.StringSetPreferenceValue import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt index 3477cb296..4e4abe744 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt @@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.AndroidSourceManager import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory import kotlinx.serialization.json.Json +import kotlinx.serialization.protobuf.ProtoBuf import nl.adaptivity.xmlutil.XmlDeclMode import nl.adaptivity.xmlutil.core.XmlVersion import nl.adaptivity.xmlutil.serialization.XML @@ -106,6 +107,9 @@ class AppModule(val app: Application) : InjektModule { xmlVersion = XmlVersion.XML10 } } + addSingletonFactory { + ProtoBuf + } addSingletonFactory { ChapterCache(app) } addSingletonFactory { CoverCache(app) } diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 4df15c79a..425551ca0 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("com.android.library") kotlin("android") + kotlin("plugin.serialization") } android { @@ -18,6 +19,7 @@ dependencies { implementation(platform(kotlinx.coroutines.bom)) implementation(kotlinx.bundles.coroutines) + implementation(kotlinx.bundles.serialization) implementation(libs.unifile) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt b/domain/src/main/java/tachiyomi/domain/backup/model/Backup.kt similarity index 54% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt rename to domain/src/main/java/tachiyomi/domain/backup/model/Backup.kt index 0bfe17e59..2c3ecff5f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt +++ b/domain/src/main/java/tachiyomi/domain/backup/model/Backup.kt @@ -1,11 +1,11 @@ -package eu.kanade.tachiyomi.data.backup.models +package tachiyomi.domain.backup.model -import eu.kanade.tachiyomi.BuildConfig import kotlinx.serialization.Serializable +import kotlinx.serialization.Serializer import kotlinx.serialization.protobuf.ProtoNumber -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale + +@Serializer(forClass = Backup::class) +object BackupSerializer @Serializable data class Backup( @@ -15,14 +15,4 @@ data class Backup( @ProtoNumber(101) var backupSources: List = emptyList(), @ProtoNumber(104) var backupPreferences: List = emptyList(), @ProtoNumber(105) var backupSourcePreferences: List = emptyList(), -) { - - companion object { - val filenameRegex = """${BuildConfig.APPLICATION_ID}_\d+-\d+-\d+_\d+-\d+.tachibk""".toRegex() - - fun getFilename(): String { - val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date()) - return "${BuildConfig.APPLICATION_ID}_$date.tachibk" - } - } -} +) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt b/domain/src/main/java/tachiyomi/domain/backup/model/BackupCategory.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt rename to domain/src/main/java/tachiyomi/domain/backup/model/BackupCategory.kt index df517e8ed..1a39ecee9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt +++ b/domain/src/main/java/tachiyomi/domain/backup/model/BackupCategory.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.models +package tachiyomi.domain.backup.model import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt b/domain/src/main/java/tachiyomi/domain/backup/model/BackupChapter.kt similarity index 97% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt rename to domain/src/main/java/tachiyomi/domain/backup/model/BackupChapter.kt index 567ca372c..c232ffa4b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt +++ b/domain/src/main/java/tachiyomi/domain/backup/model/BackupChapter.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.models +package tachiyomi.domain.backup.model import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupHistory.kt b/domain/src/main/java/tachiyomi/domain/backup/model/BackupHistory.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupHistory.kt rename to domain/src/main/java/tachiyomi/domain/backup/model/BackupHistory.kt index 1108a376e..cb692cde2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupHistory.kt +++ b/domain/src/main/java/tachiyomi/domain/backup/model/BackupHistory.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.models +package tachiyomi.domain.backup.model import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt b/domain/src/main/java/tachiyomi/domain/backup/model/BackupManga.kt similarity index 73% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt rename to domain/src/main/java/tachiyomi/domain/backup/model/BackupManga.kt index 003b1ae19..b34a29cd4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt +++ b/domain/src/main/java/tachiyomi/domain/backup/model/BackupManga.kt @@ -1,7 +1,6 @@ -package eu.kanade.tachiyomi.data.backup.models +package tachiyomi.domain.backup.model import eu.kanade.tachiyomi.source.model.UpdateStrategy -import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import tachiyomi.domain.manga.model.Manga @@ -60,28 +59,4 @@ data class BackupManga( favoriteModifiedAt = this@BackupManga.favoriteModifiedAt, ) } - - companion object { - fun copyFrom(manga: Manga): BackupManga { - return BackupManga( - url = manga.url, - title = manga.title, - artist = manga.artist, - author = manga.author, - description = manga.description, - genre = manga.genre.orEmpty(), - status = manga.status.toInt(), - thumbnailUrl = manga.thumbnailUrl, - favorite = manga.favorite, - source = manga.source, - dateAdded = manga.dateAdded, - viewer = (manga.viewerFlags.toInt() and ReadingMode.MASK), - viewer_flags = manga.viewerFlags.toInt(), - chapterFlags = manga.chapterFlags.toInt(), - updateStrategy = manga.updateStrategy, - lastModifiedAt = manga.lastModifiedAt, - favoriteModifiedAt = manga.favoriteModifiedAt, - ) - } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt b/domain/src/main/java/tachiyomi/domain/backup/model/BackupPreference.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt rename to domain/src/main/java/tachiyomi/domain/backup/model/BackupPreference.kt index 3884f37e3..516d4dac2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt +++ b/domain/src/main/java/tachiyomi/domain/backup/model/BackupPreference.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.models +package tachiyomi.domain.backup.model import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt b/domain/src/main/java/tachiyomi/domain/backup/model/BackupSource.kt similarity index 57% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt rename to domain/src/main/java/tachiyomi/domain/backup/model/BackupSource.kt index cb75805ae..aae2cf03d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt +++ b/domain/src/main/java/tachiyomi/domain/backup/model/BackupSource.kt @@ -1,6 +1,5 @@ -package eu.kanade.tachiyomi.data.backup.models +package tachiyomi.domain.backup.model -import eu.kanade.tachiyomi.source.Source import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber @@ -8,16 +7,7 @@ import kotlinx.serialization.protobuf.ProtoNumber data class BackupSource( @ProtoNumber(1) var name: String = "", @ProtoNumber(2) var sourceId: Long, -) { - companion object { - fun copyFrom(source: Source): BackupSource { - return BackupSource( - name = source.name, - sourceId = source.id, - ) - } - } -} +) @Serializable data class BrokenBackupSource( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt b/domain/src/main/java/tachiyomi/domain/backup/model/BackupTracking.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt rename to domain/src/main/java/tachiyomi/domain/backup/model/BackupTracking.kt index 35d486492..caa727ad3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt +++ b/domain/src/main/java/tachiyomi/domain/backup/model/BackupTracking.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.models +package tachiyomi.domain.backup.model import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber From 6d74a86711ad17be4bebe054d88506303dd07504 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Dec 2023 18:30:24 -0500 Subject: [PATCH 13/30] Some domain Track model migrations --- .../eu/kanade/domain/track/model/Track.kt | 4 +-- .../presentation/track/TrackInfoDialogHome.kt | 3 +- .../track/TrackerSearchPreviewProvider.kt | 2 +- .../tachiyomi/data/database/models/Track.kt | 2 +- .../data/database/models/TrackImpl.kt | 2 +- .../tachiyomi/data/track/DeletableTracker.kt | 4 +-- .../eu/kanade/tachiyomi/data/track/Tracker.kt | 5 +-- .../tachiyomi/data/track/anilist/Anilist.kt | 17 ++++----- .../data/track/anilist/AnilistApi.kt | 16 ++++----- .../data/track/anilist/AnilistModels.kt | 25 ++++++------- .../tachiyomi/data/track/bangumi/Bangumi.kt | 3 +- .../data/track/bangumi/BangumiApi.kt | 12 +++---- .../tachiyomi/data/track/kavita/Kavita.kt | 2 +- .../tachiyomi/data/track/kitsu/Kitsu.kt | 9 ++--- .../tachiyomi/data/track/kitsu/KitsuApi.kt | 35 ++++++++++--------- .../tachiyomi/data/track/kitsu/KitsuModels.kt | 8 ++--- .../tachiyomi/data/track/komga/Komga.kt | 2 +- .../data/track/mangaupdates/MangaUpdates.kt | 6 ++-- .../track/mangaupdates/MangaUpdatesApi.kt | 17 ++++----- .../data/track/mangaupdates/dto/Record.kt | 2 +- .../tachiyomi/data/track/model/TrackSearch.kt | 6 ++-- .../data/track/myanimelist/MyAnimeList.kt | 9 ++--- .../data/track/myanimelist/MyAnimeListApi.kt | 26 ++++++-------- .../data/track/shikimori/Shikimori.kt | 7 ++-- .../data/track/shikimori/ShikimoriApi.kt | 24 ++++++------- .../tachiyomi/data/track/suwayomi/Suwayomi.kt | 2 +- .../ui/manga/track/TrackInfoDialog.kt | 4 +-- .../main/java/eu/kanade/test/DummyTracker.kt | 2 +- 28 files changed, 129 insertions(+), 127 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/track/model/Track.kt b/app/src/main/java/eu/kanade/domain/track/model/Track.kt index e16846e0e..e9e97c5c6 100644 --- a/app/src/main/java/eu/kanade/domain/track/model/Track.kt +++ b/app/src/main/java/eu/kanade/domain/track/model/Track.kt @@ -16,7 +16,7 @@ fun Track.copyPersonalFrom(other: Track): Track { fun Track.toDbTrack(): DbTrack = DbTrack.create(syncId).also { it.id = id it.manga_id = mangaId - it.media_id = remoteId + it.remote_id = remoteId it.library_id = libraryId it.title = title it.last_chapter_read = lastChapterRead.toFloat() @@ -34,7 +34,7 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? { id = trackId, mangaId = manga_id, syncId = sync_id.toLong(), - remoteId = media_id, + remoteId = remote_id, libraryId = library_id, title = title, lastChapterRead = last_chapter_read.toDouble(), diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt index 07693aa3a..0aec41dce 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt @@ -47,7 +47,6 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import dev.icerock.moko.resources.StringResource -import eu.kanade.domain.track.model.toDbTrack import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.presentation.track.components.TrackLogoIcon @@ -101,7 +100,7 @@ fun TrackInfoDialogHome( } }, onChaptersClick = { onChapterClick(item) }, - score = item.tracker.displayScore(item.track.toDbTrack()) + score = item.tracker.displayScore(item.track) .takeIf { supportsScoring && item.track.score != 0.0 }, onScoreClick = { onScoreClick(item) } .takeIf { supportsScoring }, diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt b/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt index b945e2ad4..fe5578f7b 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt @@ -63,7 +63,7 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab it.id = Random.nextLong() it.manga_id = Random.nextLong() it.sync_id = Random.nextInt() - it.media_id = Random.nextLong() + it.remote_id = Random.nextLong() it.library_id = Random.nextLong() it.title = lorem((1..10).random()).joinToString() it.last_chapter_read = (0..100).random().toFloat() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt index 38df114e2..e0bc5f047 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt @@ -10,7 +10,7 @@ interface Track : Serializable { var sync_id: Int - var media_id: Long + var remote_id: Long var library_id: Long? diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt index a83a5f7a7..faf86f021 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt @@ -8,7 +8,7 @@ class TrackImpl : Track { override var sync_id: Int = 0 - override var media_id: Long = 0 + override var remote_id: Long = 0 override var library_id: Long? = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableTracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableTracker.kt index c61c55e78..900163926 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableTracker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableTracker.kt @@ -1,11 +1,11 @@ package eu.kanade.tachiyomi.data.track -import eu.kanade.tachiyomi.data.database.models.Track +import tachiyomi.domain.track.model.Track /** * Tracker that support deleting am entry from a user's list. */ interface DeletableTracker { - suspend fun delete(track: Track): Track + suspend fun delete(track: Track) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt index 3b3223de9..59cf64648 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt @@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.model.TrackSearch import kotlinx.collections.immutable.ImmutableList import okhttp3.OkHttpClient +import tachiyomi.domain.track.model.Track as DomainTrack interface Tracker { @@ -39,11 +40,11 @@ interface Tracker { fun getScoreList(): ImmutableList // TODO: Store all scores as 10 point in the future maybe? - fun get10PointScore(track: tachiyomi.domain.track.model.Track): Double + fun get10PointScore(track: DomainTrack): Double fun indexToScore(index: Int): Float - fun displayScore(track: Track): String + fun displayScore(track: DomainTrack): String suspend fun update(track: Track, didReadChapter: Boolean = false): Track diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt index 95191bc00..93fd75a45 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.track.anilist import android.graphics.Color import dev.icerock.moko.resources.StringResource +import eu.kanade.domain.track.model.toDbTrack import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.BaseTracker @@ -120,16 +121,16 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker { } } - override fun displayScore(track: Track): String { + override fun displayScore(track: DomainTrack): String { val score = track.score return when (scorePreference.get()) { POINT_5 -> when (score) { - 0f -> "0 ★" + 0.0 -> "0 ★" else -> "${((score + 10) / 20).toInt()} ★" } POINT_3 -> when { - score == 0f -> "0" + score == 0.0 -> "0" score <= 35 -> "😦" score <= 60 -> "😐" else -> "😊" @@ -167,13 +168,13 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker { return api.updateLibManga(track) } - override suspend fun delete(track: Track): Track { - if (track.library_id == null || track.library_id!! == 0L) { - val libManga = api.findLibManga(track, getUsername().toInt()) ?: return track - track.library_id = libManga.library_id + override suspend fun delete(track: DomainTrack) { + if (track.libraryId == null || track.libraryId == 0L) { + val libManga = api.findLibManga(track.toDbTrack(), getUsername().toInt()) ?: return + return api.deleteLibManga(track.copy(id = libManga.library_id!!)) } - return api.deleteLibManga(track) + api.deleteLibManga(track) } override suspend fun bind(track: Track, hasReadChapters: Boolean): Track { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index c385d7614..de6ef2f8e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -31,6 +31,7 @@ import java.time.LocalDate import java.time.ZoneId import java.time.ZonedDateTime import kotlin.time.Duration.Companion.minutes +import tachiyomi.domain.track.model.Track as DomainTrack class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { @@ -55,7 +56,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { val payload = buildJsonObject { put("query", query) putJsonObject("variables") { - put("mangaId", track.media_id) + put("mangaId", track.remote_id) put("progress", track.last_chapter_read.toInt()) put("status", track.toAnilistStatus()) } @@ -113,8 +114,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { } } - suspend fun deleteLibManga(track: Track): Track { - return withIOContext { + suspend fun deleteLibManga(track: DomainTrack) { + withIOContext { val query = """ |mutation DeleteManga(${'$'}listId: Int) { |DeleteMediaListEntry(id: ${'$'}listId) { @@ -126,12 +127,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { val payload = buildJsonObject { put("query", query) putJsonObject("variables") { - put("listId", track.library_id) + put("listId", track.libraryId) } } authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))) .awaitSuccess() - track } } suspend fun search(search: String): List { @@ -235,7 +235,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { put("query", query) putJsonObject("variables") { put("id", userid) - put("manga_id", track.media_id) + put("manga_id", track.remote_id) } } with(json) { @@ -258,8 +258,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { } } - suspend fun getLibManga(track: Track, userid: Int): Track { - return findLibManga(track, userid) ?: throw Exception("Could not find manga") + suspend fun getLibManga(track: Track, userId: Int): Track { + return findLibManga(track, userId) ?: throw Exception("Could not find manga") } fun createOAuth(token: String): OAuth { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt index eb2f15ab2..ed5550464 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt @@ -9,9 +9,10 @@ import kotlinx.serialization.Serializable import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale +import tachiyomi.domain.track.model.Track as DomainTrack data class ALManga( - val media_id: Long, + val remote_id: Long, val title_user_pref: String, val image_url_lge: String, val description: String?, @@ -23,13 +24,13 @@ data class ALManga( ) { fun toTrack() = TrackSearch.create(TrackerManager.ANILIST).apply { - media_id = this@ALManga.media_id + remote_id = this@ALManga.remote_id title = title_user_pref total_chapters = this@ALManga.total_chapters cover_url = image_url_lge summary = description?.htmlDecode() ?: "" score = average_score.toFloat() - tracking_url = AnilistApi.mangaUrl(media_id) + tracking_url = AnilistApi.mangaUrl(remote_id) publishing_status = this@ALManga.publishing_status publishing_type = format if (start_date_fuzzy != 0L) { @@ -54,7 +55,7 @@ data class ALUserManga( ) { fun toTrack() = Track.create(TrackerManager.ANILIST).apply { - media_id = manga.media_id + remote_id = manga.remote_id title = manga.title_user_pref status = toTrackStatus() score = score_raw.toFloat() @@ -98,28 +99,28 @@ fun Track.toAnilistStatus() = when (status) { private val preferences: TrackPreferences by injectLazy() -fun Track.toAnilistScore(): String = when (preferences.anilistScoreType().get()) { -// 10 point +fun DomainTrack.toAnilistScore(): String = when (preferences.anilistScoreType().get()) { + // 10 point "POINT_10" -> (score.toInt() / 10).toString() -// 100 point + // 100 point "POINT_100" -> score.toInt().toString() -// 5 stars + // 5 stars "POINT_5" -> when { - score == 0f -> "0" + score == 0.0 -> "0" score < 30 -> "1" score < 50 -> "2" score < 70 -> "3" score < 90 -> "4" else -> "5" } -// Smiley + // Smiley "POINT_3" -> when { - score == 0f -> "0" + score == 0.0 -> "0" score <= 35 -> ":(" score <= 60 -> ":|" else -> ":)" } -// 10 point decimal + // 10 point decimal "POINT_10_DECIMAL" -> (score / 10).toString() else -> throw NotImplementedError("Unknown score type") } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt index bac2bba6d..a85d5e583 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt @@ -12,6 +12,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import tachiyomi.i18n.MR import uy.kohesive.injekt.injectLazy +import tachiyomi.domain.track.model.Track as DomainTrack class Bangumi(id: Long) : BaseTracker(id, "Bangumi") { @@ -23,7 +24,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") { override fun getScoreList(): ImmutableList = SCORE_LIST - override fun displayScore(track: Track): String { + override fun displayScore(track: DomainTrack): String { return track.score.toInt().toString() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt index 29db49ec5..8dbde5324 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt @@ -42,7 +42,7 @@ class BangumiApi( .add("rating", track.score.toInt().toString()) .add("status", track.toBangumiStatus()) .build() - authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = body)) + authClient.newCall(POST("$apiUrl/collection/${track.remote_id}/update", body = body)) .awaitSuccess() track } @@ -55,7 +55,7 @@ class BangumiApi( .add("rating", track.score.toInt().toString()) .add("status", track.toBangumiStatus()) .build() - authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = sbody)) + authClient.newCall(POST("$apiUrl/collection/${track.remote_id}/update", body = sbody)) .awaitSuccess() // chapter update @@ -64,7 +64,7 @@ class BangumiApi( .build() authClient.newCall( POST( - "$apiUrl/subject/${track.media_id}/update/watched_eps", + "$apiUrl/subject/${track.remote_id}/update/watched_eps", body = body, ), ).awaitSuccess() @@ -111,7 +111,7 @@ class BangumiApi( } val rating = obj["rating"]?.jsonObject?.get("score")?.jsonPrimitive?.floatOrNull ?: -1f return TrackSearch.create(trackId).apply { - media_id = obj["id"]!!.jsonPrimitive.long + remote_id = obj["id"]!!.jsonPrimitive.long title = obj["name_cn"]!!.jsonPrimitive.content cover_url = coverUrl summary = obj["name"]!!.jsonPrimitive.content @@ -124,7 +124,7 @@ class BangumiApi( suspend fun findLibManga(track: Track): Track? { return withIOContext { with(json) { - authClient.newCall(GET("$apiUrl/subject/${track.media_id}")) + authClient.newCall(GET("$apiUrl/subject/${track.remote_id}")) .awaitSuccess() .parseAs() .let { jsonToSearch(it) } @@ -134,7 +134,7 @@ class BangumiApi( suspend fun statusLibManga(track: Track): Track? { return withIOContext { - val urlUserRead = "$apiUrl/collection/${track.media_id}" + val urlUserRead = "$apiUrl/collection/${track.remote_id}" val requestUserRead = Request.Builder() .url(urlUserRead) .cacheControl(CacheControl.FORCE_NETWORK) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt index 0fff84efa..bcfbb1ec5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt @@ -55,7 +55,7 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker { override fun getScoreList(): ImmutableList = persistentListOf() - override fun displayScore(track: Track): String = "" + override fun displayScore(track: DomainTrack): String = "" override suspend fun update(track: Track, didReadChapter: Boolean): Track { if (track.status != COMPLETED) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt index 86fc808ad..03bfcd137 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt @@ -14,6 +14,7 @@ import kotlinx.serialization.json.Json import tachiyomi.i18n.MR import uy.kohesive.injekt.injectLazy import java.text.DecimalFormat +import tachiyomi.domain.track.model.Track as DomainTrack class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker { @@ -65,7 +66,7 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker { return if (index > 0) (index + 1) / 2f else 0f } - override fun displayScore(track: Track): String { + override fun displayScore(track: DomainTrack): String { val df = DecimalFormat("0.#") return df.format(track.score) } @@ -92,15 +93,15 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker { return api.updateLibManga(track) } - override suspend fun delete(track: Track): Track { - return api.removeLibManga(track) + override suspend fun delete(track: DomainTrack) { + api.removeLibManga(track) } override suspend fun bind(track: Track, hasReadChapters: Boolean): Track { val remoteTrack = api.findLibManga(track, getUserId()) return if (remoteTrack != null) { track.copyPersonalFrom(remoteTrack) - track.media_id = remoteTrack.media_id + track.remote_id = remoteTrack.remote_id if (track.status != COMPLETED) { track.status = if (hasReadChapters) READING else track.status diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt index 5406be0ad..e6c3acadf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt @@ -29,6 +29,7 @@ import tachiyomi.core.util.lang.withIOContext import uy.kohesive.injekt.injectLazy import java.net.URLEncoder import java.nio.charset.StandardCharsets +import tachiyomi.domain.track.model.Track as DomainTrack class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) { @@ -54,7 +55,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) } putJsonObject("media") { putJsonObject("data") { - put("id", track.media_id) + put("id", track.remote_id) put("type", "manga") } } @@ -77,7 +78,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) .awaitSuccess() .parseAs() .let { - track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.long + track.remote_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.long track } } @@ -89,7 +90,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) val data = buildJsonObject { putJsonObject("data") { put("type", "libraryEntries") - put("id", track.media_id) + put("id", track.remote_id) putJsonObject("attributes") { put("status", track.toKitsuStatus()) put("progress", track.last_chapter_read.toInt()) @@ -103,7 +104,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) with(json) { authClient.newCall( Request.Builder() - .url("${baseUrl}library-entries/${track.media_id}") + .url("${baseUrl}library-entries/${track.remote_id}") .headers( headersOf( "Content-Type", @@ -124,19 +125,19 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) } } - suspend fun removeLibManga(track: Track): Track { - return withIOContext { - authClient.newCall( - DELETE( - "${baseUrl}library-entries/${track.media_id}", - headers = headersOf( - "Content-Type", - "application/vnd.api+json", + suspend fun removeLibManga(track: DomainTrack) { + withIOContext { + authClient + .newCall( + DELETE( + "${baseUrl}library-entries/${track.remoteId}", + headers = headersOf( + "Content-Type", + "application/vnd.api+json", + ), ), - ), - ) + ) .awaitSuccess() - track } } suspend fun search(query: String): List { @@ -187,7 +188,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) suspend fun findLibManga(track: Track, userId: String): Track? { return withIOContext { val url = "${baseUrl}library-entries".toUri().buildUpon() - .encodedQuery("filter[manga_id]=${track.media_id}&filter[user_id]=$userId") + .encodedQuery("filter[manga_id]=${track.remote_id}&filter[user_id]=$userId") .appendQueryParameter("include", "manga") .build() with(json) { @@ -210,7 +211,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) suspend fun getLibManga(track: Track): Track { return withIOContext { val url = "${baseUrl}library-entries".toUri().buildUpon() - .encodedQuery("filter[id]=${track.media_id}") + .encodedQuery("filter[id]=${track.remote_id}") .appendQueryParameter("include", "manga") .build() with(json) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt index 156f6d8f3..ba6684339 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt @@ -37,12 +37,12 @@ class KitsuSearchManga(obj: JsonObject) { @CallSuper fun toTrack() = TrackSearch.create(TrackerManager.KITSU).apply { - media_id = this@KitsuSearchManga.id + remote_id = this@KitsuSearchManga.id title = canonicalTitle total_chapters = chapterCount ?: 0 cover_url = original ?: "" summary = synopsis ?: "" - tracking_url = KitsuApi.mangaUrl(media_id) + tracking_url = KitsuApi.mangaUrl(remote_id) score = rating ?: -1f publishing_status = if (endDate == null) { "Publishing" @@ -70,12 +70,12 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) { val progress = obj["attributes"]!!.jsonObject["progress"]!!.jsonPrimitive.int fun toTrack() = TrackSearch.create(TrackerManager.KITSU).apply { - media_id = libraryId + remote_id = libraryId title = canonicalTitle total_chapters = chapterCount ?: 0 cover_url = original summary = synopsis - tracking_url = KitsuApi.mangaUrl(media_id) + tracking_url = KitsuApi.mangaUrl(remote_id) publishing_status = this@KitsuLibManga.status publishing_type = type start_date = startDate diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt index 76eabfda9..6e69bf764 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt @@ -52,7 +52,7 @@ class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker { override fun getScoreList(): ImmutableList = persistentListOf() - override fun displayScore(track: Track): String = "" + override fun displayScore(track: DomainTrack): String = "" override suspend fun update(track: Track, didReadChapter: Boolean): Track { if (track.status != COMPLETED) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt index 698ad123c..f5c33cf8d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt @@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import tachiyomi.i18n.MR +import tachiyomi.domain.track.model.Track as DomainTrack class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker { @@ -60,7 +61,7 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker override fun indexToScore(index: Int): Float = SCORE_LIST[index].toFloat() - override fun displayScore(track: Track): String = track.score.toString() + override fun displayScore(track: DomainTrack): String = track.score.toString() override suspend fun update(track: Track, didReadChapter: Boolean): Track { if (track.status != COMPLETE_LIST && didReadChapter) { @@ -70,9 +71,8 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker return track } - override suspend fun delete(track: Track): Track { + override suspend fun delete(track: DomainTrack) { api.deleteSeriesFromList(track) - return track } override suspend fun bind(track: Track, hasReadChapters: Boolean): Track { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt index ab6b8a309..794ad11ef 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt @@ -30,6 +30,7 @@ import okhttp3.OkHttpClient import okhttp3.RequestBody.Companion.toRequestBody import tachiyomi.core.util.system.logcat import uy.kohesive.injekt.injectLazy +import tachiyomi.domain.track.model.Track as DomainTrack class MangaUpdatesApi( interceptor: MangaUpdatesInterceptor, @@ -48,7 +49,7 @@ class MangaUpdatesApi( suspend fun getSeriesListItem(track: Track): Pair { val listItem = with(json) { - authClient.newCall(GET("$baseUrl/v1/lists/series/${track.media_id}")) + authClient.newCall(GET("$baseUrl/v1/lists/series/${track.remote_id}")) .awaitSuccess() .parseAs() } @@ -63,7 +64,7 @@ class MangaUpdatesApi( val body = buildJsonArray { addJsonObject { putJsonObject("series") { - put("id", track.media_id) + put("id", track.remote_id) } put("list_id", status) } @@ -87,7 +88,7 @@ class MangaUpdatesApi( val body = buildJsonArray { addJsonObject { putJsonObject("series") { - put("id", track.media_id) + put("id", track.remote_id) } put("list_id", track.status) putJsonObject("status") { @@ -106,9 +107,9 @@ class MangaUpdatesApi( updateSeriesRating(track) } - suspend fun deleteSeriesFromList(track: Track) { + suspend fun deleteSeriesFromList(track: DomainTrack) { val body = buildJsonArray { - add(track.media_id) + add(track.remoteId) } authClient.newCall( POST( @@ -122,7 +123,7 @@ class MangaUpdatesApi( private suspend fun getSeriesRating(track: Track): Rating? { return try { with(json) { - authClient.newCall(GET("$baseUrl/v1/series/${track.media_id}/rating")) + authClient.newCall(GET("$baseUrl/v1/series/${track.remote_id}/rating")) .awaitSuccess() .parseAs() } @@ -138,7 +139,7 @@ class MangaUpdatesApi( } authClient.newCall( PUT( - url = "$baseUrl/v1/series/${track.media_id}/rating", + url = "$baseUrl/v1/series/${track.remote_id}/rating", body = body.toString().toRequestBody(contentType), ), ) @@ -146,7 +147,7 @@ class MangaUpdatesApi( } else { authClient.newCall( DELETE( - url = "$baseUrl/v1/series/${track.media_id}/rating", + url = "$baseUrl/v1/series/${track.remote_id}/rating", ), ) .awaitSuccess() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/dto/Record.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/dto/Record.kt index fb959a89b..4b66273e8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/dto/Record.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/dto/Record.kt @@ -25,7 +25,7 @@ data class Record( fun Record.toTrackSearch(id: Long): TrackSearch { return TrackSearch.create(id).apply { - media_id = this@toTrackSearch.seriesId ?: 0L + remote_id = this@toTrackSearch.seriesId ?: 0L title = this@toTrackSearch.title?.htmlDecode() ?: "" total_chapters = 0 cover_url = this@toTrackSearch.image?.url?.original ?: "" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt index 7151aab62..b43d8009a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt @@ -10,7 +10,7 @@ class TrackSearch : Track { override var sync_id: Int = 0 - override var media_id: Long = 0 + override var remote_id: Long = 0 override var library_id: Long? = null @@ -48,7 +48,7 @@ class TrackSearch : Track { if (manga_id != other.manga_id) return false if (sync_id != other.sync_id) return false - if (media_id != other.media_id) return false + if (remote_id != other.remote_id) return false return true } @@ -56,7 +56,7 @@ class TrackSearch : Track { override fun hashCode(): Int { var result = manga_id.hashCode() result = 31 * result + sync_id - result = 31 * result + media_id.hashCode() + result = 31 * result + remote_id.hashCode() return result } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt index 5ab94265c..5d66b73d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt @@ -13,6 +13,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import tachiyomi.i18n.MR import uy.kohesive.injekt.injectLazy +import tachiyomi.domain.track.model.Track as DomainTrack class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker { @@ -65,7 +66,7 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker { override fun getScoreList(): ImmutableList = SCORE_LIST - override fun displayScore(track: Track): String { + override fun displayScore(track: DomainTrack): String { return track.score.toInt().toString() } @@ -91,15 +92,15 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker { return api.updateItem(track) } - override suspend fun delete(track: Track): Track { - return api.deleteItem(track) + override suspend fun delete(track: DomainTrack) { + api.deleteItem(track) } override suspend fun bind(track: Track, hasReadChapters: Boolean): Track { val remoteTrack = api.findListItem(track) return if (remoteTrack != null) { track.copyPersonalFrom(remoteTrack) - track.media_id = remoteTrack.media_id + track.remote_id = remoteTrack.remote_id if (track.status != COMPLETED) { val isRereading = track.status == REREADING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt index 444c5512f..c67cc2a2a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt @@ -4,6 +4,7 @@ import android.net.Uri import androidx.core.net.toUri import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.model.TrackSearch +import eu.kanade.tachiyomi.network.DELETE import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.awaitSuccess @@ -31,6 +32,7 @@ import tachiyomi.core.util.lang.withIOContext import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale +import tachiyomi.domain.track.model.Track as DomainTrack class MyAnimeListApi( private val trackId: Long, @@ -114,7 +116,7 @@ class MyAnimeListApi( .let { val obj = it.jsonObject TrackSearch.create(trackId).apply { - media_id = obj["id"]!!.jsonPrimitive.long + remote_id = obj["id"]!!.jsonPrimitive.long title = obj["title"]!!.jsonPrimitive.content summary = obj["synopsis"]?.jsonPrimitive?.content ?: "" total_chapters = obj["num_chapters"]!!.jsonPrimitive.int @@ -122,7 +124,7 @@ class MyAnimeListApi( cover_url = obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content ?: "" - tracking_url = "https://myanimelist.net/manga/$media_id" + tracking_url = "https://myanimelist.net/manga/$remote_id" publishing_status = obj["status"]!!.jsonPrimitive.content.replace("_", " ") publishing_type = @@ -154,7 +156,7 @@ class MyAnimeListApi( } val request = Request.Builder() - .url(mangaUrl(track.media_id).toString()) + .url(mangaUrl(track.remote_id).toString()) .put(formBodyBuilder.build()) .build() with(json) { @@ -166,24 +168,18 @@ class MyAnimeListApi( } } - suspend fun deleteItem(track: Track): Track { - return withIOContext { - val request = Request.Builder() - .url(mangaUrl(track.media_id).toString()) - .delete() - .build() - with(json) { - authClient.newCall(request) - .awaitSuccess() - track - } + suspend fun deleteItem(track: DomainTrack) { + withIOContext { + authClient + .newCall(DELETE(mangaUrl(track.remoteId).toString())) + .awaitSuccess() } } suspend fun findListItem(track: Track): Track? { return withIOContext { val uri = "$baseApiUrl/manga".toUri().buildUpon() - .appendPath(track.media_id.toString()) + .appendPath(track.remote_id.toString()) .appendQueryParameter("fields", "num_chapters,my_list_status{start_date,finish_date}") .build() with(json) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt index d8e2bdd97..8f70e03dd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt @@ -13,6 +13,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import tachiyomi.i18n.MR import uy.kohesive.injekt.injectLazy +import tachiyomi.domain.track.model.Track as DomainTrack class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker { @@ -37,7 +38,7 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker { override fun getScoreList(): ImmutableList = SCORE_LIST - override fun displayScore(track: Track): String { + override fun displayScore(track: DomainTrack): String { return track.score.toInt().toString() } @@ -59,8 +60,8 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker { return api.updateLibManga(track, getUsername()) } - override suspend fun delete(track: Track): Track { - return api.deleteLibManga(track) + override suspend fun delete(track: DomainTrack) { + api.deleteLibManga(track) } override suspend fun bind(track: Track, hasReadChapters: Boolean): Track { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt index 0a5bba772..dca1c290d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt @@ -27,6 +27,7 @@ import okhttp3.OkHttpClient import okhttp3.RequestBody.Companion.toRequestBody import tachiyomi.core.util.lang.withIOContext import uy.kohesive.injekt.injectLazy +import tachiyomi.domain.track.model.Track as DomainTrack class ShikimoriApi( private val trackId: Long, @@ -44,7 +45,7 @@ class ShikimoriApi( val payload = buildJsonObject { putJsonObject("user_rate") { put("user_id", userId) - put("target_id", track.media_id) + put("target_id", track.remote_id) put("target_type", "Manga") put("chapters", track.last_chapter_read.toInt()) put("score", track.score.toInt()) @@ -69,14 +70,11 @@ class ShikimoriApi( suspend fun updateLibManga(track: Track, userId: String): Track = addLibManga(track, userId) - suspend fun deleteLibManga(track: Track): Track { - return withIOContext { - authClient.newCall( - DELETE( - "$apiUrl/v2/user_rates/${track.library_id}", - ), - ).awaitSuccess() - track + suspend fun deleteLibManga(track: DomainTrack) { + withIOContext { + authClient + .newCall(DELETE("$apiUrl/v2/user_rates/${track.libraryId}")) + .awaitSuccess() } } @@ -102,7 +100,7 @@ class ShikimoriApi( private fun jsonToSearch(obj: JsonObject): TrackSearch { return TrackSearch.create(trackId).apply { - media_id = obj["id"]!!.jsonPrimitive.long + remote_id = obj["id"]!!.jsonPrimitive.long title = obj["name"]!!.jsonPrimitive.content total_chapters = obj["chapters"]!!.jsonPrimitive.int cover_url = baseUrl + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content @@ -118,7 +116,7 @@ class ShikimoriApi( private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track { return Track.create(trackId).apply { title = mangas["name"]!!.jsonPrimitive.content - media_id = obj["id"]!!.jsonPrimitive.long + remote_id = obj["id"]!!.jsonPrimitive.long total_chapters = mangas["chapters"]!!.jsonPrimitive.int library_id = obj["id"]!!.jsonPrimitive.long last_chapter_read = obj["chapters"]!!.jsonPrimitive.float @@ -131,7 +129,7 @@ class ShikimoriApi( suspend fun findLibManga(track: Track, userId: String): Track? { return withIOContext { val urlMangas = "$apiUrl/mangas".toUri().buildUpon() - .appendPath(track.media_id.toString()) + .appendPath(track.remote_id.toString()) .build() val mangas = with(json) { authClient.newCall(GET(urlMangas.toString())) @@ -141,7 +139,7 @@ class ShikimoriApi( val url = "$apiUrl/v2/user_rates".toUri().buildUpon() .appendQueryParameter("user_id", userId) - .appendQueryParameter("target_id", track.media_id.toString()) + .appendQueryParameter("target_id", track.remote_id.toString()) .appendQueryParameter("target_type", "Manga") .build() with(json) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt index dedafe486..d8d1ba975 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt @@ -45,7 +45,7 @@ class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker { override fun getScoreList(): ImmutableList = persistentListOf() - override fun displayScore(track: Track): String = "" + override fun displayScore(track: DomainTrack): String = "" override suspend fun update(track: Track, didReadChapter: Boolean): Track { if (track.status != COMPLETED) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt index ce6f27686..aab8f802a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt @@ -399,7 +399,7 @@ private data class TrackScoreSelectorScreen( private class Model( private val track: Track, private val tracker: Tracker, - ) : StateScreenModel(State(tracker.displayScore(track.toDbTrack()))) { + ) : StateScreenModel(State(tracker.displayScore(track))) { fun getSelections(): ImmutableList { return tracker.getScoreList() @@ -816,7 +816,7 @@ private data class TrackerRemoveScreen( fun deleteMangaFromService() { screenModelScope.launchNonCancellable { - (tracker as DeletableTracker).delete(track.toDbTrack()) + (tracker as DeletableTracker).delete(track) } } diff --git a/app/src/main/java/eu/kanade/test/DummyTracker.kt b/app/src/main/java/eu/kanade/test/DummyTracker.kt index 10d9bd957..e8183310d 100644 --- a/app/src/main/java/eu/kanade/test/DummyTracker.kt +++ b/app/src/main/java/eu/kanade/test/DummyTracker.kt @@ -58,7 +58,7 @@ data class DummyTracker( override fun indexToScore(index: Int): Float = getScoreList()[index].toFloat() - override fun displayScore(track: eu.kanade.tachiyomi.data.database.models.Track): String = + override fun displayScore(track: Track): String = track.score.toString() override suspend fun update( From 6887d98f15b4b9a84230217800fba2d4ae5b186a Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Dec 2023 22:25:22 -0500 Subject: [PATCH 14/30] Minor tracking cleanups --- .../domain/track/interactor/RefreshTracks.kt | 2 +- .../domain/track/interactor/TrackChapter.kt | 2 +- .../eu/kanade/domain/track/model/Track.kt | 4 +-- .../domain/track/service/TrackPreferences.kt | 26 +++++++------- .../more/settings/PreferenceItem.kt | 5 ++- .../presentation/reader/ChapterTransition.kt | 34 +++++++++---------- .../TrackInfoDialogHomePreviewProvider.kt | 2 +- .../track/TrackerSearchPreviewProvider.kt | 2 +- .../backup/restore/restorers/MangaRestorer.kt | 6 ++-- .../tachiyomi/data/database/models/Track.kt | 4 +-- .../data/database/models/TrackImpl.kt | 2 +- .../tachiyomi/data/track/TrackerManager.kt | 2 -- .../tachiyomi/data/track/model/TrackSearch.kt | 8 ++--- .../ui/library/LibraryScreenModel.kt | 4 +-- .../tachiyomi/ui/manga/MangaScreenModel.kt | 2 +- .../ui/manga/track/TrackInfoDialog.kt | 2 +- .../ui/reader/model/ReaderChapter.kt | 3 ++ .../tachiyomi/ui/stats/StatsScreenModel.kt | 4 +-- .../java/tachiyomi/data/track/TrackMapper.kt | 2 +- .../data/track/TrackRepositoryImpl.kt | 6 ++-- .../domain/backup/model/BackupTracking.kt | 3 +- .../domain/track/interactor/DeleteTrack.kt | 4 +-- .../tachiyomi/domain/track/model/Track.kt | 2 +- .../track/repository/TrackRepository.kt | 2 +- 24 files changed, 64 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/track/interactor/RefreshTracks.kt b/app/src/main/java/eu/kanade/domain/track/interactor/RefreshTracks.kt index 8c8952304..457dbf87e 100644 --- a/app/src/main/java/eu/kanade/domain/track/interactor/RefreshTracks.kt +++ b/app/src/main/java/eu/kanade/domain/track/interactor/RefreshTracks.kt @@ -25,7 +25,7 @@ class RefreshTracks( suspend fun await(mangaId: Long): List> { return supervisorScope { return@supervisorScope getTracks.await(mangaId) - .map { it to trackerManager.get(it.syncId) } + .map { it to trackerManager.get(it.trackerId) } .filter { (_, service) -> service?.isLoggedIn == true } .map { (track, service) -> async { diff --git a/app/src/main/java/eu/kanade/domain/track/interactor/TrackChapter.kt b/app/src/main/java/eu/kanade/domain/track/interactor/TrackChapter.kt index 789a784ef..557c3be83 100644 --- a/app/src/main/java/eu/kanade/domain/track/interactor/TrackChapter.kt +++ b/app/src/main/java/eu/kanade/domain/track/interactor/TrackChapter.kt @@ -27,7 +27,7 @@ class TrackChapter( if (tracks.isEmpty()) return@withNonCancellableContext tracks.mapNotNull { track -> - val service = trackerManager.get(track.syncId) + val service = trackerManager.get(track.trackerId) if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) { return@mapNotNull null } diff --git a/app/src/main/java/eu/kanade/domain/track/model/Track.kt b/app/src/main/java/eu/kanade/domain/track/model/Track.kt index e9e97c5c6..e84e28ff0 100644 --- a/app/src/main/java/eu/kanade/domain/track/model/Track.kt +++ b/app/src/main/java/eu/kanade/domain/track/model/Track.kt @@ -13,7 +13,7 @@ fun Track.copyPersonalFrom(other: Track): Track { ) } -fun Track.toDbTrack(): DbTrack = DbTrack.create(syncId).also { +fun Track.toDbTrack(): DbTrack = DbTrack.create(trackerId).also { it.id = id it.manga_id = mangaId it.remote_id = remoteId @@ -33,7 +33,7 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? { return Track( id = trackId, mangaId = manga_id, - syncId = sync_id.toLong(), + trackerId = tracker_id.toLong(), remoteId = remote_id, libraryId = library_id, title = title, diff --git a/app/src/main/java/eu/kanade/domain/track/service/TrackPreferences.kt b/app/src/main/java/eu/kanade/domain/track/service/TrackPreferences.kt index c7fb47581..8ed234b90 100644 --- a/app/src/main/java/eu/kanade/domain/track/service/TrackPreferences.kt +++ b/app/src/main/java/eu/kanade/domain/track/service/TrackPreferences.kt @@ -9,26 +9,24 @@ class TrackPreferences( private val preferenceStore: PreferenceStore, ) { - fun trackUsername(sync: Tracker) = preferenceStore.getString(trackUsername(sync.id), "") + fun trackUsername(tracker: Tracker) = preferenceStore.getString( + Preference.privateKey("pref_mangasync_username_${tracker.id}"), + "", + ) - fun trackPassword(sync: Tracker) = preferenceStore.getString(trackPassword(sync.id), "") + fun trackPassword(tracker: Tracker) = preferenceStore.getString( + Preference.privateKey("pref_mangasync_password_${tracker.id}"), + "", + ) - fun setCredentials(sync: Tracker, username: String, password: String) { - trackUsername(sync).set(username) - trackPassword(sync).set(password) + fun setCredentials(tracker: Tracker, username: String, password: String) { + trackUsername(tracker).set(username) + trackPassword(tracker).set(password) } - fun trackToken(sync: Tracker) = preferenceStore.getString(trackToken(sync.id), "") + fun trackToken(tracker: Tracker) = preferenceStore.getString(Preference.privateKey("track_token_${tracker.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: Long) = Preference.privateKey("pref_mangasync_username_$syncId") - - private fun trackPassword(syncId: Long) = Preference.privateKey("pref_mangasync_password_$syncId") - - private fun trackToken(syncId: Long) = Preference.privateKey("track_token_$syncId") - } } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt index b68f17fcd..b22e69323 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt @@ -21,7 +21,6 @@ import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget import kotlinx.coroutines.launch -import tachiyomi.core.preference.PreferenceStore import tachiyomi.presentation.core.components.SliderItem import tachiyomi.presentation.core.util.collectAsState import uy.kohesive.injekt.Injekt @@ -157,8 +156,8 @@ internal fun PreferenceItem( ) } is Preference.PreferenceItem.TrackerPreference -> { - val uName by Injekt.get() - .getString(TrackPreferences.trackUsername(item.tracker.id)) + val uName by Injekt.get() + .trackUsername(item.tracker) .collectAsState() item.tracker.run { TrackingPreferenceWidget( diff --git a/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt b/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt index 422192383..2961529a3 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt @@ -34,11 +34,10 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import eu.kanade.presentation.theme.TachiyomiTheme -import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.database.models.ChapterImpl import eu.kanade.tachiyomi.data.database.models.toDomainChapter import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter +import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.service.calculateChapterGap import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.pluralStringResource @@ -51,8 +50,8 @@ fun ChapterTransition( currChapterDownloaded: Boolean, goingToChapterDownloaded: Boolean, ) { - val currChapter = transition.from.chapter - val goingToChapter = transition.to?.chapter + val currChapter = transition.from.chapter.toDomainChapter() + val goingToChapter = transition.to?.chapter?.toDomainChapter() ProvideTextStyle(MaterialTheme.typography.bodyMedium) { when (transition) { @@ -65,7 +64,7 @@ fun ChapterTransition( bottomChapter = currChapter, bottomChapterDownloaded = currChapterDownloaded, fallbackLabel = stringResource(MR.strings.transition_no_previous), - chapterGap = calculateChapterGap(currChapter.toDomainChapter(), goingToChapter?.toDomainChapter()), + chapterGap = calculateChapterGap(currChapter, goingToChapter), ) } is ChapterTransition.Next -> { @@ -77,7 +76,7 @@ fun ChapterTransition( bottomChapter = goingToChapter, bottomChapterDownloaded = goingToChapterDownloaded, fallbackLabel = stringResource(MR.strings.transition_no_next), - chapterGap = calculateChapterGap(goingToChapter?.toDomainChapter(), currChapter.toDomainChapter()), + chapterGap = calculateChapterGap(goingToChapter, currChapter), ) } } @@ -275,24 +274,23 @@ private val CardColor: CardColors private val VerticalSpacerSize = 24.dp private const val DownloadedIconContentId = "downloaded" -private fun previewChapter(name: String, scanlator: String, chapterNumber: Float) = ChapterImpl().apply { - this.name = name - this.scanlator = scanlator - this.chapter_number = chapterNumber - - this.id = 0 - this.manga_id = 0 - this.url = "" -} +private fun previewChapter(name: String, scanlator: String, chapterNumber: Double) = Chapter.create().copy( + id = 0L, + mangaId = 0L, + url = "", + name = name, + scanlator = scanlator, + chapterNumber = chapterNumber, +) private val FakeChapter = previewChapter( name = "Vol.1, Ch.1 - Fake Chapter Title", scanlator = "Scanlator Name", - chapterNumber = 1f, + chapterNumber = 1.0, ) private val FakeGapChapter = previewChapter( name = "Vol.5, Ch.44 - Fake Gap Chapter Title", scanlator = "Scanlator Name", - chapterNumber = 44f, + chapterNumber = 44.0, ) private val FakeChapterLongTitle = previewChapter( name = "Vol.1, Ch.0 - The Mundane Musings of a Metafictional Manga: A Chapter About a Chapter, Featuring" + @@ -301,7 +299,7 @@ private val FakeChapterLongTitle = previewChapter( "Fictional Realities and Reality-Bending Fiction, Where the Fourth Wall is Always in Danger of Being Broken " + "and the Line Between Author and Character is Forever Blurred.", scanlator = "Long Long Funny Scanlator Sniper Group Name Reborn", - chapterNumber = 1f, + chapterNumber = 1.0, ) @PreviewLightDark diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt index e2733b7bf..51b7ca3f8 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt @@ -13,7 +13,7 @@ internal class TrackInfoDialogHomePreviewProvider : private val aTrack = Track( id = 1L, mangaId = 2L, - syncId = 3L, + trackerId = 3L, remoteId = 4L, libraryId = null, title = "Manage Name On Tracker Site", diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt b/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt index fe5578f7b..7bc78781b 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt @@ -62,7 +62,7 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab private fun randTrackSearch() = TrackSearch().let { it.id = Random.nextLong() it.manga_id = Random.nextLong() - it.sync_id = Random.nextInt() + it.tracker_id = Random.nextInt() it.remote_id = Random.nextLong() it.library_id = Random.nextLong() it.title = lorem((1..10).random()).joinToString() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt index deb4e5553..6ca9f95d9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt @@ -346,12 +346,12 @@ class MangaRestorer( } private suspend fun restoreTracking(manga: Manga, backupTracks: List) { - val dbTrackBySyncId = getTracks.await(manga.id).associateBy { it.syncId } + val dbTrackByTrackerId = getTracks.await(manga.id).associateBy { it.trackerId } val (existingTracks, newTracks) = backupTracks .mapNotNull { val track = it.getTrackImpl() - val dbTrack = dbTrackBySyncId[track.syncId] + val dbTrack = dbTrackByTrackerId[track.trackerId] ?: // New track return@mapNotNull track.copy( id = 0, // Let DB assign new ID @@ -380,7 +380,7 @@ class MangaRestorer( existingTracks.forEach { track -> manga_syncQueries.update( track.mangaId, - track.syncId, + track.trackerId, track.remoteId, track.libraryId, track.title, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt index e0bc5f047..9d7f98983 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt @@ -8,7 +8,7 @@ interface Track : Serializable { var manga_id: Long - var sync_id: Int + var tracker_id: Int var remote_id: Long @@ -40,7 +40,7 @@ interface Track : Serializable { companion object { fun create(serviceId: Long): Track = TrackImpl().apply { - sync_id = serviceId.toInt() + tracker_id = serviceId.toInt() } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt index faf86f021..2a0abce31 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt @@ -6,7 +6,7 @@ class TrackImpl : Track { override var manga_id: Long = 0 - override var sync_id: Int = 0 + override var tracker_id: Int = 0 override var remote_id: Long = 0 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt index a62ad4401..598a0c06c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt @@ -33,6 +33,4 @@ class TrackerManager { fun loggedInTrackers() = trackers.filter { it.isLoggedIn } fun get(id: Long) = trackers.find { it.id == id } - - fun hasLoggedIn() = trackers.any { it.isLoggedIn } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt index b43d8009a..b8a71687f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt @@ -8,7 +8,7 @@ class TrackSearch : Track { override var manga_id: Long = 0 - override var sync_id: Int = 0 + override var tracker_id: Int = 0 override var remote_id: Long = 0 @@ -47,7 +47,7 @@ class TrackSearch : Track { other as TrackSearch if (manga_id != other.manga_id) return false - if (sync_id != other.sync_id) return false + if (tracker_id != other.tracker_id) return false if (remote_id != other.remote_id) return false return true @@ -55,14 +55,14 @@ class TrackSearch : Track { override fun hashCode(): Int { var result = manga_id.hashCode() - result = 31 * result + sync_id + result = 31 * result + tracker_id result = 31 * result + remote_id.hashCode() return result } companion object { fun create(serviceId: Long): TrackSearch = TrackSearch().apply { - sync_id = serviceId.toInt() + tracker_id = serviceId.toInt() } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt index 72fa1cc6c..a19753a84 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt @@ -217,7 +217,7 @@ class LibraryScreenModel( if (isNotLoggedInAnyTrack || trackFiltersIsIgnored) return@tracking true val mangaTracks = trackMap - .mapValues { entry -> entry.value.map { it.syncId } }[item.libraryManga.id] + .mapValues { entry -> entry.value.map { it.trackerId } }[item.libraryManga.id] .orEmpty() val isExcluded = excludedTracks.isNotEmpty() && mangaTracks.fastAny { it in excludedTracks } @@ -257,7 +257,7 @@ class LibraryScreenModel( entry.value.isEmpty() -> null else -> entry.value - .mapNotNull { trackerMap[it.syncId]?.get10PointScore(it) } + .mapNotNull { trackerMap[it.trackerId]?.get10PointScore(it) } .average() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index a6541fe30..1c36d19d9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -983,7 +983,7 @@ class MangaScreenModel( .map { tracks -> loggedInTrackers // Map to TrackItem - .map { service -> TrackItem(tracks.find { it.syncId == service.id }, service) } + .map { service -> TrackItem(tracks.find { it.trackerId == service.id }, service) } // Show only if the service supports this manga's source .filter { (it.tracker as? EnhancedTracker)?.accept(source!!) ?: true } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt index aab8f802a..9ad9f5bb2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt @@ -244,7 +244,7 @@ data class TrackInfoDialogHomeScreen( val source = Injekt.get().getOrStub(sourceId) return loggedInTrackers // Map to TrackItem - .map { service -> TrackItem(find { it.syncId == service.id }, service) } + .map { service -> TrackItem(find { it.trackerId == service.id }, service) } // Show only if the service supports this manga's source .filter { (it.tracker as? EnhancedTracker)?.accept(source) ?: true } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderChapter.kt index af32c11b3..c0cdfdd7c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderChapter.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.reader.model +import eu.kanade.domain.chapter.model.toDbChapter import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.ui.reader.loader.PageLoader import kotlinx.coroutines.flow.MutableStateFlow @@ -23,6 +24,8 @@ data class ReaderChapter(val chapter: Chapter) { private var references = 0 + constructor(chapter: tachiyomi.domain.chapter.model.Chapter) : this(chapter.toDbChapter()) + fun ref() { references++ } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt index e925c7dcb..9ed04cd78 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt @@ -118,7 +118,7 @@ class StatsScreenModel( val loggedInTrackerIds = loggedInTrackers.map { it.id }.toHashSet() return libraryManga.associate { manga -> val tracks = getTracks.await(manga.id) - .fastFilter { it.syncId in loggedInTrackerIds } + .fastFilter { it.trackerId in loggedInTrackerIds } manga.id to tracks } @@ -144,7 +144,7 @@ class StatsScreenModel( } private fun get10PointScore(track: Track): Double { - val service = trackerManager.get(track.syncId)!! + val service = trackerManager.get(track.trackerId)!! return service.get10PointScore(track) } } diff --git a/data/src/main/java/tachiyomi/data/track/TrackMapper.kt b/data/src/main/java/tachiyomi/data/track/TrackMapper.kt index 8ac852731..ee941d49c 100644 --- a/data/src/main/java/tachiyomi/data/track/TrackMapper.kt +++ b/data/src/main/java/tachiyomi/data/track/TrackMapper.kt @@ -20,7 +20,7 @@ object TrackMapper { ): Track = Track( id = id, mangaId = mangaId, - syncId = syncId, + trackerId = syncId, remoteId = remoteId, libraryId = libraryId, title = title, diff --git a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt index 19a3daa02..6cd8396b1 100644 --- a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt @@ -31,11 +31,11 @@ class TrackRepositoryImpl( } } - override suspend fun delete(mangaId: Long, syncId: Long) { + override suspend fun delete(mangaId: Long, trackerId: Long) { handler.await { manga_syncQueries.delete( mangaId = mangaId, - syncId = syncId, + syncId = trackerId, ) } } @@ -53,7 +53,7 @@ class TrackRepositoryImpl( tracks.forEach { mangaTrack -> manga_syncQueries.insert( mangaId = mangaTrack.mangaId, - syncId = mangaTrack.syncId, + syncId = mangaTrack.trackerId, remoteId = mangaTrack.remoteId, libraryId = mangaTrack.libraryId, title = mangaTrack.title, diff --git a/domain/src/main/java/tachiyomi/domain/backup/model/BackupTracking.kt b/domain/src/main/java/tachiyomi/domain/backup/model/BackupTracking.kt index caa727ad3..dff8f2582 100644 --- a/domain/src/main/java/tachiyomi/domain/backup/model/BackupTracking.kt +++ b/domain/src/main/java/tachiyomi/domain/backup/model/BackupTracking.kt @@ -7,7 +7,6 @@ import tachiyomi.domain.track.model.Track @Serializable data class BackupTracking( // in 1.x some of these values have different types or names - // syncId is called siteId in 1,x @ProtoNumber(1) var syncId: Int, // LibraryId is not null in 1.x @ProtoNumber(2) var libraryId: Long, @@ -34,7 +33,7 @@ data class BackupTracking( return Track( id = -1, mangaId = -1, - syncId = this@BackupTracking.syncId.toLong(), + trackerId = this@BackupTracking.syncId.toLong(), remoteId = if (this@BackupTracking.mediaIdInt != 0) { this@BackupTracking.mediaIdInt.toLong() } else { diff --git a/domain/src/main/java/tachiyomi/domain/track/interactor/DeleteTrack.kt b/domain/src/main/java/tachiyomi/domain/track/interactor/DeleteTrack.kt index 9672a6586..2a30e0ffc 100644 --- a/domain/src/main/java/tachiyomi/domain/track/interactor/DeleteTrack.kt +++ b/domain/src/main/java/tachiyomi/domain/track/interactor/DeleteTrack.kt @@ -8,9 +8,9 @@ class DeleteTrack( private val trackRepository: TrackRepository, ) { - suspend fun await(mangaId: Long, syncId: Long) { + suspend fun await(mangaId: Long, trackerId: Long) { try { - trackRepository.delete(mangaId, syncId) + trackRepository.delete(mangaId, trackerId) } catch (e: Exception) { logcat(LogPriority.ERROR, e) } diff --git a/domain/src/main/java/tachiyomi/domain/track/model/Track.kt b/domain/src/main/java/tachiyomi/domain/track/model/Track.kt index 91ac2c833..1a656fcad 100644 --- a/domain/src/main/java/tachiyomi/domain/track/model/Track.kt +++ b/domain/src/main/java/tachiyomi/domain/track/model/Track.kt @@ -3,7 +3,7 @@ package tachiyomi.domain.track.model data class Track( val id: Long, val mangaId: Long, - val syncId: Long, + val trackerId: Long, val remoteId: Long, val libraryId: Long?, val title: String, diff --git a/domain/src/main/java/tachiyomi/domain/track/repository/TrackRepository.kt b/domain/src/main/java/tachiyomi/domain/track/repository/TrackRepository.kt index 1edd76fdf..1814a7a08 100644 --- a/domain/src/main/java/tachiyomi/domain/track/repository/TrackRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/track/repository/TrackRepository.kt @@ -13,7 +13,7 @@ interface TrackRepository { fun getTracksByMangaIdAsFlow(mangaId: Long): Flow> - suspend fun delete(mangaId: Long, syncId: Long) + suspend fun delete(mangaId: Long, trackerId: Long) suspend fun insert(track: Track) From 446b146f953b41a304840453ef6f1f015c75f082 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Dec 2023 22:32:23 -0500 Subject: [PATCH 15/30] Ensure sufficiently long prefix when creating temp file Fixes #10265 --- core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt b/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt index c5c2bbbc8..65846ff6e 100644 --- a/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt +++ b/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt @@ -16,7 +16,7 @@ val UniFile.nameWithoutExtension: String? fun UniFile.toTempFile(context: Context): File { val inputStream = context.contentResolver.openInputStream(uri)!! val tempFile = File.createTempFile( - nameWithoutExtension.orEmpty(), + nameWithoutExtension.orEmpty().padEnd(3), // Prefix must be 3+ chars null, ) From 80d6d412f379ef9796b7274c13c1c03883c800d3 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Dec 2023 22:53:35 -0500 Subject: [PATCH 16/30] Avoid crashing if loading non-read-only private extension Fixes #10252 --- .../tachiyomi/extension/util/ExtensionLoader.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt index c43af2fdc..c40d78157 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt @@ -141,6 +141,11 @@ internal object ExtensionLoader { ?.asSequence() ?.filter { it.isFile && it.extension == PRIVATE_EXTENSION_EXTENSION } ?.mapNotNull { + // Just in case, since Android 14+ requires them to be read-only + if (it.canWrite()) { + it.setReadOnly() + } + val path = it.absolutePath pkgManager.getPackageArchiveInfo(path, PACKAGE_FLAGS) ?.apply { applicationInfo.fixBasePaths(path) } @@ -277,7 +282,12 @@ internal object ExtensionLoader { val hasReadme = appInfo.metaData.getInt(METADATA_HAS_README, 0) == 1 val hasChangelog = appInfo.metaData.getInt(METADATA_HAS_CHANGELOG, 0) == 1 - val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader) + val classLoader = try { + PathClassLoader(appInfo.sourceDir, null, context.classLoader) + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) { "Extension load error: $extName ($pkgName)" } + return LoadResult.Error + } val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!! .split(";") From 2d7650537db907a4129f4a07db15950339b319e9 Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 25 Dec 2023 16:31:40 -0500 Subject: [PATCH 17/30] Address some build warnings --- README.md | 1 - app/proguard-rules.pro | 2 +- app/src/main/AndroidManifest.xml | 9 ++-- .../category/components/CategoryDialogs.kt | 5 ++- .../CategoryFloatingActionButton.kt | 3 ++ .../components/DownloadDropdownMenu.kt | 20 ++++++--- .../presentation/components/DropdownMenu.kt | 45 ++++++++++++------- .../components/ChapterDownloadIndicator.kt | 4 +- .../manga/components/MangaBottomActionMenu.kt | 5 ++- .../manga/components/MangaToolbar.kt | 1 - .../more/settings/screen/Commons.kt | 1 - .../settings/screen/SettingsLibraryScreen.kt | 3 +- .../settings/widget/BasePreferenceWidget.kt | 1 + .../tachiyomi/data/backup/BackupDecoder.kt | 23 +++++----- .../data/backup/create/BackupCreator.kt | 2 +- .../data/download/DownloadProvider.kt | 2 +- .../data/library/LibraryUpdateNotifier.kt | 2 +- .../data/notification/NotificationReceiver.kt | 4 +- .../data/track/bangumi/BangumiInterceptor.kt | 2 +- .../data/track/bangumi/BangumiModels.kt | 9 ---- .../data/track/kitsu/KitsuInterceptor.kt | 2 +- .../track/shikimori/ShikimoriInterceptor.kt | 2 +- .../util/system/AnimationExtensions.kt | 19 -------- .../util/system/DisplayExtensions.kt | 8 ---- app/src/main/res/drawable/ic_done_24dp.xml | 9 ++++ .../layout/source_preferences_controller.xml | 5 --- .../SpecificHostRateLimitInterceptor.kt | 2 + .../preference/InMemoryPreferenceStore.kt | 3 +- .../core/components/AdaptiveSheet.kt | 3 +- .../src/main/res/values/colors.xml | 16 +------ .../widget/UpdatesGridGlanceWidget.kt | 2 + .../kanade/tachiyomi/source/model/SManga.kt | 30 ------------- .../tachiyomi/source/model/UpdateStrategy.kt | 1 + 33 files changed, 102 insertions(+), 144 deletions(-) create mode 100644 app/src/main/res/drawable/ic_done_24dp.xml delete mode 100644 app/src/main/res/layout/source_preferences_controller.xml diff --git a/README.md b/README.md index 0beaf83ae..a9ac2dfc6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ |-------|----------|---------|------------|---------| | [![CI](https://github.com/tachiyomiorg/tachiyomi/actions/workflows/build_push.yml/badge.svg)](https://github.com/tachiyomiorg/tachiyomi/actions/workflows/build_push.yml) | [![stable release](https://img.shields.io/github/release/tachiyomiorg/tachiyomi.svg?maxAge=3600&label=download)](https://github.com/tachiyomiorg/tachiyomi/releases) | [![latest preview build](https://img.shields.io/github/v/release/tachiyomiorg/tachiyomi-preview.svg?maxAge=3600&label=download)](https://github.com/tachiyomiorg/tachiyomi-preview/releases) | [![Translation status](https://hosted.weblate.org/widgets/tachiyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [![Discord](https://img.shields.io/discord/349436576037732353.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/tachiyomi) | - # ![app icon](./.github/readme-images/app-icon.png)Tachiyomi Tachiyomi is a free and open source manga reader for Android 6.0 and above. diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ddb8f83af..42bd63faf 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -45,7 +45,7 @@ ##---------------Begin: proguard configuration for kotlinx.serialization ---------- -keepattributes *Annotation*, InnerClasses --dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations +-dontnote kotlinx.serialization.** # core serialization annotations # kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer -keepclassmembers class kotlinx.serialization.json.** { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 900da716a..3df7beb18 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,7 +8,8 @@ - + @@ -20,10 +21,12 @@ - + - + diff --git a/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt b/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt index 0441d014a..d7a484c6d 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import eu.kanade.core.preference.asToggleableState import eu.kanade.presentation.category.visualName +import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.delay import tachiyomi.core.preference.CheckboxState import tachiyomi.domain.category.model.Category @@ -39,7 +40,7 @@ import kotlin.time.Duration.Companion.seconds fun CategoryCreateDialog( onDismissRequest: () -> Unit, onCreate: (String) -> Unit, - categories: List, + categories: ImmutableList, ) { var name by remember { mutableStateOf("") } @@ -98,7 +99,7 @@ fun CategoryCreateDialog( fun CategoryRenameDialog( onDismissRequest: () -> Unit, onRename: (String) -> Unit, - categories: List, + categories: ImmutableList, category: Category, ) { var name by remember { mutableStateOf(category.name) } diff --git a/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt b/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt index 2aaf81efd..58d7f163a 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt @@ -6,6 +6,7 @@ import androidx.compose.material.icons.outlined.Add import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton import tachiyomi.presentation.core.i18n.stringResource @@ -16,11 +17,13 @@ import tachiyomi.presentation.core.util.isScrollingUp fun CategoryFloatingActionButton( lazyListState: LazyListState, onCreate: () -> Unit, + modifier: Modifier = Modifier, ) { ExtendedFloatingActionButton( text = { Text(text = stringResource(MR.strings.action_add)) }, icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) }, onClick = onCreate, expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(), + modifier = modifier, ) } diff --git a/app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt b/app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt index 36449f13c..a116c3ee1 100644 --- a/app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt @@ -3,7 +3,9 @@ package eu.kanade.presentation.components import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import eu.kanade.presentation.manga.DownloadAction +import kotlinx.collections.immutable.persistentListOf import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.stringResource @@ -13,18 +15,22 @@ fun DownloadDropdownMenu( expanded: Boolean, onDismissRequest: () -> Unit, onDownloadClicked: (DownloadAction) -> Unit, + modifier: Modifier = Modifier, ) { + val options = persistentListOf( + DownloadAction.NEXT_1_CHAPTER to pluralStringResource(MR.plurals.download_amount, 1, 1), + DownloadAction.NEXT_5_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 5, 5), + DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 10, 10), + DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 25, 25), + DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread), + ) + DropdownMenu( expanded = expanded, onDismissRequest = onDismissRequest, + modifier = modifier, ) { - listOfNotNull( - DownloadAction.NEXT_1_CHAPTER to pluralStringResource(MR.plurals.download_amount, 1, 1), - DownloadAction.NEXT_5_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 5, 5), - DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 10, 10), - DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 25, 25), - DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread), - ).map { (downloadAction, string) -> + options.map { (downloadAction, string) -> DropdownMenuItem( text = { Text(text = string) }, onClick = { diff --git a/app/src/main/java/eu/kanade/presentation/components/DropdownMenu.kt b/app/src/main/java/eu/kanade/presentation/components/DropdownMenu.kt index 531ebd406..c1fcbf524 100644 --- a/app/src/main/java/eu/kanade/presentation/components/DropdownMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/components/DropdownMenu.kt @@ -1,7 +1,10 @@ package eu.kanade.presentation.components +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowRight import androidx.compose.material.icons.outlined.RadioButtonChecked @@ -22,12 +25,17 @@ import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.stringResource import androidx.compose.material3.DropdownMenu as ComposeDropdownMenu +/** + * DropdownMenu but overlaps anchor and has width constraints to better + * match non-Compose implementation. + */ @Composable fun DropdownMenu( expanded: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, offset: DpOffset = DpOffset(8.dp, (-56).dp), + scrollState: ScrollState = rememberScrollState(), properties: PopupProperties = PopupProperties(focusable = true), content: @Composable ColumnScope.() -> Unit, ) { @@ -36,6 +44,7 @@ fun DropdownMenu( onDismissRequest = onDismissRequest, modifier = modifier.sizeIn(minWidth = 196.dp, maxWidth = 196.dp), offset = offset, + scrollState = scrollState, properties = properties, content = content, ) @@ -45,6 +54,7 @@ fun DropdownMenu( fun RadioMenuItem( text: @Composable () -> Unit, isChecked: Boolean, + modifier: Modifier = Modifier, onClick: () -> Unit, ) { DropdownMenuItem( @@ -64,6 +74,7 @@ fun RadioMenuItem( ) } }, + modifier = modifier, ) } @@ -71,25 +82,29 @@ fun RadioMenuItem( fun NestedMenuItem( text: @Composable () -> Unit, children: @Composable ColumnScope.(() -> Unit) -> Unit, + modifier: Modifier = Modifier, ) { var nestedExpanded by remember { mutableStateOf(false) } val closeMenu = { nestedExpanded = false } - DropdownMenuItem( - text = text, - onClick = { nestedExpanded = true }, - trailingIcon = { - Icon( - imageVector = Icons.AutoMirrored.Outlined.ArrowRight, - contentDescription = null, - ) - }, - ) + Box { + DropdownMenuItem( + text = text, + onClick = { nestedExpanded = true }, + trailingIcon = { + Icon( + imageVector = Icons.AutoMirrored.Outlined.ArrowRight, + contentDescription = null, + ) + }, + ) - DropdownMenu( - expanded = nestedExpanded, - onDismissRequest = closeMenu, - ) { - children(closeMenu) + DropdownMenu( + expanded = nestedExpanded, + onDismissRequest = closeMenu, + modifier = modifier, + ) { + children(closeMenu) + } } } diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/ChapterDownloadIndicator.kt b/app/src/main/java/eu/kanade/presentation/manga/components/ChapterDownloadIndicator.kt index 7e1d0d60b..162aa92a3 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/ChapterDownloadIndicator.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/ChapterDownloadIndicator.kt @@ -49,10 +49,10 @@ enum class ChapterDownloadAction { @Composable fun ChapterDownloadIndicator( enabled: Boolean, - modifier: Modifier = Modifier, downloadStateProvider: () -> Download.State, downloadProgressProvider: () -> Int, onClick: (ChapterDownloadAction) -> Unit, + modifier: Modifier = Modifier, ) { when (val downloadState = downloadStateProvider()) { Download.State.NOT_DOWNLOADED -> NotDownloadedIndicator( @@ -109,10 +109,10 @@ private fun NotDownloadedIndicator( @Composable private fun DownloadingIndicator( enabled: Boolean, - modifier: Modifier = Modifier, downloadState: Download.State, downloadProgressProvider: () -> Int, onClick: (ChapterDownloadAction) -> Unit, + modifier: Modifier = Modifier, ) { var isMenuExpanded by remember { mutableStateOf(false) } Box( diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt index 0f2701b7f..905a457a6 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt @@ -182,7 +182,10 @@ private fun RowScope.Button( onClick: () -> Unit, content: (@Composable () -> Unit)? = null, ) { - val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f) + val animatedWeight by animateFloatAsState( + targetValue = if (toConfirm) 2f else 1f, + label = "weight", + ) Column( modifier = Modifier .size(48.dp) diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt index 12c14ce78..4415bbf27 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt @@ -20,7 +20,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import eu.kanade.presentation.components.AppBar diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/Commons.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/Commons.kt index dc128a7aa..4c44abce3 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/Commons.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/Commons.kt @@ -11,7 +11,6 @@ import tachiyomi.presentation.core.i18n.stringResource /** * Returns a string of categories name for settings subtitle */ - @ReadOnlyComposable @Composable fun getCategoriesLabel( diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt index 564b0b647..a12e5790e 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt @@ -65,7 +65,6 @@ object SettingsLibraryScreen : SearchableSettings { allCategories: List, libraryPreferences: LibraryPreferences, ): Preference.PreferenceGroup { - val context = LocalContext.current val scope = rememberCoroutineScope() val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size @@ -76,7 +75,7 @@ object SettingsLibraryScreen : SearchableSettings { val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) + allCategories.fastMap { it.id.toInt() } val labels = listOf(stringResource(MR.strings.default_category_summary)) + - allCategories.fastMap { it.visualName(context) } + allCategories.fastMap { it.visualName } return Preference.PreferenceGroup( title = stringResource(MR.strings.categories), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt index 4af36dc03..bba72cf98 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt @@ -114,6 +114,7 @@ internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier = comp } else { tween(200) }, + label = "highlight", ) Modifier.background(color = highlight) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt index e7778aad5..98667586d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt @@ -20,17 +20,20 @@ class BackupDecoder( * Decode a potentially-gzipped backup. */ fun decode(uri: Uri): Backup { - val backupStringSource = context.contentResolver.openInputStream(uri)!!.source().buffer() + return context.contentResolver.openInputStream(uri)!!.use { inputStream -> + val source = inputStream.source().buffer() - val peeked = backupStringSource.peek() - peeked.require(2) - val id1id2 = peeked.readShort() - val backupString = if (id1id2.toInt() == 0x1f8b) { // 0x1f8b is gzip magic bytes - backupStringSource.gzip().buffer() - } else { - backupStringSource - }.use { it.readByteArray() } + val peeked = source.peek().apply { + require(2) + } + val id1id2 = peeked.readShort() + val backupString = if (id1id2.toInt() == 0x1f8b) { // 0x1f8b is gzip magic bytes + source.gzip().buffer() + } else { + source + }.use { it.readByteArray() } - return parser.decodeFromByteArray(BackupSerializer, backupString) + parser.decodeFromByteArray(BackupSerializer, backupString) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt index ef2a5328d..29969d1e7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt @@ -68,7 +68,7 @@ class BackupCreator( .forEach { it.delete() } // Create new file to place backup - dir?.createFile(BackupCreator.getFilename()) + dir?.createFile(getFilename()) } else { UniFile.fromUri(context, uri) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt index e29628878..e1339f986 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt @@ -25,7 +25,7 @@ class DownloadProvider( private val storageManager: StorageManager = Injekt.get(), ) { - val downloadsDir: UniFile? + private val downloadsDir: UniFile? get() = storageManager.getDownloadsDirectory() /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index d9e8dc54a..b1fdfd414 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -246,7 +246,7 @@ class LibraryUpdateNotifier(private val context: Context) { // Mark chapters as read action addAction( - R.drawable.ic_glasses_24dp, + R.drawable.ic_done_24dp, context.stringResource(MR.strings.action_mark_as_read), NotificationReceiver.markAsReadPendingBroadcast( context, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 92d5311d5..9e5d60d7b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -235,7 +235,6 @@ class NotificationReceiver : BroadcastReceiver() { private const val NAME = "NotificationReceiver" private const val ACTION_SHARE_IMAGE = "$ID.$NAME.SHARE_IMAGE" - private const val ACTION_DELETE_IMAGE = "$ID.$NAME.DELETE_IMAGE" private const val ACTION_SHARE_BACKUP = "$ID.$NAME.SEND_BACKUP" @@ -256,7 +255,6 @@ class NotificationReceiver : BroadcastReceiver() { private const val ACTION_DISMISS_NOTIFICATION = "$ID.$NAME.ACTION_DISMISS_NOTIFICATION" - private const val EXTRA_FILE_LOCATION = "$ID.$NAME.FILE_LOCATION" private const val EXTRA_URI = "$ID.$NAME.URI" private const val EXTRA_NOTIFICATION_ID = "$ID.$NAME.NOTIFICATION_ID" private const val EXTRA_GROUP_ID = "$ID.$NAME.EXTRA_GROUP_ID" @@ -361,7 +359,7 @@ class NotificationReceiver : BroadcastReceiver() { it.id == notificationId }?.groupKey - if (groupId != null && groupId != 0 && groupKey != null && groupKey.isNotEmpty()) { + if (groupId != null && groupId != 0 && !groupKey.isNullOrEmpty()) { val notifications = context.notificationManager.activeNotifications.filter { it.groupKey == groupKey } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt index 7507ba3ef..d2183fdbf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt @@ -6,7 +6,7 @@ import okhttp3.Interceptor import okhttp3.Response import uy.kohesive.injekt.injectLazy -class BangumiInterceptor(val bangumi: Bangumi) : Interceptor { +class BangumiInterceptor(private val bangumi: Bangumi) : Interceptor { private val json: Json by injectLazy() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiModels.kt index 92fa49f24..445fb5194 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiModels.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiModels.kt @@ -62,12 +62,3 @@ fun Track.toBangumiStatus() = when (status) { Bangumi.PLAN_TO_READ -> "wish" else -> throw NotImplementedError("Unknown status: $status") } - -fun toTrackStatus(status: String) = when (status) { - "do" -> Bangumi.READING - "collect" -> Bangumi.COMPLETED - "on_hold" -> Bangumi.ON_HOLD - "dropped" -> Bangumi.DROPPED - "wish" -> Bangumi.PLAN_TO_READ - else -> throw NotImplementedError("Unknown status: $status") -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt index 45abfa675..fe7e42292 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt @@ -5,7 +5,7 @@ import okhttp3.Interceptor import okhttp3.Response import uy.kohesive.injekt.injectLazy -class KitsuInterceptor(val kitsu: Kitsu) : Interceptor { +class KitsuInterceptor(private val kitsu: Kitsu) : Interceptor { private val json: Json by injectLazy() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt index 5ecea52e6..84c64462f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt @@ -5,7 +5,7 @@ import okhttp3.Interceptor import okhttp3.Response import uy.kohesive.injekt.injectLazy -class ShikimoriInterceptor(val shikimori: Shikimori) : Interceptor { +class ShikimoriInterceptor(private val shikimori: Shikimori) : Interceptor { private val json: Json by injectLazy() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt index 01c6b581b..3e0761854 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt @@ -2,9 +2,6 @@ package eu.kanade.tachiyomi.util.system import android.content.Context import android.provider.Settings -import android.view.ViewPropertyAnimator -import android.view.animation.Animation -import androidx.constraintlayout.motion.widget.MotionScene.Transition /** * Gets the duration multiplier for general animations on the device @@ -12,19 +9,3 @@ import androidx.constraintlayout.motion.widget.MotionScene.Transition */ val Context.animatorDurationScale: Float get() = Settings.Global.getFloat(this.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) - -/** Scale the duration of this [Animation] by [Context.animatorDurationScale] */ -fun Animation.applySystemAnimatorScale(context: Context) { - this.duration = (this.duration * context.animatorDurationScale).toLong() -} - -/** Scale the duration of this [Transition] by [Context.animatorDurationScale] */ -fun Transition.applySystemAnimatorScale(context: Context) { - // End layout of cover expanding animation tends to break when the transition is less than ~25ms - this.duration = (this.duration * context.animatorDurationScale).toInt().coerceAtLeast(25) -} - -/** Scale the duration of this [ViewPropertyAnimator] by [Context.animatorDurationScale] */ -fun ViewPropertyAnimator.applySystemAnimatorScale(context: Context): ViewPropertyAnimator = apply { - this.duration = (this.duration * context.animatorDurationScale).toLong() -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt index 3e2834fd2..f051ccbc8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt @@ -17,18 +17,10 @@ private const val TABLET_UI_MIN_SCREEN_WIDTH_PORTRAIT_DP = 700 // make sure icons on the nav rail fit private const val TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP = 600 -fun Context.isTabletUi(): Boolean { - return resources.configuration.isTabletUi() -} - fun Configuration.isTabletUi(): Boolean { return smallestScreenWidthDp >= TABLET_UI_REQUIRED_SCREEN_WIDTH_DP } -fun Configuration.isAutoTabletUiAvailable(): Boolean { - return smallestScreenWidthDp >= TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP -} - // TODO: move the logic to `isTabletUi()` when main activity is rewritten in Compose fun Context.prepareTabletUiContext(): Context { val configuration = resources.configuration diff --git a/app/src/main/res/drawable/ic_done_24dp.xml b/app/src/main/res/drawable/ic_done_24dp.xml new file mode 100644 index 000000000..f6887725c --- /dev/null +++ b/app/src/main/res/drawable/ic_done_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/source_preferences_controller.xml b/app/src/main/res/layout/source_preferences_controller.xml deleted file mode 100644 index 7bf61a875..000000000 --- a/app/src/main/res/layout/source_preferences_controller.xml +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt index c86f780d9..9f860faab 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt @@ -51,6 +51,7 @@ fun OkHttpClient.Builder.rateLimitHost( * @param permits [Int] Number of requests allowed within a period of units. * @param period [Duration] The limiting duration. Defaults to 1.seconds. */ +@Suppress("UNUSED") fun OkHttpClient.Builder.rateLimitHost( httpUrl: HttpUrl, permits: Int, @@ -71,5 +72,6 @@ fun OkHttpClient.Builder.rateLimitHost( * @param permits [Int] Number of requests allowed within a period of units. * @param period [Duration] The limiting duration. Defaults to 1.seconds. */ +@Suppress("UNUSED") fun OkHttpClient.Builder.rateLimitHost(url: String, permits: Int, period: Duration = 1.seconds) = addInterceptor(RateLimitInterceptor(url.toHttpUrlOrNull()?.host, permits, period)) diff --git a/core/src/main/java/tachiyomi/core/preference/InMemoryPreferenceStore.kt b/core/src/main/java/tachiyomi/core/preference/InMemoryPreferenceStore.kt index 83106999f..2fb3ee9ec 100644 --- a/core/src/main/java/tachiyomi/core/preference/InMemoryPreferenceStore.kt +++ b/core/src/main/java/tachiyomi/core/preference/InMemoryPreferenceStore.kt @@ -51,6 +51,7 @@ class InMemoryPreferenceStore( TODO("Not yet implemented") } + @Suppress("UNCHECKED_CAST") override fun getObject( key: String, defaultValue: T, @@ -59,7 +60,7 @@ class InMemoryPreferenceStore( ): Preference { val default = InMemoryPreference(key, null, defaultValue) val data: T? = preferences[key]?.get() as? T - return if (data == null) default else InMemoryPreference(key, data, defaultValue) + return if (data == null) default else InMemoryPreference(key, data, defaultValue) } override fun getAll(): Map { diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt index 51089cf52..d36e2593f 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.gestures.animateTo import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize @@ -78,7 +77,7 @@ fun AdaptiveSheet( onDismissRequest() } } - BoxWithConstraints( + Box( modifier = Modifier .clickable( enabled = true, diff --git a/presentation-core/src/main/res/values/colors.xml b/presentation-core/src/main/res/values/colors.xml index 3d66ea077..551dcc0a7 100644 --- a/presentation-core/src/main/res/values/colors.xml +++ b/presentation-core/src/main/res/values/colors.xml @@ -1,5 +1,5 @@ - + @color/accent_blue #1F888888 @@ -20,21 +20,7 @@ #000000 - #000000 - #DE000000 - #8A000000 - #61000000 #1F000000 - #14000000 - #0F000000 - - #FFFFFFFF - #B3FFFFFF - #8AFFFFFF - #80FFFFFF - #33FFFFFF #1FFFFFFF - #14FFFFFF - #0FFFFFFF diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt index 2de89ecce..20cb4279f 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt @@ -1,10 +1,12 @@ package tachiyomi.presentation.widget +import android.annotation.SuppressLint import androidx.compose.ui.unit.dp import androidx.glance.ImageProvider import androidx.glance.unit.ColorProvider class UpdatesGridGlanceWidget : BaseUpdatesGridGlanceWidget() { + @SuppressLint("RestrictedApi") override val foreground = ColorProvider(R.color.appwidget_on_secondary_container) override val background = ImageProvider(R.drawable.appwidget_background) override val topPadding = 0.dp diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt index f0a014e2a..490540aba 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt @@ -29,36 +29,6 @@ interface SManga : Serializable { return genre?.split(", ")?.map { it.trim() }?.filterNot { it.isBlank() }?.distinct() } - fun copyFrom(other: SManga) { - if (other.author != null) { - author = other.author - } - - if (other.artist != null) { - artist = other.artist - } - - if (other.description != null) { - description = other.description - } - - if (other.genre != null) { - genre = other.genre - } - - if (other.thumbnail_url != null) { - thumbnail_url = other.thumbnail_url - } - - status = other.status - - update_strategy = other.update_strategy - - if (!initialized) { - initialized = other.initialized - } - } - fun copy() = create().also { it.url = url it.title = title diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt index 91b5f5e29..d76a7dd00 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt @@ -6,6 +6,7 @@ package eu.kanade.tachiyomi.source.model * * @since extensions-lib 1.4 */ +@Suppress("UNUSED") enum class UpdateStrategy { /** * Series marked as always update will be included in the library From 950b4a6c907262e80aaf423d523af518b790cd32 Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 25 Dec 2023 16:35:13 -0500 Subject: [PATCH 18/30] Fix read duration statistic getting inflated when restoring history --- .../tachiyomi/data/backup/restore/restorers/MangaRestorer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt index 6ca9f95d9..4cfdde7a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt @@ -328,7 +328,7 @@ class MangaRestorer( readAt = max(item.readAt?.time ?: 0L, dbHistory.last_read?.time ?: 0L) .takeIf { it > 0L } ?.let { Date(it) }, - readDuration = max(item.readDuration, dbHistory.time_read), + readDuration = max(item.readDuration, dbHistory.time_read) - dbHistory.time_read, ) } From f31bc47757b3792f92c2c8721739b5e2d91b825d Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 25 Dec 2023 18:11:22 -0500 Subject: [PATCH 19/30] Clean up storage usage info - Show bar representation of used/total space - Handle all mounted storages - Also included a bunch of unrelated immutables changes, sorry --- .../presentation/more/settings/Preference.kt | 23 ++-- .../settings/screen/SettingsAdvancedScreen.kt | 18 +-- .../screen/SettingsAppearanceScreen.kt | 18 ++- .../settings/screen/SettingsBrowseScreen.kt | 5 +- .../settings/screen/SettingsDataScreen.kt | 57 +++------ .../settings/screen/SettingsDownloadScreen.kt | 29 +++-- .../settings/screen/SettingsLibraryScreen.kt | 21 ++-- .../settings/screen/SettingsReaderScreen.kt | 51 +++++--- .../settings/screen/SettingsSecurityScreen.kt | 10 +- .../settings/screen/SettingsTrackingScreen.kt | 24 ++-- .../screen/data/CreateBackupScreen.kt | 3 +- .../more/settings/screen/data/StorageInfo.kt | 117 ++++++++++++++++++ .../settings/screen/debug/DebugInfoScreen.kt | 14 ++- .../presentation/reader/ChapterTransition.kt | 3 +- .../track/TrackInfoDialogSelector.kt | 3 +- .../tachiyomi/data/cache/ChapterCache.kt | 8 +- .../java/eu/kanade/tachiyomi/di/AppModule.kt | 2 +- .../kanade/tachiyomi/util/storage/DiskUtil.kt | 19 +++ .../commonMain/resources/MR/base/strings.xml | 1 + .../presentation/core/components/Badges.kt | 3 +- 20 files changed, 301 insertions(+), 128 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/more/settings/screen/data/StorageInfo.kt diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt b/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt index c8f99b593..fd8d12067 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt @@ -4,6 +4,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.graphics.vector.ImageVector import eu.kanade.tachiyomi.data.track.Tracker +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.core.preference.Preference as PreferenceData @@ -64,20 +66,20 @@ sealed class Preference { val pref: PreferenceData, override val title: String, override val subtitle: String? = "%s", - val subtitleProvider: @Composable (value: T, entries: Map) -> String? = + val subtitleProvider: @Composable (value: T, entries: ImmutableMap) -> String? = { v, e -> subtitle?.format(e[v]) }, override val icon: ImageVector? = null, override val enabled: Boolean = true, override val onValueChanged: suspend (newValue: T) -> Boolean = { true }, - val entries: Map, + val entries: ImmutableMap, ) : PreferenceItem() { internal fun internalSet(newValue: Any) = pref.set(newValue as T) internal suspend fun internalOnValueChanged(newValue: Any) = onValueChanged(newValue as T) @Composable - internal fun internalSubtitleProvider(value: Any?, entries: Map) = - subtitleProvider(value as T, entries as Map) + internal fun internalSubtitleProvider(value: Any?, entries: ImmutableMap) = + subtitleProvider(value as T, entries as ImmutableMap) } /** @@ -87,13 +89,13 @@ sealed class Preference { val value: String, override val title: String, override val subtitle: String? = "%s", - val subtitleProvider: @Composable (value: String, entries: Map) -> String? = + val subtitleProvider: @Composable (value: String, entries: ImmutableMap) -> String? = { v, e -> subtitle?.format(e[v]) }, override val icon: ImageVector? = null, override val enabled: Boolean = true, override val onValueChanged: suspend (newValue: String) -> Boolean = { true }, - val entries: Map, + val entries: ImmutableMap, ) : PreferenceItem() /** @@ -104,7 +106,10 @@ sealed class Preference { val pref: PreferenceData>, override val title: String, override val subtitle: String? = "%s", - val subtitleProvider: @Composable (value: Set, entries: Map) -> String? = { v, e -> + val subtitleProvider: @Composable ( + value: Set, + entries: ImmutableMap, + ) -> String? = { v, e -> val combined = remember(v) { v.map { e[it] } .takeIf { it.isNotEmpty() } @@ -116,7 +121,7 @@ sealed class Preference { override val enabled: Boolean = true, override val onValueChanged: suspend (newValue: Set) -> Boolean = { true }, - val entries: Map, + val entries: ImmutableMap, ) : PreferenceItem>() /** @@ -170,6 +175,6 @@ sealed class Preference { override val title: String, override val enabled: Boolean = true, - val preferenceItems: List>, + val preferenceItems: ImmutableList>, ) : Preference() } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt index 56f606567..921fd4ae4 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt @@ -51,6 +51,9 @@ import eu.kanade.tachiyomi.util.system.isShizukuInstalled import eu.kanade.tachiyomi.util.system.powerManager import eu.kanade.tachiyomi.util.system.setDefaultSettings import eu.kanade.tachiyomi.util.system.toast +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.launch import logcat.LogPriority import okhttp3.Headers @@ -149,7 +152,7 @@ object SettingsAdvancedScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.label_background_activity), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.TextPreference( title = stringResource(MR.strings.pref_disable_battery_optimization), subtitle = stringResource(MR.strings.pref_disable_battery_optimization_summary), @@ -188,7 +191,7 @@ object SettingsAdvancedScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.label_data), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.TextPreference( title = stringResource(MR.strings.pref_invalidate_download_cache), subtitle = stringResource(MR.strings.pref_invalidate_download_cache_summary), @@ -218,7 +221,7 @@ object SettingsAdvancedScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.label_network), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.TextPreference( title = stringResource(MR.strings.pref_clear_cookies), onClick = { @@ -249,7 +252,7 @@ object SettingsAdvancedScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( pref = networkPreferences.dohProvider(), title = stringResource(MR.strings.pref_dns_over_https), - entries = mapOf( + entries = persistentMapOf( -1 to stringResource(MR.strings.disabled), PREF_DOH_CLOUDFLARE to "Cloudflare", PREF_DOH_GOOGLE to "Google", @@ -302,7 +305,7 @@ object SettingsAdvancedScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.label_library), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.TextPreference( title = stringResource(MR.strings.pref_refresh_library_covers), onClick = { MetadataUpdateJob.startNow(context) }, @@ -362,12 +365,13 @@ object SettingsAdvancedScreen : SearchableSettings { } return Preference.PreferenceGroup( title = stringResource(MR.strings.label_extensions), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.ListPreference( pref = extensionInstallerPref, title = stringResource(MR.strings.ext_installer_pref), entries = extensionInstallerPref.entries - .associateWith { stringResource(it.titleRes) }, + .associateWith { stringResource(it.titleRes) } + .toImmutableMap(), onValueChanged = { if (it == BasePreferences.ExtensionInstaller.SHIZUKU && !context.isShizukuInstalled diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt index 8523de930..365e86b6f 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt @@ -24,6 +24,9 @@ import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.toast +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableMap import org.xmlpull.v1.XmlPullParser import tachiyomi.core.i18n.stringResource import tachiyomi.i18n.MR @@ -66,7 +69,7 @@ object SettingsAppearanceScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_category_theme), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.CustomPreference( title = stringResource(MR.strings.pref_app_theme), ) { @@ -127,7 +130,7 @@ object SettingsAppearanceScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_category_display), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.BasicListPreference( value = currentLanguage, title = stringResource(MR.strings.pref_app_language), @@ -140,7 +143,9 @@ object SettingsAppearanceScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( pref = uiPreferences.tabletUiMode(), title = stringResource(MR.strings.pref_tablet_ui_mode), - entries = TabletUiMode.entries.associateWith { stringResource(it.titleRes) }, + entries = TabletUiMode.entries + .associateWith { stringResource(it.titleRes) } + .toImmutableMap(), onValueChanged = { context.toast(MR.strings.requires_app_restart) true @@ -153,7 +158,8 @@ object SettingsAppearanceScreen : SearchableSettings { .associateWith { val formattedDate = UiPreferences.dateFormat(it).format(now) "${it.ifEmpty { stringResource(MR.strings.label_default) }} ($formattedDate)" - }, + } + .toImmutableMap(), ), Preference.PreferenceItem.SwitchPreference( pref = uiPreferences.relativeTime(), @@ -167,7 +173,7 @@ object SettingsAppearanceScreen : SearchableSettings { ), ) } - private fun getLangs(context: Context): Map { + private fun getLangs(context: Context): ImmutableMap { val langs = mutableListOf>() val parser = context.resources.getXml(R.xml.locales_config) var eventType = parser.eventType @@ -189,7 +195,7 @@ object SettingsAppearanceScreen : SearchableSettings { langs.sortBy { it.second } langs.add(0, Pair("", context.stringResource(MR.strings.label_default))) - return langs.toMap() + return langs.toMap().toImmutableMap() } } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt index 4d9aa2796..61c1db21e 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt @@ -8,6 +8,7 @@ import androidx.fragment.app.FragmentActivity import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.more.settings.Preference import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate +import kotlinx.collections.immutable.persistentListOf import tachiyomi.core.i18n.stringResource import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.stringResource @@ -27,7 +28,7 @@ object SettingsBrowseScreen : SearchableSettings { return listOf( Preference.PreferenceGroup( title = stringResource(MR.strings.label_sources), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.SwitchPreference( pref = sourcePreferences.hideInLibraryItems(), title = stringResource(MR.strings.pref_hide_in_library_items), @@ -36,7 +37,7 @@ object SettingsBrowseScreen : SearchableSettings { ), Preference.PreferenceGroup( title = stringResource(MR.strings.pref_category_nsfw_content), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.SwitchPreference( pref = sourcePreferences.showNsfwSource(), title = stringResource(MR.strings.pref_show_nsfw_source), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index 25b5ca763..aef277da0 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -3,12 +3,9 @@ package eu.kanade.presentation.more.settings.screen import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri -import android.os.Environment -import android.text.format.Formatter import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MultiChoiceSegmentedButtonRow @@ -31,13 +28,15 @@ import com.hippo.unifile.UniFile import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen +import eu.kanade.presentation.more.settings.screen.data.StorageInfo import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding import eu.kanade.presentation.util.relativeTimeSpanString import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob import eu.kanade.tachiyomi.data.cache.ChapterCache -import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.system.toast +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf import logcat.LogPriority import tachiyomi.core.i18n.stringResource import tachiyomi.core.util.lang.launchNonCancellable @@ -65,7 +64,7 @@ object SettingsDataScreen : SearchableSettings { val backupPreferences = Injekt.get() val storagePreferences = Injekt.get() - return listOf( + return persistentListOf( getStorageLocationPref(storagePreferences = storagePreferences), Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.pref_storage_location_info)), @@ -142,7 +141,7 @@ object SettingsDataScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.label_backup), - preferenceItems = listOf( + preferenceItems = persistentListOf( // Manual actions Preference.PreferenceItem.CustomPreference( title = stringResource(restorePreferenceKeyString), @@ -177,7 +176,7 @@ object SettingsDataScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( pref = backupPreferences.backupInterval(), title = stringResource(MR.strings.pref_backup_interval), - entries = mapOf( + entries = persistentMapOf( 0 to stringResource(MR.strings.off), 6 to stringResource(MR.strings.update_6hour), 12 to stringResource(MR.strings.update_12hour), @@ -200,8 +199,8 @@ object SettingsDataScreen : SearchableSettings { @Composable private fun getDataGroup(): Preference.PreferenceGroup { - val scope = rememberCoroutineScope() val context = LocalContext.current + val scope = rememberCoroutineScope() val libraryPreferences = remember { Injekt.get() } val chapterCache = remember { Injekt.get() } @@ -210,8 +209,19 @@ object SettingsDataScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.label_data), - preferenceItems = listOf( - getStorageInfoPref(cacheReadableSize), + preferenceItems = persistentListOf( + Preference.PreferenceItem.CustomPreference( + title = stringResource(MR.strings.pref_storage_usage), + ) { + BasePreferenceWidget( + title = stringResource(MR.strings.pref_storage_usage), + subcomponent = { + StorageInfo( + modifier = Modifier.padding(horizontal = PrefsHorizontalPadding), + ) + }, + ) + }, Preference.PreferenceItem.TextPreference( title = stringResource(MR.strings.pref_clear_chapter_cache), @@ -238,31 +248,4 @@ object SettingsDataScreen : SearchableSettings { ), ) } - - @Composable - fun getStorageInfoPref( - chapterCacheReadableSize: String, - ): Preference.PreferenceItem.CustomPreference { - val context = LocalContext.current - val available = remember { - Formatter.formatFileSize(context, DiskUtil.getAvailableStorageSpace(Environment.getDataDirectory())) - } - val total = remember { - Formatter.formatFileSize(context, DiskUtil.getTotalStorageSpace(Environment.getDataDirectory())) - } - - return Preference.PreferenceItem.CustomPreference( - title = stringResource(MR.strings.pref_storage_usage), - ) { - BasePreferenceWidget( - title = stringResource(MR.strings.pref_storage_usage), - subcomponent = { - // TODO: downloads, SD cards, bar representation?, i18n - Box(modifier = Modifier.padding(horizontal = PrefsHorizontalPadding)) { - Text(text = "Available: $available / $total (chapter cache: $chapterCacheReadableSize)") - } - }, - ) - } - } } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt index c9dd746f1..072013415 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt @@ -12,6 +12,9 @@ import androidx.compose.ui.util.fastMap import eu.kanade.presentation.category.visualName import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.widget.TriStateListDialog +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.runBlocking import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.model.Category @@ -68,7 +71,7 @@ object SettingsDownloadScreen : SearchableSettings { ): Preference.PreferenceGroup { return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_category_delete_chapters), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.SwitchPreference( pref = downloadPreferences.removeAfterMarkedAsRead(), title = stringResource(MR.strings.pref_remove_after_marked_as_read), @@ -76,7 +79,7 @@ object SettingsDownloadScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( pref = downloadPreferences.removeAfterReadSlots(), title = stringResource(MR.strings.pref_remove_after_read), - entries = mapOf( + entries = persistentMapOf( -1 to stringResource(MR.strings.disabled), 0 to stringResource(MR.strings.last_read_chapter), 1 to stringResource(MR.strings.second_to_last), @@ -105,7 +108,9 @@ object SettingsDownloadScreen : SearchableSettings { return Preference.PreferenceItem.MultiSelectListPreference( pref = downloadPreferences.removeExcludeCategories(), title = stringResource(MR.strings.pref_remove_exclude_categories), - entries = categories().associate { it.id.toString() to it.visualName }, + entries = categories() + .associate { it.id.toString() to it.visualName } + .toImmutableMap(), ) } @@ -142,7 +147,7 @@ object SettingsDownloadScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_category_auto_download), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.SwitchPreference( pref = downloadNewChaptersPref, title = stringResource(MR.strings.pref_download_new), @@ -167,17 +172,19 @@ object SettingsDownloadScreen : SearchableSettings { ): Preference.PreferenceGroup { return Preference.PreferenceGroup( title = stringResource(MR.strings.download_ahead), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.ListPreference( pref = downloadPreferences.autoDownloadWhileReading(), title = stringResource(MR.strings.auto_download_while_reading), - entries = listOf(0, 2, 3, 5, 10).associateWith { - if (it == 0) { - stringResource(MR.strings.disabled) - } else { - pluralStringResource(MR.plurals.next_unread_chapters, count = it, it) + entries = listOf(0, 2, 3, 5, 10) + .associateWith { + if (it == 0) { + stringResource(MR.strings.disabled) + } else { + pluralStringResource(MR.plurals.next_unread_chapters, count = it, it) + } } - }, + .toImmutableMap(), ), Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.download_ahead_info)), ), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt index a12e5790e..1ad7410be 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt @@ -20,6 +20,9 @@ import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.widget.TriStateListDialog import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.ui.category.CategoryScreen +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import tachiyomi.domain.category.interactor.GetCategories @@ -79,7 +82,7 @@ object SettingsLibraryScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.categories), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.TextPreference( title = stringResource(MR.strings.action_edit_categories), subtitle = pluralStringResource( @@ -93,7 +96,7 @@ object SettingsLibraryScreen : SearchableSettings { pref = libraryPreferences.defaultCategory(), title = stringResource(MR.strings.default_category), subtitle = selectedCategory?.visualName ?: stringResource(MR.strings.default_category_summary), - entries = ids.zip(labels).toMap(), + entries = ids.zip(labels).toMap().toImmutableMap(), ), Preference.PreferenceItem.SwitchPreference( pref = libraryPreferences.categorizedDisplaySettings(), @@ -146,11 +149,11 @@ object SettingsLibraryScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_category_library_update), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.ListPreference( pref = autoUpdateIntervalPref, title = stringResource(MR.strings.pref_library_update_interval), - entries = mapOf( + entries = persistentMapOf( 0 to stringResource(MR.strings.update_never), 12 to stringResource(MR.strings.update_12hour), 24 to stringResource(MR.strings.update_24hour), @@ -168,7 +171,7 @@ object SettingsLibraryScreen : SearchableSettings { enabled = autoUpdateInterval > 0, title = stringResource(MR.strings.pref_library_update_restriction), subtitle = stringResource(MR.strings.restrictions), - entries = mapOf( + entries = persistentMapOf( DEVICE_ONLY_ON_WIFI to stringResource(MR.strings.connected_to_wifi), DEVICE_NETWORK_NOT_METERED to stringResource(MR.strings.network_not_metered), DEVICE_CHARGING to stringResource(MR.strings.charging), @@ -196,7 +199,7 @@ object SettingsLibraryScreen : SearchableSettings { Preference.PreferenceItem.MultiSelectListPreference( pref = libraryPreferences.autoUpdateMangaRestrictions(), title = stringResource(MR.strings.pref_library_update_manga_restriction), - entries = mapOf( + entries = persistentMapOf( MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read), MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started), MANGA_NON_COMPLETED to stringResource(MR.strings.pref_update_only_non_completed), @@ -217,11 +220,11 @@ object SettingsLibraryScreen : SearchableSettings { ): Preference.PreferenceGroup { return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_chapter_swipe), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.ListPreference( pref = libraryPreferences.swipeToStartAction(), title = stringResource(MR.strings.pref_chapter_swipe_start), - entries = mapOf( + entries = persistentMapOf( LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(MR.strings.disabled), LibraryPreferences.ChapterSwipeAction.ToggleBookmark to @@ -235,7 +238,7 @@ object SettingsLibraryScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( pref = libraryPreferences.swipeToEndAction(), title = stringResource(MR.strings.pref_chapter_swipe_end), - entries = mapOf( + entries = persistentMapOf( LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(MR.strings.disabled), LibraryPreferences.ChapterSwipeAction.ToggleBookmark to diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt index aac9259a1..c28b12d48 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt @@ -10,6 +10,9 @@ import eu.kanade.presentation.more.settings.Preference import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toImmutableMap import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.collectAsState @@ -31,12 +34,13 @@ object SettingsReaderScreen : SearchableSettings { pref = readerPref.defaultReadingMode(), title = stringResource(MR.strings.pref_viewer_type), entries = ReadingMode.entries.drop(1) - .associate { it.flagValue to stringResource(it.stringRes) }, + .associate { it.flagValue to stringResource(it.stringRes) } + .toImmutableMap(), ), Preference.PreferenceItem.ListPreference( pref = readerPref.doubleTapAnimSpeed(), title = stringResource(MR.strings.pref_double_tap_anim_speed), - entries = mapOf( + entries = persistentMapOf( 1 to stringResource(MR.strings.double_tap_anim_speed_0), 500 to stringResource(MR.strings.double_tap_anim_speed_normal), 250 to stringResource(MR.strings.double_tap_anim_speed_fast), @@ -82,17 +86,18 @@ object SettingsReaderScreen : SearchableSettings { val fullscreen by fullscreenPref.collectAsState() return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_category_display), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.ListPreference( pref = readerPreferences.defaultOrientationType(), title = stringResource(MR.strings.pref_rotation_type), entries = ReaderOrientation.entries.drop(1) - .associate { it.flagValue to stringResource(it.stringRes) }, + .associate { it.flagValue to stringResource(it.stringRes) } + .toImmutableMap(), ), Preference.PreferenceItem.ListPreference( pref = readerPreferences.readerTheme(), title = stringResource(MR.strings.pref_reader_theme), - entries = mapOf( + entries = persistentMapOf( 1 to stringResource(MR.strings.black_background), 2 to stringResource(MR.strings.gray_background), 0 to stringResource(MR.strings.white_background), @@ -126,7 +131,7 @@ object SettingsReaderScreen : SearchableSettings { private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_category_reading), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.SwitchPreference( pref = readerPreferences.skipRead(), title = stringResource(MR.strings.pref_skip_read_chapters), @@ -161,23 +166,26 @@ object SettingsReaderScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.pager_viewer), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.ListPreference( pref = navModePref, title = stringResource(MR.strings.pref_viewer_nav), entries = ReaderPreferences.TapZones .mapIndexed { index, it -> index to stringResource(it) } - .toMap(), + .toMap() + .toImmutableMap(), ), Preference.PreferenceItem.ListPreference( pref = readerPreferences.pagerNavInverted(), title = stringResource(MR.strings.pref_read_with_tapping_inverted), - entries = listOf( + entries = persistentListOf( ReaderPreferences.TappingInvertMode.NONE, ReaderPreferences.TappingInvertMode.HORIZONTAL, ReaderPreferences.TappingInvertMode.VERTICAL, ReaderPreferences.TappingInvertMode.BOTH, - ).associateWith { stringResource(it.titleRes) }, + ) + .associateWith { stringResource(it.titleRes) } + .toImmutableMap(), enabled = navMode != 5, ), Preference.PreferenceItem.ListPreference( @@ -185,14 +193,16 @@ object SettingsReaderScreen : SearchableSettings { title = stringResource(MR.strings.pref_image_scale_type), entries = ReaderPreferences.ImageScaleType .mapIndexed { index, it -> index + 1 to stringResource(it) } - .toMap(), + .toMap() + .toImmutableMap(), ), Preference.PreferenceItem.ListPreference( pref = readerPreferences.zoomStart(), title = stringResource(MR.strings.pref_zoom_start), entries = ReaderPreferences.ZoomStart .mapIndexed { index, it -> index + 1 to stringResource(it) } - .toMap(), + .toMap() + .toImmutableMap(), ), Preference.PreferenceItem.SwitchPreference( pref = readerPreferences.cropBorders(), @@ -255,23 +265,26 @@ object SettingsReaderScreen : SearchableSettings { return Preference.PreferenceGroup( title = stringResource(MR.strings.webtoon_viewer), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.ListPreference( pref = navModePref, title = stringResource(MR.strings.pref_viewer_nav), entries = ReaderPreferences.TapZones .mapIndexed { index, it -> index to stringResource(it) } - .toMap(), + .toMap() + .toImmutableMap(), ), Preference.PreferenceItem.ListPreference( pref = readerPreferences.webtoonNavInverted(), title = stringResource(MR.strings.pref_read_with_tapping_inverted), - entries = listOf( + entries = persistentListOf( ReaderPreferences.TappingInvertMode.NONE, ReaderPreferences.TappingInvertMode.HORIZONTAL, ReaderPreferences.TappingInvertMode.VERTICAL, ReaderPreferences.TappingInvertMode.BOTH, - ).associateWith { stringResource(it.titleRes) }, + ) + .associateWith { stringResource(it.titleRes) } + .toImmutableMap(), enabled = navMode != 5, ), Preference.PreferenceItem.SliderPreference( @@ -288,7 +301,7 @@ object SettingsReaderScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( pref = readerPreferences.readerHideThreshold(), title = stringResource(MR.strings.pref_hide_threshold), - entries = mapOf( + entries = persistentMapOf( ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(MR.strings.pref_highest), ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(MR.strings.pref_high), ReaderPreferences.ReaderHideThreshold.LOW to stringResource(MR.strings.pref_low), @@ -341,7 +354,7 @@ object SettingsReaderScreen : SearchableSettings { val readWithVolumeKeys by readWithVolumeKeysPref.collectAsState() return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_reader_navigation), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.SwitchPreference( pref = readWithVolumeKeysPref, title = stringResource(MR.strings.pref_read_with_volume_keys), @@ -359,7 +372,7 @@ object SettingsReaderScreen : SearchableSettings { private fun getActionsGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_reader_actions), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.SwitchPreference( pref = readerPreferences.readWithLongTap(), title = stringResource(MR.strings.pref_read_with_long_tap), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt index 0acb22083..fb1e0932c 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt @@ -10,6 +10,8 @@ import eu.kanade.presentation.more.settings.Preference import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableMap import tachiyomi.core.i18n.stringResource import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.pluralStringResource @@ -55,7 +57,8 @@ object SettingsSecurityScreen : SearchableSettings { 0 -> stringResource(MR.strings.lock_always) else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it) } - }, + } + .toImmutableMap(), onValueChanged = { (context as FragmentActivity).authenticate( title = context.stringResource(MR.strings.lock_when_idle), @@ -70,14 +73,15 @@ object SettingsSecurityScreen : SearchableSettings { pref = securityPreferences.secureScreen(), title = stringResource(MR.strings.secure_screen), entries = SecurityPreferences.SecureScreenMode.entries - .associateWith { stringResource(it.titleRes) }, + .associateWith { stringResource(it.titleRes) } + .toImmutableMap(), ), Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)), ) } } -private val LockAfterValues = listOf( +private val LockAfterValues = persistentListOf( 0, // Always 1, 2, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt index 4aa7a26bd..f81f1ba5d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt @@ -51,6 +51,8 @@ import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.toast +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.withUIContext import tachiyomi.domain.source.service.SourceManager @@ -125,7 +127,7 @@ object SettingsTrackingScreen : SearchableSettings { ), Preference.PreferenceGroup( title = stringResource(MR.strings.services), - preferenceItems = listOf( + preferenceItems = persistentListOf( Preference.PreferenceItem.TrackerPreference( title = trackerManager.myAnimeList.name, tracker = trackerManager.myAnimeList, @@ -167,15 +169,17 @@ object SettingsTrackingScreen : SearchableSettings { ), Preference.PreferenceGroup( title = stringResource(MR.strings.enhanced_services), - preferenceItems = enhancedTrackers.first - .map { service -> - Preference.PreferenceItem.TrackerPreference( - title = service.name, - tracker = service, - login = { (service as EnhancedTracker).loginNoop() }, - logout = service::logout, - ) - } + listOf(Preference.PreferenceItem.InfoPreference(enhancedTrackerInfo)), + preferenceItems = ( + enhancedTrackers.first + .map { service -> + Preference.PreferenceItem.TrackerPreference( + title = service.name, + tracker = service, + login = { (service as EnhancedTracker).loginNoop() }, + logout = service::logout, + ) + } + listOf(Preference.PreferenceItem.InfoPreference(enhancedTrackerInfo)) + ).toImmutableList(), ), ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt index 1d1dfd1b5..9811a3cce 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt @@ -36,6 +36,7 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.toast import kotlinx.collections.immutable.PersistentSet import kotlinx.collections.immutable.minus +import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.plus import kotlinx.coroutines.flow.update @@ -170,7 +171,7 @@ private class CreateBackupScreenModel : StateScreenModel>().mutate { + it.add( Preference.PreferenceItem.TextPreference( title = "Model", subtitle = "${Build.MANUFACTURER} ${Build.MODEL} (${Build.DEVICE})", @@ -105,14 +107,14 @@ class DebugInfoScreen : Screen() { ) if (DeviceUtil.oneUiVersion != null) { - add( + it.add( Preference.PreferenceItem.TextPreference( title = "OneUI version", subtitle = "${DeviceUtil.oneUiVersion}", ), ) } else if (DeviceUtil.miuiMajorVersion != null) { - add( + it.add( Preference.PreferenceItem.TextPreference( title = "MIUI version", subtitle = "${DeviceUtil.miuiMajorVersion}", @@ -127,7 +129,7 @@ class DebugInfoScreen : Screen() { } else { Build.VERSION.RELEASE } - add( + it.add( Preference.PreferenceItem.TextPreference( title = "Android version", subtitle = "$androidVersion (${Build.DISPLAY})", diff --git a/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt b/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt index 2961529a3..d464382c3 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt @@ -37,6 +37,7 @@ import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.data.database.models.toDomainChapter import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter +import kotlinx.collections.immutable.persistentMapOf import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.service.calculateChapterGap import tachiyomi.i18n.MR @@ -234,7 +235,7 @@ private fun ChapterText( maxLines = 5, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleLarge, - inlineContent = mapOf( + inlineContent = persistentMapOf( DownloadedIconContentId to InlineTextContent( Placeholder( width = 22.sp, diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt index c7675fbe7..3531df865 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.unit.dp import dev.icerock.moko.resources.StringResource import eu.kanade.presentation.theme.TachiyomiTheme import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toImmutableList import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.ScrollbarLazyColumn @@ -233,7 +234,7 @@ private fun TrackStatusSelectorPreviews() { TrackStatusSelector( selection = 1, onSelectionChange = {}, - selections = mapOf( + selections = persistentMapOf( // Anilist values 1 to MR.strings.reading, 2 to MR.strings.plan_to_read, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt index 3f155e7f5..56d6262c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt @@ -14,7 +14,6 @@ import okio.buffer import okio.sink import tachiyomi.core.util.system.logcat import tachiyomi.domain.chapter.model.Chapter -import uy.kohesive.injekt.injectLazy import java.io.File import java.io.IOException @@ -26,9 +25,10 @@ import java.io.IOException * * @param context the application context. */ -class ChapterCache(private val context: Context) { - - private val json: Json by injectLazy() +class ChapterCache( + private val context: Context, + private val json: Json, +) { /** Cache class used for cache management. */ private val diskCache = DiskLruCache.open( diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt index 4e4abe744..4e0866dad 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt @@ -111,7 +111,7 @@ class AppModule(val app: Application) : InjektModule { ProtoBuf } - addSingletonFactory { ChapterCache(app) } + addSingletonFactory { ChapterCache(app, get()) } addSingletonFactory { CoverCache(app) } addSingletonFactory { NetworkHelper(app, get()) } diff --git a/core/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt b/core/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt index fc0e81e65..6d5d2ffb6 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt @@ -3,13 +3,32 @@ package eu.kanade.tachiyomi.util.storage import android.content.Context import android.media.MediaScannerConnection import android.net.Uri +import android.os.Environment import android.os.StatFs +import androidx.core.content.ContextCompat import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.util.lang.Hash import java.io.File object DiskUtil { + /** + * Returns the root folders of all the available external storages. + */ + fun getExternalStorages(context: Context): List { + return ContextCompat.getExternalFilesDirs(context, null) + .filterNotNull() + .mapNotNull { + val file = File(it.absolutePath.substringBefore("/Android/")) + val state = Environment.getExternalStorageState(file) + if (state == Environment.MEDIA_MOUNTED || state == Environment.MEDIA_MOUNTED_READ_ONLY) { + file + } else { + null + } + } + } + fun hashKeyForDisk(key: String): String { return Hash.md5(key) } diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml index 0961e4706..e70244387 100644 --- a/i18n/src/commonMain/resources/MR/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/base/strings.xml @@ -519,6 +519,7 @@ Last automatically backed up: %s Data Storage usage + Available: %1$s / Total: %2$s Clear chapter cache Used: %1$s Cache cleared, %1$d files deleted diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/Badges.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/Badges.kt index ee2a05dac..448ee64e2 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/Badges.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/Badges.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.text.PlaceholderVerticalAlign import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import kotlinx.collections.immutable.persistentMapOf @Composable fun BadgeGroup( @@ -66,7 +67,7 @@ fun Badge( val text = buildAnnotatedString { appendInlineContent(iconContentPlaceholder) } - val inlineContent = mapOf( + val inlineContent = persistentMapOf( Pair( iconContentPlaceholder, InlineTextContent( From 4571dc6b5694d8a32446da2cc9bb131a7175c93e Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 25 Dec 2023 18:13:52 -0500 Subject: [PATCH 20/30] Tweak page flashing Closes #10269, maybe. I don't really have something to test with. --- .../java/eu/kanade/presentation/reader/DisplayRefreshHost.kt | 4 ++-- i18n/src/commonMain/resources/MR/base/strings.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt b/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt index 018dbb948..f0afd08a9 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt @@ -30,7 +30,7 @@ fun DisplayRefreshHost( val currentDisplayRefresh = hostState.currentDisplayRefresh LaunchedEffect(currentDisplayRefresh) { if (currentDisplayRefresh) { - delay(200) + delay(1500) hostState.currentDisplayRefresh = false } } @@ -39,7 +39,7 @@ fun DisplayRefreshHost( Canvas( modifier = modifier.fillMaxSize(), ) { - drawRect(Color.White) + drawRect(Color.Black) } } } diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml index e70244387..15edcdd38 100644 --- a/i18n/src/commonMain/resources/MR/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/base/strings.xml @@ -350,7 +350,7 @@ Double tap to zoom Show content in cutout area Animate page transitions - Flash white on page change + Flash on page change Reduces ghosting on e-ink displays Double tap animation speed Show page number From 087da2b2f3f6ca333aa0534b56ab059294f34f9e Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 25 Dec 2023 22:30:41 -0500 Subject: [PATCH 21/30] Update Coil --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fd49de226..146727016 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,7 +40,7 @@ preferencektx = "androidx.preference:preference-ktx:1.2.1" injekt-core = "com.github.inorichi.injekt:injekt-core:65b0440" -coil-bom = { module = "io.coil-kt:coil-bom", version = "2.4.0" } +coil-bom = { module = "io.coil-kt:coil-bom", version = "2.5.0" } coil-core = { module = "io.coil-kt:coil" } coil-gif = { module = "io.coil-kt:coil-gif" } coil-compose = { module = "io.coil-kt:coil-compose" } From 8939274b5cbef7cd8e0c0c0e771f1ae54025baf3 Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 25 Dec 2023 22:58:05 -0500 Subject: [PATCH 22/30] Simplify storage usage bar UI implementation --- .../settings/screen/SettingsDataScreen.kt | 1 - .../more/settings/screen/data/StorageInfo.kt | 65 ++++--------------- 2 files changed, 11 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index aef277da0..752031c74 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -214,7 +214,6 @@ object SettingsDataScreen : SearchableSettings { title = stringResource(MR.strings.pref_storage_usage), ) { BasePreferenceWidget( - title = stringResource(MR.strings.pref_storage_usage), subcomponent = { StorageInfo( modifier = Modifier.padding(horizontal = PrefsHorizontalPadding), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/StorageInfo.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/StorageInfo.kt index c63421689..e45b6bafc 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/StorageInfo.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/StorageInfo.kt @@ -1,30 +1,24 @@ package eu.kanade.presentation.more.settings.screen.data import android.text.format.Formatter -import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.CornerRadius -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.geometry.RoundRect -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Path +import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import eu.kanade.tachiyomi.util.storage.DiskUtil import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.stringResource +import tachiyomi.presentation.core.theme.header +import tachiyomi.presentation.core.util.secondaryItemAlpha import java.io.File @Composable @@ -49,69 +43,32 @@ private fun StorageInfo( file: File, ) { val context = LocalContext.current - val layoutDirection = LocalLayoutDirection.current val available = remember(file) { DiskUtil.getAvailableStorageSpace(file) } val availableText = remember(available) { Formatter.formatFileSize(context, available) } val total = remember(file) { DiskUtil.getTotalStorageSpace(file) } val totalText = remember(total) { Formatter.formatFileSize(context, total) } - val cornerRadius = CornerRadius(100f, 100f) - val usedBarColor = MaterialTheme.colorScheme.primary - val totalBarColor = MaterialTheme.colorScheme.surfaceVariant - Column( verticalArrangement = Arrangement.spacedBy(4.dp), ) { Text( text = file.absolutePath, - fontWeight = FontWeight.Medium, + style = MaterialTheme.typography.header, ) - Canvas( + LinearProgressIndicator( modifier = Modifier + .clip(MaterialTheme.shapes.small) .fillMaxWidth() .height(12.dp), - ) { - drawRoundRect( - color = totalBarColor, - cornerRadius = cornerRadius, - ) - - drawPath( - path = Path().apply { - val pathSize = Size( - width = (1 - (available / total.toFloat())) * size.width, - height = size.height, - ) - addRoundRect( - if (layoutDirection == LayoutDirection.Ltr) { - RoundRect( - rect = Rect( - offset = Offset(0f, 0f), - size = pathSize, - ), - topLeft = cornerRadius, - bottomLeft = cornerRadius, - ) - } else { - RoundRect( - rect = Rect( - offset = Offset(size.width - pathSize.width, 0f), - size = pathSize, - ), - topRight = cornerRadius, - bottomRight = cornerRadius, - ) - }, - ) - }, - color = usedBarColor, - ) - } + progress = { (1 - (available / total.toFloat())) }, + ) Text( text = stringResource(MR.strings.available_disk_space_info, availableText, totalText), + modifier = Modifier.secondaryItemAlpha(), + style = MaterialTheme.typography.bodySmall, ) } } From bfb0d31ff61a6609cdf8ca421d08cbb4236fc5af Mon Sep 17 00:00:00 2001 From: arkon Date: Tue, 26 Dec 2023 13:13:33 -0500 Subject: [PATCH 23/30] Remove skipped updates notification Seems to cause more confusion than it's worth. Will update the UI for the library update skip options to better explain what they're for later. --- .../settings/screen/SettingsDataScreen.kt | 2 +- .../data/library/LibraryUpdateJob.kt | 1 - .../data/library/LibraryUpdateNotifier.kt | 22 ------------------- .../data/notification/Notifications.kt | 8 +------ .../commonMain/resources/MR/base/strings.xml | 2 -- 5 files changed, 2 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index 752031c74..46b37c9e8 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -208,7 +208,7 @@ object SettingsDataScreen : SearchableSettings { val cacheReadableSize = remember(cacheReadableSizeSema) { chapterCache.readableSize } return Preference.PreferenceGroup( - title = stringResource(MR.strings.label_data), + title = stringResource(MR.strings.pref_storage_usage), preferenceItems = persistentListOf( Preference.PreferenceItem.CustomPreference( title = stringResource(MR.strings.pref_storage_usage), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index 7e0f89966..c7a714a01 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -235,7 +235,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet .map { (reason, entries) -> "$reason: [${entries.map { it.first.title }.sorted().joinToString()}]" } .joinToString() } - notifier.showUpdateSkippedNotification(skippedUpdates.size) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index b1fdfd414..06d61589d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -139,27 +139,6 @@ class LibraryUpdateNotifier(private val context: Context) { } } - /** - * Shows notification containing update entries that were skipped. - * - * @param skipped Number of entries that were skipped during the update. - */ - fun showUpdateSkippedNotification(skipped: Int) { - if (skipped == 0) { - return - } - - context.notify( - Notifications.ID_LIBRARY_SKIPPED, - Notifications.CHANNEL_LIBRARY_SKIPPED, - ) { - setContentTitle(context.stringResource(MR.strings.notification_update_skipped, skipped)) - setContentText(context.stringResource(MR.strings.learn_more)) - setSmallIcon(R.drawable.ic_tachi) - setContentIntent(NotificationHandler.openUrl(context, HELP_SKIPPED_URL)) - } - } - /** * Shows the notification containing the result of the update done by the service. * @@ -385,4 +364,3 @@ class LibraryUpdateNotifier(private val context: Context) { private const val NOTIF_MAX_CHAPTERS = 5 private const val NOTIF_TITLE_MAX_LEN = 45 private const val NOTIF_ICON_SIZE = 192 -private const val HELP_SKIPPED_URL = "https://tachiyomi.org/docs/faq/library#why-is-global-update-skipping-entries" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt index 3a76b045d..7632d06db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt @@ -30,8 +30,6 @@ object Notifications { const val ID_LIBRARY_SIZE_WARNING = -103 const val CHANNEL_LIBRARY_ERROR = "library_errors_channel" const val ID_LIBRARY_ERROR = -102 - const val CHANNEL_LIBRARY_SKIPPED = "library_skipped_channel" - const val ID_LIBRARY_SKIPPED = -104 /** * Notification channel and ids used by the downloader. @@ -86,6 +84,7 @@ object Notifications { "updates_ext_channel", "downloader_cache_renewal", "crash_logs_channel", + "library_skipped_channel", ) /** @@ -132,11 +131,6 @@ object Notifications { setGroup(GROUP_LIBRARY) setShowBadge(false) }, - buildNotificationChannel(CHANNEL_LIBRARY_SKIPPED, IMPORTANCE_LOW) { - setName(context.stringResource(MR.strings.channel_skipped)) - setGroup(GROUP_LIBRARY) - setShowBadge(false) - }, buildNotificationChannel(CHANNEL_NEW_CHAPTERS, IMPORTANCE_DEFAULT) { setName(context.stringResource(MR.strings.channel_new_chapters)) }, diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml index 15edcdd38..3e4c68608 100644 --- a/i18n/src/commonMain/resources/MR/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/base/strings.xml @@ -830,7 +830,6 @@ Chapter %1$s and %2$d more Chapters %1$s %1$d update(s) failed - %1$d update(s) skipped Tap to learn more Failed to update cover Please add the entry to your library before doing this @@ -896,7 +895,6 @@ Progress Complete Errors - Skipped Chapter updates App updates Extension updates From fca4f251227f983d8313eda546f35644692f39cb Mon Sep 17 00:00:00 2001 From: arkon Date: Tue, 26 Dec 2023 17:11:26 -0500 Subject: [PATCH 24/30] Always show chapter download indicators - Local chapters are just always "downloaded", but you can't delete them from the app - Unavailable sources show proper state so long as it's stubbed with the name still, but you can't download anything new --- .../eu/kanade/presentation/manga/MangaScreen.kt | 5 +++-- .../manga/components/MangaChapterListItem.kt | 16 +++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index 18fbbc3f5..69690ac5f 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -77,6 +77,7 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.isScrolledToEnd import tachiyomi.presentation.core.util.isScrollingUp +import tachiyomi.source.local.isLocal import java.text.DateFormat import java.util.Date @@ -718,13 +719,13 @@ fun MangaScreenLargeImpl( @Composable private fun SharedMangaBottomActionMenu( selected: List, - modifier: Modifier = Modifier, onMultiBookmarkClicked: (List, bookmarked: Boolean) -> Unit, onMultiMarkAsReadClicked: (List, markAsRead: Boolean) -> Unit, onMarkPreviousAsReadClicked: (Chapter) -> Unit, onDownloadChapter: ((List, ChapterDownloadAction) -> Unit)?, onMultiDeleteClicked: (List) -> Unit, fillFraction: Float, + modifier: Modifier = Modifier, ) { MangaBottomActionMenu( visible = selected.isNotEmpty(), @@ -818,7 +819,7 @@ private fun LazyListScope.sharedChapterItems( read = item.chapter.read, bookmark = item.chapter.bookmark, selected = item.selected, - downloadIndicatorEnabled = !isAnyChapterSelected, + downloadIndicatorEnabled = !isAnyChapterSelected && !manga.isLocal(), downloadStateProvider = { item.downloadState }, downloadProgressProvider = { item.downloadProgress }, chapterSwipeStartAction = chapterSwipeStartAction, diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt index b14d2ed14..9c9f07c1a 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt @@ -203,15 +203,13 @@ fun MangaChapterListItem( } } - if (onDownloadClick != null) { - ChapterDownloadIndicator( - enabled = downloadIndicatorEnabled, - modifier = Modifier.padding(start = 4.dp), - downloadStateProvider = downloadStateProvider, - downloadProgressProvider = downloadProgressProvider, - onClick = onDownloadClick, - ) - } + ChapterDownloadIndicator( + enabled = downloadIndicatorEnabled, + modifier = Modifier.padding(start = 4.dp), + downloadStateProvider = downloadStateProvider, + downloadProgressProvider = downloadProgressProvider, + onClick = { onDownloadClick?.invoke(it) }, + ) } } } From a9b0ac43c490eff063c8b3c8173a90184409ab69 Mon Sep 17 00:00:00 2001 From: arkon Date: Tue, 26 Dec 2023 17:14:24 -0500 Subject: [PATCH 25/30] Allow deleting downloaded chapters even if source isn't available Fixes #9160 --- app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index 69690ac5f..c3ce971e6 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -753,7 +753,7 @@ private fun SharedMangaBottomActionMenu( onDeleteClicked = { onMultiDeleteClicked(selected.fastMap { it.chapter }) }.takeIf { - onDownloadChapter != null && selected.fastAny { it.downloadState == Download.State.DOWNLOADED } + selected.fastAny { it.downloadState == Download.State.DOWNLOADED } }, ) } From 4b208fc7ce95fb3b3746446da54351bcead0533c Mon Sep 17 00:00:00 2001 From: arkon Date: Wed, 27 Dec 2023 13:45:44 -0500 Subject: [PATCH 26/30] Move backup models back to main app module I didn't realize the package name change would break compatibility with forks that still have the old package names... --- .../settings/screen/debug/BackupSchemaScreen.kt | 2 +- .../tachiyomi/data/backup/BackupDecoder.kt | 4 ++-- .../data/backup/create/BackupCreator.kt | 14 +++++++------- .../create/creators/CategoriesBackupCreator.kt | 4 ++-- .../backup/create/creators/MangaBackupCreator.kt | 10 +++++----- .../create/creators/PreferenceBackupCreator.kt | 16 ++++++++-------- .../create/creators/SourcesBackupCreator.kt | 2 +- .../tachiyomi/data/backup/models}/Backup.kt | 2 +- .../data/backup/models}/BackupCategory.kt | 2 +- .../data/backup/models}/BackupChapter.kt | 2 +- .../data/backup/models}/BackupHistory.kt | 2 +- .../tachiyomi/data/backup/models}/BackupManga.kt | 2 +- .../data/backup/models}/BackupPreference.kt | 2 +- .../data/backup/models}/BackupSource.kt | 2 +- .../data/backup/models}/BackupTracking.kt | 2 +- .../data/backup/restore/BackupRestorer.kt | 8 ++++---- .../restore/restorers/CategoriesRestorer.kt | 2 +- .../backup/restore/restorers/MangaRestorer.kt | 10 +++++----- .../restore/restorers/PreferenceRestorer.kt | 16 ++++++++-------- 19 files changed, 52 insertions(+), 52 deletions(-) rename {domain/src/main/java/tachiyomi/domain/backup/model => app/src/main/java/eu/kanade/tachiyomi/data/backup/models}/Backup.kt (93%) rename {domain/src/main/java/tachiyomi/domain/backup/model => app/src/main/java/eu/kanade/tachiyomi/data/backup/models}/BackupCategory.kt (94%) rename {domain/src/main/java/tachiyomi/domain/backup/model => app/src/main/java/eu/kanade/tachiyomi/data/backup/models}/BackupChapter.kt (97%) rename {domain/src/main/java/tachiyomi/domain/backup/model => app/src/main/java/eu/kanade/tachiyomi/data/backup/models}/BackupHistory.kt (94%) rename {domain/src/main/java/tachiyomi/domain/backup/model => app/src/main/java/eu/kanade/tachiyomi/data/backup/models}/BackupManga.kt (98%) rename {domain/src/main/java/tachiyomi/domain/backup/model => app/src/main/java/eu/kanade/tachiyomi/data/backup/models}/BackupPreference.kt (95%) rename {domain/src/main/java/tachiyomi/domain/backup/model => app/src/main/java/eu/kanade/tachiyomi/data/backup/models}/BackupSource.kt (89%) rename {domain/src/main/java/tachiyomi/domain/backup/model => app/src/main/java/eu/kanade/tachiyomi/data/backup/models}/BackupTracking.kt (98%) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/BackupSchemaScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/BackupSchemaScreen.kt index 7d42ebb42..d5652b16a 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/BackupSchemaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/BackupSchemaScreen.kt @@ -17,10 +17,10 @@ import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.util.Screen +import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.util.system.copyToClipboard import kotlinx.collections.immutable.persistentListOf import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator -import tachiyomi.domain.backup.model.Backup import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt index 98667586d..e33572caf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt @@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.data.backup import android.content.Context import android.net.Uri +import eu.kanade.tachiyomi.data.backup.models.Backup +import eu.kanade.tachiyomi.data.backup.models.BackupSerializer import kotlinx.serialization.protobuf.ProtoBuf import okio.buffer import okio.gzip import okio.source -import tachiyomi.domain.backup.model.Backup -import tachiyomi.domain.backup.model.BackupSerializer import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt index 29969d1e7..4065197c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt @@ -12,6 +12,13 @@ import eu.kanade.tachiyomi.data.backup.create.creators.CategoriesBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.MangaBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.PreferenceBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.SourcesBackupCreator +import eu.kanade.tachiyomi.data.backup.models.Backup +import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.backup.models.BackupManga +import eu.kanade.tachiyomi.data.backup.models.BackupPreference +import eu.kanade.tachiyomi.data.backup.models.BackupSerializer +import eu.kanade.tachiyomi.data.backup.models.BackupSource +import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import kotlinx.serialization.protobuf.ProtoBuf import logcat.LogPriority import okio.buffer @@ -19,13 +26,6 @@ import okio.gzip import okio.sink import tachiyomi.core.i18n.stringResource import tachiyomi.core.util.system.logcat -import tachiyomi.domain.backup.model.Backup -import tachiyomi.domain.backup.model.BackupCategory -import tachiyomi.domain.backup.model.BackupManga -import tachiyomi.domain.backup.model.BackupPreference -import tachiyomi.domain.backup.model.BackupSerializer -import tachiyomi.domain.backup.model.BackupSource -import tachiyomi.domain.backup.model.BackupSourcePreferences import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.manga.interactor.GetFavorites import tachiyomi.domain.manga.model.Manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt index 164957768..e1ed56ee1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.backup.create.creators -import tachiyomi.domain.backup.model.BackupCategory -import tachiyomi.domain.backup.model.backupCategoryMapper +import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.backup.models.backupCategoryMapper import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.model.Category import uy.kohesive.injekt.Injekt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt index 94a550e31..67182ba83 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt @@ -1,13 +1,13 @@ package eu.kanade.tachiyomi.data.backup.create.creators import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags +import eu.kanade.tachiyomi.data.backup.models.BackupChapter +import eu.kanade.tachiyomi.data.backup.models.BackupHistory +import eu.kanade.tachiyomi.data.backup.models.BackupManga +import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper +import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import tachiyomi.data.DatabaseHandler -import tachiyomi.domain.backup.model.BackupChapter -import tachiyomi.domain.backup.model.BackupHistory -import tachiyomi.domain.backup.model.BackupManga -import tachiyomi.domain.backup.model.backupChapterMapper -import tachiyomi.domain.backup.model.backupTrackMapper import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.history.interactor.GetHistory import tachiyomi.domain.manga.model.Manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt index 74e36da19..c75612de9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt @@ -1,18 +1,18 @@ package eu.kanade.tachiyomi.data.backup.create.creators +import eu.kanade.tachiyomi.data.backup.models.BackupPreference +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.source.ConfigurableSource import eu.kanade.tachiyomi.source.preferenceKey import eu.kanade.tachiyomi.source.sourcePreferences import tachiyomi.core.preference.Preference import tachiyomi.core.preference.PreferenceStore -import tachiyomi.domain.backup.model.BackupPreference -import tachiyomi.domain.backup.model.BackupSourcePreferences -import tachiyomi.domain.backup.model.BooleanPreferenceValue -import tachiyomi.domain.backup.model.FloatPreferenceValue -import tachiyomi.domain.backup.model.IntPreferenceValue -import tachiyomi.domain.backup.model.LongPreferenceValue -import tachiyomi.domain.backup.model.StringPreferenceValue -import tachiyomi.domain.backup.model.StringSetPreferenceValue import tachiyomi.domain.source.service.SourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt index bf1c7ba36..075e449a7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.backup.create.creators +import eu.kanade.tachiyomi.data.backup.models.BackupSource import eu.kanade.tachiyomi.source.Source -import tachiyomi.domain.backup.model.BackupSource import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.source.service.SourceManager import uy.kohesive.injekt.Injekt diff --git a/domain/src/main/java/tachiyomi/domain/backup/model/Backup.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt similarity index 93% rename from domain/src/main/java/tachiyomi/domain/backup/model/Backup.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt index 2c3ecff5f..cdc5c4ad2 100644 --- a/domain/src/main/java/tachiyomi/domain/backup/model/Backup.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt @@ -1,4 +1,4 @@ -package tachiyomi.domain.backup.model +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.Serializer diff --git a/domain/src/main/java/tachiyomi/domain/backup/model/BackupCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt similarity index 94% rename from domain/src/main/java/tachiyomi/domain/backup/model/BackupCategory.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt index 1a39ecee9..df517e8ed 100644 --- a/domain/src/main/java/tachiyomi/domain/backup/model/BackupCategory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt @@ -1,4 +1,4 @@ -package tachiyomi.domain.backup.model +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/domain/src/main/java/tachiyomi/domain/backup/model/BackupChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt similarity index 97% rename from domain/src/main/java/tachiyomi/domain/backup/model/BackupChapter.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt index c232ffa4b..567ca372c 100644 --- a/domain/src/main/java/tachiyomi/domain/backup/model/BackupChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt @@ -1,4 +1,4 @@ -package tachiyomi.domain.backup.model +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/domain/src/main/java/tachiyomi/domain/backup/model/BackupHistory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupHistory.kt similarity index 94% rename from domain/src/main/java/tachiyomi/domain/backup/model/BackupHistory.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupHistory.kt index cb692cde2..1108a376e 100644 --- a/domain/src/main/java/tachiyomi/domain/backup/model/BackupHistory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupHistory.kt @@ -1,4 +1,4 @@ -package tachiyomi.domain.backup.model +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/domain/src/main/java/tachiyomi/domain/backup/model/BackupManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt similarity index 98% rename from domain/src/main/java/tachiyomi/domain/backup/model/BackupManga.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt index b34a29cd4..43a8a906c 100644 --- a/domain/src/main/java/tachiyomi/domain/backup/model/BackupManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt @@ -1,4 +1,4 @@ -package tachiyomi.domain.backup.model +package eu.kanade.tachiyomi.data.backup.models import eu.kanade.tachiyomi.source.model.UpdateStrategy import kotlinx.serialization.Serializable diff --git a/domain/src/main/java/tachiyomi/domain/backup/model/BackupPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt similarity index 95% rename from domain/src/main/java/tachiyomi/domain/backup/model/BackupPreference.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt index 516d4dac2..3884f37e3 100644 --- a/domain/src/main/java/tachiyomi/domain/backup/model/BackupPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt @@ -1,4 +1,4 @@ -package tachiyomi.domain.backup.model +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/domain/src/main/java/tachiyomi/domain/backup/model/BackupSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt similarity index 89% rename from domain/src/main/java/tachiyomi/domain/backup/model/BackupSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt index aae2cf03d..bfd2c93bf 100644 --- a/domain/src/main/java/tachiyomi/domain/backup/model/BackupSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt @@ -1,4 +1,4 @@ -package tachiyomi.domain.backup.model +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/domain/src/main/java/tachiyomi/domain/backup/model/BackupTracking.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt similarity index 98% rename from domain/src/main/java/tachiyomi/domain/backup/model/BackupTracking.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt index dff8f2582..910a8adac 100644 --- a/domain/src/main/java/tachiyomi/domain/backup/model/BackupTracking.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt @@ -1,4 +1,4 @@ -package tachiyomi.domain.backup.model +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt index 5d9fd8f99..796d74ae1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt @@ -4,6 +4,10 @@ import android.content.Context import android.net.Uri import eu.kanade.tachiyomi.data.backup.BackupDecoder import eu.kanade.tachiyomi.data.backup.BackupNotifier +import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.backup.models.BackupManga +import eu.kanade.tachiyomi.data.backup.models.BackupPreference +import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import eu.kanade.tachiyomi.data.backup.restore.restorers.CategoriesRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceRestorer @@ -13,10 +17,6 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch import tachiyomi.core.i18n.stringResource -import tachiyomi.domain.backup.model.BackupCategory -import tachiyomi.domain.backup.model.BackupManga -import tachiyomi.domain.backup.model.BackupPreference -import tachiyomi.domain.backup.model.BackupSourcePreferences import tachiyomi.i18n.MR import java.io.File import java.text.SimpleDateFormat diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt index 00f7fe96c..f98af1045 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers +import eu.kanade.tachiyomi.data.backup.models.BackupCategory import tachiyomi.data.DatabaseHandler -import tachiyomi.domain.backup.model.BackupCategory import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.library.service.LibraryPreferences import uy.kohesive.injekt.Injekt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt index 4cfdde7a4..a09d5b1d7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt @@ -1,13 +1,13 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers import eu.kanade.domain.manga.interactor.UpdateManga +import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.backup.models.BackupChapter +import eu.kanade.tachiyomi.data.backup.models.BackupHistory +import eu.kanade.tachiyomi.data.backup.models.BackupManga +import eu.kanade.tachiyomi.data.backup.models.BackupTracking import tachiyomi.data.DatabaseHandler import tachiyomi.data.UpdateStrategyColumnAdapter -import tachiyomi.domain.backup.model.BackupCategory -import tachiyomi.domain.backup.model.BackupChapter -import tachiyomi.domain.backup.model.BackupHistory -import tachiyomi.domain.backup.model.BackupManga -import tachiyomi.domain.backup.model.BackupTracking import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.chapter.model.Chapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt index ac215fbb6..1062937d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt @@ -2,18 +2,18 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers import android.content.Context import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob +import eu.kanade.tachiyomi.data.backup.models.BackupPreference +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.library.LibraryUpdateJob import eu.kanade.tachiyomi.source.sourcePreferences import tachiyomi.core.preference.AndroidPreferenceStore import tachiyomi.core.preference.PreferenceStore -import tachiyomi.domain.backup.model.BackupPreference -import tachiyomi.domain.backup.model.BackupSourcePreferences -import tachiyomi.domain.backup.model.BooleanPreferenceValue -import tachiyomi.domain.backup.model.FloatPreferenceValue -import tachiyomi.domain.backup.model.IntPreferenceValue -import tachiyomi.domain.backup.model.LongPreferenceValue -import tachiyomi.domain.backup.model.StringPreferenceValue -import tachiyomi.domain.backup.model.StringSetPreferenceValue import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get From fbf8c46b74aad584c744469daa2b4719f94bd725 Mon Sep 17 00:00:00 2001 From: KaiserBh Date: Thu, 28 Dec 2023 23:10:14 +1100 Subject: [PATCH 27/30] refactor: tidy up and fix upstream conflicts. Signed-off-by: KaiserBh --- .../kanade/tachiyomi/data/sync/SyncManager.kt | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt index b1801297f..16b17697f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt @@ -9,7 +9,8 @@ import eu.kanade.tachiyomi.data.backup.models.BackupChapter import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupSerializer import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob -import eu.kanade.tachiyomi.data.backup.restore.MangaRestorer +import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions +import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaRestorer import eu.kanade.tachiyomi.data.sync.service.GoogleDriveSyncService import eu.kanade.tachiyomi.data.sync.service.SyncData import eu.kanade.tachiyomi.data.sync.service.SyncYomiSyncService @@ -48,7 +49,7 @@ class SyncManager( private val getFavorites: GetFavorites = Injekt.get(), private val getCategories: GetCategories = Injekt.get(), ) { - private val backupCreator: BackupCreator = BackupCreator(context) + private val backupCreator: BackupCreator = BackupCreator(context, false) private val notifier: SyncNotifier = SyncNotifier(context) private val mangaRestorer: MangaRestorer = MangaRestorer() @@ -72,12 +73,11 @@ class SyncManager( suspend fun syncData() { val databaseManga = getAllMangaFromDB() val backup = Backup( - backupCreator.backupMangas(databaseManga, BackupCreateFlags.AutomaticDefaults), - backupCreator.backupCategories(BackupCreateFlags.AutomaticDefaults), - emptyList(), - backupCreator.prepExtensionInfoForSync(databaseManga), - backupCreator.backupAppPreferences(BackupCreateFlags.AutomaticDefaults), - backupCreator.backupSourcePreferences(BackupCreateFlags.AutomaticDefaults), + backupManga = backupCreator.backupMangas(databaseManga, BackupCreateFlags.AutomaticDefaults), + backupCategories = backupCreator.backupCategories(BackupCreateFlags.AutomaticDefaults), + backupSources = backupCreator.backupSources(databaseManga), + backupPreferences = backupCreator.backupAppPreferences(BackupCreateFlags.AutomaticDefaults), + backupSourcePreferences = backupCreator.backupSourcePreferences(BackupCreateFlags.AutomaticDefaults), ) // Create the SyncData object @@ -116,6 +116,8 @@ class SyncManager( backupManga = filteredFavorites, backupCategories = remoteBackup.backupCategories, backupSources = remoteBackup.backupSources, + backupPreferences = remoteBackup.backupPreferences, + backupSourcePreferences = remoteBackup.backupSourcePreferences, ) // It's local sync no need to restore data. (just update remote data) @@ -128,7 +130,11 @@ class SyncManager( val backupUri = writeSyncDataToCache(context, newSyncData) logcat(LogPriority.DEBUG) { "Got Backup Uri: $backupUri" } if (backupUri != null) { - BackupRestoreJob.start(context, backupUri, sync = true) + BackupRestoreJob.start(context, backupUri, sync = true, options = RestoreOptions( + appSettings = true, + sourceSettings = true, + library = true, + )) } else { logcat(LogPriority.ERROR) { "Failed to write sync data to file" } } From 309bfd1733118e2774015423853d1ad3a333ab01 Mon Sep 17 00:00:00 2001 From: KaiserBh Date: Thu, 28 Dec 2023 23:10:51 +1100 Subject: [PATCH 28/30] refactor: SyncManager uses them as well can't be private. Signed-off-by: KaiserBh --- .../eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt index ddc4c586d..2ec832e3e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt @@ -118,7 +118,7 @@ class BackupCreator( } } - private suspend fun backupCategories(options: Int): List { + suspend fun backupCategories(options: Int): List { if (options and BACKUP_CATEGORY != BACKUP_CATEGORY) return emptyList() return categoriesBackupCreator.backupCategories() @@ -128,7 +128,7 @@ class BackupCreator( return mangaBackupCreator.backupMangas(mangas, flags) } - private fun backupSources(mangas: List): List { + fun backupSources(mangas: List): List { return sourcesBackupCreator.backupSources(mangas) } From 6677c90a39e866be1814db0e125107f9a6bb187a Mon Sep 17 00:00:00 2001 From: KaiserBh Date: Thu, 28 Dec 2023 23:52:32 +1100 Subject: [PATCH 29/30] chore: ktlint Signed-off-by: KaiserBh --- .../more/settings/screen/SettingsDataScreen.kt | 5 ----- .../eu/kanade/tachiyomi/data/sync/SyncManager.kt | 15 ++++++++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index 8249713ae..ca12e19a2 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -41,9 +41,6 @@ import eu.kanade.tachiyomi.data.sync.SyncDataJob import eu.kanade.tachiyomi.data.sync.SyncManager import eu.kanade.tachiyomi.data.sync.service.GoogleDriveService import eu.kanade.tachiyomi.data.sync.service.GoogleDriveSyncService -import eu.kanade.tachiyomi.util.storage.DiskUtil -import eu.kanade.tachiyomi.util.system.DeviceUtil -import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.toast import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf @@ -284,7 +281,6 @@ object SettingsDataScreen : SearchableSettings { ) + getSyncServicePreferences(syncPreferences, syncService) } - @Composable private fun getSyncServicePreferences(syncPreferences: SyncPreferences, syncService: Int): List { val syncServiceType = SyncManager.SyncService.fromInt(syncService) @@ -505,5 +501,4 @@ object SettingsDataScreen : SearchableSettings { }, ) } - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt index 16b17697f..6deee8429 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt @@ -130,11 +130,16 @@ class SyncManager( val backupUri = writeSyncDataToCache(context, newSyncData) logcat(LogPriority.DEBUG) { "Got Backup Uri: $backupUri" } if (backupUri != null) { - BackupRestoreJob.start(context, backupUri, sync = true, options = RestoreOptions( - appSettings = true, - sourceSettings = true, - library = true, - )) + BackupRestoreJob.start( + context, + backupUri, + sync = true, + options = RestoreOptions( + appSettings = true, + sourceSettings = true, + library = true, + ), + ) } else { logcat(LogPriority.ERROR) { "Failed to write sync data to file" } } From ae8b1d891399e3d1b337e7f4e4c54c96e0163f8b Mon Sep 17 00:00:00 2001 From: KaiserBh Date: Fri, 29 Dec 2023 00:58:40 +1100 Subject: [PATCH 30/30] refactor: clean up comparison. Signed-off-by: KaiserBh --- .../kanade/tachiyomi/data/sync/SyncManager.kt | 88 +++++++++++-------- 1 file changed, 52 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt index 6deee8429..414218cc7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncManager.kt @@ -22,6 +22,7 @@ import tachiyomi.data.Chapters import tachiyomi.data.DatabaseHandler import tachiyomi.data.manga.MangaMapper.mapManga import tachiyomi.domain.category.interactor.GetCategories +import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.manga.interactor.GetFavorites import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.sync.SyncPreferences @@ -169,58 +170,73 @@ class SyncManager( } /** - * Compares two Manga objects (one from the local database and one from the backup) to check if they are different. - * @param localManga the Manga object from the local database. - * @param remoteManga the BackupManga object from the backup. - * @return true if the Manga objects are different, otherwise false. + * Determines if there are differences between the local manga details and categories and their corresponding + * remote backup versions. This is used to identify changes or updates that might have occurred. + * + * @param localManga The manga object from the local database of type [Manga]. + * @param backupManga The manga object from the remote backup of type [BackupManga]. + * + * @return Boolean indicating whether there are differences. Returns true if any of the following is true: + * - The local manga details differ from the remote backup manga details. + * - The chapters of the local manga differ from the chapters of the remote backup manga. + * - The categories of the local manga differ from the categories of the remote backup manga. + * + * This function uses a combination of direct property comparisons and delegated comparison functions + * to assess differences across manga details, chapters, and categories. It relies on proper conversion + * of remote manga and chapters to the local format for accurate comparison. */ - private suspend fun isMangaDifferent(localManga: Manga, remoteManga: BackupManga): Boolean { + private suspend fun isMangaDifferent(localManga: Manga, backupManga: BackupManga): Boolean { + val remoteManga = backupManga.getMangaImpl() val localChapters = handler.await { chaptersQueries.getChaptersByMangaId(localManga.id, 0).executeAsList() } val localCategories = getCategories.await(localManga.id).map { it.order } - return localManga.source != remoteManga.source || - localManga.url != remoteManga.url || - localManga.title != remoteManga.title || - localManga.artist != remoteManga.artist || - localManga.author != remoteManga.author || - localManga.description != remoteManga.description || - localManga.genre != remoteManga.genre || - localManga.status.toInt() != remoteManga.status || - localManga.thumbnailUrl != remoteManga.thumbnailUrl || - localManga.dateAdded != remoteManga.dateAdded || - localManga.chapterFlags.toInt() != remoteManga.chapterFlags || - localManga.favorite != remoteManga.favorite || - localManga.viewerFlags.toInt() != remoteManga.viewer_flags || - localManga.updateStrategy != remoteManga.updateStrategy || - areChaptersDifferent(localChapters, remoteManga.chapters) || - localCategories != remoteManga.categories + return localManga != remoteManga || areChaptersDifferent(localChapters, backupManga.chapters) || localCategories != backupManga.categories } /** - * Compares two lists of chapters (one from the local database and one from the backup) to check if they are different. - * @param localChapters the list of chapters from the local database. - * @param remoteChapters the list of BackupChapter objects from the backup. - * @return true if the lists of chapters are different, otherwise false. + * Checks if there are any differences between a list of local chapters and a list of backup chapters. + * This function is used to determine if updates or changes have occurred between the two sets of chapters. + * + * @param localChapters The list of chapters from the local source, of type [Chapters]. + * @param remoteChapters The list of chapters from the remote backup source, of type [BackupChapter]. + * + * @return Boolean indicating whether there are differences. Returns true if any of the following is true: + * - The count of local and remote chapters differs. + * - Any corresponding chapters (matched by URL) have differing attributes including name, scanlator, + * read status, bookmark status, last page read, chapter number, source order, fetch date, upload date, + * or last modified date. + * + * Each chapter is compared based on a set of fields that define its content and state. If any of these fields + * differ between the local chapter and its corresponding remote chapter, it is considered a difference. + * */ private fun areChaptersDifferent(localChapters: List, remoteChapters: List): Boolean { + // Early return if the sizes are different if (localChapters.size != remoteChapters.size) { return true } + // Convert all remote chapters to Chapter instances + val convertedRemoteChapters = remoteChapters.map { it.toChapterImpl() } + + // Create a map for the local chapters for efficient comparison val localChapterMap = localChapters.associateBy { it.url } - return remoteChapters.any { remoteChapter -> - localChapterMap[remoteChapter.url]?.let { localChapter -> + // Check for any differences + return convertedRemoteChapters.any { remoteChapter -> + val localChapter = localChapterMap[remoteChapter.url] + localChapter == null || // No corresponding local chapter + localChapter.url != remoteChapter.url || localChapter.name != remoteChapter.name || - localChapter.scanlator != remoteChapter.scanlator || - localChapter.read != remoteChapter.read || - localChapter.bookmark != remoteChapter.bookmark || - localChapter.last_page_read != remoteChapter.lastPageRead || - localChapter.date_fetch != remoteChapter.dateFetch || - localChapter.date_upload != remoteChapter.dateUpload || - localChapter.chapter_number.toFloat() != remoteChapter.chapterNumber || - localChapter.source_order != remoteChapter.sourceOrder - } ?: true + localChapter.scanlator != remoteChapter.scanlator || + localChapter.read != remoteChapter.read || + localChapter.bookmark != remoteChapter.bookmark || + localChapter.last_page_read != remoteChapter.lastPageRead || + localChapter.chapter_number != remoteChapter.chapterNumber || + localChapter.source_order != remoteChapter.sourceOrder || + localChapter.date_fetch != remoteChapter.dateFetch || + localChapter.date_upload != remoteChapter.dateUpload || + localChapter.last_modified_at != remoteChapter.lastModifiedAt } }