Fix compile time errors and make it build
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ fun ChapterListDialog(
|
||||
downloadManager.isChapterDownloaded(
|
||||
chapterItem.chapter.name,
|
||||
chapterItem.chapter.scanlator,
|
||||
chapterItem.chapter.url,
|
||||
chapterItem.manga.ogTitle,
|
||||
chapterItem.manga.source,
|
||||
)
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -8,7 +8,7 @@ import uy.kohesive.injekt.api.get
|
||||
|
||||
fun Source.getNameForMangaInfo(
|
||||
// SY -->
|
||||
mergeSources: List<Source>?,
|
||||
mergeSources: List<Source>? = null,
|
||||
enabledLanguages: List<String> = Injekt.get<SourcePreferences>().enabledLanguages().get()
|
||||
.filterNot { it in listOf("all", "other") },
|
||||
// SY <--
|
||||
|
||||
-135
@@ -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<Boolean>) {
|
||||
isChecked = pref.get()
|
||||
setOnCheckedChangeListener { _, isChecked -> pref.set(isChecked) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a radio group with a boolean preference.
|
||||
*/
|
||||
private fun RadioGroup.bindToPreference(pref: Preference<Boolean>) {
|
||||
(getChildAt(pref.get().toLong().toInt()) as RadioButton).isChecked = true
|
||||
setOnCheckedChangeListener { _, value ->
|
||||
val index = indexOfChild(findViewById(value))
|
||||
pref.set(index == 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
-213
@@ -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<Long>) : 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<Long>) {
|
||||
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))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-137
@@ -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<MigrationSourceItem>())
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
private val _migrationSheetOpen = MutableStateFlow(false)
|
||||
val migrationSheetOpen = _migrationSheetOpen.asStateFlow()
|
||||
|
||||
lateinit var controllerBinding: PreMigrationListBinding
|
||||
var adapter: MutableStateFlow<MigrationSourceAdapter?> = 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<MigrationSourceItem> {
|
||||
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<HttpSource>()
|
||||
.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<Long>,
|
||||
disabledSources: List<Long>,
|
||||
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<MigrationSourceItem>()
|
||||
?.filter {
|
||||
it.sourceEnabled
|
||||
}
|
||||
?.joinToString("/") { it.source.id.toString() }
|
||||
.orEmpty()
|
||||
|
||||
sourcePreferences.migrationSources().set(listOfSources)
|
||||
}
|
||||
}
|
||||
-155
@@ -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<Long, Long>? = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
-609
@@ -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<ImmutableList<MigratingManga>?>(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<Unit>()
|
||||
|
||||
val dialog = MutableStateFlow<Dialog?>(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<MigratingManga>) {
|
||||
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<ChapterUpdate>()
|
||||
val historyUpdates = mutableListOf<HistoryUpdate>()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
-9
@@ -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
|
||||
-3
@@ -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
|
||||
|
||||
|
||||
+5
-6
@@ -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<SourcePreferences>().skipPreMigration().get(),
|
||||
navigator,
|
||||
sourceMangas,
|
||||
navigator.push(
|
||||
MigrationConfigScreen(
|
||||
sourceMangas
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<CheckboxState<Category>>,
|
||||
) : 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 {
|
||||
|
||||
@@ -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 -> {}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -764,6 +764,7 @@ class LibraryScreenModel(
|
||||
downloadManager.isChapterDownloaded(
|
||||
chapter.name,
|
||||
chapter.scanlator,
|
||||
chapter.url,
|
||||
mergedManga.ogTitle,
|
||||
mergedManga.source,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Chapter>) : Dialog
|
||||
data class DuplicateManga(val manga: Manga, val duplicates: List<MangaWithChapterCount>) : 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<String>) {
|
||||
screenModelScope.launchIO {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<SourcePreferences>().skipPreMigration().get(),
|
||||
navigator,
|
||||
it.id,
|
||||
dialog.manga.id,
|
||||
)
|
||||
},
|
||||
onMigrate = { screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(dialog.manga, it)) },
|
||||
)
|
||||
}
|
||||
is BrowseSourceScreenModel.Dialog.RemoveManga -> {
|
||||
|
||||
Reference in New Issue
Block a user