Added Select all and source migration to selected manga on library screen

This commit is contained in:
Jay 2019-11-07 00:43:25 -08:00
parent f63db1cc2c
commit ae5ad2a9a6
27 changed files with 208 additions and 23 deletions

View File

@ -126,6 +126,16 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
subscriptions += controller.selectionRelay subscriptions += controller.selectionRelay
.subscribe { onSelectionChanged(it) } .subscribe { onSelectionChanged(it) }
subscriptions += controller.selectAllRelay
.subscribe {
if (it == category.id) {
adapter.currentItems.forEach { item ->
controller.setSelection(item.manga, true)
}
controller.invalidateActionMode()
}
}
} }
fun onRecycle() { fun onRecycle() {

View File

@ -5,20 +5,24 @@ import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import com.google.android.material.tabs.TabLayout import android.view.LayoutInflater
import androidx.core.graphics.drawable.DrawableCompat import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import android.view.* import androidx.core.graphics.drawable.DrawableCompat
import androidx.appcompat.widget.ActionBarContextView
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.jakewharton.rxbinding.support.v4.view.pageSelections import com.jakewharton.rxbinding.support.v4.view.pageSelections
import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
@ -37,7 +41,15 @@ import eu.kanade.tachiyomi.ui.category.CategoryController
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.migration.MigrationController import eu.kanade.tachiyomi.ui.migration.MigrationController
import eu.kanade.tachiyomi.util.* import eu.kanade.tachiyomi.ui.migration.MigrationInterface
import eu.kanade.tachiyomi.ui.migration.SearchController
import eu.kanade.tachiyomi.util.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.util.marginBottom
import eu.kanade.tachiyomi.util.marginTop
import eu.kanade.tachiyomi.util.snack
import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.util.updatePaddingRelative
import kotlinx.android.synthetic.main.library_controller.* import kotlinx.android.synthetic.main.library_controller.*
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import rx.Subscription import rx.Subscription
@ -46,7 +58,6 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.IOException import java.io.IOException
class LibraryController( class LibraryController(
bundle: Bundle? = null, bundle: Bundle? = null,
private val preferences: PreferencesHelper = Injekt.get() private val preferences: PreferencesHelper = Injekt.get()
@ -55,7 +66,8 @@ class LibraryController(
SecondaryDrawerController, SecondaryDrawerController,
ActionMode.Callback, ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener, ChangeMangaCategoriesDialog.Listener,
DeleteLibraryMangasDialog.Listener { DeleteLibraryMangasDialog.Listener,
MigrationInterface {
/** /**
* Position of the active category. * Position of the active category.
@ -78,6 +90,11 @@ class LibraryController(
*/ */
val selectedMangas = mutableSetOf<Manga>() val selectedMangas = mutableSetOf<Manga>()
/**
* Current mangas to move.
*/
private var migratingMangas = mutableSetOf<Manga>()
private var selectedCoverManga: Manga? = null private var selectedCoverManga: Manga? = null
/** /**
@ -95,6 +112,11 @@ class LibraryController(
*/ */
val libraryMangaRelay: BehaviorRelay<LibraryMangaEvent> = BehaviorRelay.create() val libraryMangaRelay: BehaviorRelay<LibraryMangaEvent> = BehaviorRelay.create()
/**
* Relay to notify the library's viewpager to select all manga
*/
val selectAllRelay: PublishRelay<Int> = PublishRelay.create()
/** /**
* Number of manga per row in grid mode. * Number of manga per row in grid mode.
*/ */
@ -425,11 +447,36 @@ class LibraryController(
} }
R.id.action_move_to_category -> showChangeMangaCategoriesDialog() R.id.action_move_to_category -> showChangeMangaCategoriesDialog()
R.id.action_delete -> deleteMangasFromLibrary() R.id.action_delete -> deleteMangasFromLibrary()
R.id.action_select_all -> {
adapter?.categories?.getOrNull(library_pager.currentItem)?.id?.let {
selectAllRelay.call(it)
}
}
R.id.action_migrate -> startMangaMigration()
else -> return false else -> return false
} }
return true return true
} }
override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? {
presenter.migrateManga(prevManga, manga, replace = replace)
val nextManga = migratingMangas.firstOrNull() ?: return null
migratingMangas.remove(nextManga)
return nextManga
}
private fun startMangaMigration() {
migratingMangas.clear()
migratingMangas.addAll(selectedMangas)
destroyActionModeIfNeeded()
val manga = migratingMangas.firstOrNull() ?: return
val searchController = SearchController(manga)
searchController.totalProgress = migratingMangas.size
searchController.targetController = this
router.pushController(searchController.withFadeTransaction())
migratingMangas.remove(manga)
}
override fun onDestroyActionMode(mode: ActionMode?) { override fun onDestroyActionMode(mode: ActionMode?) {
// Clear all the manga selections and notify child views. // Clear all the manga selections and notify child views.
selectedMangas.clear() selectedMangas.clear()

View File

@ -11,12 +11,16 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
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.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
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.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
import eu.kanade.tachiyomi.util.combineLatest import eu.kanade.tachiyomi.util.combineLatest
import eu.kanade.tachiyomi.util.isNullOrUnsubscribed import eu.kanade.tachiyomi.util.isNullOrUnsubscribed
import eu.kanade.tachiyomi.util.syncChaptersWithSource
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -355,6 +359,79 @@ class LibraryPresenter(
db.setMangaCategories(mc, mangas) db.setMangaCategories(mc, mangas)
} }
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()
}
}
/** /**
* Update cover with local file. * Update cover with local file.
* *

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.migration
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -20,7 +19,8 @@ import kotlinx.android.synthetic.main.migration_controller.*
class MigrationController : NucleusController<MigrationPresenter>(), class MigrationController : NucleusController<MigrationPresenter>(),
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
SourceAdapter.OnSelectClickListener { SourceAdapter.OnSelectClickListener,
MigrationInterface {
private var adapter: FlexibleAdapter<IFlexible<*>>? = null private var adapter: FlexibleAdapter<IFlexible<*>>? = null
@ -38,6 +38,13 @@ class MigrationController : NucleusController<MigrationPresenter>(),
return inflater.inflate(R.layout.migration_controller, container, false) return inflater.inflate(R.layout.migration_controller, container, false)
} }
fun searchController(manga:Manga): SearchController {
val controller = SearchController(manga)
controller.targetController = this
return controller
}
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
@ -112,12 +119,9 @@ class MigrationController : NucleusController<MigrationPresenter>(),
onItemClick(view, position) onItemClick(view, position)
} }
fun migrateManga(prevManga: Manga, manga: Manga) { override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? {
presenter.migrateManga(prevManga, manga, replace = true) presenter.migrateManga(prevManga, manga, replace)
} return null
fun copyManga(prevManga: Manga, manga: Manga) {
presenter.migrateManga(prevManga, manga, replace = false)
} }
class LoadingController : DialogController() { class LoadingController : DialogController() {
@ -136,3 +140,7 @@ class MigrationController : NucleusController<MigrationPresenter>(),
} }
} }
interface MigrationInterface {
fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga?
}

View File

@ -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.withFadeTransaction
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
@ -17,6 +18,16 @@ class SearchController(
) : CatalogueSearchController(manga?.title) { ) : CatalogueSearchController(manga?.title) {
private var newManga: Manga? = null private var newManga: Manga? = null
private var progress = 1
var totalProgress = 0
override fun getTitle(): String? {
if (totalProgress > 1) {
return "($progress/$totalProgress) ${super.getTitle()}"
}
else
return super.getTitle()
}
override fun createPresenter(): CatalogueSearchPresenter { override fun createPresenter(): CatalogueSearchPresenter {
return SearchPresenter(initialQuery, manga!!) return SearchPresenter(initialQuery, manga!!)
@ -35,21 +46,32 @@ class SearchController(
} }
fun migrateManga() { fun migrateManga() {
val target = targetController as? MigrationController ?: return val target = targetController as? MigrationInterface ?: return
val manga = manga ?: return val manga = manga ?: return
val newManga = newManga ?: return val newManga = newManga ?: return
router.popController(this) val nextManga = target.migrateManga(manga, newManga, true)
target.migrateManga(manga, newManga) replaceWithNewSearchController(nextManga)
} }
fun copyManga() { fun copyManga() {
val target = targetController as? MigrationController ?: return val target = targetController as? MigrationInterface ?: return
val manga = manga ?: return val manga = manga ?: return
val newManga = newManga ?: return val newManga = newManga ?: return
router.popController(this) val nextManga = target.migrateManga(manga, newManga, false)
target.copyManga(manga, newManga) replaceWithNewSearchController(nextManga)
}
private fun replaceWithNewSearchController(manga: Manga?) {
if (manga != null) {
router.popCurrentController()
val searchController = SearchController(manga)
searchController.targetController = targetController
searchController.progress = progress + 1
searchController.totalProgress = totalProgress
router.replaceTopController(searchController.withFadeTransaction())
} else router.popController(this)
} }
override fun onMangaClick(manga: Manga) { override fun onMangaClick(manga: Manga) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M18,4l-4,4h3v7c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2V8c0,-2.21 -1.79,-4 -4,-4S5,5.79 5,8v7H2l4,4 4,-4H7V8c0,-1.1 0.9,-2 2,-2s2,0.9 2,2v7c0,2.21 1.79,4 4,4s4,-1.79 4,-4V8h3l-4,-4z"/>
</vector>

View File

@ -18,4 +18,15 @@
android:icon="@drawable/ic_delete_white_24dp" android:icon="@drawable/ic_delete_white_24dp"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<item android:id="@+id/action_select_all"
android:title="@string/action_select_all"
android:icon="@drawable/ic_select_all_white_24dp"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_migrate"
android:icon="@drawable/baseline_swap_calls_white_24"
android:title="@string/label_migration"
app:showAsAction="ifRoom" />
</menu> </menu>