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
.subscribe { onSelectionChanged(it) }
subscriptions += controller.selectAllRelay
.subscribe {
if (it == category.id) {
adapter.currentItems.forEach { item ->
controller.setSelection(item.manga, true)
}
controller.invalidateActionMode()
}
}
}
fun onRecycle() {

View File

@ -5,20 +5,24 @@ import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
import com.google.android.material.tabs.TabLayout
import androidx.core.graphics.drawable.DrawableCompat
import android.view.LayoutInflater
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.view.ActionMode
import androidx.appcompat.widget.SearchView
import android.view.*
import androidx.appcompat.widget.ActionBarContextView
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.GravityCompat
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.f2prateek.rx.preferences.Preference
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.v7.widget.queryTextChanges
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.manga.MangaController
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.main_activity.*
import rx.Subscription
@ -46,7 +58,6 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.IOException
class LibraryController(
bundle: Bundle? = null,
private val preferences: PreferencesHelper = Injekt.get()
@ -55,7 +66,8 @@ class LibraryController(
SecondaryDrawerController,
ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener,
DeleteLibraryMangasDialog.Listener {
DeleteLibraryMangasDialog.Listener,
MigrationInterface {
/**
* Position of the active category.
@ -78,6 +90,11 @@ class LibraryController(
*/
val selectedMangas = mutableSetOf<Manga>()
/**
* Current mangas to move.
*/
private var migratingMangas = mutableSetOf<Manga>()
private var selectedCoverManga: Manga? = null
/**
@ -95,6 +112,11 @@ class LibraryController(
*/
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.
*/
@ -425,11 +447,36 @@ class LibraryController(
}
R.id.action_move_to_category -> showChangeMangaCategoriesDialog()
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
}
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?) {
// Clear all the manga selections and notify child views.
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.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.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
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.isNullOrUnsubscribed
import eu.kanade.tachiyomi.util.syncChaptersWithSource
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
@ -355,6 +359,79 @@ class LibraryPresenter(
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.
*

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.migration
import android.app.Dialog
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -20,7 +19,8 @@ import kotlinx.android.synthetic.main.migration_controller.*
class MigrationController : NucleusController<MigrationPresenter>(),
FlexibleAdapter.OnItemClickListener,
SourceAdapter.OnSelectClickListener {
SourceAdapter.OnSelectClickListener,
MigrationInterface {
private var adapter: FlexibleAdapter<IFlexible<*>>? = null
@ -38,6 +38,13 @@ class MigrationController : NucleusController<MigrationPresenter>(),
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) {
super.onViewCreated(view)
@ -112,12 +119,9 @@ class MigrationController : NucleusController<MigrationPresenter>(),
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)
override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? {
presenter.migrateManga(prevManga, manga, replace)
return null
}
class LoadingController : DialogController() {
@ -135,4 +139,8 @@ class MigrationController : NucleusController<MigrationPresenter>(),
const val LOADING_DIALOG_TAG = "LoadingDialog"
}
}
}
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.getOrDefault
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.CatalogueSearchPresenter
import uy.kohesive.injekt.injectLazy
@ -17,6 +18,16 @@ class SearchController(
) : CatalogueSearchController(manga?.title) {
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 {
return SearchPresenter(initialQuery, manga!!)
@ -35,21 +46,32 @@ class SearchController(
}
fun migrateManga() {
val target = targetController as? MigrationController ?: return
val target = targetController as? MigrationInterface ?: return
val manga = manga ?: return
val newManga = newManga ?: return
router.popController(this)
target.migrateManga(manga, newManga)
val nextManga = target.migrateManga(manga, newManga, true)
replaceWithNewSearchController(nextManga)
}
fun copyManga() {
val target = targetController as? MigrationController ?: return
val target = targetController as? MigrationInterface ?: return
val manga = manga ?: return
val newManga = newManga ?: return
router.popController(this)
target.copyManga(manga, newManga)
val nextManga = target.migrateManga(manga, newManga, false)
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) {

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"
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>