From cce3b3a559bc32b5866bfb0e294a9b45936b0f92 Mon Sep 17 00:00:00 2001 From: FlaminSarge Date: Tue, 31 Mar 2020 19:36:23 -0700 Subject: [PATCH] Make migration manga-centric rather than source-centric (#2786) --- .../ui/migration/MigrationController.kt | 39 -------- .../ui/migration/MigrationPresenter.kt | 96 +------------------ .../ui/migration/SearchController.kt | 35 +++++-- .../tachiyomi/ui/migration/SearchPresenter.kt | 95 ++++++++++++++++++ .../tachiyomi/ui/migration/ViewState.kt | 3 +- 5 files changed, 128 insertions(+), 140 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt index 9b94a72c16..ab78eda3e0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt @@ -1,19 +1,13 @@ package eu.kanade.tachiyomi.ui.migration -import android.app.Dialog -import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager -import com.afollestad.materialdialogs.MaterialDialog import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible 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.popControllerWithTag import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import kotlinx.android.synthetic.main.migration_controller.migration_recycler @@ -81,16 +75,6 @@ class MigrationController : NucleusController(), } } - 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 { val item = adapter?.getItem(position) ?: return false @@ -108,27 +92,4 @@ class MigrationController : NucleusController(), override fun onSelectClick(position: Int) { 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" - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt index 297c579973..db037ef838 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt @@ -4,17 +4,11 @@ import android.os.Bundle import com.jakewharton.rxrelay.BehaviorRelay import eu.kanade.tachiyomi.data.database.DatabaseHelper 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.Source 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.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.lang.combineLatest -import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import uy.kohesive.injekt.Injekt @@ -22,8 +16,7 @@ import uy.kohesive.injekt.api.get class MigrationPresenter( private val sourceManager: SourceManager = Injekt.get(), - private val db: DatabaseHelper = Injekt.get(), - private val preferences: PreferencesHelper = Injekt.get() + private val db: DatabaseHelper = Injekt.get() ) : BasePresenter() { var state = ViewState() @@ -51,13 +44,8 @@ class MigrationPresenter( .doOnNext { state = state.copy(mangaForSource = it) } .subscribe() - stateRelay - // Render the view when any field other than isReplacingManga changes - .distinctUntilChanged { t1, t2 -> t1.isReplacingManga != t2.isReplacingManga } - .subscribeLatestCache(MigrationController::render) - - stateRelay.distinctUntilChanged { state -> state.isReplacingManga } - .subscribeLatestCache(MigrationController::renderIsReplacingManga) + // Render the view when any field changes + stateRelay.subscribeLatestCache(MigrationController::render) } fun setSelectedSource(source: Source) { @@ -78,82 +66,4 @@ class MigrationPresenter( private fun libraryToMigrationItem(library: List, sourceId: Long): List { 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, - 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() - } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt index a2fe8e1d1b..eb7f48bac6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt @@ -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.getOrDefault 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.CatalogueSearchPresenter import uy.kohesive.injekt.injectLazy @@ -35,21 +36,17 @@ class SearchController( } fun migrateManga() { - val target = targetController as? MigrationController ?: return val manga = manga ?: return val newManga = newManga ?: return - router.popController(this) - target.migrateManga(manga, newManga) + (presenter as? SearchPresenter)?.migrateManga(manga, newManga, true) } fun copyManga() { - val target = targetController as? MigrationController ?: return val manga = manga ?: return val newManga = newManga ?: return - router.popController(this) - target.copyManga(manga, newManga) + (presenter as? SearchPresenter)?.migrateManga(manga, newManga, false) } override fun onMangaClick(manga: Manga) { @@ -64,6 +61,17 @@ class SearchController( 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() { private val preferences: PreferencesHelper by injectLazy() @@ -96,4 +104,19 @@ class SearchController( .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" + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt index bddf2eebde..460f67d0b4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt @@ -1,17 +1,35 @@ 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.MangaCategory +import eu.kanade.tachiyomi.data.preference.getOrDefault 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.ui.catalogue.global_search.CatalogueSearchCardItem import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchItem 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( initialQuery: String? = "", private val manga: Manga ) : CatalogueSearchPresenter(initialQuery) { + private val replacingMangaRelay = BehaviorRelay.create() + + override fun onCreate(savedState: Bundle?) { + super.onCreate(savedState) + + replacingMangaRelay.subscribeLatestCache({ controller, isReplacingManga -> (controller as? SearchController)?.renderIsReplacingManga(isReplacingManga) }) + } + override fun getEnabledSources(): List { // Put the source of the selected manga at the top return super.getEnabledSources() @@ -29,4 +47,81 @@ class SearchPresenter( localManga.title = sManga.title 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, + 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() + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/ViewState.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/ViewState.kt index 0481ed6037..5fbab91221 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/ViewState.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/ViewState.kt @@ -5,6 +5,5 @@ import eu.kanade.tachiyomi.source.Source data class ViewState( val selectedSource: Source? = null, val mangaForSource: List = emptyList(), - val sourcesWithManga: List = emptyList(), - val isReplacingManga: Boolean = false + val sourcesWithManga: List = emptyList() )