mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-19 01:59:19 +01:00
Make migration manga-centric rather than source-centric (#2786)
This commit is contained in:
parent
f53cc10338
commit
cce3b3a559
@ -1,19 +1,13 @@
|
|||||||
package eu.kanade.tachiyomi.ui.migration
|
package eu.kanade.tachiyomi.ui.migration
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import kotlinx.android.synthetic.main.migration_controller.migration_recycler
|
import kotlinx.android.synthetic.main.migration_controller.migration_recycler
|
||||||
|
|
||||||
@ -81,16 +75,6 @@ class MigrationController : NucleusController<MigrationPresenter>(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun renderIsReplacingManga(state: ViewState) {
|
|
||||||
if (state.isReplacingManga) {
|
|
||||||
if (router.getControllerWithTag(LOADING_DIALOG_TAG) == null) {
|
|
||||||
LoadingController().showDialog(router, LOADING_DIALOG_TAG)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
router.popControllerWithTag(LOADING_DIALOG_TAG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemClick(view: View, position: Int): Boolean {
|
override fun onItemClick(view: View, position: Int): Boolean {
|
||||||
val item = adapter?.getItem(position) ?: return false
|
val item = adapter?.getItem(position) ?: return false
|
||||||
|
|
||||||
@ -108,27 +92,4 @@ class MigrationController : NucleusController<MigrationPresenter>(),
|
|||||||
override fun onSelectClick(position: Int) {
|
override fun onSelectClick(position: Int) {
|
||||||
onItemClick(view!!, position)
|
onItemClick(view!!, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun migrateManga(prevManga: Manga, manga: Manga) {
|
|
||||||
presenter.migrateManga(prevManga, manga, replace = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun copyManga(prevManga: Manga, manga: Manga) {
|
|
||||||
presenter.migrateManga(prevManga, manga, replace = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoadingController : DialogController() {
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
return MaterialDialog.Builder(activity!!)
|
|
||||||
.progress(true, 0)
|
|
||||||
.content(R.string.migrating)
|
|
||||||
.cancelable(false)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val LOADING_DIALOG_TAG = "LoadingDialog"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,17 +4,11 @@ import android.os.Bundle
|
|||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
|
||||||
import eu.kanade.tachiyomi.util.lang.combineLatest
|
import eu.kanade.tachiyomi.util.lang.combineLatest
|
||||||
import rx.Observable
|
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -22,8 +16,7 @@ import uy.kohesive.injekt.api.get
|
|||||||
|
|
||||||
class MigrationPresenter(
|
class MigrationPresenter(
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val db: DatabaseHelper = Injekt.get(),
|
private val db: DatabaseHelper = Injekt.get()
|
||||||
private val preferences: PreferencesHelper = Injekt.get()
|
|
||||||
) : BasePresenter<MigrationController>() {
|
) : BasePresenter<MigrationController>() {
|
||||||
|
|
||||||
var state = ViewState()
|
var state = ViewState()
|
||||||
@ -51,13 +44,8 @@ class MigrationPresenter(
|
|||||||
.doOnNext { state = state.copy(mangaForSource = it) }
|
.doOnNext { state = state.copy(mangaForSource = it) }
|
||||||
.subscribe()
|
.subscribe()
|
||||||
|
|
||||||
stateRelay
|
// Render the view when any field changes
|
||||||
// Render the view when any field other than isReplacingManga changes
|
stateRelay.subscribeLatestCache(MigrationController::render)
|
||||||
.distinctUntilChanged { t1, t2 -> t1.isReplacingManga != t2.isReplacingManga }
|
|
||||||
.subscribeLatestCache(MigrationController::render)
|
|
||||||
|
|
||||||
stateRelay.distinctUntilChanged { state -> state.isReplacingManga }
|
|
||||||
.subscribeLatestCache(MigrationController::renderIsReplacingManga)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSelectedSource(source: Source) {
|
fun setSelectedSource(source: Source) {
|
||||||
@ -78,82 +66,4 @@ class MigrationPresenter(
|
|||||||
private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> {
|
private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> {
|
||||||
return library.filter { it.source == sourceId }.map(::MangaItem)
|
return library.filter { it.source == sourceId }.map(::MangaItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) {
|
|
||||||
val source = sourceManager.get(manga.source) ?: return
|
|
||||||
|
|
||||||
state = state.copy(isReplacingManga = true)
|
|
||||||
|
|
||||||
Observable.defer { source.fetchChapterList(manga) }
|
|
||||||
.onErrorReturn { emptyList() }
|
|
||||||
.doOnNext { migrateMangaInternal(source, it, prevManga, manga, replace) }
|
|
||||||
.onErrorReturn { emptyList() }
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnUnsubscribe { state = state.copy(isReplacingManga = false) }
|
|
||||||
.subscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun migrateMangaInternal(
|
|
||||||
source: Source,
|
|
||||||
sourceChapters: List<SChapter>,
|
|
||||||
prevManga: Manga,
|
|
||||||
manga: Manga,
|
|
||||||
replace: Boolean
|
|
||||||
) {
|
|
||||||
|
|
||||||
val flags = preferences.migrateFlags().getOrDefault()
|
|
||||||
val migrateChapters = MigrationFlags.hasChapters(flags)
|
|
||||||
val migrateCategories = MigrationFlags.hasCategories(flags)
|
|
||||||
val migrateTracks = MigrationFlags.hasTracks(flags)
|
|
||||||
|
|
||||||
db.inTransaction {
|
|
||||||
// Update chapters read
|
|
||||||
if (migrateChapters) {
|
|
||||||
try {
|
|
||||||
syncChaptersWithSource(db, sourceChapters, manga, source)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Worst case, chapters won't be synced
|
|
||||||
}
|
|
||||||
|
|
||||||
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
|
||||||
val maxChapterRead = prevMangaChapters.filter { it.read }
|
|
||||||
.maxBy { it.chapter_number }?.chapter_number
|
|
||||||
if (maxChapterRead != null) {
|
|
||||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
|
||||||
for (chapter in dbChapters) {
|
|
||||||
if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) {
|
|
||||||
chapter.read = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
db.insertChapters(dbChapters).executeAsBlocking()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Update categories
|
|
||||||
if (migrateCategories) {
|
|
||||||
val categories = db.getCategoriesForManga(prevManga).executeAsBlocking()
|
|
||||||
val mangaCategories = categories.map { MangaCategory.create(manga, it) }
|
|
||||||
db.setMangaCategories(mangaCategories, listOf(manga))
|
|
||||||
}
|
|
||||||
// Update track
|
|
||||||
if (migrateTracks) {
|
|
||||||
val tracks = db.getTracks(prevManga).executeAsBlocking()
|
|
||||||
for (track in tracks) {
|
|
||||||
track.id = null
|
|
||||||
track.manga_id = manga.id!!
|
|
||||||
}
|
|
||||||
db.insertTracks(tracks).executeAsBlocking()
|
|
||||||
}
|
|
||||||
// Update favorite status
|
|
||||||
if (replace) {
|
|
||||||
prevManga.favorite = false
|
|
||||||
db.updateMangaFavorite(prevManga).executeAsBlocking()
|
|
||||||
}
|
|
||||||
manga.favorite = true
|
|
||||||
db.updateMangaFavorite(manga).executeAsBlocking()
|
|
||||||
|
|
||||||
// SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title
|
|
||||||
db.updateMangaTitle(manga).executeAsBlocking()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@ -35,21 +36,17 @@ class SearchController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun migrateManga() {
|
fun migrateManga() {
|
||||||
val target = targetController as? MigrationController ?: return
|
|
||||||
val manga = manga ?: return
|
val manga = manga ?: return
|
||||||
val newManga = newManga ?: return
|
val newManga = newManga ?: return
|
||||||
|
|
||||||
router.popController(this)
|
(presenter as? SearchPresenter)?.migrateManga(manga, newManga, true)
|
||||||
target.migrateManga(manga, newManga)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copyManga() {
|
fun copyManga() {
|
||||||
val target = targetController as? MigrationController ?: return
|
|
||||||
val manga = manga ?: return
|
val manga = manga ?: return
|
||||||
val newManga = newManga ?: return
|
val newManga = newManga ?: return
|
||||||
|
|
||||||
router.popController(this)
|
(presenter as? SearchPresenter)?.migrateManga(manga, newManga, false)
|
||||||
target.copyManga(manga, newManga)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMangaClick(manga: Manga) {
|
override fun onMangaClick(manga: Manga) {
|
||||||
@ -64,6 +61,17 @@ class SearchController(
|
|||||||
super.onMangaClick(manga)
|
super.onMangaClick(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun renderIsReplacingManga(isReplacingManga: Boolean) {
|
||||||
|
if (isReplacingManga) {
|
||||||
|
if (router.getControllerWithTag(LOADING_DIALOG_TAG) == null) {
|
||||||
|
LoadingController().showDialog(router, LOADING_DIALOG_TAG)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
router.popControllerWithTag(LOADING_DIALOG_TAG)
|
||||||
|
router.popController(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class MigrationDialog : DialogController() {
|
class MigrationDialog : DialogController() {
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
@ -96,4 +104,19 @@ class SearchController(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LoadingController : DialogController() {
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
|
return MaterialDialog.Builder(activity!!)
|
||||||
|
.progress(true, 0)
|
||||||
|
.content(R.string.migrating)
|
||||||
|
.cancelable(false)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LOADING_DIALOG_TAG = "LoadingDialog"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,35 @@
|
|||||||
package eu.kanade.tachiyomi.ui.migration
|
package eu.kanade.tachiyomi.ui.migration
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchCardItem
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchCardItem
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchItem
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchItem
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter
|
||||||
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||||
|
import rx.Observable
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
|
||||||
class SearchPresenter(
|
class SearchPresenter(
|
||||||
initialQuery: String? = "",
|
initialQuery: String? = "",
|
||||||
private val manga: Manga
|
private val manga: Manga
|
||||||
) : CatalogueSearchPresenter(initialQuery) {
|
) : CatalogueSearchPresenter(initialQuery) {
|
||||||
|
|
||||||
|
private val replacingMangaRelay = BehaviorRelay.create<Boolean>()
|
||||||
|
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
|
||||||
|
replacingMangaRelay.subscribeLatestCache({ controller, isReplacingManga -> (controller as? SearchController)?.renderIsReplacingManga(isReplacingManga) })
|
||||||
|
}
|
||||||
|
|
||||||
override fun getEnabledSources(): List<CatalogueSource> {
|
override fun getEnabledSources(): List<CatalogueSource> {
|
||||||
// Put the source of the selected manga at the top
|
// Put the source of the selected manga at the top
|
||||||
return super.getEnabledSources()
|
return super.getEnabledSources()
|
||||||
@ -29,4 +47,81 @@ class SearchPresenter(
|
|||||||
localManga.title = sManga.title
|
localManga.title = sManga.title
|
||||||
return localManga
|
return localManga
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) {
|
||||||
|
val source = sourceManager.get(manga.source) ?: return
|
||||||
|
|
||||||
|
replacingMangaRelay.call(true)
|
||||||
|
|
||||||
|
Observable.defer { source.fetchChapterList(manga) }
|
||||||
|
.onErrorReturn { emptyList() }
|
||||||
|
.doOnNext { migrateMangaInternal(source, it, prevManga, manga, replace) }
|
||||||
|
.onErrorReturn { emptyList() }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doOnUnsubscribe { replacingMangaRelay.call(false) }
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun migrateMangaInternal(
|
||||||
|
source: Source,
|
||||||
|
sourceChapters: List<SChapter>,
|
||||||
|
prevManga: Manga,
|
||||||
|
manga: Manga,
|
||||||
|
replace: Boolean
|
||||||
|
) {
|
||||||
|
val flags = preferencesHelper.migrateFlags().getOrDefault()
|
||||||
|
val migrateChapters = MigrationFlags.hasChapters(flags)
|
||||||
|
val migrateCategories = MigrationFlags.hasCategories(flags)
|
||||||
|
val migrateTracks = MigrationFlags.hasTracks(flags)
|
||||||
|
|
||||||
|
db.inTransaction {
|
||||||
|
// Update chapters read
|
||||||
|
if (migrateChapters) {
|
||||||
|
try {
|
||||||
|
syncChaptersWithSource(db, sourceChapters, manga, source)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Worst case, chapters won't be synced
|
||||||
|
}
|
||||||
|
|
||||||
|
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
||||||
|
val maxChapterRead = prevMangaChapters.filter { it.read }
|
||||||
|
.maxBy { it.chapter_number }?.chapter_number
|
||||||
|
if (maxChapterRead != null) {
|
||||||
|
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||||
|
for (chapter in dbChapters) {
|
||||||
|
if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) {
|
||||||
|
chapter.read = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.insertChapters(dbChapters).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update categories
|
||||||
|
if (migrateCategories) {
|
||||||
|
val categories = db.getCategoriesForManga(prevManga).executeAsBlocking()
|
||||||
|
val mangaCategories = categories.map { MangaCategory.create(manga, it) }
|
||||||
|
db.setMangaCategories(mangaCategories, listOf(manga))
|
||||||
|
}
|
||||||
|
// Update track
|
||||||
|
if (migrateTracks) {
|
||||||
|
val tracks = db.getTracks(prevManga).executeAsBlocking()
|
||||||
|
for (track in tracks) {
|
||||||
|
track.id = null
|
||||||
|
track.manga_id = manga.id!!
|
||||||
|
}
|
||||||
|
db.insertTracks(tracks).executeAsBlocking()
|
||||||
|
}
|
||||||
|
// Update favorite status
|
||||||
|
if (replace) {
|
||||||
|
prevManga.favorite = false
|
||||||
|
db.updateMangaFavorite(prevManga).executeAsBlocking()
|
||||||
|
}
|
||||||
|
manga.favorite = true
|
||||||
|
db.updateMangaFavorite(manga).executeAsBlocking()
|
||||||
|
|
||||||
|
// SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title
|
||||||
|
db.updateMangaTitle(manga).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,5 @@ import eu.kanade.tachiyomi.source.Source
|
|||||||
data class ViewState(
|
data class ViewState(
|
||||||
val selectedSource: Source? = null,
|
val selectedSource: Source? = null,
|
||||||
val mangaForSource: List<MangaItem> = emptyList(),
|
val mangaForSource: List<MangaItem> = emptyList(),
|
||||||
val sourcesWithManga: List<SourceItem> = emptyList(),
|
val sourcesWithManga: List<SourceItem> = emptyList()
|
||||||
val isReplacingManga: Boolean = false
|
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user