From 4c1124fdb0c624eec6c65820539d35f7e6d65ef3 Mon Sep 17 00:00:00 2001 From: NGB-Was-Taken <76197326+NGB-Was-Taken@users.noreply.github.com> Date: Sat, 15 Nov 2025 16:56:17 +0545 Subject: [PATCH] Fix compile time errors and make it build --- .../source/service/SourcePreferences.kt | 2 +- .../presentation/reader/ChapterListDialog.kt | 1 + .../data/download/DownloadProvider.kt | 2 +- .../tachiyomi/source/SourceExtensions.kt | 2 +- .../design/MigrationBottomSheetDialog.kt | 135 ---- .../advanced/design/PreMigrationScreen.kt | 213 ------ .../design/PreMigrationScreenModel.kt | 137 ---- .../advanced/process/MigrationListScreen.kt | 155 ----- .../process/MigrationListScreenModel.kt | 609 ------------------ .../process/MigrationProcedureConfig.kt | 9 - .../search/MigrateSearchScreenModel.kt | 3 - .../migration/sources/MigrateSourceTab.kt | 11 +- .../ui/history/HistoryScreenModel.kt | 6 +- .../kanade/tachiyomi/ui/history/HistoryTab.kt | 5 +- .../eu/kanade/tachiyomi/ui/home/HomeScreen.kt | 1 + .../ui/library/LibraryScreenModel.kt | 1 + .../kanade/tachiyomi/ui/main/MainActivity.kt | 4 +- .../tachiyomi/ui/manga/MangaScreenModel.kt | 8 +- .../tachiyomi/ui/reader/ReaderViewModel.kt | 1 + .../ui/reader/loader/ChapterLoader.kt | 1 + .../main/java/exh/log/CrashlyticsPrinter.kt | 4 +- .../exh/md/follows/MangaDexFollowsScreen.kt | 24 +- 22 files changed, 35 insertions(+), 1299 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationBottomSheetDialog.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreenModel.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt diff --git a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt index faffa3bd3..91d93db70 100644 --- a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt @@ -95,7 +95,7 @@ class SourcePreferences( fun defaultMangaOrder() = preferenceStore.getString("default_manga_order", "") - fun migrationSources() = preferenceStore.getString("migrate_sources", "") + //fun migrationSources() = preferenceStore.getString("migrate_sources", "") fun smartMigration() = preferenceStore.getBoolean("smart_migrate", false) diff --git a/app/src/main/java/eu/kanade/presentation/reader/ChapterListDialog.kt b/app/src/main/java/eu/kanade/presentation/reader/ChapterListDialog.kt index 7d5369c78..846c17d60 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/ChapterListDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/ChapterListDialog.kt @@ -74,6 +74,7 @@ fun ChapterListDialog( downloadManager.isChapterDownloaded( chapterItem.chapter.name, chapterItem.chapter.scanlator, + chapterItem.chapter.url, chapterItem.manga.ogTitle, chapterItem.manga.source, ) 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 b591b3342..ec08f7052 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt @@ -145,7 +145,7 @@ class DownloadProvider( val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return emptyList() return mangaDir.listFiles().orEmpty().asList().filter { chapters.find { chp -> - getValidChapterDirNames(chp.name, chp.scanlator).any { dir -> + getValidChapterDirNames(chp.name, chp.scanlator, chp.url).any { dir -> mangaDir.findFile(dir) != null } } == null || diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceExtensions.kt index 5580d039d..66d20f32b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/SourceExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceExtensions.kt @@ -8,7 +8,7 @@ import uy.kohesive.injekt.api.get fun Source.getNameForMangaInfo( // SY --> - mergeSources: List?, + mergeSources: List? = null, enabledLanguages: List = Injekt.get().enabledLanguages().get() .filterNot { it in listOf("all", "other") }, // SY <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationBottomSheetDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationBottomSheetDialog.kt deleted file mode 100644 index dcc74446f..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationBottomSheetDialog.kt +++ /dev/null @@ -1,135 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.design - -import android.view.LayoutInflater -import android.widget.CompoundButton -import android.widget.RadioButton -import android.widget.RadioGroup -import android.widget.Toast -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.Modifier -import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.view.isVisible -import eu.kanade.domain.source.service.SourcePreferences -import eu.kanade.presentation.components.AdaptiveSheet -import eu.kanade.tachiyomi.databinding.MigrationBottomSheetBinding -import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags -import eu.kanade.tachiyomi.util.system.toast -import tachiyomi.core.common.preference.Preference -import tachiyomi.core.common.util.lang.toLong -import tachiyomi.i18n.sy.SYMR -import uy.kohesive.injekt.injectLazy - -@Composable -fun MigrationBottomSheetDialog( - onDismissRequest: () -> Unit, - onStartMigration: (extraParam: String?) -> Unit, -) { - val startMigration = rememberUpdatedState(onStartMigration) - val state = remember { - MigrationBottomSheetDialogState(startMigration) - } - AdaptiveSheet(onDismissRequest = onDismissRequest) { - AndroidView( - factory = { factoryContext -> - val binding = MigrationBottomSheetBinding.inflate(LayoutInflater.from(factoryContext)) - state.initPreferences(binding) - binding.root - }, - modifier = Modifier.fillMaxWidth(), - ) - } -} - -class MigrationBottomSheetDialogState(private val onStartMigration: State<(extraParam: String?) -> Unit>) { - private val preferences: SourcePreferences by injectLazy() - - /** - * Init general reader preferences. - */ - fun initPreferences(binding: MigrationBottomSheetBinding) { - val flags = preferences.migrateFlags().get() - - binding.migChapters.isChecked = MigrationFlags.hasChapters(flags) - binding.migCategories.isChecked = MigrationFlags.hasCategories(flags) - binding.migTracking.isChecked = MigrationFlags.hasTracks(flags) - binding.migCustomCover.isChecked = MigrationFlags.hasCustomCover(flags) - binding.migExtra.isChecked = MigrationFlags.hasExtra(flags) - binding.migDeleteDownloaded.isChecked = MigrationFlags.hasDeleteChapters(flags) - binding.migNotes.isChecked = MigrationFlags.hasNotes(flags) - - binding.migChapters.setOnCheckedChangeListener { _, _ -> setFlags(binding) } - binding.migCategories.setOnCheckedChangeListener { _, _ -> setFlags(binding) } - binding.migTracking.setOnCheckedChangeListener { _, _ -> setFlags(binding) } - binding.migCustomCover.setOnCheckedChangeListener { _, _ -> setFlags(binding) } - binding.migExtra.setOnCheckedChangeListener { _, _ -> setFlags(binding) } - binding.migDeleteDownloaded.setOnCheckedChangeListener { _, _ -> setFlags(binding) } - binding.migNotes.setOnCheckedChangeListener { _, _ -> setFlags(binding) } - - binding.useSmartSearch.bindToPreference(preferences.smartMigration()) - binding.extraSearchParamText.isVisible = false - binding.extraSearchParam.setOnCheckedChangeListener { _, isChecked -> - binding.extraSearchParamText.isVisible = isChecked - } - binding.sourceGroup.bindToPreference(preferences.useSourceWithMost()) - - binding.skipStep.isChecked = preferences.skipPreMigration().get() - binding.HideNotFoundManga.isChecked = preferences.hideNotFoundMigration().get() - binding.OnlyShowUpdates.isChecked = preferences.showOnlyUpdatesMigration().get() - binding.skipStep.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) { - binding.root.context.toast( - SYMR.strings.pre_migration_skip_toast, - Toast.LENGTH_LONG, - ) - } - } - - binding.migrateBtn.setOnClickListener { - preferences.skipPreMigration().set(binding.skipStep.isChecked) - preferences.hideNotFoundMigration().set(binding.HideNotFoundManga.isChecked) - preferences.showOnlyUpdatesMigration().set(binding.OnlyShowUpdates.isChecked) - onStartMigration.value( - if (binding.useSmartSearch.isChecked && binding.extraSearchParamText.text.isNotBlank()) { - binding.extraSearchParamText.toString() - } else { - null - }, - ) - } - } - - private fun setFlags(binding: MigrationBottomSheetBinding) { - var flags = 0 - if (binding.migChapters.isChecked) flags = flags or MigrationFlags.CHAPTERS - if (binding.migCategories.isChecked) flags = flags or MigrationFlags.CATEGORIES - if (binding.migTracking.isChecked) flags = flags or MigrationFlags.TRACK - if (binding.migCustomCover.isChecked) flags = flags or MigrationFlags.CUSTOM_COVER - if (binding.migExtra.isChecked) flags = flags or MigrationFlags.EXTRA - if (binding.migDeleteDownloaded.isChecked) flags = flags or MigrationFlags.DELETE_CHAPTERS - if (binding.migNotes.isChecked) flags = flags or MigrationFlags.NOTES - preferences.migrateFlags().set(flags) - } - - /** - * Binds a checkbox or switch view with a boolean preference. - */ - private fun CompoundButton.bindToPreference(pref: Preference) { - isChecked = pref.get() - setOnCheckedChangeListener { _, isChecked -> pref.set(isChecked) } - } - - /** - * Binds a radio group with a boolean preference. - */ - private fun RadioGroup.bindToPreference(pref: Preference) { - (getChildAt(pref.get().toLong().toInt()) as RadioButton).isChecked = true - setOnCheckedChangeListener { _, value -> - val index = indexOfChild(findViewById(value)) - pref.set(index == 1) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt deleted file mode 100644 index 61a61a47f..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt +++ /dev/null @@ -1,213 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.design - -import android.view.LayoutInflater -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.ArrowForward -import androidx.compose.material.icons.outlined.Deselect -import androidx.compose.material.icons.outlined.SelectAll -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.Velocity -import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.view.ViewCompat -import androidx.core.view.updatePadding -import androidx.recyclerview.widget.LinearLayoutManager -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.Navigator -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.databinding.PreMigrationListBinding -import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen -import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationProcedureConfig -import kotlinx.collections.immutable.persistentListOf -import tachiyomi.i18n.MR -import tachiyomi.i18n.sy.SYMR -import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton -import tachiyomi.presentation.core.components.material.Scaffold -import tachiyomi.presentation.core.i18n.stringResource -import java.io.Serializable -import kotlin.math.roundToInt - -sealed class MigrationType : Serializable { - data class MangaList(val mangaIds: List) : MigrationType() - data class MangaSingle(val fromMangaId: Long, val toManga: Long?) : MigrationType() -} - -class PreMigrationScreen(val migration: MigrationType) : Screen() { - @Composable - override fun Content() { - val screenModel = rememberScreenModel { PreMigrationScreenModel() } - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - val navigator = LocalNavigator.currentOrThrow - var fabExpanded by remember { mutableStateOf(true) } - val items by screenModel.state.collectAsState() - val adapter by screenModel.adapter.collectAsState() - LaunchedEffect(items.isNotEmpty(), adapter != null) { - if (adapter != null && items.isNotEmpty()) { - adapter?.updateDataSet(items) - } - } - - val nestedScrollConnection = remember { - // All this lines just for fab state :/ - object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - fabExpanded = available.y >= 0 - return scrollBehavior.nestedScrollConnection.onPreScroll(available, source) - } - - override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { - return scrollBehavior.nestedScrollConnection.onPostScroll(consumed, available, source) - } - - override suspend fun onPreFling(available: Velocity): Velocity { - return scrollBehavior.nestedScrollConnection.onPreFling(available) - } - - override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { - return scrollBehavior.nestedScrollConnection.onPostFling(consumed, available) - } - } - } - Scaffold( - topBar = { - AppBar( - title = stringResource(SYMR.strings.select_sources), - navigateUp = navigator::pop, - scrollBehavior = scrollBehavior, - actions = { - AppBarActions( - persistentListOf( - AppBar.Action( - title = stringResource(SYMR.strings.select_none), - icon = Icons.Outlined.Deselect, - onClick = { screenModel.massSelect(false) }, - ), - AppBar.Action( - title = stringResource(MR.strings.action_select_all), - icon = Icons.Outlined.SelectAll, - onClick = { screenModel.massSelect(true) }, - ), - AppBar.OverflowAction( - title = stringResource(SYMR.strings.match_enabled_sources), - onClick = { screenModel.matchSelection(true) }, - ), - AppBar.OverflowAction( - title = stringResource(SYMR.strings.match_pinned_sources), - onClick = { screenModel.matchSelection(false) }, - ), - ), - ) - }, - ) - }, - floatingActionButton = { - ExtendedFloatingActionButton( - text = { Text(text = stringResource(MR.strings.action_migrate)) }, - icon = { - Icon( - imageVector = Icons.AutoMirrored.Outlined.ArrowForward, - contentDescription = stringResource(MR.strings.action_migrate), - ) - }, - onClick = { - screenModel.onMigrationSheet(true) - }, - expanded = fabExpanded, - ) - }, - ) { contentPadding -> - val density = LocalDensity.current - val layoutDirection = LocalLayoutDirection.current - val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() } - val top = with(density) { contentPadding.calculateTopPadding().toPx().roundToInt() } - val right = with(density) { contentPadding.calculateRightPadding(layoutDirection).toPx().roundToInt() } - val bottom = with(density) { contentPadding.calculateBottomPadding().toPx().roundToInt() } - Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) { - AndroidView( - modifier = Modifier.fillMaxWidth(), - factory = { context -> - screenModel.controllerBinding = PreMigrationListBinding.inflate(LayoutInflater.from(context)) - screenModel.adapter.value = MigrationSourceAdapter(screenModel.clickListener) - screenModel.controllerBinding.root.adapter = screenModel.adapter.value - screenModel.adapter.value?.isHandleDragEnabled = true - screenModel.controllerBinding.root.layoutManager = LinearLayoutManager(context) - - ViewCompat.setNestedScrollingEnabled(screenModel.controllerBinding.root, true) - - screenModel.controllerBinding.root - }, - update = { - screenModel.controllerBinding.root - .updatePadding( - left = left, - top = top, - right = right, - bottom = bottom, - ) - }, - ) - } - } - - val migrationSheetOpen by screenModel.migrationSheetOpen.collectAsState() - if (migrationSheetOpen) { - MigrationBottomSheetDialog( - onDismissRequest = { screenModel.onMigrationSheet(false) }, - onStartMigration = { extraParam -> - screenModel.onMigrationSheet(false) - screenModel.saveEnabledSources() - - navigator replace MigrationListScreen(MigrationProcedureConfig(migration, extraParam)) - }, - ) - } - } - - companion object { - fun navigateToMigration(skipPre: Boolean, navigator: Navigator, mangaIds: List) { - navigator.push( - if (skipPre) { - MigrationListScreen( - MigrationProcedureConfig(MigrationType.MangaList(mangaIds), null), - ) - } else { - PreMigrationScreen(MigrationType.MangaList(mangaIds)) - }, - ) - } - - fun navigateToMigration(skipPre: Boolean, navigator: Navigator, fromMangaId: Long, toManga: Long?) { - navigator.push( - if (skipPre) { - MigrationListScreen( - MigrationProcedureConfig(MigrationType.MangaSingle(fromMangaId, toManga), null), - ) - } else { - PreMigrationScreen(MigrationType.MangaSingle(fromMangaId, toManga)) - }, - ) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreenModel.kt deleted file mode 100644 index 6dad92d9f..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreenModel.kt +++ /dev/null @@ -1,137 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.design - -import cafe.adriel.voyager.core.model.ScreenModel -import cafe.adriel.voyager.core.model.screenModelScope -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.kanade.domain.source.service.SourcePreferences -import eu.kanade.tachiyomi.databinding.PreMigrationListBinding -import eu.kanade.tachiyomi.source.online.HttpSource -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import tachiyomi.core.common.util.lang.launchIO -import tachiyomi.domain.source.service.SourceManager -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class PreMigrationScreenModel( - private val sourceManager: SourceManager = Injekt.get(), - private val sourcePreferences: SourcePreferences = Injekt.get(), -) : ScreenModel { - - private val _state = MutableStateFlow(emptyList()) - val state = _state.asStateFlow() - - private val _migrationSheetOpen = MutableStateFlow(false) - val migrationSheetOpen = _migrationSheetOpen.asStateFlow() - - lateinit var controllerBinding: PreMigrationListBinding - var adapter: MutableStateFlow = MutableStateFlow(null) - - val clickListener = FlexibleAdapter.OnItemClickListener { _, position -> - val adapter = adapter.value ?: return@OnItemClickListener false - adapter.getItem(position)?.let { - it.sourceEnabled = !it.sourceEnabled - } - adapter.notifyItemChanged(position) - false - } - - init { - screenModelScope.launchIO { - val enabledSources = getEnabledSources() - _state.update { enabledSources } - } - } - - /** - * Returns a list of enabled sources ordered by language and name. - * - * @return list containing enabled sources. - */ - private fun getEnabledSources(): List { - val languages = sourcePreferences.enabledLanguages().get() - val sourcesSaved = sourcePreferences.migrationSources().get().split("/") - .mapNotNull { it.toLongOrNull() } - val disabledSources = sourcePreferences.disabledSources().get() - .mapNotNull { it.toLongOrNull() } - val sources = sourceManager.getVisibleCatalogueSources() - .asSequence() - .filterIsInstance() - .filter { it.lang in languages } - .sortedBy { "(${it.lang}) ${it.name}" } - .map { - MigrationSourceItem( - it, - isEnabled( - sourcesSaved, - disabledSources, - it.id, - ), - ) - } - .toList() - - return sources - .filter { it.sourceEnabled } - .sortedBy { sourcesSaved.indexOf(it.source.id) } - .plus( - sources.filterNot { it.sourceEnabled }, - ) - } - - fun isEnabled( - sourcesSaved: List, - disabledSources: List, - id: Long, - ): Boolean { - return if (sourcesSaved.isEmpty()) { - id !in disabledSources - } else { - id in sourcesSaved - } - } - - fun massSelect(selectAll: Boolean) { - val adapter = adapter.value ?: return - adapter.currentItems.forEach { - it.sourceEnabled = selectAll - } - adapter.notifyDataSetChanged() - } - - fun matchSelection(matchEnabled: Boolean) { - val adapter = adapter.value ?: return - val enabledSources = if (matchEnabled) { - sourcePreferences.disabledSources().get().mapNotNull { it.toLongOrNull() } - } else { - sourcePreferences.pinnedSources().get().mapNotNull { it.toLongOrNull() } - } - val items = adapter.currentItems.toList() - items.forEach { - it.sourceEnabled = if (matchEnabled) { - it.source.id !in enabledSources - } else { - it.source.id in enabledSources - } - } - val sortedItems = items.sortedBy { it.source.name }.sortedBy { !it.sourceEnabled } - adapter.updateDataSet(sortedItems) - } - - fun onMigrationSheet(isOpen: Boolean) { - _migrationSheetOpen.value = isOpen - } - - fun saveEnabledSources() { - val listOfSources = adapter.value?.currentItems - ?.filterIsInstance() - ?.filter { - it.sourceEnabled - } - ?.joinToString("/") { it.source.id.toString() } - .orEmpty() - - sourcePreferences.migrationSources().set(listOfSources) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt deleted file mode 100644 index 38001ebfc..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt +++ /dev/null @@ -1,155 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.process - -import androidx.activity.compose.BackHandler -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.platform.LocalContext -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.presentation.browse.MigrationListScreen -import eu.kanade.presentation.browse.components.MigrationExitDialog -import eu.kanade.presentation.browse.components.MigrationMangaDialog -import eu.kanade.presentation.browse.components.MigrationProgressDialog -import eu.kanade.presentation.util.Screen -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen -import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen -import eu.kanade.tachiyomi.ui.manga.MangaScreen -import eu.kanade.tachiyomi.util.system.toast -import exh.util.overEq -import exh.util.underEq -import kotlinx.collections.immutable.persistentListOf -import tachiyomi.core.common.i18n.pluralStringResource -import tachiyomi.core.common.util.lang.withUIContext -import tachiyomi.i18n.sy.SYMR - -class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen() { - - var newSelectedItem: Pair? = null - - @Composable - override fun Content() { - val screenModel = rememberScreenModel { MigrationListScreenModel(config) } - val items by screenModel.migratingItems.collectAsState() - val migrationDone by screenModel.migrationDone.collectAsState() - val unfinishedCount by screenModel.unfinishedCount.collectAsState() - val dialog by screenModel.dialog.collectAsState() - val migrateProgress by screenModel.migratingProgress.collectAsState() - val navigator = LocalNavigator.currentOrThrow - val context = LocalContext.current - LaunchedEffect(items) { - if (items?.isEmpty() == true) { - val manualMigrations = screenModel.manualMigrations.value - context.toast( - context.pluralStringResource( - SYMR.plurals.entry_migrated, - manualMigrations, - manualMigrations, - ), - ) - if (!screenModel.hideNotFound) { - navigator.pop() - } - } - } - - LaunchedEffect(newSelectedItem) { - if (newSelectedItem != null) { - val (oldId, newId) = newSelectedItem!! - screenModel.useMangaForMigration(context, newId, oldId) - newSelectedItem = null - } - } - - LaunchedEffect(screenModel) { - screenModel.navigateOut.collect { - if (items.orEmpty().size == 1 && navigator.items.any { it is MangaScreen }) { - val mangaId = (items.orEmpty().firstOrNull()?.searchResult?.value as? MigratingManga.SearchResult.Result)?.id - withUIContext { - if (mangaId != null) { - val newStack = navigator.items.filter { - it !is MangaScreen && - it !is MigrationListScreen && - it !is PreMigrationScreen - } + MangaScreen(mangaId) - navigator replaceAll newStack.first() - navigator.push(newStack.drop(1)) - - // need to set the navigator in a pop state to dispose of everything properly - navigator.push(this@MigrationListScreen) - navigator.pop() - } else { - navigator.pop() - } - } - } else { - withUIContext { - navigator.pop() - } - } - } - } - MigrationListScreen( - items = items ?: persistentListOf(), - migrationDone = migrationDone, - unfinishedCount = unfinishedCount, - getManga = screenModel::getManga, - getChapterInfo = screenModel::getChapterInfo, - getSourceName = screenModel::getSourceName, - onMigrationItemClick = { - navigator.push(MangaScreen(it.id, true)) - }, - openMigrationDialog = screenModel::openMigrateDialog, - skipManga = { screenModel.removeManga(it) }, - searchManually = { migrationItem -> - val sources = screenModel.getMigrationSources() - val validSources = if (sources.size == 1) { - sources - } else { - sources.filter { it.id != migrationItem.manga.source } - } - val searchScreen = MigrateSearchScreen(migrationItem.manga.id, validSources.map { it.id }) - navigator push searchScreen - }, - migrateNow = { screenModel.migrateManga(it, false) }, - copyNow = { screenModel.migrateManga(it, true) }, - ) - - val onDismissRequest = { screenModel.dialog.value = null } - when ( - @Suppress("NAME_SHADOWING") - val dialog = dialog - ) { - is MigrationListScreenModel.Dialog.MigrateMangaDialog -> { - MigrationMangaDialog( - onDismissRequest = onDismissRequest, - copy = dialog.copy, - mangaSet = dialog.mangaSet, - mangaSkipped = dialog.mangaSkipped, - copyManga = screenModel::copyMangas, - migrateManga = screenModel::migrateMangas, - ) - } - MigrationListScreenModel.Dialog.MigrationExitDialog -> { - MigrationExitDialog( - onDismissRequest = onDismissRequest, - exitMigration = navigator::pop, - ) - } - null -> Unit - } - - if (!migrateProgress.isNaN() && migrateProgress overEq 0f && migrateProgress underEq 1f) { - MigrationProgressDialog( - progress = migrateProgress, - exitMigration = screenModel::cancelMigrate, - ) - } - - BackHandler(true) { - screenModel.dialog.value = MigrationListScreenModel.Dialog.MigrationExitDialog - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt deleted file mode 100644 index c6d53e4d8..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt +++ /dev/null @@ -1,609 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.process - -import android.content.Context -import android.widget.Toast -import cafe.adriel.voyager.core.model.ScreenModel -import cafe.adriel.voyager.core.model.screenModelScope -import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource -import eu.kanade.domain.manga.interactor.UpdateManga -import eu.kanade.domain.manga.model.hasCustomCover -import eu.kanade.domain.manga.model.toSManga -import eu.kanade.domain.source.service.SourcePreferences -import eu.kanade.tachiyomi.data.cache.CoverCache -import eu.kanade.tachiyomi.data.download.DownloadManager -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.source.getNameForMangaInfo -import eu.kanade.tachiyomi.source.online.all.EHentai -import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.MigrationType -import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigratingManga.SearchResult -import eu.kanade.tachiyomi.util.system.toast -import exh.smartsearch.SmartSourceSearchEngine -import exh.source.MERGED_SOURCE_ID -import exh.util.ThrottleManager -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Job -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.cancel -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.ensureActive -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.isActive -import kotlinx.coroutines.sync.Semaphore -import kotlinx.coroutines.sync.withPermit -import logcat.LogPriority -import tachiyomi.core.common.util.lang.launchIO -import tachiyomi.core.common.util.lang.withUIContext -import tachiyomi.core.common.util.system.logcat -import tachiyomi.domain.category.interactor.GetCategories -import tachiyomi.domain.category.interactor.SetMangaCategories -import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId -import tachiyomi.domain.chapter.interactor.UpdateChapter -import tachiyomi.domain.chapter.model.Chapter -import tachiyomi.domain.chapter.model.ChapterUpdate -import tachiyomi.domain.history.interactor.GetHistory -import tachiyomi.domain.history.interactor.UpsertHistory -import tachiyomi.domain.history.model.HistoryUpdate -import tachiyomi.domain.manga.interactor.GetManga -import tachiyomi.domain.manga.interactor.GetMergedReferencesById -import tachiyomi.domain.manga.interactor.NetworkToLocalManga -import tachiyomi.domain.manga.model.Manga -import tachiyomi.domain.manga.model.MangaUpdate -import tachiyomi.domain.source.service.SourceManager -import tachiyomi.domain.track.interactor.DeleteTrack -import tachiyomi.domain.track.interactor.GetTracks -import tachiyomi.domain.track.interactor.InsertTrack -import tachiyomi.i18n.sy.SYMR -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.util.concurrent.atomic.AtomicInteger - -class MigrationListScreenModel( - private val config: MigrationProcedureConfig, - private val preferences: SourcePreferences = Injekt.get(), - private val sourceManager: SourceManager = Injekt.get(), - private val downloadManager: DownloadManager = Injekt.get(), - private val coverCache: CoverCache = Injekt.get(), - private val getManga: GetManga = Injekt.get(), - private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), - private val updateManga: UpdateManga = Injekt.get(), - private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(), - private val updateChapter: UpdateChapter = Injekt.get(), - private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(), - private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(), - private val getHistoryByMangaId: GetHistory = Injekt.get(), - private val upsertHistory: UpsertHistory = Injekt.get(), - private val getCategories: GetCategories = Injekt.get(), - private val setMangaCategories: SetMangaCategories = Injekt.get(), - private val getTracks: GetTracks = Injekt.get(), - private val insertTrack: InsertTrack = Injekt.get(), - private val deleteTrack: DeleteTrack = Injekt.get(), -) : ScreenModel { - - private val smartSearchEngine = SmartSourceSearchEngine(config.extraSearchParams) - private val throttleManager = ThrottleManager() - - val migratingItems = MutableStateFlow?>(null) - val migrationDone = MutableStateFlow(false) - val unfinishedCount = MutableStateFlow(0) - - val manualMigrations = MutableStateFlow(0) - - val hideNotFound = preferences.hideNotFoundMigration().get() - val showOnlyUpdates = preferences.showOnlyUpdatesMigration().get() - - val navigateOut = MutableSharedFlow() - - val dialog = MutableStateFlow(null) - - val migratingProgress = MutableStateFlow(Float.MAX_VALUE) - - private var migrateJob: Job? = null - - init { - screenModelScope.launchIO { - val mangaIds = when (val migration = config.migration) { - is MigrationType.MangaList -> { - migration.mangaIds - } - is MigrationType.MangaSingle -> listOf(migration.fromMangaId) - } - runMigrations( - mangaIds - .map { - async { - val manga = getManga.await(it) ?: return@async null - MigratingManga( - manga = manga, - chapterInfo = getChapterInfo(it), - sourcesString = sourceManager.getOrStub(manga.source).getNameForMangaInfo( - if (manga.source == MERGED_SOURCE_ID) { - getMergedReferencesById.await(manga.id) - .map { sourceManager.getOrStub(it.mangaSourceId) } - } else { - null - }, - ), - parentContext = screenModelScope.coroutineContext, - ) - } - } - .awaitAll() - .filterNotNull() - .also { - migratingItems.value = it.toImmutableList() - }, - ) - } - } - - suspend fun getManga(result: SearchResult.Result) = getManga(result.id) - suspend fun getManga(id: Long) = getManga.await(id) - suspend fun getChapterInfo(result: SearchResult.Result) = getChapterInfo(result.id) - suspend fun getChapterInfo(id: Long) = getChaptersByMangaId.await(id).let { chapters -> - MigratingManga.ChapterInfo( - latestChapter = chapters.maxOfOrNull { it.chapterNumber }, - chapterCount = chapters.size, - ) - } - fun getSourceName(manga: Manga) = sourceManager.getOrStub(manga.source).getNameForMangaInfo(null) - - fun getMigrationSources() = preferences.migrationSources().get().split("/").mapNotNull { - val value = it.toLongOrNull() ?: return@mapNotNull null - sourceManager.get(value) as? CatalogueSource - } - - private suspend fun runMigrations(mangas: List) { - throttleManager.resetThrottle() - unfinishedCount.value = mangas.size - val useSourceWithMost = preferences.useSourceWithMost().get() - val useSmartSearch = preferences.smartMigration().get() - - val sources = getMigrationSources() - for (manga in mangas) { - if (!currentCoroutineContext().isActive) { - break - } - // in case it was removed - when (val migration = config.migration) { - is MigrationType.MangaList -> if (manga.manga.id !in migration.mangaIds) { - continue - } - else -> Unit - } - - if (manga.searchResult.value == SearchResult.Searching && manga.migrationScope.isActive) { - val mangaObj = manga.manga - val mangaSource = sourceManager.getOrStub(mangaObj.source) - - val result = try { - manga.migrationScope.async { - val validSources = if (sources.size == 1) { - sources - } else { - sources.filter { it.id != mangaSource.id } - } - when (val migration = config.migration) { - is MigrationType.MangaSingle -> if (migration.toManga != null) { - val localManga = getManga.await(migration.toManga) - if (localManga != null) { - val source = sourceManager.get(localManga.source) as? CatalogueSource - if (source != null) { - val chapters = if (source is EHentai) { - source.getChapterList(localManga.toSManga(), throttleManager::throttle) - } else { - source.getChapterList(localManga.toSManga()) - } - try { - syncChaptersWithSource.await(chapters, localManga, source) - } catch (_: Exception) { - } - manga.progress.value = validSources.size to validSources.size - return@async localManga - } - } - } - else -> Unit - } - if (useSourceWithMost) { - val sourceSemaphore = Semaphore(3) - val processedSources = AtomicInteger() - - validSources.map { source -> - async async2@{ - sourceSemaphore.withPermit { - try { - val searchResult = if (useSmartSearch) { - smartSearchEngine.smartSearch(source, mangaObj.ogTitle) - } else { - smartSearchEngine.normalSearch(source, mangaObj.ogTitle) - } - - if (searchResult != null && - !(searchResult.url == mangaObj.url && source.id == mangaObj.source) - ) { - val localManga = networkToLocalManga(searchResult) - - val chapters = if (source is EHentai) { - source.getChapterList(localManga.toSManga(), throttleManager::throttle) - } else { - source.getChapterList(localManga.toSManga()) - } - - try { - syncChaptersWithSource.await(chapters, localManga, source) - } catch (_: Exception) { - return@async2 null - } - manga.progress.value = - validSources.size to processedSources.incrementAndGet() - localManga to chapters.size - } else { - null - } - } catch (e: CancellationException) { - // Ignore cancellations - throw e - } catch (_: Exception) { - null - } - } - } - }.mapNotNull { it.await() }.maxByOrNull { it.second }?.first - } else { - validSources.forEachIndexed { index, source -> - val searchResult = try { - val searchResult = if (useSmartSearch) { - smartSearchEngine.smartSearch(source, mangaObj.ogTitle) - } else { - smartSearchEngine.normalSearch(source, mangaObj.ogTitle) - } - - if (searchResult != null) { - val localManga = networkToLocalManga(searchResult) - val chapters = try { - if (source is EHentai) { - source.getChapterList(localManga.toSManga(), throttleManager::throttle) - } else { - source.getChapterList(localManga.toSManga()) - } - } catch (e: Exception) { - this@MigrationListScreenModel.logcat(LogPriority.ERROR, e) - emptyList() - } - syncChaptersWithSource.await(chapters, localManga, source) - localManga - } else { - null - } - } catch (e: CancellationException) { - // Ignore cancellations - throw e - } catch (_: Exception) { - null - } - manga.progress.value = validSources.size to (index + 1) - if (searchResult != null) return@async searchResult - } - - null - } - }.await() - } catch (_: CancellationException) { - // Ignore canceled migrations - continue - } - - if (result != null && result.thumbnailUrl == null) { - try { - val newManga = sourceManager.getOrStub(result.source).getMangaDetails(result.toSManga()) - updateManga.awaitUpdateFromSource(result, newManga, true) - } catch (e: CancellationException) { - // Ignore cancellations - throw e - } catch (_: Exception) { - } - } - - manga.searchResult.value = if (result == null) { - SearchResult.NotFound - } else { - SearchResult.Result(result.id) - } - if (result == null && hideNotFound) { - removeManga(manga) - } - if (result != null && - showOnlyUpdates && - (getChapterInfo(result.id).latestChapter ?: 0.0) <= (manga.chapterInfo.latestChapter ?: 0.0) - ) { - removeManga(manga) - } - - sourceFinished() - } - } - } - - private suspend fun sourceFinished() { - unfinishedCount.value = migratingItems.value.orEmpty().count { - it.searchResult.value != SearchResult.Searching - } - if (allMangasDone()) { - migrationDone.value = true - } - if (migratingItems.value?.isEmpty() == true) { - navigateOut() - } - } - - fun allMangasDone() = migratingItems.value.orEmpty().all { it.searchResult.value != SearchResult.Searching } && - migratingItems.value.orEmpty().any { it.searchResult.value is SearchResult.Result } - - fun mangasSkipped() = migratingItems.value.orEmpty().count { it.searchResult.value == SearchResult.NotFound } - - private suspend fun migrateMangaInternal( - prevManga: Manga, - manga: Manga, - replace: Boolean, - ) { - if (prevManga.id == manga.id) return // Nothing to migrate - - val flags = preferences.migrateFlags().get() - // Update chapters read - if (MigrationFlags.hasChapters(flags)) { - val prevMangaChapters = getChaptersByMangaId.await(prevManga.id) - val maxChapterRead = prevMangaChapters.filter(Chapter::read) - .maxOfOrNull(Chapter::chapterNumber) - val dbChapters = getChaptersByMangaId.await(manga.id) - val prevHistoryList = getHistoryByMangaId.await(prevManga.id) - - val chapterUpdates = mutableListOf() - val historyUpdates = mutableListOf() - - dbChapters.forEach { chapter -> - if (chapter.isRecognizedNumber) { - val prevChapter = prevMangaChapters.find { - it.isRecognizedNumber && - it.chapterNumber == chapter.chapterNumber - } - if (prevChapter != null) { - chapterUpdates += ChapterUpdate( - id = chapter.id, - bookmark = prevChapter.bookmark, - read = prevChapter.read, - dateFetch = prevChapter.dateFetch, - ) - prevHistoryList.find { it.chapterId == prevChapter.id }?.let { prevHistory -> - historyUpdates += HistoryUpdate( - chapter.id, - prevHistory.readAt ?: return@let, - prevHistory.readDuration, - ) - } - } else if (maxChapterRead != null && chapter.chapterNumber <= maxChapterRead) { - chapterUpdates += ChapterUpdate( - id = chapter.id, - read = true, - ) - } - } - } - - updateChapter.awaitAll(chapterUpdates) - upsertHistory.awaitAll(historyUpdates) - } - // Update categories - if (MigrationFlags.hasCategories(flags)) { - val categories = getCategories.await(prevManga.id) - setMangaCategories.await(manga.id, categories.map { it.id }) - } - // Update track - if (MigrationFlags.hasTracks(flags)) { - val tracks = getTracks.await(prevManga.id) - if (tracks.isNotEmpty()) { - getTracks.await(manga.id).forEach { - deleteTrack.await(manga.id, it.trackerId) - } - insertTrack.awaitAll(tracks.map { it.copy(mangaId = manga.id) }) - } - } - // Update custom cover - if (MigrationFlags.hasCustomCover(flags) && prevManga.hasCustomCover(coverCache)) { - coverCache.setCustomCoverToCache(manga, coverCache.getCustomCoverFile(prevManga.id).inputStream()) - } - - var mangaUpdate = MangaUpdate(manga.id, favorite = true, dateAdded = System.currentTimeMillis()) - var prevMangaUpdate: MangaUpdate? = null - // Update extras - if (MigrationFlags.hasExtra(flags)) { - mangaUpdate = mangaUpdate.copy( - chapterFlags = prevManga.chapterFlags, - viewerFlags = prevManga.viewerFlags, - ) - } - // Delete downloaded - if (MigrationFlags.hasDeleteChapters(flags)) { - val oldSource = sourceManager.get(prevManga.source) - if (oldSource != null) { - downloadManager.deleteManga(prevManga, oldSource) - } - } - // Update favorite status - if (replace) { - prevMangaUpdate = MangaUpdate( - id = prevManga.id, - favorite = false, - dateAdded = 0, - ) - mangaUpdate = mangaUpdate.copy( - dateAdded = prevManga.dateAdded, - ) - } - - updateManga.awaitAll(listOfNotNull(mangaUpdate, prevMangaUpdate)) - } - - fun useMangaForMigration(context: Context, newMangaId: Long, selectedMangaId: Long) { - val migratingManga = migratingItems.value.orEmpty().find { it.manga.id == selectedMangaId } - ?: return - migratingManga.searchResult.value = SearchResult.Searching - screenModelScope.launchIO { - val result = migratingManga.migrationScope.async { - val manga = getManga(newMangaId)!! - val localManga = networkToLocalManga(manga) - try { - val source = sourceManager.get(manga.source)!! - val chapters = source.getChapterList(localManga.toSManga()) - syncChaptersWithSource.await(chapters, localManga, source) - } catch (_: Exception) { - return@async null - } - localManga - }.await() - - if (result != null) { - try { - val newManga = sourceManager.getOrStub(result.source).getMangaDetails(result.toSManga()) - updateManga.awaitUpdateFromSource(result, newManga, true) - } catch (e: CancellationException) { - // Ignore cancellations - throw e - } catch (_: Exception) { - } - - migratingManga.searchResult.value = SearchResult.Result(result.id) - } else { - migratingManga.searchResult.value = SearchResult.NotFound - withUIContext { - context.toast(SYMR.strings.no_chapters_found_for_migration, Toast.LENGTH_LONG) - } - } - } - } - - fun migrateMangas() { - migrateMangas(true) - } - - fun copyMangas() { - migrateMangas(false) - } - - private fun migrateMangas(replace: Boolean) { - dialog.value = null - migrateJob = screenModelScope.launchIO { - migratingProgress.value = 0f - val items = migratingItems.value.orEmpty() - try { - items.forEachIndexed { index, manga -> - try { - ensureActive() - val toMangaObj = manga.searchResult.value.let { - if (it is SearchResult.Result) { - getManga.await(it.id) - } else { - null - } - } - if (toMangaObj != null) { - migrateMangaInternal( - manga.manga, - toMangaObj, - replace, - ) - } - } catch (e: Exception) { - if (e is CancellationException) throw e - logcat(LogPriority.WARN, throwable = e) - } - migratingProgress.value = index.toFloat() / items.size - } - - navigateOut() - } finally { - migratingProgress.value = Float.MAX_VALUE - migrateJob = null - } - } - } - - fun cancelMigrate() { - migrateJob?.cancel() - migrateJob = null - } - - private suspend fun navigateOut() { - navigateOut.emit(Unit) - } - - fun migrateManga(mangaId: Long, copy: Boolean) { - manualMigrations.value++ - screenModelScope.launchIO { - val manga = migratingItems.value.orEmpty().find { it.manga.id == mangaId } - ?: return@launchIO - - val toMangaObj = getManga.await((manga.searchResult.value as? SearchResult.Result)?.id ?: return@launchIO) - ?: return@launchIO - migrateMangaInternal( - manga.manga, - toMangaObj, - !copy, - ) - - removeManga(mangaId) - } - } - - fun removeManga(mangaId: Long) { - screenModelScope.launchIO { - val item = migratingItems.value.orEmpty().find { it.manga.id == mangaId } - ?: return@launchIO - removeManga(item) - item.migrationScope.cancel() - sourceFinished() - } - } - - fun removeManga(item: MigratingManga) { - when (val migration = config.migration) { - is MigrationType.MangaList -> { - val ids = migration.mangaIds.toMutableList() - val index = ids.indexOf(item.manga.id) - if (index > -1) { - ids.removeAt(index) - config.migration = MigrationType.MangaList(ids) - val index2 = migratingItems.value.orEmpty().indexOf(item) - if (index2 > -1) migratingItems.value = (migratingItems.value.orEmpty() - item).toImmutableList() - } - } - is MigrationType.MangaSingle -> Unit - } - } - - override fun onDispose() { - super.onDispose() - migratingItems.value.orEmpty().forEach { - it.migrationScope.cancel() - } - } - - fun openMigrateDialog( - copy: Boolean, - ) { - dialog.value = Dialog.MigrateMangaDialog( - copy, - migratingItems.value.orEmpty().size, - mangasSkipped(), - ) - } - - sealed class Dialog { - data class MigrateMangaDialog(val copy: Boolean, val mangaSet: Int, val mangaSkipped: Int) : Dialog() - object MigrationExitDialog : Dialog() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt deleted file mode 100644 index 7a57710a1..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt +++ /dev/null @@ -1,9 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.process - -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.MigrationType -import java.io.Serializable - -data class MigrationProcedureConfig( - var migration: MigrationType, - val extraSearchParams: String?, -) : Serializable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt index 1faf00187..87f8a074d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt @@ -2,16 +2,13 @@ package eu.kanade.tachiyomi.ui.browse.migration.search import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.domain.source.service.SourcePreferences -import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult -import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.source.service.SourceManager -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/ui/browse/migration/sources/MigrateSourceTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt index 8a0618fab..cca4bb67a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt @@ -10,14 +10,13 @@ import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.browse.MigrateSourceScreen import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.TabContent -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreen import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.DelicateCoroutinesApi +import mihon.feature.migration.config.MigrationConfigScreen import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.domain.manga.interactor.GetFavorites @@ -62,10 +61,10 @@ fun Screen.migrateSourceTab(): TabContent { val sourceMangas = manga.asSequence().filter { it.source == source.id }.map { it.id }.toList() withUIContext { - PreMigrationScreen.navigateToMigration( - Injekt.get().skipPreMigration().get(), - navigator, - sourceMangas, + navigator.push( + MigrationConfigScreen( + sourceMangas + ) ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt index a95fae575..32a4f7a69 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt @@ -216,11 +216,11 @@ class HistoryScreenModel( } } - /*SY -->fun showMigrateDialog(target: Manga, current: Manga) { + fun showMigrateDialog(target: Manga, current: Manga) { mutableState.update { currentState -> currentState.copy(dialog = Dialog.Migrate(target = target, current = current)) } - } SY <--*/ + } fun showChangeCategoryDialog(manga: Manga) { screenModelScope.launch { @@ -252,7 +252,7 @@ class HistoryScreenModel( val manga: Manga, val initialSelection: ImmutableList>, ) : Dialog - /* SY --> data class Migrate(val target: Manga, val current: Manga) : Dialog SY <-- */ + data class Migrate(val target: Manga, val current: Manga) : Dialog } sealed interface Event { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt index 204c09dcd..73ba3a93f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt @@ -34,6 +34,7 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow +import mihon.feature.migration.dialog.MigrateMangaDialog import tachiyomi.core.common.i18n.stringResource import tachiyomi.domain.chapter.model.Chapter import tachiyomi.i18n.MR @@ -129,7 +130,7 @@ data object HistoryTab : Tab { }, ) } - /*SY -->is HistoryScreenModel.Dialog.Migrate -> { + is HistoryScreenModel.Dialog.Migrate -> { MigrateMangaDialog( current = dialog.current, target = dialog.target, @@ -137,7 +138,7 @@ data object HistoryTab : Tab { onClickTitle = { navigator.push(MangaScreen(dialog.current.id)) }, onDismissRequest = onDismissRequest, ) - } SY <--*/ + } null -> {} } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt index 09e5aa426..6f6662019 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.contentDescription 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 aa155dcce..2b481edc1 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt @@ -764,6 +764,7 @@ class LibraryScreenModel( downloadManager.isChapterDownloaded( chapter.name, chapter.scanlator, + chapter.url, mergedManga.ogTitle, mergedManga.source, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 8843fe88a..81fbe01f1 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -51,8 +51,8 @@ import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior import cafe.adriel.voyager.navigator.currentOrThrow -import com.google.firebase.analytics.ktx.analytics -import com.google.firebase.ktx.Firebase +import com.google.firebase.analytics.analytics +import com.google.firebase.Firebase import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.source.interactor.GetIncognitoState import eu.kanade.presentation.components.AppStateBanners 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 6686d7bad..734817f88 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 @@ -52,9 +52,6 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.chapter.getNextUnread import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.system.toast -import exh.metadata.metadata.RaisedSearchMetadata -import exh.source.getMainSource -import exh.source.isEhBasedManga import exh.debug.DebugToggles import exh.eh.EHentaiUpdateHelper import exh.log.xLogD @@ -1656,9 +1653,7 @@ class MangaScreenModel( data class DeleteChapters(val chapters: List) : Dialog data class DuplicateManga(val manga: Manga, val duplicates: List) : Dialog - /* SY --> data class Migrate(val target: Manga, val current: Manga) : Dialog - SY <-- */ data class SetFetchInterval(val manga: Manga) : Dialog // SY --> @@ -1691,11 +1686,10 @@ class MangaScreenModel( updateSuccessState { it.copy(dialog = Dialog.FullCover) } } - /* SY --> fun showMigrateDialog(duplicate: Manga) { val manga = successState?.manga ?: return updateSuccessState { it.copy(dialog = Dialog.Migrate(target = manga, current = duplicate)) } - } SY <-- */ + } fun setExcludedScanlators(excludedScanlators: Set) { screenModelScope.launchIO { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index b6896be9e..9f1ac8614 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -48,6 +48,7 @@ import eu.kanade.tachiyomi.util.chapter.filterDownloaded import eu.kanade.tachiyomi.util.chapter.removeDuplicates import eu.kanade.tachiyomi.util.editCover import eu.kanade.tachiyomi.util.lang.byteSize +import eu.kanade.tachiyomi.util.lang.takeBytes import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil.MAX_FILE_NAME_BYTES import eu.kanade.tachiyomi.util.storage.cacheImageDir diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt index dc5e7c102..47289433e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt @@ -111,6 +111,7 @@ class ChapterLoader( val isMergedMangaDownloaded = downloadManager.isChapterDownloaded( chapterName = chapter.chapter.name, chapterScanlator = chapter.chapter.scanlator, + chapterUrl = chapter.chapter.url, mangaTitle = manga.ogTitle, sourceId = manga.source, skipCache = true, diff --git a/app/src/main/java/exh/log/CrashlyticsPrinter.kt b/app/src/main/java/exh/log/CrashlyticsPrinter.kt index e813a28a4..813436cc0 100644 --- a/app/src/main/java/exh/log/CrashlyticsPrinter.kt +++ b/app/src/main/java/exh/log/CrashlyticsPrinter.kt @@ -1,8 +1,8 @@ package exh.log import com.elvishew.xlog.printer.Printer -import com.google.firebase.crashlytics.ktx.crashlytics -import com.google.firebase.ktx.Firebase +import com.google.firebase.crashlytics.crashlytics +import com.google.firebase.Firebase import eu.kanade.tachiyomi.BuildConfig class CrashlyticsPrinter(private val logLevel: Int) : Printer { diff --git a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt index 37835f17d..eedaaa0a8 100644 --- a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt +++ b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt @@ -13,26 +13,23 @@ import androidx.compose.ui.platform.LocalHapticFeedback import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.browse.BrowseSourceContent import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar import eu.kanade.presentation.browse.components.RemoveMangaDialog import eu.kanade.presentation.category.components.ChangeCategoryDialog import eu.kanade.presentation.manga.DuplicateMangaDialog import eu.kanade.presentation.util.Screen -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen import exh.ui.ifSourcesLoaded +import mihon.feature.migration.dialog.MigrateMangaDialog import mihon.presentation.core.util.collectAsLazyPagingItems import tachiyomi.core.common.util.lang.launchIO import tachiyomi.i18n.sy.SYMR import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.LoadingScreen -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get class MangaDexFollowsScreen(private val sourceId: Long) : Screen() { @@ -100,21 +97,22 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() { val onDismissRequest = { screenModel.setDialog(null) } when (val dialog = state.dialog) { - is BrowseSourceScreenModel.Dialog.Migrate -> {} + is BrowseSourceScreenModel.Dialog.Migrate -> { + MigrateMangaDialog( + current = dialog.current, + target = dialog.target, + // Initiated from the context of [dialog.target] so we show [dialog.current]. + onClickTitle = { navigator.push(MangaScreen(dialog.current.id)) }, + onDismissRequest = onDismissRequest, + ) + } is BrowseSourceScreenModel.Dialog.AddDuplicateManga -> { DuplicateMangaDialog( duplicates = dialog.duplicates, onDismissRequest = onDismissRequest, onConfirm = { screenModel.addFavorite(dialog.manga) }, onOpenManga = { navigator.push(MangaScreen(it.id)) }, - onMigrate = { - PreMigrationScreen.navigateToMigration( - Injekt.get().skipPreMigration().get(), - navigator, - it.id, - dialog.manga.id, - ) - }, + onMigrate = { screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(dialog.manga, it)) }, ) } is BrowseSourceScreenModel.Dialog.RemoveManga -> {