Fixes and such for integration to AZ

This commit is contained in:
jobobby04 2020-04-17 15:08:21 -04:00 committed by Jobobby04
parent 18f90587f2
commit aefa7a1a4a
13 changed files with 260 additions and 242 deletions

View File

@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.await import exh.util.await
import info.debatty.java.stringsimilarity.NormalizedLevenshtein import info.debatty.java.stringsimilarity.NormalizedLevenshtein
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope

View File

@ -22,11 +22,11 @@ class MigrationMangaDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
} }
override fun onCreateDialog(savedViewState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val confirmRes = if (copy) R.string.confirm_copy else R.string.confirm_migration val confirmRes = if (copy) R.plurals.copy_manga else R.plurals.migrate_manga
val confirmString = applicationContext?.getString(confirmRes, mangaSet, ( val confirmString = applicationContext?.resources?.getQuantityString(confirmRes, mangaSet,
if (mangaSkipped > 0) mangaSet, (
" " + applicationContext?.getString(R.string.skipping_x, mangaSkipped) ?: "" if (mangaSkipped > 0) " " + applicationContext?.getString(R.string.skipping_, mangaSkipped)
else "")) ?: "" else "")) ?: ""
return MaterialDialog.Builder(activity!!) return MaterialDialog.Builder(activity!!)
.content(confirmString) .content(confirmString)
.positiveText(if (copy) R.string.copy else R.string.migrate) .positiveText(if (copy) R.string.copy else R.string.migrate)

View File

@ -1,34 +1,16 @@
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.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchCardItem
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchItem
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchPresenter
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchCardItem
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItem
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
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
) : GlobalSearchPresenter(initialQuery) { ) : GlobalSearchPresenter(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()
@ -39,106 +21,4 @@ class SearchPresenter(
// Set the catalogue search item as highlighted if the source matches that of the selected manga // Set the catalogue search item as highlighted if the source matches that of the selected manga
return GlobalSearchItem(source, results, source.id == manga.source) return GlobalSearchItem(source, results, source.id == manga.source)
} }
override fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
val localManga = super.networkToLocalManga(sManga, sourceId)
// For migration, displayed title should always match source rather than local DB
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 = preferences.migrateFlags().get()
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
val bookmarkedChapters = prevMangaChapters
.filter { it.bookmark && it.isRecognizedNumber }
.map { it.chapter_number }
if (maxChapterRead != null) {
val dbChapters = db.getChapters(manga).executeAsBlocking()
for (chapter in dbChapters) {
if (chapter.isRecognizedNumber) {
if (chapter.chapter_number <= maxChapterRead) {
chapter.read = true
}
if (chapter.chapter_number in bookmarkedChapters) {
chapter.bookmark = 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()
// Update reading preferences
manga.chapter_flags = prevManga.chapter_flags
db.updateFlags(manga).executeAsBlocking()
manga.viewer = prevManga.viewer
db.updateMangaViewer(manga).executeAsBlocking()
// SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title
db.updateMangaTitle(manga).executeAsBlocking()
}
}
} }

View File

@ -7,7 +7,7 @@ import eu.davidea.flexibleadapter.items.AbstractHeaderItem
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.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import kotlinx.android.synthetic.main.source_main_controller_card_header.title import kotlinx.android.synthetic.main.source_main_controller_card.title
/** /**
* Item that contains the selection header. * Item that contains the selection header.
@ -18,7 +18,7 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
* Returns the layout resource of this item. * Returns the layout resource of this item.
*/ */
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return R.layout.source_main_controller_card_header return R.layout.source_main_controller_card
} }
/** /**
@ -35,14 +35,14 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: Holder, holder: Holder,
position: Int, position: Int,
payloads: List<Any?>? payloads: MutableList<Any?>?
) { ) {
// Intentionally empty // Intentionally empty
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : BaseFlexibleViewHolder(view, adapter) { class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : BaseFlexibleViewHolder(view, adapter) {
init { init {
title.text = view.context.getString(R.string.migration_selection_prompt) title.text = view.context.getString(R.string.select_a_source_to_migrate_from)
} }
} }

View File

@ -19,12 +19,12 @@ import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import exh.util.applyWindowInsetsForController
import eu.kanade.tachiyomi.util.view.marginBottom import exh.util.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.updateLayoutParams import exh.util.marginBottom
import eu.kanade.tachiyomi.util.view.updatePaddingRelative import exh.util.updateLayoutParams
import kotlinx.android.synthetic.main.pre_migration_controller.fab import exh.util.updatePaddingRelative
import kotlinx.android.synthetic.main.pre_migration_controller.recycler import kotlinx.android.synthetic.main.pre_migration_controller.*
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class PreMigrationController(bundle: Bundle? = null) : BaseController(bundle), FlexibleAdapter class PreMigrationController(bundle: Bundle? = null) : BaseController(bundle), FlexibleAdapter
@ -48,10 +48,11 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController(bundle), F
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
view.applyWindowInsetsForController()
val ourAdapter = adapter ?: MigrationSourceAdapter( val ourAdapter = adapter ?: MigrationSourceAdapter(
getEnabledSources().map { MigrationSourceItem(it, isEnabled(it.id.toString())) }, getEnabledSources().map { MigrationSourceItem(it, isEnabled(it.id.toString())) },
this this
) )
adapter = ourAdapter adapter = ourAdapter
recycler.layoutManager = LinearLayoutManager(view.context) recycler.layoutManager = LinearLayoutManager(view.context)
@ -99,7 +100,7 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController(bundle), F
config.toList(), config.toList(),
extraSearchParams = extraParam extraSearchParams = extraParam
) )
).withFadeTransaction()) ).withFadeTransaction().tag(MigrationListController.TAG))
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@ -129,7 +130,7 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController(bundle), F
private fun getEnabledSources(): List<HttpSource> { private fun getEnabledSources(): List<HttpSource> {
val languages = prefs.enabledLanguages().getOrDefault() val languages = prefs.enabledLanguages().getOrDefault()
val sourcesSaved = prefs.migrationSources().getOrDefault().split("/") val sourcesSaved = prefs.migrationSources().getOrDefault().split("/")
var sources = sourceManager.getCatalogueSources() var sources = sourceManager.getVisibleCatalogueSources()
.filterIsInstance<HttpSource>() .filterIsInstance<HttpSource>()
.filter { it.lang in languages } .filter { it.lang in languages }
.sortedBy { "(${it.lang}) ${it.name}" } .sortedBy { "(${it.lang}) ${it.name}" }

View File

@ -31,12 +31,14 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.migration.MigrationMangaDialog import eu.kanade.tachiyomi.ui.migration.MigrationMangaDialog
import eu.kanade.tachiyomi.ui.migration.SearchController import eu.kanade.tachiyomi.ui.migration.SearchController
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.util.await
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.executeOnIO import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener import exh.util.RecyclerWindowInsetsListener
import exh.util.applyWindowInsetsForController
import exh.util.await
import exh.util.executeOnIO
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlinx.android.synthetic.main.chapters_controller.* import kotlinx.android.synthetic.main.chapters_controller.*
@ -55,8 +57,7 @@ import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class MigrationListController(bundle: Bundle? = null) : BaseController(bundle), class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
MigrationProcessAdapter.MigrationProcessInterface, MigrationProcessAdapter.MigrationProcessInterface, CoroutineScope {
CoroutineScope {
init { init {
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -74,7 +75,8 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
private val smartSearchEngine = SmartSearchEngine(coroutineContext, config?.extraSearchParams) private val smartSearchEngine = SmartSearchEngine(coroutineContext, config?.extraSearchParams)
private var migrationsJob: Job? = null var migrationsJob: Job? = null
private set
private var migratingManga: MutableList<MigratingManga>? = null private var migratingManga: MutableList<MigratingManga>? = null
private var selectedPosition: Int? = null private var selectedPosition: Int? = null
private var manaulMigrations = 0 private var manaulMigrations = 0
@ -84,12 +86,15 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
} }
override fun getTitle(): String? { override fun getTitle(): String? {
return resources?.getString(R.string.migration) + " (${adapter?.items?.count { it.manga return resources?.getString(R.string.migration) + " (${adapter?.items?.count {
.migrationStatus != MigrationStatus.RUNNUNG }}/${adapter?.itemCount ?: 0})" it.manga.migrationStatus != MigrationStatus.RUNNUNG
}}/${adapter?.itemCount ?: 0})"
} }
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
view.applyWindowInsetsForController()
setTitle() setTitle()
val config = this.config ?: return val config = this.config ?: return
@ -101,7 +106,7 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
new new
} }
adapter = MigrationProcessAdapter(this, view.context) adapter = MigrationProcessAdapter(this)
recycler.adapter = adapter recycler.adapter = adapter
recycler.layoutManager = LinearLayoutManager(view.context) recycler.layoutManager = LinearLayoutManager(view.context)
@ -117,23 +122,14 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
} }
} }
fun migrationFailure() { private suspend fun runMigrations(mangas: List<MigratingManga>) {
activity?.let {
MaterialDialog.Builder(it)
.title("Migration failure")
.content("An unknown error occured while migrating this manga!")
.positiveText("Ok")
.show()
}
}
suspend fun runMigrations(mangas: List<MigratingManga>) {
val useSourceWithMost = preferences.useSourceWithMost().getOrDefault() val useSourceWithMost = preferences.useSourceWithMost().getOrDefault()
val useSmartSearch = preferences.smartMigration().getOrDefault() val useSmartSearch = preferences.smartMigration().getOrDefault()
val sources = preferences.migrationSources().getOrDefault().split("/").mapNotNull { val sources = preferences.migrationSources().getOrDefault().split("/").mapNotNull {
val value = it.toLongOrNull() ?: return val value = it.toLongOrNull() ?: return
sourceManager.get(value) as? CatalogueSource } sourceManager.get(value) as? CatalogueSource
}
if (config == null) return if (config == null) return
for (manga in mangas) { for (manga in mangas) {
if (migrationsJob?.isCancelled == true) { if (migrationsJob?.isCancelled == true) {
@ -173,11 +169,23 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
} }
if (searchResult != null) { if (searchResult != null) {
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id) val localManga =
val chapters = source.fetchChapterList(localManga).toSingle().await( smartSearchEngine.networkToLocalManga(
Schedulers.io()) searchResult,
source.id
)
val chapters =
source.fetchChapterList(localManga).toSingle()
.await(
Schedulers.io()
)
try { try {
syncChaptersWithSource(db, chapters, localManga, source) syncChaptersWithSource(
db,
chapters,
localManga,
source
)
} catch (e: Exception) { } catch (e: Exception) {
return@async null return@async null
} }
@ -208,7 +216,7 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id) val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
val chapters = try { val chapters = try {
source.fetchChapterList(localManga) source.fetchChapterList(localManga)
.toSingle().await(Schedulers.io()) } catch (e: java.lang.Exception) { .toSingle().await(Schedulers.io()) } catch (e: java.lang.Exception) {
Timber.e(e) Timber.e(e)
emptyList<SChapter>() emptyList<SChapter>()
} ?: emptyList() } ?: emptyList()
@ -239,10 +247,9 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
if (result != null && result.thumbnail_url == null) { if (result != null && result.thumbnail_url == null) {
try { try {
val newManga = sourceManager.getOrStub(result.source) val newManga =
.fetchMangaDetails(result) sourceManager.getOrStub(result.source).fetchMangaDetails(result)
.toSingle() .toSingle().await()
.await()
result.copyFrom(newManga) result.copyFrom(newManga)
db.insertManga(result).executeAsBlocking() db.insertManga(result).executeAsBlocking()
@ -253,8 +260,8 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
} }
} }
manga.migrationStatus = if (result == null) MigrationStatus.MANGA_NOT_FOUND else manga.migrationStatus =
MigrationStatus.MANGA_FOUND if (result == null) MigrationStatus.MANGA_NOT_FOUND else MigrationStatus.MANGA_FOUND
adapter?.sourceFinished() adapter?.sourceFinished()
manga.searchResult.initialize(result?.id) manga.searchResult.initialize(result?.id)
} }
@ -286,8 +293,7 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
ids.removeAt(index) ids.removeAt(index)
config.mangaIds = ids config.mangaIds = ids
val index2 = migratingManga?.indexOf(item.manga) ?: return val index2 = migratingManga?.indexOf(item.manga) ?: return
if (index2 > -1) if (index2 > -1) migratingManga?.removeAt(index2)
migratingManga?.removeAt(index2)
} }
} }
@ -296,8 +302,9 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
val res = resources val res = resources
if (res != null) { if (res != null) {
activity?.toast( activity?.toast(
res.getQuantityString(R.plurals.manga_migrated, res.getQuantityString(
manaulMigrations, manaulMigrations) R.plurals.manga_migrated, manaulMigrations, manaulMigrations
)
) )
} }
router.popCurrentController() router.popCurrentController()
@ -366,7 +373,7 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
adapter?.notifyDataSetChanged() adapter?.notifyDataSetChanged()
} else { } else {
migratingManga.manga.migrationStatus = MigrationStatus.MANGA_NOT_FOUND migratingManga.manga.migrationStatus = MigrationStatus.MANGA_NOT_FOUND
activity?.toast(R.string.error_fetching_migration, Toast.LENGTH_LONG) activity?.toast(R.string.no_chapters_found_for_migration, Toast.LENGTH_LONG)
adapter?.notifyDataSetChanged() adapter?.notifyDataSetChanged()
} }
} }
@ -397,8 +404,8 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
if (manga != null) { if (manga != null) {
val newStack = router.backstack.filter { val newStack = router.backstack.filter {
it.controller() !is MangaController && it.controller() !is MangaController &&
it.controller() !is MigrationListController && it.controller() !is MigrationListController &&
it.controller() !is PreMigrationController it.controller() !is PreMigrationController
} + MangaController(manga).withFadeTransaction() } + MangaController(manga).withFadeTransaction()
router.setBackstack(newStack, FadeChangeHandler()) router.setBackstack(newStack, FadeChangeHandler())
return@launchUI return@launchUI
@ -411,7 +418,7 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
override fun handleBack(): Boolean { override fun handleBack(): Boolean {
activity?.let { activity?.let {
MaterialDialog.Builder(it).title(R.string.stop_migration) MaterialDialog.Builder(it).title(R.string.stop_migrating)
.positiveText(R.string.action_stop) .positiveText(R.string.action_stop)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.onPositive { _, _ -> .onPositive { _, _ ->
@ -441,12 +448,16 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
if (adapter?.itemCount == 1) { if (adapter?.itemCount == 1) {
menuMigrate.icon = VectorDrawableCompat.create( menuMigrate.icon = VectorDrawableCompat.create(
resources!!, R.drawable.ic_done, null resources!!, R.drawable.ic_done_24dp, null
) )
} }
val translucentWhite = ColorUtils.setAlphaComponent(Color.WHITE, 127)
menuCopy.icon?.setTint(if (allMangasDone) Color.WHITE else translucentWhite) menuCopy.icon.mutate()
menuMigrate?.icon?.setTint(if (allMangasDone) Color.WHITE else translucentWhite) menuMigrate.icon.mutate()
val tintColor = activity?.getResourceColor(R.attr.colorPrimary) ?: Color.WHITE
val translucentWhite = ColorUtils.setAlphaComponent(tintColor, 127)
menuCopy.icon?.setTint(if (allMangasDone) tintColor else translucentWhite)
menuMigrate?.icon?.setTint(if (allMangasDone) tintColor else translucentWhite)
menuCopy.isEnabled = allMangasDone menuCopy.isEnabled = allMangasDone
menuMigrate.isEnabled = allMangasDone menuMigrate.isEnabled = allMangasDone
} }
@ -455,10 +466,18 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
val totalManga = adapter?.itemCount ?: 0 val totalManga = adapter?.itemCount ?: 0
val mangaSkipped = adapter?.mangasSkipped() ?: 0 val mangaSkipped = adapter?.mangasSkipped() ?: 0
when (item.itemId) { when (item.itemId) {
R.id.action_copy_manga -> MigrationMangaDialog(this, true, totalManga, mangaSkipped) R.id.action_copy_manga -> MigrationMangaDialog(
.showDialog(router) this,
R.id.action_migrate_manga -> MigrationMangaDialog(this, false, totalManga, mangaSkipped) true,
.showDialog(router) totalManga,
mangaSkipped
).showDialog(router)
R.id.action_migrate_manga -> MigrationMangaDialog(
this,
false,
totalManga,
mangaSkipped
).showDialog(router)
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
} }
return true return true

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.migration.manga.process package eu.kanade.tachiyomi.ui.migration.manga.process
import android.content.Context
import android.view.MenuItem import android.view.MenuItem
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -16,8 +15,7 @@ import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class MigrationProcessAdapter( class MigrationProcessAdapter(
val controller: MigrationListController, val controller: MigrationListController
context: Context
) : FlexibleAdapter<MigrationProcessItem>(null, controller, true) { ) : FlexibleAdapter<MigrationProcessItem>(null, controller, true) {
private val db: DatabaseHelper by injectLazy() private val db: DatabaseHelper by injectLazy()
@ -73,8 +71,9 @@ class MigrationProcessAdapter(
launchUI { launchUI {
val manga = getItem(position)?.manga ?: return@launchUI val manga = getItem(position)?.manga ?: return@launchUI
db.inTransaction { db.inTransaction {
val toMangaObj = db.getManga(manga.searchResult.get() ?: return@launchUI).executeAsBlocking() val toMangaObj =
?: return@launchUI db.getManga(manga.searchResult.get() ?: return@launchUI).executeAsBlocking()
?: return@launchUI
migrateMangaInternal( migrateMangaInternal(
manga.manga() ?: return@launchUI, toMangaObj, !copy manga.manga() ?: return@launchUI, toMangaObj, !copy
) )
@ -135,8 +134,8 @@ class MigrationProcessAdapter(
db.updateMangaFavorite(prevManga).executeAsBlocking() db.updateMangaFavorite(prevManga).executeAsBlocking()
} }
manga.favorite = true manga.favorite = true
// SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title
db.updateMangaFavorite(manga).executeAsBlocking()
db.updateMangaTitle(manga).executeAsBlocking() db.updateMangaTitle(manga).executeAsBlocking()
// }
} }
} }

View File

@ -152,10 +152,10 @@ class MigrationProcessHolder(
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
if (latestChapter > 0f) { if (latestChapter > 0f) {
manga_last_chapter_label.text = context.getString(R.string.latest_x, manga_last_chapter_label.text = context.getString(R.string.latest_,
DecimalFormat("#.#").format(latestChapter)) DecimalFormat("#.#").format(latestChapter))
} else { } else {
manga_last_chapter_label.text = context.getString(R.string.latest_x, manga_last_chapter_label.text = context.getString(R.string.latest_,
context.getString(R.string.unknown)) context.getString(R.string.unknown))
} }
} }

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.util.system package exh.util
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetObject import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetObject

View File

@ -0,0 +1,101 @@
package exh.util
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.annotation.Px
inline val View.marginTop: Int
get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin ?: 0
inline val View.marginBottom: Int
get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.bottomMargin ?: 0
inline val View.marginRight: Int
get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.rightMargin ?: 0
inline val View.marginLeft: Int
get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.leftMargin ?: 0
fun View.doOnApplyWindowInsets(f: (View, WindowInsets, ViewPaddingState) -> Unit) {
// Create a snapshot of the view's padding state
val paddingState = createStateForView(this)
setOnApplyWindowInsetsListener { v, insets ->
f(v, insets, paddingState)
insets
}
requestApplyInsetsWhenAttached()
}
object ControllerViewWindowInsetsListener : View.OnApplyWindowInsetsListener {
override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
v.updateLayoutParams<FrameLayout.LayoutParams> {
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
val array = v.context.obtainStyledAttributes(attrsArray)
topMargin = insets.systemWindowInsetTop + array.getDimensionPixelSize(0, 0)
array.recycle()
}
return insets
}
}
fun View.requestApplyInsetsWhenAttached() {
if (isAttachedToWindow) {
requestApplyInsets()
} else {
addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
v.requestApplyInsets()
}
override fun onViewDetachedFromWindow(v: View) = Unit
})
}
}
inline fun <reified T : ViewGroup.LayoutParams> View.updateLayoutParams(block: T.() -> Unit) {
val params = layoutParams as T
block(params)
layoutParams = params
}
fun View.applyWindowInsetsForController() {
setOnApplyWindowInsetsListener(ControllerViewWindowInsetsListener)
requestApplyInsetsWhenAttached()
}
inline fun View.updatePaddingRelative(
@Px start: Int = paddingStart,
@Px top: Int = paddingTop,
@Px end: Int = paddingEnd,
@Px bottom: Int = paddingBottom
) {
setPaddingRelative(start, top, end, bottom)
}
private fun createStateForView(view: View) = ViewPaddingState(
view.paddingLeft,
view.paddingTop,
view.paddingRight,
view.paddingBottom,
view.paddingStart,
view.paddingEnd
)
data class ViewPaddingState(
val left: Int,
val top: Int,
val right: Int,
val bottom: Int,
val start: Int,
val end: Int
)
object RecyclerWindowInsetsListener : View.OnApplyWindowInsetsListener {
override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
v.updatePaddingRelative(bottom = insets.systemWindowInsetBottom)
// v.updatePaddingRelative(bottom = v.paddingBottom + insets.systemWindowInsetBottom)
return insets
}
}

View File

@ -333,12 +333,18 @@
app:layout_constraintTop_toBottomOf="@id/manga_genres_tags_wrapper" /> app:layout_constraintTop_toBottomOf="@id/manga_genres_tags_wrapper" />
<Button <Button
android:id="@+id/smartsearch_merge_btn" android:id="@+id/merge_btn"
style="@style/Widget.AppCompat.Button.Colored" style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:text="Merge with current" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle"
android:text="@string/merge"
android:visibility="gone"
tools:visibility="visible"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -357,12 +357,14 @@
android:textSize="12sp" /> android:textSize="12sp" />
<Button <Button
android:id="@+id/smartsearch_replace_btn" android:id="@+id/merge_btn"
style="@style/Widget.AppCompat.Button.Colored" style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:text="Migrate from current" /> android:text="@string/merge"
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout> </LinearLayout>

View File

@ -488,17 +488,6 @@
<string name="download_unread">Unread</string> <string name="download_unread">Unread</string>
<string name="confirm_delete_chapters">Are you sure you want to delete the selected chapters?</string> <string name="confirm_delete_chapters">Are you sure you want to delete the selected chapters?</string>
<string name="invalid_download_dir">Invalid download location</string> <string name="invalid_download_dir">Invalid download location</string>
<string name="confirm_migration">Migrate %1$d%2$s manga?</string>
<string name="confirm_copy">Copy %1$d%2$s manga?</string>
<string name="skipping_x">(skipping %1$d)</string>
<plurals name="manga_migrated">
<item quantity="one">%d manga migrated</item>
<item quantity="other">%d manga migrated</item>
</plurals>
<string name="error_fetching_migration">No chapters found, this manga cannot be used for
migration</string>
<string name="no_alternatives_found">No Alternatives Found</string>
<string name="stop_migration">Stop migrating?</string>
<!-- Tracking Screen --> <!-- Tracking Screen -->
<string name="manga_tracking_tab">Tracking</string> <string name="manga_tracking_tab">Tracking</string>
@ -567,18 +556,6 @@
<!-- History fragment --> <!-- History fragment -->
<string name="recent_manga_time">Ch. %1$s - %2$s</string> <string name="recent_manga_time">Ch. %1$s - %2$s</string>
<!-- Source migration screen -->
<string name="migration_info">Tap to select the source to migrate from</string>
<string name="migration_dialog_what_to_include">Select data to include</string>
<string name="migration_selection_prompt">Select a source to migrate from</string>
<string name="select">Select</string>
<string name="migrate">Migrate</string>
<string name="migrate_">Migrate %1$s</string>
<string name="copy">Copy</string>
<string name="migrating">Migrating…</string>
<string name="migration">Migration</string>
<string name="latest_x">Latest: %1$s</string>
<!-- Downloads activity and service --> <!-- Downloads activity and service -->
<string name="download_queue_error">Could not download chapters. You can try again in the downloads section</string> <string name="download_queue_error">Could not download chapters. You can try again in the downloads section</string>
@ -662,18 +639,50 @@
<string name="channel_downloader">Downloader</string> <string name="channel_downloader">Downloader</string>
<string name="channel_new_chapters">Chapter updates</string> <string name="channel_new_chapters">Chapter updates</string>
<string name="channel_ext_updates">Extension updates</string> <string name="channel_ext_updates">Extension updates</string>
<string name="channel_backup_restore">Backup and restore</string>
<string name="channel_backup_restore_progress">Progress</string> <!-- Migration -->
<string name="channel_backup_restore_complete">Complete</string> <string name="source_migration">Source migration</string>
<string name="data_to_include_in_migration">Data to include in migration</string> <string name="migration">Migration</string>
<string name="search_parameter">Search parameter (e.g. language:english)</string> <string name="skip_pre_migration">Skip pre-migration</string>
<string name="include_extra_search_parameter">Include extra search parameter when searching</string> <string name="pre_migration_skip_toast">To show this screen again, go to Settings -> Library.</string>
<string name="select_a_source_to_migrate_from">Select a source to migrate from</string>
<string name="use_intelligent_search">Search title + keywords of title</string> <string name="use_intelligent_search">Search title + keywords of title</string>
<string name="migrating_to">migrating to</string> <string name="data_to_include_in_migration">Data to include in migration</string>
<string name="search_parameter_eg">Search parameter (e.g. language:english)</string>
<string name="include_extra_search_parameter">Include extra search parameter when searching</string>
<string name="use_most_chapters">Use source with the most chapters (slower)</string> <string name="use_most_chapters">Use source with the most chapters (slower)</string>
<string name="use_first_source">Use first source with alternative</string> <string name="use_first_source">Use first source with alternative</string>
<string name="skip_this_step_next_time">Skip this step next time</string> <string name="skip_this_step_next_time">Skip this step next time</string>
<string name="pre_migration_skip_toast">To show this screen again, go to Settings -> Library.</string> <string name="search_parameter">Search parameter (e.g. language:english)</string>
<string name="include_extra_search_parameter">Include extra search parameter when searching</string>
<string name="to_show_again_setting_library">To show this screen again, go to Settings -> Library.</string>
<string name="latest_">Latest: %1$s</string>
<string name="migrating_to">migrating to</string>
<string name="dont_migrate">Don\'t migrate</string>
<string name="search_manually">Search manually</string>
<string name="migrate_now">Migrate now</string>
<string name="copy_now">Copy now</string>
<string name="select">Select</string>
<string name="migrate">Migrate</string>
<string name="migrate_">Migrate %1$s</string>
<string name="copy_value">Copy</string>
<string name="no_chapters_found_for_migration">No chapters found, this manga cannot be used for
migration</string>
<string name="no_alternatives_found">No Alternatives Found</string>
<string name="stop_migrating">Stop migrating?</string>
<plurals name="migrate_manga">
<item quantity="one">Migrate %1$d%2$s manga?</item>
<item quantity="other">Migrate %1$d%2$s manga?</item>
</plurals>
<plurals name="copy_manga">
<item quantity="one">Copy %1$d%2$s manga?</item>
<item quantity="other">Copy %1$d%2$s manga?</item>
</plurals>
<string name="skipping_">(skipping %1$d)</string>
<plurals name="manga_migrated">
<item quantity="one">%d manga migrated</item>
<item quantity="other">%d manga migrated</item>
</plurals>
<!-- EXH --> <!-- EXH -->
<string name="label_login">Login</string> <string name="label_login">Login</string>
@ -699,7 +708,8 @@
<string name="eh_rounded_corner_9">Radius of 9</string> <string name="eh_rounded_corner_9">Radius of 9</string>
<string name="eh_rounded_corner_10">Radius of 10</string> <string name="eh_rounded_corner_10">Radius of 10</string>
<string name="eh_rounded_corners_desc">The level of radius that the corners are rounded to. Current value is: %s</string> <string name="eh_rounded_corners_desc">The level of radius that the corners are rounded to. Current value is: %s</string>
<string name="merge">Merge with current</string>
<string name="eh_merge_with_another_source">Merge With Another</string>
</resources> </resources>