Implemented J2K Auto Source Migration

(cherry picked from commit 8ba75831e6f51f6472d85f813405ede3f679cfd8)
This commit is contained in:
jobobby04 2020-04-16 20:56:52 -04:00 committed by Jobobby04
parent c62d3abbc5
commit e7b39f29f2
40 changed files with 742 additions and 326 deletions

View File

@ -16,7 +16,7 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable
import exh.metadata.sql.tables.SearchMetadataTable
import eu.kanade.tachiyomi.data.database.tables.SearchMetadataTable
interface MangaQueries : DbProvider {

View File

@ -1,12 +1,12 @@
package exh.smartsearch
package eu.kanade.tachiyomi.smartsearch
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import exh.ui.smartsearch.SmartSearchPresenter
import exh.util.await
import eu.kanade.tachiyomi.ui.smartsearch.SmartSearchPresenter
import eu.kanade.tachiyomi.util.await
import info.debatty.java.stringsimilarity.NormalizedLevenshtein
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@ -39,7 +39,8 @@ class SmartSearchEngine(
"$query ${extraSearchParams.trim()}"
} else query
val searchResults = source.fetchSearchManga(1, builtQuery, FilterList()).toSingle().await(Schedulers.io())
val searchResults = source.fetchSearchManga(1, builtQuery, FilterList())
.toSingle().await(Schedulers.io())
searchResults.mangas.map {
val cleanedMangaTitle = cleanSmartSearchTitle(it.title)
@ -171,11 +172,11 @@ class SmartSearchEngine(
* @return a manga from the database.
*/
suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = db.getManga(sManga.url, sourceId).await()
var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking()
if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga)
val result = db.insertManga(newManga).await()
val result = db.insertManga(newManga).executeAsBlocking()
newManga.id = result.insertedId()
localManga = newManga
}

View File

@ -28,6 +28,11 @@ import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController
import eu.kanade.tachiyomi.ui.smartsearch.SmartSearchController
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController
import eu.kanade.tachiyomi.ui.source.latest.LatestUpdatesController
import kotlinx.android.parcel.Parcelize
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -56,16 +61,22 @@ class SourceController :
*/
private var adapter: SourceAdapter? = null
// EXH -->
private val mode = if (smartSearchConfig == null) Mode.CATALOGUE else Mode.SMART_SEARCH
// EXH <--
init {
setHasOptionsMenu(true)
// Enable the option menu
setHasOptionsMenu(mode == Mode.CATALOGUE)
}
override fun getTitle(): String? {
return applicationContext?.getString(R.string.label_sources)
returnwhen (mode) {
Mode.CATALOGUE -> applicationContext?.getString(R.string.label_sources)
Mode.SMART_SEARCH -> "Find in another source"
}
override fun createPresenter(): SourcePresenter {
return SourcePresenter()
return SourcePresenter(controllerMode = mode)
}
/**
@ -115,7 +126,16 @@ class SourceController :
override fun onItemClick(view: View, position: Int): Boolean {
val item = adapter?.getItem(position) as? SourceItem ?: return false
val source = item.source
when (mode) {
Mode.CATALOGUE -> {
// Open the catalogue view.
openCatalogue(source, BrowseSourceController(source))
}
Mode.SMART_SEARCH -> router.pushController(SmartSearchController(Bundle().apply {
putLong(SmartSearchController.ARG_SOURCE_ID, source.id)
putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig)
}).withFadeTransaction())
}
return false
}
@ -250,4 +270,16 @@ class SourceController :
adapter?.addScrollableHeader(LangItem(SourcePresenter.LAST_USED_KEY))
}
}
@Parcelize
data class SmartSearchConfig(val origTitle: String, val origMangaId: Long) : Parcelable
enum class Mode {
CATALOGUE,
SMART_SEARCH
}
companion object {
const val SMART_SEARCH_CONFIG = "SMART_SEARCH_CONFIG"
}
}

View File

@ -614,9 +614,8 @@ open class BrowseSourceController(bundle: Bundle) :
protected companion object {
const val SOURCE_ID_KEY = "sourceId"
const val SEARCH_QUERY_KEY = "searchQuery"
// EXH -->
const val SMART_SEARCH_CONFIG_KEY = "smartSearchConfig"
// EXH <--
}
}

View File

@ -5,9 +5,9 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.data.database.tables.SearchMetadataTable
import eu.kanade.tachiyomi.ui.category.CategoryAdapter
import exh.isLewdSource
import exh.metadata.sql.tables.SearchMetadataTable
import exh.search.SearchEngine
import exh.util.await
import exh.util.cancellable

View File

@ -34,9 +34,17 @@ import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.main.offsetFabAppbarHeight
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.migration.MigrationController
import eu.kanade.tachiyomi.ui.migration.MigrationInterface
import eu.kanade.tachiyomi.ui.migration.SearchController
import eu.kanade.tachiyomi.ui.migration.manga.design.MigrationDesignController
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.visible
import exh.favorites.FavoritesIntroDialog
import exh.favorites.FavoritesSyncStatus
import exh.ui.LoaderManager
import java.io.IOException
import kotlinx.android.synthetic.main.main_activity.tabs
import kotlinx.coroutines.flow.filter
@ -57,7 +65,8 @@ class LibraryController(
TabbedController,
ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener,
DeleteLibraryMangasDialog.Listener {
DeleteLibraryMangasDialog.Listener,
MigrationInterface {
/**
* Position of the active category.
@ -86,6 +95,11 @@ class LibraryController(
*/
val selectionRelay: PublishRelay<LibrarySelectionEvent> = PublishRelay.create()
/**
* Current mangas to move.
*/
private var migratingMangas = mutableSetOf<Manga>()
/**
* Relay to notify search query changes.
*/
@ -468,8 +482,9 @@ class LibraryController(
R.id.action_delete -> showDeleteMangaDialog()
R.id.action_select_all -> selectAllCategoryManga()
R.id.action_select_inverse -> selectInverseCategoryManga()
R.id.action_auto_source_migration -> {
router.pushController(MigrationDesignController.create(
R.id.action_migrate -> {
router.pushController(
MigrationDesignController.create(
selectedMangas.mapNotNull { it.id }
).withFadeTransaction())
destroyActionModeIfNeeded()
@ -479,6 +494,27 @@ class LibraryController(
return true
}
override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? {
if (manga.id != prevManga.id) {
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

@ -10,10 +10,14 @@ import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
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.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.combineLatest
import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
import java.io.IOException
@ -367,6 +371,84 @@ 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

@ -1,35 +1,33 @@
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 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.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.MigrationControllerBinding
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 exh.ui.migration.manga.design.MigrationDesignController
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.util.lang.launchUI
import exh.util.RecyclerWindowInsetsListener
import exh.util.applyWindowInsetsForController
import exh.util.await
import kotlinx.android.synthetic.main.migration_controller.migration_recycler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MigrationController : NucleusController<MigrationPresenter>(),
class MigrationController :
NucleusController<MigrationControllerBinding, MigrationPresenter>(),
FlexibleAdapter.OnItemClickListener,
SourceAdapter.OnSelectClickListener,
SourceAdapter.OnAutoClickListener {
SourceAdapter.OnAutoClickListener,
MigrationInterface {
private var adapter: FlexibleAdapter<IFlexible<*>>? = null
@ -44,15 +42,26 @@ class MigrationController : NucleusController<MigrationPresenter>(),
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.migration_controller, container, false)
binding = MigrationControllerBinding.inflate(inflater)
return binding.root
}
fun searchController(manga: Manga): SearchController {
val controller = SearchController(manga)
controller.targetController = this
return controller
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
view.applyWindowInsetsForController()
adapter = FlexibleAdapter(null, this)
migration_recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
migration_recycler.adapter = adapter
binding.migrationRecycler.layoutManager =
androidx.recyclerview.widget.LinearLayoutManager(view.context)
binding.migrationRecycler.adapter = adapter
binding.migrationRecycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
}
override fun onDestroyView(view: View) {
@ -75,29 +84,24 @@ class MigrationController : NucleusController<MigrationPresenter>(),
fun render(state: ViewState) {
if (state.selectedSource == null) {
title = resources?.getString(R.string.label_migration)
title = resources?.getString(R.string.source_migration)
if (adapter !is SourceAdapter) {
adapter = SourceAdapter(this)
binding.migrationRecycler.adapter = adapter
}
adapter?.updateDataSet(state.sourcesWithManga)
} else {
// val switching = title == resources?.getString(R.string.source_migration)
title = state.selectedSource.toString()
if (adapter !is MangaAdapter) {
adapter = MangaAdapter(this)
binding.migrationRecycler.adapter = adapter
}
adapter?.updateDataSet(state.mangaForSource)
}
}
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)
adapter?.updateDataSet(state.mangaForSource, true)
/*if (switching) launchUI {
migration_recycler.alpha = 0f
migration_recycler.animate().alpha(1f).setStartDelay(100).setDuration(200).start()
}*/
}
}
@ -105,10 +109,11 @@ class MigrationController : NucleusController<MigrationPresenter>(),
val item = adapter?.getItem(position) ?: return false
if (item is MangaItem) {
val controller = SearchController(item.manga)
controller.targetController = this
router.pushController(controller.withFadeTransaction())
PreMigrationController.navigateToMigration(
Injekt.get<PreferencesHelper>().skipPreMigration().get(),
router,
listOf(item.manga.id!!)
)
} else if (item is SourceItem) {
presenter.setSelectedSource(item.source)
}
@ -116,41 +121,34 @@ class MigrationController : NucleusController<MigrationPresenter>(),
}
override fun onSelectClick(position: Int) {
onItemClick(null, position)
onItemClick(view, position)
}
override fun onAutoClick(position: Int) {
val item = adapter?.getItem(position) as? SourceItem ?: return
GlobalScope.launch {
val manga = Injekt.get<DatabaseHelper>().getFavoriteMangas().asRxSingle().await(Schedulers.io())
val sourceMangas = manga.asSequence().filter { it.source == item.source.id }.map { it.id!! }.toList()
launchUI {
val manga = Injekt.get<DatabaseHelper>().getFavoriteMangas().asRxSingle().await(
Schedulers.io()
)
val sourceMangas =
manga.asSequence().filter { it.source == item.source.id }.map { it.id!! }.toList()
withContext(Dispatchers.Main) {
router.pushController(MigrationDesignController.create(sourceMangas).withFadeTransaction())
PreMigrationController.navigateToMigration(
Injekt.get<PreferencesHelper>().skipPreMigration().get(),
router,
sourceMangas
)
}
}
}
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"
override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? {
presenter.migrateManga(prevManga, manga, replace)
return null
}
}
interface MigrationInterface {
fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga?
}

View File

@ -4,11 +4,16 @@ 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.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
@ -16,7 +21,8 @@ import uy.kohesive.injekt.api.get
class MigrationPresenter(
private val sourceManager: SourceManager = Injekt.get(),
private val db: DatabaseHelper = Injekt.get()
private val db: DatabaseHelper = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get()
) : BasePresenter<MigrationController>() {
var state = ViewState()
@ -45,8 +51,10 @@ class MigrationPresenter(
.doOnNext { state = state.copy(mangaForSource = it) }
.subscribe()
// Render the view when any field changes
stateRelay.subscribeLatestCache(MigrationController::render)
stateRelay
// Render the view when any field other than isReplacingManga changes
.distinctUntilChanged { t1, t2 -> t1.isReplacingManga != t2.isReplacingManga }
.subscribeLatestCache(MigrationController::render)
}
fun setSelectedSource(source: Source) {
@ -67,4 +75,78 @@ 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().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
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()
}
}
}

View File

@ -1,4 +1,4 @@
package exh.ui.migration
package eu.kanade.tachiyomi.ui.migration
class MigrationStatus {
companion object {

View File

@ -2,16 +2,26 @@ package eu.kanade.tachiyomi.ui.migration
import android.app.Dialog
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchPresenter
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.appcompat.QueryTextEvent
import reactivecircus.flowbinding.appcompat.queryTextEvents
import uy.kohesive.injekt.injectLazy
class SearchController(
@ -19,6 +29,23 @@ class SearchController(
) : GlobalSearchController(manga?.title) {
private var newManga: Manga? = null
private var progress = 1
var totalProgress = 0
/**
* Called when controller is initialized.
*/
init {
setHasOptionsMenu(true)
}
override fun getTitle(): String? {
if (totalProgress > 1) {
return "($progress/$totalProgress) ${super.getTitle()}"
} else {
return super.getTitle()
}
}
override fun createPresenter(): GlobalSearchPresenter {
return SearchPresenter(initialQuery, manga!!)
@ -36,21 +63,62 @@ class SearchController(
newManga = savedInstanceState.getSerializable(::newManga.name) as? Manga
}
/*override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
if (totalProgress > 1) {
val menuItem = menu.add(Menu.NONE, 1, Menu.NONE, R.string.action_skip_manga)
menuItem.icon = VectorDrawableCompat.create(resources!!, R.drawable
.baseline_skip_next_white_24, null)
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
1 -> {
newManga = manga
migrateManga()
}
}
return true
}*/
fun migrateManga() {
val target = targetController as? MigrationInterface ?: return
val manga = manga ?: return
val newManga = newManga ?: return
(presenter as? SearchPresenter)?.migrateManga(manga, newManga, true)
val nextManga = target.migrateManga(manga, newManga, true)
replaceWithNewSearchController(nextManga)
}
fun copyManga() {
val target = targetController as? MigrationInterface ?: return
val manga = manga ?: return
val newManga = newManga ?: return
(presenter as? SearchPresenter)?.migrateManga(manga, newManga, false)
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) {
if (targetController is MigrationListController) {
val migrationListController = targetController as? MigrationListController
val sourceManager: SourceManager by injectLazy()
val source = sourceManager.get(manga.source) ?: return
migrationListController?.useMangaForMigration(manga, source)
router.popCurrentController()
return
}
newManga = manga
val dialog = MigrationDialog()
dialog.targetController = this
@ -62,15 +130,6 @@ class SearchController(
super.onMangaClick(manga)
}
fun renderIsReplacingManga(isReplacingManga: Boolean) {
if (isReplacingManga) {
binding.progress.visible()
} else {
binding.progress.gone()
router.popController(this)
}
}
class MigrationDialog : DialogController() {
private val preferences: PreferencesHelper by injectLazy()
@ -81,7 +140,7 @@ class SearchController(
val preselected = MigrationFlags.getEnabledFlagsPositions(prefValue)
return MaterialDialog(activity!!)
.message(R.string.migration_dialog_what_to_include)
.message(R.string.data_to_include_in_migration)
.listItemsMultiChoice(
items = MigrationFlags.titles.map { resources?.getString(it) as CharSequence },
initialSelection = preselected.toIntArray()
@ -99,4 +158,40 @@ class SearchController(
.neutralButton(android.R.string.cancel)
}
}
/**
* Adds items to the options menu.
*
* @param menu menu containing options.
* @param inflater used to load the menu xml.
*/
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
// Inflate menu.
inflater.inflate(R.menu.source_browse, menu)
// Initialize search menu
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
searchView.onActionViewExpanded() // Required to show the query in the view
searchView.setQuery(presenter.query, false)
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
return true
}
})
searchView.queryTextEvents()
.filter { it is QueryTextEvent.QuerySubmitted }
.onEach {
presenter.search(it.queryText.toString())
searchItem.collapseActionView()
setTitle() // Update toolbar title
}
.launchIn(scope)
}
}

View File

@ -21,20 +21,6 @@ class SourceAdapter(val controller: MigrationController) :
setDisplayHeadersAtStartUp(true)
}
// EXH -->
/**
* Listener for auto item clicks.
*/
val autoClickListener: OnAutoClickListener? = controller
/**
* Listener which should be called when user clicks select.
*/
interface OnAutoClickListener {
fun onAutoClick(position: Int)
}
// EXH <--
/**
* Listener for browse item clicks.
*/
@ -47,6 +33,18 @@ class SourceAdapter(val controller: MigrationController) :
fun onSelectClick(position: Int)
}
/**
* Listener for auto item clicks.
*/
val autoClickListener: OnAutoClickListener? = controller
/**
* Listener which should be called when user clicks select.
*/
interface OnAutoClickListener {
fun onAutoClick(position: Int)
}
override fun updateDataSet(items: MutableList<IFlexible<*>>?) {
if (this.items !== items) {
this.items = items

View File

@ -26,13 +26,13 @@ class SourceHolder(view: View, override val adapter: SourceAdapter) :
init {
source_latest.text = "Auto"
source_latest.setOnClickListener {
adapter.autoClickListener?.onAutoClick(adapterPosition)
}
source_browse.setText(R.string.select)
source_browse.setOnClickListener {
adapter.selectClickListener?.onSelectClick(bindingAdapterPosition)
}
source_latest.setOnClickListener {
adapter.autoClickListener?.onAutoClick(adapterPosition)
}
}
fun bind(item: SourceItem) {

View File

@ -5,5 +5,6 @@ import eu.kanade.tachiyomi.source.Source
data class ViewState(
val selectedSource: Source? = null,
val mangaForSource: List<MangaItem> = emptyList(),
val sourcesWithManga: List<SourceItem> = emptyList()
val sourcesWithManga: List<SourceItem> = emptyList(),
val isReplacingManga: Boolean = false
)

View File

@ -1,43 +1,44 @@
package exh.ui.migration.manga.design
package eu.kanade.tachiyomi.ui.migration.manga.design
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureController
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.visible
import exh.ui.base.BaseExhController
import exh.ui.migration.manga.process.MigrationProcedureConfig
import exh.ui.migration.manga.process.MigrationProcedureController
import kotlinx.android.synthetic.main.eh_migration_design.begin_migration_btn
import kotlinx.android.synthetic.main.eh_migration_design.copy_manga
import kotlinx.android.synthetic.main.eh_migration_design.copy_manga_desc
import kotlinx.android.synthetic.main.eh_migration_design.extra_search_param
import kotlinx.android.synthetic.main.eh_migration_design.extra_search_param_desc
import kotlinx.android.synthetic.main.eh_migration_design.extra_search_param_text
import kotlinx.android.synthetic.main.eh_migration_design.fuzzy_search
import kotlinx.android.synthetic.main.eh_migration_design.mig_categories
import kotlinx.android.synthetic.main.eh_migration_design.mig_chapters
import kotlinx.android.synthetic.main.eh_migration_design.migration_mode
import kotlinx.android.synthetic.main.eh_migration_design.options_group
import kotlinx.android.synthetic.main.eh_migration_design.prioritize_chapter_count
import kotlinx.android.synthetic.main.eh_migration_design.recycler
import kotlinx.android.synthetic.main.eh_migration_design.use_smart_search
import kotlinx.android.synthetic.main.migration_design_controller.begin_migration_btn
import kotlinx.android.synthetic.main.migration_design_controller.copy_manga
import kotlinx.android.synthetic.main.migration_design_controller.copy_manga_desc
import kotlinx.android.synthetic.main.migration_design_controller.extra_search_param
import kotlinx.android.synthetic.main.migration_design_controller.extra_search_param_desc
import kotlinx.android.synthetic.main.migration_design_controller.extra_search_param_text
import kotlinx.android.synthetic.main.migration_design_controller.fuzzy_search
import kotlinx.android.synthetic.main.migration_design_controller.mig_categories
import kotlinx.android.synthetic.main.migration_design_controller.mig_chapters
import kotlinx.android.synthetic.main.migration_design_controller.migration_mode
import kotlinx.android.synthetic.main.migration_design_controller.options_group
import kotlinx.android.synthetic.main.migration_design_controller.prioritize_chapter_count
import kotlinx.android.synthetic.main.migration_design_controller.recycler
import kotlinx.android.synthetic.main.migration_design_controller.use_smart_search
import uy.kohesive.injekt.injectLazy
// TODO Select all in library
class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bundle), FlexibleAdapter.OnItemClickListener {
class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle), FlexibleAdapter
.OnItemClickListener {
private val sourceManager: SourceManager by injectLazy()
private val prefs: PreferencesHelper by injectLazy()
override val layoutId: Int = R.layout.eh_migration_design
private var adapter: MigrationSourceAdapter? = null
private val config: LongArray = args.getLongArray(MANGA_IDS_EXTRA) ?: LongArray(0)
@ -46,6 +47,10 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
override fun getTitle() = "Select target sources"
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.migration_design_controller, container, false)
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
@ -54,7 +59,7 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
this
)
adapter = ourAdapter
recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
recycler.layoutManager = LinearLayoutManager(view.context)
recycler.setHasFixedSize(true)
recycler.adapter = ourAdapter
ourAdapter.itemTouchHelperCallback = null // Reset adapter touch adapter to fix drag after rotation
@ -100,7 +105,8 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
if (mig_categories.isChecked) flags = flags or MigrationFlags.CATEGORIES
if (mig_categories.isChecked) flags = flags or MigrationFlags.TRACK
router.replaceTopController(MigrationProcedureController.create(
router.replaceTopController(
MigrationProcedureController.create(
MigrationProcedureConfig(
config.toList(),
ourAdapter.items.filter {

View File

@ -1,8 +1,9 @@
package exh.ui.migration.manga.design
package eu.kanade.tachiyomi.ui.migration.manga.design
import android.os.Bundle
import eu.davidea.flexibleadapter.FlexibleAdapter
import exh.debug.DebugFunctions.sourceManager
import eu.kanade.tachiyomi.source.SourceManager
import uy.kohesive.injekt.injectLazy
class MigrationSourceAdapter(
val items: List<MigrationSourceItem>,
@ -21,7 +22,10 @@ class MigrationSourceAdapter(
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
savedInstanceState.getParcelableArrayList<MigrationSourceItem.ParcelableSI>(SELECTED_SOURCES_KEY)?.let {
val sourceManager: SourceManager by injectLazy()
savedInstanceState.getParcelableArrayList<MigrationSourceItem.ParcelableSI>(
SELECTED_SOURCES_KEY
)?.let {
updateDataSet(it.map { MigrationSourceItem.fromParcelable(sourceManager, it) })
}

View File

@ -0,0 +1,58 @@
package eu.kanade.tachiyomi.ui.migration.manga.design
import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.view.View
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.icon
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import kotlinx.android.synthetic.main.migration_source_item.image
import kotlinx.android.synthetic.main.migration_source_item.reorder
import kotlinx.android.synthetic.main.migration_source_item.title
import uy.kohesive.injekt.injectLazy
class MigrationSourceHolder(view: View, val adapter: MigrationSourceAdapter) :
BaseFlexibleViewHolder(view, adapter) {
init {
setDragHandleView(reorder)
}
fun bind(source: HttpSource, sourceEnabled: Boolean) {
val preferences by injectLazy<PreferencesHelper>()
val isMultiLanguage = preferences.enabledLanguages().get().size > 1
// Set capitalized title.
val sourceName = if (isMultiLanguage) source.toString() else source.name.capitalize()
title.text = sourceName
// Update circle letter image.
itemView.post {
val icon = source.icon()
if (icon != null) {
image.setImageDrawable(icon)
}
}
if (sourceEnabled) {
title.alpha = 1.0f
image.alpha = 1.0f
title.paintFlags = title.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
} else {
title.alpha = DISABLED_ALPHA
image.alpha = DISABLED_ALPHA
title.paintFlags = title.paintFlags or STRIKE_THRU_TEXT_FLAG
}
}
/**
* Called when an item is released.
*
* @param position The position of the released item.
*/
override fun onItemReleased(position: Int) {
super.onItemReleased(position)
adapter.updateItems()
}
companion object {
private const val DISABLED_ALPHA = 0.3f
}
}

View File

@ -1,7 +1,8 @@
package exh.ui.migration.manga.design
package eu.kanade.tachiyomi.ui.migration.manga.design
import android.os.Parcelable
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
@ -11,9 +12,9 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.android.parcel.Parcelize
class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) : AbstractFlexibleItem<MigrationSourceHolder>() {
override fun getLayoutRes() = R.layout.eh_source_item
override fun getLayoutRes() = R.layout.migration_source_item
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): MigrationSourceHolder {
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MigrationSourceHolder {
return MigrationSourceHolder(view, adapter as MigrationSourceAdapter)
}
@ -26,7 +27,7 @@ class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) :
* @param payloads List of partial changes.
*/
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: MigrationSourceHolder,
position: Int,
payloads: List<Any?>?

View File

@ -1,10 +1,11 @@
package exh.ui.migration.manga.process
package eu.kanade.tachiyomi.ui.migration.manga.process
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.viewpager.widget.ViewPager
class DeactivatableViewPager : androidx.viewpager.widget.ViewPager {
class DeactivatableViewPager : ViewPager {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

View File

@ -1,11 +1,10 @@
package exh.ui.migration.manga.process
package eu.kanade.tachiyomi.ui.migration.manga.process
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import exh.util.DeferredField
import exh.util.await
import eu.kanade.tachiyomi.util.DeferredField
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@ -27,7 +26,7 @@ class MigratingManga(
@Volatile
private var manga: Manga? = null
suspend fun manga(): Manga? {
if (manga == null) manga = db.getManga(mangaId).await()
if (manga == null) manga = db.getManga(mangaId).executeAsBlocking()
return manga
}

View File

@ -1,9 +1,9 @@
package exh.ui.migration.manga.process
package eu.kanade.tachiyomi.ui.migration.manga.process
import android.view.View
import android.view.ViewGroup
import androidx.viewpager.widget.PagerAdapter
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.elvishew.xlog.XLog
import com.google.gson.Gson
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -21,29 +21,28 @@ import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.visible
import exh.MERGED_SOURCE_ID
import exh.util.await
import java.text.DateFormat
import java.text.DecimalFormat
import java.util.Date
import kotlin.coroutines.CoroutineContext
import kotlinx.android.synthetic.main.eh_manga_card.view.loading_group
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_artist
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_author
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_chapters
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_cover
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_full_title
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_last_chapter
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_last_update
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_source
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_source_label
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_status
import kotlinx.android.synthetic.main.eh_manga_card.view.search_progress
import kotlinx.android.synthetic.main.eh_manga_card.view.search_status
import kotlinx.android.synthetic.main.eh_migration_process_item.view.accept_migration
import kotlinx.android.synthetic.main.eh_migration_process_item.view.eh_manga_card_from
import kotlinx.android.synthetic.main.eh_migration_process_item.view.eh_manga_card_to
import kotlinx.android.synthetic.main.eh_migration_process_item.view.migrating_frame
import kotlinx.android.synthetic.main.eh_migration_process_item.view.skip_migration
import kotlinx.android.synthetic.main.migration_manga_card.view.loading_group
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_artist
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_author
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_chapters
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_cover
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_full_title
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_last_chapter
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_last_update
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_source
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_source_label
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_status
import kotlinx.android.synthetic.main.migration_manga_card.view.search_progress
import kotlinx.android.synthetic.main.migration_manga_card.view.search_status
import kotlinx.android.synthetic.main.migration_process_item.view.accept_migration
import kotlinx.android.synthetic.main.migration_process_item.view.migrating_frame
import kotlinx.android.synthetic.main.migration_process_item.view.migration_manga_card_from
import kotlinx.android.synthetic.main.migration_process_item.view.migration_manga_card_to
import kotlinx.android.synthetic.main.migration_process_item.view.skip_migration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -58,13 +57,11 @@ class MigrationProcedureAdapter(
val controller: MigrationProcedureController,
val migratingManga: List<MigratingManga>,
override val coroutineContext: CoroutineContext
) : androidx.viewpager.widget.PagerAdapter(), CoroutineScope {
) : PagerAdapter(), CoroutineScope {
private val db: DatabaseHelper by injectLazy()
private val gson: Gson by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val logger = XLog.tag(this::class.simpleName)
override fun isViewFromObject(p0: View, p1: Any): Boolean {
return p0 == p1
}
@ -73,7 +70,7 @@ class MigrationProcedureAdapter(
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val item = migratingManga[position]
val view = container.inflate(R.layout.eh_migration_process_item)
val view = container.inflate(R.layout.migration_process_item)
container.addView(view)
view.skip_migration.setOnClickListener {
@ -93,7 +90,6 @@ class MigrationProcedureAdapter(
}
controller.nextMigration()
} catch (e: Exception) {
logger.e("Migration failure!", e)
controller.migrationFailure()
}
view.migrating_frame.gone()
@ -108,13 +104,13 @@ class MigrationProcedureAdapter(
return
}
val toMangaObj = db.getManga(manga.searchResult.get() ?: return).await() ?: return
val toMangaObj = db.getManga(manga.searchResult.get() ?: return).executeAsBlocking() ?: return
withContext(Dispatchers.IO) {
migrateMangaInternal(
manga.manga() ?: return@withContext,
toMangaObj,
!controller.config.copy
!(controller.config?.copy ?: false)
)
}
}
@ -124,6 +120,7 @@ class MigrationProcedureAdapter(
manga: Manga,
replace: Boolean
) {
val config = controller.config ?: return
db.inTransaction {
// Update chapters read
if (MigrationFlags.hasChapters(controller.config.migrationFlags)) {
@ -174,9 +171,9 @@ class MigrationProcedureAdapter(
val source = migratingManga.mangaSource()
if (manga != null) {
withContext(Dispatchers.Main) {
eh_manga_card_from.loading_group.gone()
eh_manga_card_from.attachManga(tag, manga, source)
eh_manga_card_from.setOnClickListener {
migration_manga_card_from.loading_group.gone()
migration_manga_card_from.attachManga(tag, manga, source)
migration_manga_card_from.setOnClickListener {
controller.router.pushController(MangaController(manga, true).withFadeTransaction())
}
}
@ -184,7 +181,7 @@ class MigrationProcedureAdapter(
tag.launch {
migratingManga.progress.asFlow().collect { (max, progress) ->
withContext(Dispatchers.Main) {
eh_manga_card_to.search_progress.let { progressBar ->
migration_manga_card_to.search_progress.let { progressBar ->
progressBar.max = max
progressBar.progress = progress
}
@ -193,23 +190,23 @@ class MigrationProcedureAdapter(
}
val searchResult = migratingManga.searchResult.get()?.let {
db.getManga(it).await()
db.getManga(it).executeAsBlocking()
}
val resultSource = searchResult?.source?.let {
sourceManager.get(it)
}
withContext(Dispatchers.Main) {
if (searchResult != null && resultSource != null) {
eh_manga_card_to.loading_group.gone()
eh_manga_card_to.attachManga(tag, searchResult, resultSource)
eh_manga_card_to.setOnClickListener {
migration_manga_card_to.loading_group.gone()
migration_manga_card_to.attachManga(tag, searchResult, resultSource)
migration_manga_card_to.setOnClickListener {
controller.router.pushController(MangaController(searchResult, true).withFadeTransaction())
}
accept_migration.isEnabled = true
accept_migration.alpha = 1.0f
} else {
eh_manga_card_to.search_progress.gone()
eh_manga_card_to.search_status.text = "Found no manga"
migration_manga_card_to.search_progress.gone()
migration_manga_card_to.search_status.text = "Found no manga"
}
}
}
@ -264,7 +261,7 @@ class MigrationProcedureAdapter(
else -> R.string.unknown
})
val mangaChapters = db.getChapters(manga).await()
val mangaChapters = db.getChapters(manga).executeAsBlocking()
manga_chapters.text = mangaChapters.size.toString()
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
val lastUpdate = Date(mangaChapters.maxBy { it.date_upload }?.date_upload ?: 0)

View File

@ -1,4 +1,4 @@
package exh.ui.migration.manga.process
package eu.kanade.tachiyomi.ui.migration.manga.process
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

View File

@ -1,22 +1,23 @@
package exh.ui.migration.manga.process
package eu.kanade.tachiyomi.ui.migration.manga.process
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.afollestad.materialdialogs.MaterialDialog
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.smartsearch.SmartSearchEngine
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.util.await
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.system.toast
import exh.smartsearch.SmartSearchEngine
import exh.ui.base.BaseExhController
import exh.util.await
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.android.synthetic.main.eh_migration_process.pager
import kotlin.coroutines.CoroutineContext
import kotlinx.android.synthetic.main.migration_process.pager
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -32,25 +33,28 @@ import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy
// TODO Will probably implode if activity is fully destroyed
class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(bundle), CoroutineScope {
override val layoutId = R.layout.eh_migration_process
class MigrationProcedureController(bundle: Bundle? = null) : BaseController(bundle), CoroutineScope {
private var titleText = "Migrate manga"
private var adapter: MigrationProcedureAdapter? = null
val config: MigrationProcedureConfig = args.getParcelable(CONFIG_EXTRA)
override val coroutineContext: CoroutineContext = Job() + Dispatchers.Default
val config: MigrationProcedureConfig? = args.getParcelable(CONFIG_EXTRA)
private val db: DatabaseHelper by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val smartSearchEngine = SmartSearchEngine(coroutineContext, config.extraSearchParams)
private val logger = XLog.tag("MigrationProcedureController")
private val smartSearchEngine = SmartSearchEngine(coroutineContext, config?.extraSearchParams)
private var migrationsJob: Job? = null
private var migratingManga: List<MigratingManga>? = null
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.migration_process, container, false)
}
override fun getTitle(): String {
return titleText
}
@ -58,12 +62,8 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
override fun onViewCreated(view: View) {
super.onViewCreated(view)
setTitle()
activity?.requestedOrientation = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
} else {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
val config = this.config ?: return
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
val newMigratingManga = migratingManga ?: run {
val new = config.mangaIds.map {
@ -121,7 +121,8 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
}
suspend fun runMigrations(mangas: List<MigratingManga>) {
val sources = config.targetSourceIds.mapNotNull { sourceManager.get(it) as? CatalogueSource }
val sources = config?.targetSourceIds?.mapNotNull { sourceManager.get(it) as?
CatalogueSource } ?: return
for (manga in mangas) {
if (!manga.searchResult.initialized && manga.migrationJob.isActive) {
@ -147,7 +148,8 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
async {
sourceSemaphore.withPermit {
try {
val searchResult = if (config.enableLenientSearch) {
val searchResult = if (config?.enableLenientSearch ==
true) {
smartSearchEngine.smartSearch(source, mangaObj.title)
} else {
smartSearchEngine.normalSearch(source, mangaObj.title)
@ -168,7 +170,6 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
// Ignore cancellations
throw e
} catch (e: Exception) {
logger.e("Failed to search in source: ${source.id}!", e)
null
}
}
@ -195,7 +196,6 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
// Ignore cancellations
throw e
} catch (e: Exception) {
logger.e("Failed to search in source: ${source.id}!", e)
null
}
@ -220,12 +220,11 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
.await()
result.copyFrom(newManga)
db.insertManga(result).await()
db.insertManga(result).executeAsBlocking()
} catch (e: CancellationException) {
// Ignore cancellations
throw e
} catch (e: Exception) {
logger.e("Could not load search manga details", e)
}
}

View File

@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.migration.MetadataFetchDialog
import eu.kanade.tachiyomi.util.preference.defaultValue
import eu.kanade.tachiyomi.util.preference.onClick
import eu.kanade.tachiyomi.util.preference.preference

View File

@ -1,4 +1,4 @@
package exh.ui.smartsearch
package eu.kanade.tachiyomi.ui.smartsearch
import android.os.Bundle
import android.view.LayoutInflater
@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.source.SourceController
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.android.synthetic.main.eh_smart_search.appbar
import kotlinx.android.synthetic.main.smart_search.appbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -29,10 +29,12 @@ class SmartSearchController(bundle: Bundle? = null) : NucleusController<SmartSea
private val sourceManager: SourceManager by injectLazy()
private val source = sourceManager.get(bundle?.getLong(ARG_SOURCE_ID, -1) ?: -1) as? CatalogueSource
private val smartSearchConfig: SourceController.SmartSearchConfig? = bundle?.getParcelable(ARG_SMART_SEARCH_CONFIG)
private val smartSearchConfig: SourceController.SmartSearchConfig? = bundle?.getParcelable(
ARG_SMART_SEARCH_CONFIG
)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup) =
inflater.inflate(R.layout.eh_smart_search, container, false)!!
inflater.inflate(R.layout.smart_search, container, false)!!
override fun getTitle() = source?.name ?: ""

View File

@ -1,13 +1,12 @@
package exh.ui.smartsearch
package eu.kanade.tachiyomi.ui.smartsearch
import android.os.Bundle
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.smartsearch.SmartSearchEngine
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.source.SourceController
import exh.smartsearch.SmartSearchEngine
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -18,7 +17,6 @@ import kotlinx.coroutines.launch
class SmartSearchPresenter(private val source: CatalogueSource?, private val config: SourceController.SmartSearchConfig?) :
BasePresenter<SmartSearchController>(), CoroutineScope {
private val logger = XLog.tag("SmartSearchPresenter")
override val coroutineContext = Job() + Dispatchers.Main
@ -43,7 +41,6 @@ class SmartSearchPresenter(private val source: CatalogueSource?, private val con
if (e is CancellationException) {
throw e
} else {
logger.e("Smart search error", e)
SearchResults.Error
}
}

View File

@ -0,0 +1,47 @@
package eu.kanade.tachiyomi.util
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* Field that can be initialized later. Users can suspend while waiting for the field to initialize.
*
* @author nulldev
*/
class DeferredField<T> {
@Volatile
private var content: T? = null
@Volatile
var initialized = false
private set
private val mutex = Mutex(true)
/**
* Initialize the field
*/
fun initialize(content: T) {
// Fast-path new listeners
this.content = content
initialized = true
// Notify current listeners
mutex.unlock()
}
/**
* Will only suspend if !initialized.
*/
suspend fun get(): T {
// Check if field is initialized and return immediately if it is
if (initialized) return content as T
// Wait for field to initialize
mutex.withLock {}
// Field is initialized, return value
return content as T
}
}

View File

@ -2,7 +2,7 @@ package exh.metadata.metadata.base
import com.pushtorefresh.storio.operations.PreparedOperation
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import exh.metadata.sql.models.SearchMetadata
import eu.kanade.tachiyomi.data.database.models.SearchMetadata
import exh.metadata.sql.models.SearchTag
import exh.metadata.sql.models.SearchTitle
import kotlin.reflect.KClass

View File

@ -1,9 +1,9 @@
package exh.metadata.metadata.base
import com.google.gson.GsonBuilder
import eu.kanade.tachiyomi.data.database.models.SearchMetadata
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.forEach
import exh.metadata.sql.models.SearchMetadata
import exh.metadata.sql.models.SearchTag
import exh.metadata.sql.models.SearchTitle
import exh.plusAssign

View File

@ -1,6 +1,6 @@
package exh.search
import exh.metadata.sql.tables.SearchMetadataTable
import eu.kanade.tachiyomi.data.database.tables.SearchMetadataTable
import exh.metadata.sql.tables.SearchTagTable
import exh.metadata.sql.tables.SearchTitleTable

View File

@ -1,42 +0,0 @@
package exh.ui.migration.manga.design
import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.view.getRound
import kotlinx.android.synthetic.main.eh_source_item.image
import kotlinx.android.synthetic.main.eh_source_item.reorder
import kotlinx.android.synthetic.main.eh_source_item.title
class MigrationSourceHolder(view: View, val adapter: FlexibleAdapter<MigrationSourceItem>) :
BaseFlexibleViewHolder(view, adapter) {
init {
setDragHandleView(reorder)
}
fun bind(source: HttpSource, sourceEnabled: Boolean) {
// Set capitalized title.
title.text = source.name.capitalize()
// Update circle letter image.
itemView.post {
image.setImageDrawable(image.getRound(source.name.take(1).toUpperCase(), false))
}
if (sourceEnabled) {
title.alpha = 1.0f
image.alpha = 1.0f
title.paintFlags = title.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
} else {
title.alpha = DISABLED_ALPHA
image.alpha = DISABLED_ALPHA
title.paintFlags = title.paintFlags or STRIKE_THRU_TEXT_FLAG
}
}
companion object {
private const val DISABLED_ALPHA = 0.3f
}
}

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

@ -16,7 +16,7 @@
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/eh_source_item">
tools:listitem="@layout/migration_source_item">
</androidx.recyclerview.widget.RecyclerView>
@ -25,7 +25,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="Data to include in migration"
android:text="@string/data_to_include_in_migration"
android:textAppearance="@style/TextAppearance.Medium.Body2"
app:layout_constraintBottom_toTopOf="@+id/mig_chapters"
app:layout_constraintStart_toStartOf="@+id/textView" />
@ -45,7 +45,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:checked="true"
android:text="@string/categories"
app:layout_constraintBottom_toBottomOf="@+id/mig_chapters"
@ -56,7 +55,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:checked="true"
android:text="@string/track"
app:layout_constraintBottom_toBottomOf="@+id/mig_categories"
@ -67,9 +65,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
android:text="Options"
android:text="@string/options"
android:textAppearance="@style/TextAppearance.Medium.Body2"
app:layout_constraintBottom_toTopOf="@+id/prioritize_chapter_count"
app:layout_constraintStart_toStartOf="parent" />
@ -95,7 +92,8 @@
android:clickable="true"
app:layout_constraintBottom_toTopOf="@+id/fuzzy_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" />
app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count"
android:focusable="true" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/use_smart_search"
@ -114,11 +112,12 @@
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:gravity="start|center_vertical"
android:text="Use intelligent search algorithm"
android:text="@string/use_intelligent_search"
android:clickable="true"
app:layout_constraintBottom_toTopOf="@+id/copy_manga"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" />
app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count"
android:focusable="true" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/copy_manga"
@ -137,11 +136,12 @@
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:gravity="start|center_vertical"
android:text="Keep old manga"
android:text="@string/keep_old_manga"
android:clickable="true"
app:layout_constraintBottom_toTopOf="@+id/extra_search_param"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" />
app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count"
android:focusable="true" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/extra_search_param"
@ -160,11 +160,12 @@
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:gravity="start|center_vertical"
android:text="Include extra search parameter when searching"
android:text="@string/include_extra_search_parameter"
android:clickable="true"
app:layout_constraintBottom_toTopOf="@+id/extra_search_param_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" />
app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count"
android:focusable="true" />
<EditText
android:id="@+id/extra_search_param_text"
@ -175,11 +176,12 @@
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:ems="10"
android:hint="Search parameter (e.g. language:english)"
android:hint="@string/search_parameter"
android:inputType="textPersonName"
app:layout_constraintBottom_toTopOf="@+id/begin_migration_btn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
android:importantForAutofill="no" />
<Button
android:id="@+id/begin_migration_btn"
@ -191,7 +193,7 @@
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:text="Begin migration"
android:text="@string/begin_migration"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

View File

@ -17,7 +17,7 @@
android:id="@+id/manga_cover"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:contentDescription="@string/description_cover"
app:layout_constraintDimensionRatio="l,2:3"
@ -71,7 +71,7 @@
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:clickable="false"
android:ellipsize="end"
android:maxLines="1"
@ -96,7 +96,7 @@
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:clickable="false"
android:ellipsize="end"
android:maxLines="1"
@ -121,7 +121,7 @@
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:clickable="false"
android:ellipsize="end"
android:maxLines="1"
@ -146,7 +146,7 @@
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:clickable="false"
android:textIsSelectable="false"
app:layout_constraintLeft_toRightOf="@+id/manga_chapters_label"
@ -169,7 +169,7 @@
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:clickable="false"
android:textIsSelectable="false"
app:layout_constraintLeft_toRightOf="@+id/manga_last_chapter_label"
@ -192,7 +192,7 @@
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:clickable="false"
android:textIsSelectable="false"
app:layout_constraintLeft_toRightOf="@+id/manga_last_update_label"
@ -215,7 +215,7 @@
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:clickable="false"
android:textIsSelectable="false"
app:layout_constraintLeft_toRightOf="@+id/manga_source_label"

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimary">
android:background="?attr/colorPrimary" >
<exh.ui.migration.manga.process.DeactivatableViewPager
<eu.kanade.tachiyomi.ui.migration.manga.process.DeactivatableViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
@ -10,8 +11,8 @@
android:layout_height="match_parent">
<include
android:id="@+id/eh_manga_card_from"
layout="@layout/eh_manga_card"
android:id="@+id/migration_manga_card_from"
layout="@layout/migration_manga_card"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@ -33,12 +34,12 @@
android:scaleType="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/eh_manga_card_from"
app:srcCompat="@drawable/eh_ic_arrow_drop_down_white_100dp" />
app:layout_constraintTop_toBottomOf="@+id/migration_manga_card_from"
app:srcCompat="@drawable/ic_arrow_down_white_32dp" />
<include
android:id="@+id/eh_manga_card_to"
layout="@layout/eh_manga_card"
android:id="@+id/migration_manga_card_to"
layout="@layout/migration_manga_card"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@ -55,10 +56,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:drawableStart="@drawable/eh_ic_clear_white_24dp"
android:drawableLeft="@drawable/eh_ic_clear_white_24dp"
android:drawableStart="@drawable/ic_clear_grey"
android:drawablePadding="6dp"
android:text="Skip manga"
android:textColor="#ffffff"
@ -73,8 +72,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.5"
android:drawableStart="@drawable/eh_ic_check_white_24dp"
android:drawableLeft="@drawable/eh_ic_check_white_24dp"
android:drawableStart="@drawable/ic_check_box_24dp"
android:drawablePadding="6dp"
android:enabled="false"
android:text="Migrate manga"

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
@ -40,7 +42,7 @@
android:layout_marginBottom="8dp"
android:text="Searching source..."
android:textAppearance="@style/TextAppearance.Medium.Title"
android:textColor="@color/white" />
android:textColor="@android:color/white" />
<ProgressBar
android:id="@+id/intercept_progress"
@ -48,7 +50,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminateTint="@color/white" />
android:indeterminateTint="@android:color/white" />
</LinearLayout>
</LinearLayout>

View File

@ -31,8 +31,10 @@
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_auto_source_migration"
android:title="Source migration (automatic)"
app:showAsAction="never"/>
android:id="@+id/action_migrate"
android:icon="@drawable/baseline_swap_calls_24"
android:title="@string/label_migration"
app:iconTint="?attr/colorOnPrimary"
app:showAsAction="ifRoom" />
</menu>

View File

@ -644,6 +644,12 @@
<string name="channel_backup_restore">Backup and restore</string>
<string name="channel_backup_restore_progress">Progress</string>
<string name="channel_backup_restore_complete">Complete</string>
<string name="data_to_include_in_migration">Data to include in migration</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="keep_old_manga">Keep old manga</string>
<string name="use_intelligent_search">Use intelligent search algorithm</string>
<string name="begin_migration">Begin migration</string>
<!-- EXH -->
<string name="label_login">Login</string>