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.ChapterTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable 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 { 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.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga 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 exh.ui.smartsearch.SmartSearchPresenter import eu.kanade.tachiyomi.ui.smartsearch.SmartSearchPresenter
import exh.util.await import eu.kanade.tachiyomi.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
@ -39,7 +39,8 @@ class SmartSearchEngine(
"$query ${extraSearchParams.trim()}" "$query ${extraSearchParams.trim()}"
} else query } 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 { searchResults.mangas.map {
val cleanedMangaTitle = cleanSmartSearchTitle(it.title) val cleanedMangaTitle = cleanSmartSearchTitle(it.title)
@ -88,11 +89,11 @@ class SmartSearchEngine(
// Search first word // Search first word
val searchQueries = listOf( val searchQueries = listOf(
listOf(cleanedTitle), listOf(cleanedTitle),
splitSortedByLargest.take(2), splitSortedByLargest.take(2),
splitSortedByLargest.take(1), splitSortedByLargest.take(1),
splitCleanedTitle.take(2), splitCleanedTitle.take(2),
splitCleanedTitle.take(1) splitCleanedTitle.take(1)
) )
return searchQueries.map { return searchQueries.map {
@ -120,10 +121,10 @@ class SmartSearchEngine(
private fun removeTextInBrackets(text: String, readForward: Boolean): String { private fun removeTextInBrackets(text: String, readForward: Boolean): String {
val bracketPairs = listOf( val bracketPairs = listOf(
'(' to ')', '(' to ')',
'[' to ']', '[' to ']',
'<' to '>', '<' to '>',
'{' to '}' '{' to '}'
) )
var openingBracketPairs = bracketPairs.mapIndexed { index, (opening, _) -> var openingBracketPairs = bracketPairs.mapIndexed { index, (opening, _) ->
opening to index opening to index
@ -171,11 +172,11 @@ class SmartSearchEngine(
* @return a manga from the database. * @return a manga from the database.
*/ */
suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { 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) { if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId) val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga) newManga.copyFrom(sManga)
val result = db.insertManga(newManga).await() val result = db.insertManga(newManga).executeAsBlocking()
newManga.id = result.insertedId() newManga.id = result.insertedId()
localManga = newManga 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.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController 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.filter
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -56,16 +61,22 @@ class SourceController :
*/ */
private var adapter: SourceAdapter? = null private var adapter: SourceAdapter? = null
// EXH -->
private val mode = if (smartSearchConfig == null) Mode.CATALOGUE else Mode.SMART_SEARCH
// EXH <--
init { init {
setHasOptionsMenu(true) // Enable the option menu
setHasOptionsMenu(mode == Mode.CATALOGUE)
} }
override fun getTitle(): String? { 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 { override fun createPresenter(): SourcePresenter {
return SourcePresenter() return SourcePresenter(controllerMode = mode)
} }
/** /**
@ -115,7 +126,16 @@ class SourceController :
override fun onItemClick(view: View, position: Int): Boolean { override fun onItemClick(view: View, position: Int): Boolean {
val item = adapter?.getItem(position) as? SourceItem ?: return false val item = adapter?.getItem(position) as? SourceItem ?: return false
val source = item.source val source = item.source
openCatalogue(source, BrowseSourceController(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 return false
} }
@ -250,4 +270,16 @@ class SourceController :
adapter?.addScrollableHeader(LangItem(SourcePresenter.LAST_USED_KEY)) 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 { protected companion object {
const val SOURCE_ID_KEY = "sourceId" const val SOURCE_ID_KEY = "sourceId"
const val SEARCH_QUERY_KEY = "searchQuery" const val SEARCH_QUERY_KEY = "searchQuery"
// EXH -->
const val SMART_SEARCH_CONFIG_KEY = "smartSearchConfig" 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.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.data.database.tables.SearchMetadataTable
import eu.kanade.tachiyomi.ui.category.CategoryAdapter import eu.kanade.tachiyomi.ui.category.CategoryAdapter
import exh.isLewdSource import exh.isLewdSource
import exh.metadata.sql.tables.SearchMetadataTable
import exh.search.SearchEngine import exh.search.SearchEngine
import exh.util.await import exh.util.await
import exh.util.cancellable 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.MainActivity
import eu.kanade.tachiyomi.ui.main.offsetFabAppbarHeight import eu.kanade.tachiyomi.ui.main.offsetFabAppbarHeight
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.migration.MigrationController
import eu.kanade.tachiyomi.ui.migration.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.getResourceColor
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import exh.favorites.FavoritesIntroDialog
import exh.favorites.FavoritesSyncStatus
import exh.ui.LoaderManager
import java.io.IOException import java.io.IOException
import kotlinx.android.synthetic.main.main_activity.tabs import kotlinx.android.synthetic.main.main_activity.tabs
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
@ -57,7 +65,8 @@ class LibraryController(
TabbedController, TabbedController,
ActionMode.Callback, ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener, ChangeMangaCategoriesDialog.Listener,
DeleteLibraryMangasDialog.Listener { DeleteLibraryMangasDialog.Listener,
MigrationInterface {
/** /**
* Position of the active category. * Position of the active category.
@ -86,6 +95,11 @@ class LibraryController(
*/ */
val selectionRelay: PublishRelay<LibrarySelectionEvent> = PublishRelay.create() val selectionRelay: PublishRelay<LibrarySelectionEvent> = PublishRelay.create()
/**
* Current mangas to move.
*/
private var migratingMangas = mutableSetOf<Manga>()
/** /**
* Relay to notify search query changes. * Relay to notify search query changes.
*/ */
@ -468,10 +482,11 @@ class LibraryController(
R.id.action_delete -> showDeleteMangaDialog() R.id.action_delete -> showDeleteMangaDialog()
R.id.action_select_all -> selectAllCategoryManga() R.id.action_select_all -> selectAllCategoryManga()
R.id.action_select_inverse -> selectInverseCategoryManga() R.id.action_select_inverse -> selectInverseCategoryManga()
R.id.action_auto_source_migration -> { R.id.action_migrate -> {
router.pushController(MigrationDesignController.create( router.pushController(
MigrationDesignController.create(
selectedMangas.mapNotNull { it.id } selectedMangas.mapNotNull { it.id }
).withFadeTransaction()) ).withFadeTransaction())
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
} }
else -> return false else -> return false
@ -479,6 +494,27 @@ class LibraryController(
return true 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?) { override fun onDestroyActionMode(mode: ActionMode?) {
// Clear all the manga selections and notify child views. // Clear all the manga selections and notify child views.
selectedMangas.clear() selectedMangas.clear()

View File

@ -10,10 +10,14 @@ import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.combineLatest import eu.kanade.tachiyomi.util.lang.combineLatest
import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
import java.io.IOException import java.io.IOException
@ -367,6 +371,84 @@ class LibraryPresenter(
db.setMangaCategories(mc, mangas) db.setMangaCategories(mc, mangas)
} }
fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) {
val source = sourceManager.get(manga.source) ?: return
// state = state.copy(isReplacingManga = true)
Observable.defer { source.fetchChapterList(manga) }
.onErrorReturn { emptyList() }
.doOnNext { migrateMangaInternal(source, it, prevManga, manga, replace) }
.onErrorReturn { emptyList() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
// .doOnUnsubscribe { state = state.copy(isReplacingManga = false) }
.subscribe()
}
private fun migrateMangaInternal(
source: Source,
sourceChapters: List<SChapter>,
prevManga: Manga,
manga: Manga,
replace: Boolean
) {
val flags = preferences.migrateFlags().getOrDefault()
val migrateChapters = MigrationFlags.hasChapters(flags)
val migrateCategories = MigrationFlags.hasCategories(flags)
val migrateTracks = MigrationFlags.hasTracks(flags)
db.inTransaction {
// Update chapters read
if (migrateChapters) {
try {
syncChaptersWithSource(db, sourceChapters, manga, source)
} catch (e: Exception) {
// Worst case, chapters won't be synced
}
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
val maxChapterRead =
prevMangaChapters.filter { it.read }.maxBy { it.chapter_number }?.chapter_number
if (maxChapterRead != null) {
val dbChapters = db.getChapters(manga).executeAsBlocking()
for (chapter in dbChapters) {
if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) {
chapter.read = true
}
}
db.insertChapters(dbChapters).executeAsBlocking()
}
}
// Update categories
if (migrateCategories) {
val categories = db.getCategoriesForManga(prevManga).executeAsBlocking()
val mangaCategories = categories.map { MangaCategory.create(manga, it) }
db.setMangaCategories(mangaCategories, listOf(manga))
}
// Update track
if (migrateTracks) {
val tracks = db.getTracks(prevManga).executeAsBlocking()
for (track in tracks) {
track.id = null
track.manga_id = manga.id!!
}
db.insertTracks(tracks).executeAsBlocking()
}
// Update favorite status
if (replace) {
prevManga.favorite = false
db.updateMangaFavorite(prevManga).executeAsBlocking()
}
manga.favorite = true
db.updateMangaFavorite(manga).executeAsBlocking()
// SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title
db.updateMangaTitle(manga).executeAsBlocking()
}
}
/** /**
* Update cover with local file. * Update cover with local file.
* *

View File

@ -1,35 +1,33 @@
package eu.kanade.tachiyomi.ui.migration package eu.kanade.tachiyomi.ui.migration
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.afollestad.materialdialogs.MaterialDialog
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
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.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga 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.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.util.lang.launchUI
import exh.ui.migration.manga.design.MigrationDesignController import exh.util.RecyclerWindowInsetsListener
import exh.util.applyWindowInsetsForController
import exh.util.await import exh.util.await
import kotlinx.android.synthetic.main.migration_controller.migration_recycler
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class MigrationController : NucleusController<MigrationPresenter>(), class MigrationController :
FlexibleAdapter.OnItemClickListener, NucleusController<MigrationControllerBinding, MigrationPresenter>(),
SourceAdapter.OnSelectClickListener, FlexibleAdapter.OnItemClickListener,
SourceAdapter.OnAutoClickListener { SourceAdapter.OnSelectClickListener,
SourceAdapter.OnAutoClickListener,
MigrationInterface {
private var adapter: FlexibleAdapter<IFlexible<*>>? = null private var adapter: FlexibleAdapter<IFlexible<*>>? = null
@ -44,15 +42,26 @@ class MigrationController : NucleusController<MigrationPresenter>(),
} }
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { 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) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
view.applyWindowInsetsForController()
adapter = FlexibleAdapter(null, this) adapter = FlexibleAdapter(null, this)
migration_recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) binding.migrationRecycler.layoutManager =
migration_recycler.adapter = adapter androidx.recyclerview.widget.LinearLayoutManager(view.context)
binding.migrationRecycler.adapter = adapter
binding.migrationRecycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
@ -75,29 +84,24 @@ class MigrationController : NucleusController<MigrationPresenter>(),
fun render(state: ViewState) { fun render(state: ViewState) {
if (state.selectedSource == null) { if (state.selectedSource == null) {
title = resources?.getString(R.string.label_migration) title = resources?.getString(R.string.source_migration)
if (adapter !is SourceAdapter) { if (adapter !is SourceAdapter) {
adapter = SourceAdapter(this) adapter = SourceAdapter(this)
binding.migrationRecycler.adapter = adapter binding.migrationRecycler.adapter = adapter
} }
adapter?.updateDataSet(state.sourcesWithManga) adapter?.updateDataSet(state.sourcesWithManga)
} else { } else {
// val switching = title == resources?.getString(R.string.source_migration)
title = state.selectedSource.toString() title = state.selectedSource.toString()
if (adapter !is MangaAdapter) { if (adapter !is MangaAdapter) {
adapter = MangaAdapter(this) adapter = MangaAdapter(this)
binding.migrationRecycler.adapter = adapter binding.migrationRecycler.adapter = adapter
} }
adapter?.updateDataSet(state.mangaForSource) adapter?.updateDataSet(state.mangaForSource, true)
} /*if (switching) launchUI {
} migration_recycler.alpha = 0f
migration_recycler.animate().alpha(1f).setStartDelay(100).setDuration(200).start()
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)
} }
} }
@ -105,10 +109,11 @@ class MigrationController : NucleusController<MigrationPresenter>(),
val item = adapter?.getItem(position) ?: return false val item = adapter?.getItem(position) ?: return false
if (item is MangaItem) { if (item is MangaItem) {
val controller = SearchController(item.manga) PreMigrationController.navigateToMigration(
controller.targetController = this Injekt.get<PreferencesHelper>().skipPreMigration().get(),
router,
router.pushController(controller.withFadeTransaction()) listOf(item.manga.id!!)
)
} else if (item is SourceItem) { } else if (item is SourceItem) {
presenter.setSelectedSource(item.source) presenter.setSelectedSource(item.source)
} }
@ -116,41 +121,34 @@ class MigrationController : NucleusController<MigrationPresenter>(),
} }
override fun onSelectClick(position: Int) { override fun onSelectClick(position: Int) {
onItemClick(null, position) onItemClick(view, position)
} }
override fun onAutoClick(position: Int) { override fun onAutoClick(position: Int) {
val item = adapter?.getItem(position) as? SourceItem ?: return val item = adapter?.getItem(position) as? SourceItem ?: return
GlobalScope.launch { launchUI {
val manga = Injekt.get<DatabaseHelper>().getFavoriteMangas().asRxSingle().await(Schedulers.io()) val manga = Injekt.get<DatabaseHelper>().getFavoriteMangas().asRxSingle().await(
val sourceMangas = manga.asSequence().filter { it.source == item.source.id }.map { it.id!! }.toList() Schedulers.io()
)
val sourceMangas =
manga.asSequence().filter { it.source == item.source.id }.map { it.id!! }.toList()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
router.pushController(MigrationDesignController.create(sourceMangas).withFadeTransaction()) PreMigrationController.navigateToMigration(
Injekt.get<PreferencesHelper>().skipPreMigration().get(),
router,
sourceMangas
)
} }
} }
} }
fun migrateManga(prevManga: Manga, manga: Manga) { override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? {
presenter.migrateManga(prevManga, manga, replace = true) presenter.migrateManga(prevManga, manga, replace)
} return null
fun copyManga(prevManga: Manga, manga: Manga) {
presenter.migrateManga(prevManga, manga, replace = false)
}
class LoadingController : DialogController() {
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"
} }
}
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 com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
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.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.combineLatest import eu.kanade.tachiyomi.util.lang.combineLatest
import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -16,7 +21,8 @@ import uy.kohesive.injekt.api.get
class MigrationPresenter( class MigrationPresenter(
private val sourceManager: SourceManager = Injekt.get(), 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>() { ) : BasePresenter<MigrationController>() {
var state = ViewState() var state = ViewState()
@ -45,8 +51,10 @@ class MigrationPresenter(
.doOnNext { state = state.copy(mangaForSource = it) } .doOnNext { state = state.copy(mangaForSource = it) }
.subscribe() .subscribe()
// Render the view when any field changes stateRelay
stateRelay.subscribeLatestCache(MigrationController::render) // Render the view when any field other than isReplacingManga changes
.distinctUntilChanged { t1, t2 -> t1.isReplacingManga != t2.isReplacingManga }
.subscribeLatestCache(MigrationController::render)
} }
fun setSelectedSource(source: Source) { fun setSelectedSource(source: Source) {
@ -67,4 +75,78 @@ class MigrationPresenter(
private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> { private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> {
return library.filter { it.source == sourceId }.map(::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 { class MigrationStatus {
companion object { companion object {

View File

@ -2,16 +2,26 @@ package eu.kanade.tachiyomi.ui.migration
import android.app.Dialog import android.app.Dialog
import android.os.Bundle 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.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice import com.afollestad.materialdialogs.list.listItemsMultiChoice
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.util.view.visible 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 import uy.kohesive.injekt.injectLazy
class SearchController( class SearchController(
@ -19,6 +29,23 @@ class SearchController(
) : GlobalSearchController(manga?.title) { ) : GlobalSearchController(manga?.title) {
private var newManga: Manga? = null 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 { override fun createPresenter(): GlobalSearchPresenter {
return SearchPresenter(initialQuery, manga!!) return SearchPresenter(initialQuery, manga!!)
@ -36,21 +63,62 @@ class SearchController(
newManga = savedInstanceState.getSerializable(::newManga.name) as? Manga 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() { fun migrateManga() {
val target = targetController as? MigrationInterface ?: return
val manga = manga ?: return val manga = manga ?: return
val newManga = newManga ?: return val newManga = newManga ?: return
(presenter as? SearchPresenter)?.migrateManga(manga, newManga, true) val nextManga = target.migrateManga(manga, newManga, true)
replaceWithNewSearchController(nextManga)
} }
fun copyManga() { fun copyManga() {
val target = targetController as? MigrationInterface ?: return
val manga = manga ?: return val manga = manga ?: return
val newManga = newManga ?: 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) { 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 newManga = manga
val dialog = MigrationDialog() val dialog = MigrationDialog()
dialog.targetController = this dialog.targetController = this
@ -62,15 +130,6 @@ class SearchController(
super.onMangaClick(manga) super.onMangaClick(manga)
} }
fun renderIsReplacingManga(isReplacingManga: Boolean) {
if (isReplacingManga) {
binding.progress.visible()
} else {
binding.progress.gone()
router.popController(this)
}
}
class MigrationDialog : DialogController() { class MigrationDialog : DialogController() {
private val preferences: PreferencesHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy()
@ -81,7 +140,7 @@ class SearchController(
val preselected = MigrationFlags.getEnabledFlagsPositions(prefValue) val preselected = MigrationFlags.getEnabledFlagsPositions(prefValue)
return MaterialDialog(activity!!) return MaterialDialog(activity!!)
.message(R.string.migration_dialog_what_to_include) .message(R.string.data_to_include_in_migration)
.listItemsMultiChoice( .listItemsMultiChoice(
items = MigrationFlags.titles.map { resources?.getString(it) as CharSequence }, items = MigrationFlags.titles.map { resources?.getString(it) as CharSequence },
initialSelection = preselected.toIntArray() initialSelection = preselected.toIntArray()
@ -99,4 +158,40 @@ class SearchController(
.neutralButton(android.R.string.cancel) .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) 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. * Listener for browse item clicks.
*/ */
@ -47,6 +33,18 @@ class SourceAdapter(val controller: MigrationController) :
fun onSelectClick(position: Int) 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<*>>?) { override fun updateDataSet(items: MutableList<IFlexible<*>>?) {
if (this.items !== items) { if (this.items !== items) {
this.items = items this.items = items

View File

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

View File

@ -5,5 +5,6 @@ import eu.kanade.tachiyomi.source.Source
data class ViewState( data class ViewState(
val selectedSource: Source? = null, val selectedSource: Source? = null,
val mangaForSource: List<MangaItem> = emptyList(), 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.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource 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.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.migration.MigrationFlags 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.gone
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import exh.ui.base.BaseExhController import kotlinx.android.synthetic.main.migration_design_controller.begin_migration_btn
import exh.ui.migration.manga.process.MigrationProcedureConfig import kotlinx.android.synthetic.main.migration_design_controller.copy_manga
import exh.ui.migration.manga.process.MigrationProcedureController import kotlinx.android.synthetic.main.migration_design_controller.copy_manga_desc
import kotlinx.android.synthetic.main.eh_migration_design.begin_migration_btn import kotlinx.android.synthetic.main.migration_design_controller.extra_search_param
import kotlinx.android.synthetic.main.eh_migration_design.copy_manga import kotlinx.android.synthetic.main.migration_design_controller.extra_search_param_desc
import kotlinx.android.synthetic.main.eh_migration_design.copy_manga_desc import kotlinx.android.synthetic.main.migration_design_controller.extra_search_param_text
import kotlinx.android.synthetic.main.eh_migration_design.extra_search_param import kotlinx.android.synthetic.main.migration_design_controller.fuzzy_search
import kotlinx.android.synthetic.main.eh_migration_design.extra_search_param_desc import kotlinx.android.synthetic.main.migration_design_controller.mig_categories
import kotlinx.android.synthetic.main.eh_migration_design.extra_search_param_text import kotlinx.android.synthetic.main.migration_design_controller.mig_chapters
import kotlinx.android.synthetic.main.eh_migration_design.fuzzy_search import kotlinx.android.synthetic.main.migration_design_controller.migration_mode
import kotlinx.android.synthetic.main.eh_migration_design.mig_categories import kotlinx.android.synthetic.main.migration_design_controller.options_group
import kotlinx.android.synthetic.main.eh_migration_design.mig_chapters import kotlinx.android.synthetic.main.migration_design_controller.prioritize_chapter_count
import kotlinx.android.synthetic.main.eh_migration_design.migration_mode import kotlinx.android.synthetic.main.migration_design_controller.recycler
import kotlinx.android.synthetic.main.eh_migration_design.options_group import kotlinx.android.synthetic.main.migration_design_controller.use_smart_search
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 uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
// TODO Select all in library class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle), FlexibleAdapter
class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bundle), FlexibleAdapter.OnItemClickListener { .OnItemClickListener {
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val prefs: PreferencesHelper by injectLazy() private val prefs: PreferencesHelper by injectLazy()
override val layoutId: Int = R.layout.eh_migration_design
private var adapter: MigrationSourceAdapter? = null private var adapter: MigrationSourceAdapter? = null
private val config: LongArray = args.getLongArray(MANGA_IDS_EXTRA) ?: LongArray(0) 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 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) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
@ -54,7 +59,7 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
this this
) )
adapter = ourAdapter adapter = ourAdapter
recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) recycler.layoutManager = LinearLayoutManager(view.context)
recycler.setHasFixedSize(true) recycler.setHasFixedSize(true)
recycler.adapter = ourAdapter recycler.adapter = ourAdapter
ourAdapter.itemTouchHelperCallback = null // Reset adapter touch adapter to fix drag after rotation 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.CATEGORIES
if (mig_categories.isChecked) flags = flags or MigrationFlags.TRACK if (mig_categories.isChecked) flags = flags or MigrationFlags.TRACK
router.replaceTopController(MigrationProcedureController.create( router.replaceTopController(
MigrationProcedureController.create(
MigrationProcedureConfig( MigrationProcedureConfig(
config.toList(), config.toList(),
ourAdapter.items.filter { 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 android.os.Bundle
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import exh.debug.DebugFunctions.sourceManager import eu.kanade.tachiyomi.source.SourceManager
import uy.kohesive.injekt.injectLazy
class MigrationSourceAdapter( class MigrationSourceAdapter(
val items: List<MigrationSourceItem>, val items: List<MigrationSourceItem>,
@ -21,7 +22,10 @@ class MigrationSourceAdapter(
} }
override fun onRestoreInstanceState(savedInstanceState: Bundle) { 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) }) 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.os.Parcelable
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
@ -11,9 +12,9 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) : AbstractFlexibleItem<MigrationSourceHolder>() { 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) return MigrationSourceHolder(view, adapter as MigrationSourceAdapter)
} }
@ -26,7 +27,7 @@ class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) :
* @param payloads List of partial changes. * @param payloads List of partial changes.
*/ */
override fun bindViewHolder( override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: MigrationSourceHolder, holder: MigrationSourceHolder,
position: Int, position: Int,
payloads: List<Any?>? 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.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent 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) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) 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.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import exh.util.DeferredField import eu.kanade.tachiyomi.util.DeferredField
import exh.util.await
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@ -27,7 +26,7 @@ class MigratingManga(
@Volatile @Volatile
private var manga: Manga? = null private var manga: Manga? = null
suspend fun manga(): Manga? { suspend fun manga(): Manga? {
if (manga == null) manga = db.getManga(mangaId).await() if (manga == null) manga = db.getManga(mangaId).executeAsBlocking()
return manga 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.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.viewpager.widget.PagerAdapter
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.elvishew.xlog.XLog
import com.google.gson.Gson import com.google.gson.Gson
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper 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.inflate
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import exh.MERGED_SOURCE_ID import exh.MERGED_SOURCE_ID
import exh.util.await
import java.text.DateFormat import java.text.DateFormat
import java.text.DecimalFormat import java.text.DecimalFormat
import java.util.Date import java.util.Date
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlinx.android.synthetic.main.eh_manga_card.view.loading_group import kotlinx.android.synthetic.main.migration_manga_card.view.loading_group
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_artist import kotlinx.android.synthetic.main.migration_manga_card.view.manga_artist
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_author import kotlinx.android.synthetic.main.migration_manga_card.view.manga_author
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_chapters import kotlinx.android.synthetic.main.migration_manga_card.view.manga_chapters
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_cover import kotlinx.android.synthetic.main.migration_manga_card.view.manga_cover
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_full_title import kotlinx.android.synthetic.main.migration_manga_card.view.manga_full_title
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_last_chapter import kotlinx.android.synthetic.main.migration_manga_card.view.manga_last_chapter
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_last_update import kotlinx.android.synthetic.main.migration_manga_card.view.manga_last_update
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_source import kotlinx.android.synthetic.main.migration_manga_card.view.manga_source
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_source_label import kotlinx.android.synthetic.main.migration_manga_card.view.manga_source_label
import kotlinx.android.synthetic.main.eh_manga_card.view.manga_status import kotlinx.android.synthetic.main.migration_manga_card.view.manga_status
import kotlinx.android.synthetic.main.eh_manga_card.view.search_progress import kotlinx.android.synthetic.main.migration_manga_card.view.search_progress
import kotlinx.android.synthetic.main.eh_manga_card.view.search_status import kotlinx.android.synthetic.main.migration_manga_card.view.search_status
import kotlinx.android.synthetic.main.eh_migration_process_item.view.accept_migration import kotlinx.android.synthetic.main.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.migration_process_item.view.migrating_frame
import kotlinx.android.synthetic.main.eh_migration_process_item.view.eh_manga_card_to import kotlinx.android.synthetic.main.migration_process_item.view.migration_manga_card_from
import kotlinx.android.synthetic.main.eh_migration_process_item.view.migrating_frame import kotlinx.android.synthetic.main.migration_process_item.view.migration_manga_card_to
import kotlinx.android.synthetic.main.eh_migration_process_item.view.skip_migration import kotlinx.android.synthetic.main.migration_process_item.view.skip_migration
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -58,13 +57,11 @@ class MigrationProcedureAdapter(
val controller: MigrationProcedureController, val controller: MigrationProcedureController,
val migratingManga: List<MigratingManga>, val migratingManga: List<MigratingManga>,
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
) : androidx.viewpager.widget.PagerAdapter(), CoroutineScope { ) : PagerAdapter(), CoroutineScope {
private val db: DatabaseHelper by injectLazy() private val db: DatabaseHelper by injectLazy()
private val gson: Gson by injectLazy() private val gson: Gson by injectLazy()
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val logger = XLog.tag(this::class.simpleName)
override fun isViewFromObject(p0: View, p1: Any): Boolean { override fun isViewFromObject(p0: View, p1: Any): Boolean {
return p0 == p1 return p0 == p1
} }
@ -73,7 +70,7 @@ class MigrationProcedureAdapter(
override fun instantiateItem(container: ViewGroup, position: Int): Any { override fun instantiateItem(container: ViewGroup, position: Int): Any {
val item = migratingManga[position] 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) container.addView(view)
view.skip_migration.setOnClickListener { view.skip_migration.setOnClickListener {
@ -93,7 +90,6 @@ class MigrationProcedureAdapter(
} }
controller.nextMigration() controller.nextMigration()
} catch (e: Exception) { } catch (e: Exception) {
logger.e("Migration failure!", e)
controller.migrationFailure() controller.migrationFailure()
} }
view.migrating_frame.gone() view.migrating_frame.gone()
@ -108,13 +104,13 @@ class MigrationProcedureAdapter(
return return
} }
val toMangaObj = db.getManga(manga.searchResult.get() ?: return).await() ?: return val toMangaObj = db.getManga(manga.searchResult.get() ?: return).executeAsBlocking() ?: return
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
migrateMangaInternal( migrateMangaInternal(
manga.manga() ?: return@withContext, manga.manga() ?: return@withContext,
toMangaObj, toMangaObj,
!controller.config.copy !(controller.config?.copy ?: false)
) )
} }
} }
@ -124,6 +120,7 @@ class MigrationProcedureAdapter(
manga: Manga, manga: Manga,
replace: Boolean replace: Boolean
) { ) {
val config = controller.config ?: return
db.inTransaction { db.inTransaction {
// Update chapters read // Update chapters read
if (MigrationFlags.hasChapters(controller.config.migrationFlags)) { if (MigrationFlags.hasChapters(controller.config.migrationFlags)) {
@ -174,9 +171,9 @@ class MigrationProcedureAdapter(
val source = migratingManga.mangaSource() val source = migratingManga.mangaSource()
if (manga != null) { if (manga != null) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
eh_manga_card_from.loading_group.gone() migration_manga_card_from.loading_group.gone()
eh_manga_card_from.attachManga(tag, manga, source) migration_manga_card_from.attachManga(tag, manga, source)
eh_manga_card_from.setOnClickListener { migration_manga_card_from.setOnClickListener {
controller.router.pushController(MangaController(manga, true).withFadeTransaction()) controller.router.pushController(MangaController(manga, true).withFadeTransaction())
} }
} }
@ -184,7 +181,7 @@ class MigrationProcedureAdapter(
tag.launch { tag.launch {
migratingManga.progress.asFlow().collect { (max, progress) -> migratingManga.progress.asFlow().collect { (max, progress) ->
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
eh_manga_card_to.search_progress.let { progressBar -> migration_manga_card_to.search_progress.let { progressBar ->
progressBar.max = max progressBar.max = max
progressBar.progress = progress progressBar.progress = progress
} }
@ -193,23 +190,23 @@ class MigrationProcedureAdapter(
} }
val searchResult = migratingManga.searchResult.get()?.let { val searchResult = migratingManga.searchResult.get()?.let {
db.getManga(it).await() db.getManga(it).executeAsBlocking()
} }
val resultSource = searchResult?.source?.let { val resultSource = searchResult?.source?.let {
sourceManager.get(it) sourceManager.get(it)
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (searchResult != null && resultSource != null) { if (searchResult != null && resultSource != null) {
eh_manga_card_to.loading_group.gone() migration_manga_card_to.loading_group.gone()
eh_manga_card_to.attachManga(tag, searchResult, resultSource) migration_manga_card_to.attachManga(tag, searchResult, resultSource)
eh_manga_card_to.setOnClickListener { migration_manga_card_to.setOnClickListener {
controller.router.pushController(MangaController(searchResult, true).withFadeTransaction()) controller.router.pushController(MangaController(searchResult, true).withFadeTransaction())
} }
accept_migration.isEnabled = true accept_migration.isEnabled = true
accept_migration.alpha = 1.0f accept_migration.alpha = 1.0f
} else { } else {
eh_manga_card_to.search_progress.gone() migration_manga_card_to.search_progress.gone()
eh_manga_card_to.search_status.text = "Found no manga" migration_manga_card_to.search_status.text = "Found no manga"
} }
} }
} }
@ -264,7 +261,7 @@ class MigrationProcedureAdapter(
else -> R.string.unknown else -> R.string.unknown
}) })
val mangaChapters = db.getChapters(manga).await() val mangaChapters = db.getChapters(manga).executeAsBlocking()
manga_chapters.text = mangaChapters.size.toString() manga_chapters.text = mangaChapters.size.toString()
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
val lastUpdate = Date(mangaChapters.maxBy { it.date_upload }?.date_upload ?: 0) 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 android.os.Parcelable
import kotlinx.android.parcel.Parcelize 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.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.smartsearch.SmartSearchEngine
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager 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.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.system.toast 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 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.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -32,25 +33,28 @@ import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
// TODO Will probably implode if activity is fully destroyed // TODO Will probably implode if activity is fully destroyed
class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(bundle), CoroutineScope { class MigrationProcedureController(bundle: Bundle? = null) : BaseController(bundle), CoroutineScope {
override val layoutId = R.layout.eh_migration_process
private var titleText = "Migrate manga" private var titleText = "Migrate manga"
private var adapter: MigrationProcedureAdapter? = null 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 db: DatabaseHelper by injectLazy()
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val smartSearchEngine = SmartSearchEngine(coroutineContext, config.extraSearchParams) private val smartSearchEngine = SmartSearchEngine(coroutineContext, config?.extraSearchParams)
private val logger = XLog.tag("MigrationProcedureController")
private var migrationsJob: Job? = null private var migrationsJob: Job? = null
private var migratingManga: List<MigratingManga>? = 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 { override fun getTitle(): String {
return titleText return titleText
} }
@ -58,12 +62,8 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
setTitle() setTitle()
val config = this.config ?: return
activity?.requestedOrientation = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
} else {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
val newMigratingManga = migratingManga ?: run { val newMigratingManga = migratingManga ?: run {
val new = config.mangaIds.map { val new = config.mangaIds.map {
@ -121,7 +121,8 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
} }
suspend fun runMigrations(mangas: List<MigratingManga>) { 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) { for (manga in mangas) {
if (!manga.searchResult.initialized && manga.migrationJob.isActive) { if (!manga.searchResult.initialized && manga.migrationJob.isActive) {
@ -147,7 +148,8 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
async { async {
sourceSemaphore.withPermit { sourceSemaphore.withPermit {
try { try {
val searchResult = if (config.enableLenientSearch) { val searchResult = if (config?.enableLenientSearch ==
true) {
smartSearchEngine.smartSearch(source, mangaObj.title) smartSearchEngine.smartSearch(source, mangaObj.title)
} else { } else {
smartSearchEngine.normalSearch(source, mangaObj.title) smartSearchEngine.normalSearch(source, mangaObj.title)
@ -168,7 +170,6 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
// Ignore cancellations // Ignore cancellations
throw e throw e
} catch (e: Exception) { } catch (e: Exception) {
logger.e("Failed to search in source: ${source.id}!", e)
null null
} }
} }
@ -195,7 +196,6 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
// Ignore cancellations // Ignore cancellations
throw e throw e
} catch (e: Exception) { } catch (e: Exception) {
logger.e("Failed to search in source: ${source.id}!", e)
null null
} }
@ -220,12 +220,11 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
.await() .await()
result.copyFrom(newManga) result.copyFrom(newManga)
db.insertManga(result).await() db.insertManga(result).executeAsBlocking()
} catch (e: CancellationException) { } catch (e: CancellationException) {
// Ignore cancellations // Ignore cancellations
throw e throw e
} catch (e: Exception) { } 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.network.NetworkHelper
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.library.LibraryController 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.defaultValue
import eu.kanade.tachiyomi.util.preference.onClick import eu.kanade.tachiyomi.util.preference.onClick
import eu.kanade.tachiyomi.util.preference.preference 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.os.Bundle
import android.view.LayoutInflater 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.SourceController
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.util.system.toast 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.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -29,10 +29,12 @@ class SmartSearchController(bundle: Bundle? = null) : NucleusController<SmartSea
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val source = sourceManager.get(bundle?.getLong(ARG_SOURCE_ID, -1) ?: -1) as? CatalogueSource 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) = 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 ?: "" 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 android.os.Bundle
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.data.database.models.Manga 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.CatalogueSource
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.source.SourceController import eu.kanade.tachiyomi.ui.source.SourceController
import exh.smartsearch.SmartSearchEngine
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -18,7 +17,6 @@ import kotlinx.coroutines.launch
class SmartSearchPresenter(private val source: CatalogueSource?, private val config: SourceController.SmartSearchConfig?) : class SmartSearchPresenter(private val source: CatalogueSource?, private val config: SourceController.SmartSearchConfig?) :
BasePresenter<SmartSearchController>(), CoroutineScope { BasePresenter<SmartSearchController>(), CoroutineScope {
private val logger = XLog.tag("SmartSearchPresenter")
override val coroutineContext = Job() + Dispatchers.Main override val coroutineContext = Job() + Dispatchers.Main
@ -43,7 +41,6 @@ class SmartSearchPresenter(private val source: CatalogueSource?, private val con
if (e is CancellationException) { if (e is CancellationException) {
throw e throw e
} else { } else {
logger.e("Smart search error", e)
SearchResults.Error 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 com.pushtorefresh.storio.operations.PreparedOperation
import eu.kanade.tachiyomi.data.database.DatabaseHelper 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.SearchTag
import exh.metadata.sql.models.SearchTitle import exh.metadata.sql.models.SearchTitle
import kotlin.reflect.KClass import kotlin.reflect.KClass

View File

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

View File

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

View File

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

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

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

View File

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

View File

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

View File

@ -644,6 +644,12 @@
<string name="channel_backup_restore">Backup and restore</string> <string name="channel_backup_restore">Backup and restore</string>
<string name="channel_backup_restore_progress">Progress</string> <string name="channel_backup_restore_progress">Progress</string>
<string name="channel_backup_restore_complete">Complete</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 --> <!-- EXH -->
<string name="label_login">Login</string> <string name="label_login">Login</string>