瀏覽代碼

Make migration manga-centric rather than source-centric (#2786)

FlaminSarge 5 年之前
父節點
當前提交
cce3b3a559

+ 0 - 39
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<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 {
         val item = adapter?.getItem(position) ?: return false
 
@@ -108,27 +92,4 @@ class MigrationController : NucleusController<MigrationPresenter>(),
     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"
-    }
 }

+ 3 - 93
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<MigrationController>() {
 
     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<Manga>, sourceId: Long): List<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()
-        }
-    }
 }

+ 29 - 6
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"
+    }
 }

+ 95 - 0
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<Boolean>()
+
+    override fun onCreate(savedState: Bundle?) {
+        super.onCreate(savedState)
+
+        replacingMangaRelay.subscribeLatestCache({ controller, isReplacingManga -> (controller as? SearchController)?.renderIsReplacingManga(isReplacingManga) })
+    }
+
     override fun getEnabledSources(): List<CatalogueSource> {
         // 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<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()
+        }
+    }
 }

+ 1 - 2
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<MangaItem> = emptyList(),
-    val sourcesWithManga: List<SourceItem> = emptyList(),
-    val isReplacingManga: Boolean = false
+    val sourcesWithManga: List<SourceItem> = emptyList()
 )