Cleanup of olf auto migration

This commit is contained in:
Jay 2020-01-04 18:39:42 -08:00
parent c4321e3adf
commit d64754e3e0
23 changed files with 188 additions and 1474 deletions

View File

@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.smartsearch.SmartSearchPresenter
import eu.kanade.tachiyomi.util.await import eu.kanade.tachiyomi.util.await
import info.debatty.java.stringsimilarity.NormalizedLevenshtein import info.debatty.java.stringsimilarity.NormalizedLevenshtein
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -44,7 +43,7 @@ class SmartSearchEngine(parentContext: CoroutineContext,
searchResults.mangas.map { searchResults.mangas.map {
val cleanedMangaTitle = cleanSmartSearchTitle(it.title) val cleanedMangaTitle = cleanSmartSearchTitle(it.title)
val normalizedDistance = normalizedLevenshtein.similarity(cleanedTitle, cleanedMangaTitle) val normalizedDistance = normalizedLevenshtein.similarity(cleanedTitle, cleanedMangaTitle)
SmartSearchPresenter.SearchEntry(it, normalizedDistance) SearchEntry(it, normalizedDistance)
}.filter { (_, normalizedDistance) -> }.filter { (_, normalizedDistance) ->
normalizedDistance >= MIN_SMART_ELIGIBLE_THRESHOLD normalizedDistance >= MIN_SMART_ELIGIBLE_THRESHOLD
} }
@ -64,7 +63,7 @@ class SmartSearchEngine(parentContext: CoroutineContext,
searchResults.mangas.map { searchResults.mangas.map {
val normalizedDistance = normalizedLevenshtein.similarity(title, it.title) val normalizedDistance = normalizedLevenshtein.similarity(title, it.title)
SmartSearchPresenter.SearchEntry(it, normalizedDistance) SearchEntry(it, normalizedDistance)
}.filter { (_, normalizedDistance) -> }.filter { (_, normalizedDistance) ->
normalizedDistance >= MIN_NORMAL_ELIGIBLE_THRESHOLD normalizedDistance >= MIN_NORMAL_ELIGIBLE_THRESHOLD
} }
@ -189,4 +188,6 @@ class SmartSearchEngine(parentContext: CoroutineContext,
private val titleRegex = Regex("[^a-zA-Z0-9- ]") private val titleRegex = Regex("[^a-zA-Z0-9- ]")
private val consecutiveSpacesRegex = Regex(" +") private val consecutiveSpacesRegex = Regex(" +")
} }
} }
data class SearchEntry(val manga: SManga, val dist: Double)

View File

@ -44,9 +44,9 @@ import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.migration.MigrationController import eu.kanade.tachiyomi.ui.migration.MigrationController
import eu.kanade.tachiyomi.ui.migration.MigrationInterface 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.ui.migration.manga.design.MigrationDesignController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
import eu.kanade.tachiyomi.util.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.util.marginBottom import eu.kanade.tachiyomi.util.marginBottom
@ -54,7 +54,6 @@ import eu.kanade.tachiyomi.util.marginTop
import eu.kanade.tachiyomi.util.snack import eu.kanade.tachiyomi.util.snack
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.util.updatePaddingRelative import eu.kanade.tachiyomi.util.updatePaddingRelative
import exh.ui.migration.manga.process.MigrationProcedureConfig
import kotlinx.android.synthetic.main.library_controller.* import kotlinx.android.synthetic.main.library_controller.*
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import rx.Subscription import rx.Subscription
@ -503,7 +502,7 @@ class LibraryController(
migratingMangas.remove(nextManga) migratingMangas.remove(nextManga)
return nextManga return nextManga
} }
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

@ -1,156 +0,0 @@
package eu.kanade.tachiyomi.ui.migration
import android.app.Activity
import android.content.pm.ActivityInfo
import android.text.Html
import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.source.SourceManager
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import kotlin.concurrent.thread
class MetadataFetchDialog {
val db: DatabaseHelper by injectLazy()
val sourceManager: SourceManager by injectLazy()
fun show(context: Activity) {
//Too lazy to actually deal with orientation changes
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
var running = true
val progressDialog = MaterialDialog.Builder(context)
.title("Fetching library metadata")
.content("Preparing library")
.progress(false, 0, true)
.negativeText("Stop")
.onNegative { dialog, which ->
running = false
dialog.dismiss()
notifyMigrationStopped(context)
}
.cancelable(false)
.canceledOnTouchOutside(false)
.show()
thread {
val libraryMangas = db.getLibraryMangas().executeAsBlocking()
//.filter { isLewdSource(it.source) }
.distinctBy { it.id }
context.runOnUiThread {
progressDialog.maxProgress = libraryMangas.size
}
val mangaWithMissingMetadata = libraryMangas
.filterIndexed { index, libraryManga ->
if(index % 100 == 0) {
context.runOnUiThread {
progressDialog.setContent("[Stage 1/2] Scanning for missing metadata...")
progressDialog.setProgress(index + 1)
}
}
db.getSearchMetadataForManga(libraryManga.id!!).executeAsBlocking() == null
}
.toList()
context.runOnUiThread {
progressDialog.maxProgress = mangaWithMissingMetadata.size
}
//Actual metadata fetch code
for((i, manga) in mangaWithMissingMetadata.withIndex()) {
if(!running) break
context.runOnUiThread {
progressDialog.setContent("[Stage 2/2] Processing: ${manga.title}")
progressDialog.setProgress(i + 1)
}
try {
val source = sourceManager.get(manga.source)
source?.let {
manga.copyFrom(it.fetchMangaDetails(manga).toBlocking().first())
}
} catch (t: Throwable) {
Timber.e(t, "Could not migrate manga!")
}
}
context.runOnUiThread {
// Ensure activity still exists before we do anything to the activity
if(!context.isDestroyed) {
progressDialog.dismiss()
//Enable orientation changes again
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
if (running) displayMigrationComplete(context)
}
}
}
}
fun askMigration(activity: Activity, explicit: Boolean) {
var extra = ""
db.getLibraryMangas().asRxSingle().subscribe {
/*if(!explicit && it.none { isLewdSource(it.source) }) {
// Do not open dialog on startup if no manga
// Also do not check again
preferenceHelper.migrateLibraryAsked().set(true)
} else {*/
activity.runOnUiThread {
MaterialDialog.Builder(activity)
.title("Fetch library metadata")
.content(Html.fromHtml("You need to fetch your library's metadata before tag searching in the library will function.<br><br>" +
"This process may take a long time depending on your library size and will also use up a significant amount of internet bandwidth but can be stopped and started whenever you wish.<br><br>" +
extra +
"This process can be done later if required."))
.positiveText("Migrate")
.negativeText("Later")
.onPositive { _, _ -> show(activity) }
.onNegative { _, _ -> adviseMigrationLater(activity) }
//.onAny { _, _ -> preferenceHelper.migrateLibraryAsked().set(true) }
.cancelable(false)
.canceledOnTouchOutside(false)
.show()
}
//}
}
}
fun adviseMigrationLater(activity: Activity) {
MaterialDialog.Builder(activity)
.title("Metadata fetch canceled")
.content("Library metadata fetch has been canceled.\n\n" +
"You can run this operation later by going to: Settings > Advanced > Migrate library metadata")
.positiveText("Ok")
.cancelable(true)
.canceledOnTouchOutside(true)
.show()
}
fun notifyMigrationStopped(activity: Activity) {
MaterialDialog.Builder(activity)
.title("Metadata fetch stopped")
.content("Library metadata fetch has been stopped.\n\n" +
"You can continue this operation later by going to: Settings > Advanced > Migrate library metadata")
.positiveText("Ok")
.cancelable(true)
.canceledOnTouchOutside(true)
.show()
}
fun displayMigrationComplete(activity: Activity) {
MaterialDialog.Builder(activity)
.title("Migration complete")
.content("${activity.getString(R.string.app_name)} is now ready for use!")
.positiveText("Ok")
.cancelable(true)
.canceledOnTouchOutside(true)
.show()
}
}

View File

@ -22,7 +22,7 @@ import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.await import eu.kanade.tachiyomi.util.await
import eu.kanade.tachiyomi.util.launchUI import eu.kanade.tachiyomi.util.launchUI
import exh.ui.migration.manga.process.MigrationProcedureConfig import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
import kotlinx.android.synthetic.main.migration_controller.* import kotlinx.android.synthetic.main.migration_controller.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext

View File

@ -1,16 +0,0 @@
package eu.kanade.tachiyomi.ui.migration
class MigrationStatus {
companion object {
val NOT_INITIALIZED = -1
val COMPLETED = 0
//Migration process
val NOTIFY_USER = 1
val OPEN_BACKUP_MENU = 2
val PERFORM_BACKUP = 3
val FINALIZE_MIGRATION = 4
val MAX_MIGRATION_STEPS = 2
}
}

View File

@ -102,7 +102,7 @@ StartMigrationListener) :
var flags = 0 var flags = 0
if(mig_chapters.isChecked) flags = flags or MigrationFlags.CHAPTERS if(mig_chapters.isChecked) flags = flags or MigrationFlags.CHAPTERS
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_tracking.isChecked) flags = flags or MigrationFlags.TRACK
preferences.migrateFlags().set(flags) preferences.migrateFlags().set(flags)
} }

View File

@ -15,18 +15,12 @@ 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.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.manga.process.MigrationListController import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.gone
import eu.kanade.tachiyomi.util.marginBottom import eu.kanade.tachiyomi.util.marginBottom
import eu.kanade.tachiyomi.util.updateLayoutParams import eu.kanade.tachiyomi.util.updateLayoutParams
import eu.kanade.tachiyomi.util.updatePaddingRelative import eu.kanade.tachiyomi.util.updatePaddingRelative
import eu.kanade.tachiyomi.util.visible import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
import exh.ui.migration.manga.process.MigrationProcedureConfig
import kotlinx.android.synthetic.main.chapters_controller.*
import kotlinx.android.synthetic.main.migration_design_controller.*
import kotlinx.android.synthetic.main.migration_design_controller.fab import kotlinx.android.synthetic.main.migration_design_controller.fab
import kotlinx.android.synthetic.main.migration_design_controller.recycler import kotlinx.android.synthetic.main.migration_design_controller.recycler
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy

View File

@ -1,19 +0,0 @@
package eu.kanade.tachiyomi.ui.migration.manga.process
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.viewpager.widget.ViewPager
class DeactivatableViewPager: ViewPager {
constructor(context: Context): super(context)
constructor(context: Context, attrs: AttributeSet): super(context, attrs)
override fun onTouchEvent(event: MotionEvent): Boolean {
return !isEnabled || super.onTouchEvent(event)
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
return isEnabled && super.onInterceptTouchEvent(event)
}
}

View File

@ -1,10 +1,9 @@
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 eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcessItem
import eu.kanade.tachiyomi.util.DeferredField import eu.kanade.tachiyomi.util.DeferredField
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob

View File

@ -31,8 +31,6 @@ import eu.kanade.tachiyomi.util.await
import eu.kanade.tachiyomi.util.launchUI import eu.kanade.tachiyomi.util.launchUI
import eu.kanade.tachiyomi.util.syncChaptersWithSource import eu.kanade.tachiyomi.util.syncChaptersWithSource
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import exh.ui.migration.manga.process.MigratingManga
import exh.ui.migration.manga.process.MigrationProcedureConfig
import kotlinx.android.synthetic.main.chapters_controller.* import kotlinx.android.synthetic.main.chapters_controller.*
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope

View File

@ -1,279 +0,0 @@
package eu.kanade.tachiyomi.ui.migration.manga.process
import android.view.View
import android.view.ViewGroup
import androidx.viewpager.widget.PagerAdapter
import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
import eu.kanade.tachiyomi.util.gone
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.util.visible
import exh.ui.migration.manga.process.MigratingManga
import kotlinx.android.synthetic.main.migration_manga_card.view.*
import kotlinx.android.synthetic.main.migration_process_item.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy
import java.text.DateFormat
import java.text.DecimalFormat
import java.util.Date
import kotlin.coroutines.CoroutineContext
class MigrationProcedureAdapter(val controller: MigrationProcedureController,
val migratingManga: List<MigratingManga>,
override val coroutineContext: CoroutineContext) : PagerAdapter(), CoroutineScope {
private val db: DatabaseHelper by injectLazy()
private val sourceManager: SourceManager by injectLazy()
override fun isViewFromObject(p0: View, p1: Any): Boolean {
return p0 == p1
}
override fun getCount() = migratingManga.size
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val item = migratingManga[position]
val view = container.inflate(R.layout.migration_process_item)
container.addView(view)
view.skip_migration.setOnClickListener {
//controller.nextMigration()
}
val viewTag = ViewTag(coroutineContext)
view.tag = viewTag
view.setupView(viewTag, item)
view.accept_migration.setOnClickListener {
viewTag.launch(Dispatchers.Main) {
view.migrating_frame.visible()
try {
withContext(Dispatchers.Default) {
performMigration(item)
}
controller.nextMigration()
} catch(e: Exception) {
controller.migrationFailure()
}
view.migrating_frame.gone()
}
}
return view
}
suspend fun performMigration(manga: MigratingManga) {
if(!manga.searchResult.initialized) {
return
}
val toMangaObj = db.getManga(manga.searchResult.get() ?: return).executeAsBlocking() ?: return
withContext(Dispatchers.IO) {
migrateMangaInternal(
manga.manga() ?: return@withContext,
toMangaObj,
false
)
}
}
private fun migrateMangaInternal(prevManga: Manga,
manga: Manga,
replace: Boolean) {
val config = controller.config ?: return
//db.inTransaction {
// Update chapters read
/* if (MigrationFlags.hasChapters(controller.config.migrationFlags)) {
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 (MigrationFlags.hasCategories(controller.config.migrationFlags)) {
val categories = db.getCategoriesForManga(prevManga).executeAsBlocking()
val mangaCategories = categories.map { MangaCategory.create(manga, it) }
db.setMangaCategories(mangaCategories, listOf(manga))
}
// Update track
if (MigrationFlags.hasTracks(controller.config.migrationFlags)) {
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()
//}*/
}
fun View.setupView(tag: ViewTag, migratingManga: MigratingManga) {
tag.launch {
val manga = migratingManga.manga()
val source = migratingManga.mangaSource()
if(manga != null) {
withContext(Dispatchers.Main) {
migration_manga_card_from.loading_group.gone()
migration_manga_card_from.attachManga(tag, manga, source)
migration_manga_card_from.setOnClickListener {
controller.router.pushController(MangaController(manga, true).withFadeTransaction())
}
}
tag.launch {
migratingManga.progress.asFlow().collect { (max, progress) ->
withContext(Dispatchers.Main) {
migration_manga_card_to.search_progress.let { progressBar ->
progressBar.max = max
progressBar.progress = progress
}
}
}
}
val searchResult = migratingManga.searchResult.get()?.let {
db.getManga(it).executeAsBlocking()
}
val resultSource = searchResult?.source?.let {
sourceManager.get(it)
}
withContext(Dispatchers.Main) {
if(searchResult != null && resultSource != null) {
migration_manga_card_to.loading_group.gone()
migration_manga_card_to.attachManga(tag, searchResult, resultSource)
migration_manga_card_to.setOnClickListener {
controller.router.pushController(MangaController(searchResult, true).withFadeTransaction())
}
accept_migration.isEnabled = true
accept_migration.alpha = 1.0f
} else {
migration_manga_card_to.search_progress.gone()
migration_manga_card_to.search_status.text = "Found no manga"
}
}
}
}
}
suspend fun View.attachManga(tag: ViewTag, manga: Manga, source: Source) {
// TODO Duplicated in MangaInfoController
GlideApp.with(context)
.load(manga)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(manga_cover)
manga_full_title.text = if (manga.title.isBlank()) {
context.getString(R.string.unknown)
} else {
manga.title
}
manga_artist.text = if (manga.artist.isNullOrBlank()) {
context.getString(R.string.unknown)
} else {
manga.artist
}
manga_author.text = if (manga.author.isNullOrBlank()) {
context.getString(R.string.unknown)
} else {
manga.author
}
manga_source.text = /*if (source.id == MERGED_SOURCE_ID) {
MergedSource.MangaConfig.readFromUrl(gson, manga.url).children.map {
sourceManager.getOrStub(it.source).toString()
}.distinct().joinToString()
} else {*/
source.toString()
// }
/*if (source.id == MERGED_SOURCE_ID) {
manga_source_label.text = "Sources"
} else {*/
manga_source_label.setText(R.string.manga_info_source_label)
// }
manga_status.setText(when (manga.status) {
SManga.ONGOING -> R.string.ongoing
SManga.COMPLETED -> R.string.completed
SManga.LICENSED -> R.string.licensed
else -> R.string.unknown
})
val mangaChapters = db.getChapters(manga).executeAsBlocking()
manga_chapters.text = mangaChapters.size.toString()
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
val lastUpdate = Date(mangaChapters.maxBy { it.date_upload }?.date_upload ?: 0)
if (latestChapter > 0f) {
manga_last_chapter.text = DecimalFormat("#.#").format(latestChapter)
} else {
manga_last_chapter.setText(R.string.unknown)
}
if (lastUpdate.time != 0L) {
manga_last_update.text = DateFormat.getDateInstance(DateFormat.SHORT).format(lastUpdate)
} else {
manga_last_update.setText(R.string.unknown)
}
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
val objectAsView = `object` as View
container.removeView(objectAsView)
(objectAsView.tag as? ViewTag)?.destroy()
}
class ViewTag(parent: CoroutineContext): CoroutineScope {
/**
* The context of this scope.
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
* Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
*
* By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
*/
override val coroutineContext = parent + Job() + Dispatchers.Default
fun destroy() {
cancel()
}
}
}

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,252 +0,0 @@
package eu.kanade.tachiyomi.ui.migration.manga.process
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.smartsearch.SmartSearchEngine
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.util.await
import eu.kanade.tachiyomi.util.syncChaptersWithSource
import eu.kanade.tachiyomi.util.toast
import exh.ui.migration.manga.process.MigratingManga
import exh.ui.migration.manga.process.MigrationProcedureConfig
import kotlinx.android.synthetic.main.migration_process.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.CoroutineContext
// TODO Will probably implode if activity is fully destroyed
class MigrationProcedureController(bundle: Bundle? = null) : BaseController(bundle), CoroutineScope {
private var titleText = "Migrate manga"
private var adapter: MigrationProcedureAdapter? = null
override val coroutineContext: CoroutineContext = Job() + Dispatchers.Default
val config: MigrationProcedureConfig? = args.getParcelable(CONFIG_EXTRA)
private val db: DatabaseHelper by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val smartSearchEngine = SmartSearchEngine(coroutineContext, config?.extraSearchParams)
private var migrationsJob: Job? = null
private var migratingManga: List<MigratingManga>? = null
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.migration_process, container, false)
}
override fun getTitle(): String {
return titleText
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
setTitle()
val config = this.config ?: return
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
val newMigratingManga = migratingManga ?: run {
val new = config.mangaIds.map {
MigratingManga(db, sourceManager, it, coroutineContext)
}
migratingManga = new
new
}
adapter = MigrationProcedureAdapter(this, newMigratingManga, coroutineContext)
pager.adapter = adapter
pager.isEnabled = false
if(migrationsJob == null) {
migrationsJob = launch {
runMigrations(newMigratingManga)
}
}
pager.post {
// pager.currentItem doesn't appear to be valid if we don't do this in a post
updateTitle()
}
}
fun updateTitle() {
titleText = "Migrate manga (${pager.currentItem + 1}/${adapter?.count ?: 0})"
setTitle()
}
fun nextMigration() {
adapter?.let { adapter ->
if(pager.currentItem >= adapter.count - 1) {
applicationContext?.toast("All migrations complete!")
router.popCurrentController()
} else {
adapter.migratingManga[pager.currentItem].migrationJob.cancel()
pager.setCurrentItem(pager.currentItem + 1, true)
launch(Dispatchers.Main) {
updateTitle()
}
}
}
}
fun migrationFailure() {
activity?.let {
MaterialDialog.Builder(it)
.title("Migration failure")
.content("An unknown error occured while migrating this manga!")
.positiveText("Ok")
.show()
}
}
suspend fun runMigrations(mangas: List<MigratingManga>) {
/* val sources = config?.targetSourceIds?.mapNotNull { sourceManager.get(it) as?
CatalogueSource } ?: return
for(manga in mangas) {
if(!manga.searchResult.initialized && manga.migrationJob.isActive) {
val mangaObj = manga.manga()
if(mangaObj == null) {
manga.searchResult.initialize(null)
continue
}
val mangaSource = manga.mangaSource()
val result = try {
CoroutineScope(manga.migrationJob).async {
val validSources = sources.filter {
it.id != mangaSource.id
}
if(config.useSourceWithMostChapters) {
val sourceSemaphore = Semaphore(3)
val processedSources = AtomicInteger()
validSources.map { source ->
async {
sourceSemaphore.withPermit {
try {
val searchResult = if (config.enableLenientSearch) {
smartSearchEngine.smartSearch(source, mangaObj.title)
} else {
smartSearchEngine.normalSearch(source, mangaObj.title)
}
if(searchResult != null) {
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
val chapters = source.fetchChapterList(localManga).toSingle().await(Schedulers.io())
withContext(Dispatchers.IO) {
syncChaptersWithSource(db, chapters, localManga, source)
}
manga.progress.send(validSources.size to processedSources.incrementAndGet())
localManga to chapters.size
} else {
null
}
} catch(e: CancellationException) {
// Ignore cancellations
throw e
} catch(e: Exception) {
null
}
}
}
}.mapNotNull { it.await() }.maxBy { it.second }?.first
} else {
validSources.forEachIndexed { index, source ->
val searchResult = try {
val searchResult = if (config.enableLenientSearch) {
smartSearchEngine.smartSearch(source, mangaObj.title)
} else {
smartSearchEngine.normalSearch(source, mangaObj.title)
}
if (searchResult != null) {
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
val chapters = source.fetchChapterList(localManga).toSingle().await(Schedulers.io())
withContext(Dispatchers.IO) {
syncChaptersWithSource(db, chapters, localManga, source)
}
localManga
} else null
} catch(e: CancellationException) {
// Ignore cancellations
throw e
} catch(e: Exception) {
null
}
manga.progress.send(validSources.size to (index + 1))
if(searchResult != null) return@async searchResult
}
null
}
}.await()
} catch(e: CancellationException) {
// Ignore canceled migrations
continue
}
if(result != null && result.thumbnail_url == null) {
try {
val newManga = sourceManager.getOrStub(result.source)
.fetchMangaDetails(result)
.toSingle()
.await()
result.copyFrom(newManga)
db.insertManga(result).executeAsBlocking()
} catch(e: CancellationException) {
// Ignore cancellations
throw e
} catch(e: Exception) {
}
}
manga.searchResult.initialize(result?.id)
}
}*/
}
override fun onDestroy() {
super.onDestroy()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
companion object {
const val CONFIG_EXTRA = "config_extra"
fun create(config: MigrationProcedureConfig): MigrationProcedureController {
return MigrationProcedureController(Bundle().apply {
putParcelable(CONFIG_EXTRA, config)
})
}
}
}

View File

@ -18,8 +18,8 @@ import eu.kanade.tachiyomi.util.invisible
import eu.kanade.tachiyomi.util.launchUI import eu.kanade.tachiyomi.util.launchUI
import eu.kanade.tachiyomi.util.setVectorCompat import eu.kanade.tachiyomi.util.setVectorCompat
import eu.kanade.tachiyomi.util.visible import eu.kanade.tachiyomi.util.visible
import kotlinx.android.synthetic.main.migration_new_manga_card.view.* import kotlinx.android.synthetic.main.migration_manga_card.view.*
import kotlinx.android.synthetic.main.migration_new_process_item.* import kotlinx.android.synthetic.main.migration_process_item.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -123,6 +123,7 @@ class MigrationProcessHolder(
manga_chapters.text = "" manga_chapters.text = ""
manga_chapters.gone() manga_chapters.gone()
manga_last_chapter_label.text = "" manga_last_chapter_label.text = ""
migration_manga_card_to.setOnClickListener(null)
} }
private fun View.attachManga(manga: Manga, source: Source) { private fun View.attachManga(manga: Manga, source: Source) {

View File

@ -6,14 +6,13 @@ 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
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import exh.ui.migration.manga.process.MigratingManga
class MigrationProcessItem(val manga: MigratingManga) : class MigrationProcessItem(val manga: MigratingManga) :
AbstractFlexibleItem<MigrationProcessHolder>() { AbstractFlexibleItem<MigrationProcessHolder>() {
var holder:MigrationProcessHolder? = null var holder:MigrationProcessHolder? = null
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return R.layout.migration_new_process_item return R.layout.migration_process_item
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MigrationProcessHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MigrationProcessHolder {

View File

@ -1,92 +0,0 @@
package eu.kanade.tachiyomi.ui.smartsearch
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
import eu.kanade.tachiyomi.ui.catalogue.browse.BrowseCatalogueController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.toast
import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy
class SmartSearchController(bundle: Bundle? = null) : NucleusController<SmartSearchPresenter>(), CoroutineScope {
override val coroutineContext = Job() + Dispatchers.Main
private val sourceManager: SourceManager by injectLazy()
private val source = sourceManager.get(bundle?.getLong(ARG_SOURCE_ID, -1) ?: -1) as? CatalogueSource
private val smartSearchConfig: CatalogueController.SmartSearchConfig? = bundle?.getParcelable(
ARG_SMART_SEARCH_CONFIG
)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup) =
inflater.inflate(R.layout.smart_search, container, false)!!
override fun getTitle() = source?.name ?: ""
override fun createPresenter() = SmartSearchPresenter(source, smartSearchConfig)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
appbar.bringToFront()
if(source == null || smartSearchConfig == null) {
router.popCurrentController()
applicationContext?.toast("Missing data!")
return
}
// Init presenter now to resolve threading issues
presenter
launch(Dispatchers.Default) {
for(event in presenter.smartSearchChannel) {
withContext(NonCancellable) {
if (event is SmartSearchPresenter.SearchResults.Found) {
val transaction = MangaController(event.manga, true, smartSearchConfig).withFadeTransaction()
withContext(Dispatchers.Main) {
router.replaceTopController(transaction)
}
} else {
if (event is SmartSearchPresenter.SearchResults.NotFound) {
applicationContext?.toast("Couldn't find the manga in the source!")
} else {
applicationContext?.toast("Error performing automatic search!")
}
val transaction = BrowseCatalogueController(source, smartSearchConfig.origTitle, smartSearchConfig).withFadeTransaction()
withContext(Dispatchers.Main) {
router.replaceTopController(transaction)
}
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
cancel()
}
companion object {
const val ARG_SOURCE_ID = "SOURCE_ID"
const val ARG_SMART_SEARCH_CONFIG = "SMART_SEARCH_CONFIG"
}
}

View File

@ -1,67 +0,0 @@
package eu.kanade.tachiyomi.ui.smartsearch
import android.os.Bundle
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.smartsearch.SmartSearchEngine
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
class SmartSearchPresenter(private val source: CatalogueSource?, private val config: CatalogueController.SmartSearchConfig?):
BasePresenter<SmartSearchController>(), CoroutineScope {
override val coroutineContext = Job() + Dispatchers.Main
val smartSearchChannel = Channel<SearchResults>()
private val smartSearchEngine = SmartSearchEngine(coroutineContext)
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
if(source != null && config != null) {
launch(Dispatchers.Default) {
val result = try {
val resultManga = smartSearchEngine.smartSearch(source, config.origTitle)
if (resultManga != null) {
val localManga = smartSearchEngine.networkToLocalManga(resultManga, source.id)
SearchResults.Found(localManga)
} else {
SearchResults.NotFound
}
} catch (e: Exception) {
if (e is CancellationException) {
throw e
} else {
SearchResults.Error
}
}
smartSearchChannel.send(result)
}
}
}
override fun onDestroy() {
super.onDestroy()
cancel()
}
data class SearchEntry(val manga: SManga, val dist: Double)
sealed class SearchResults {
data class Found(val manga: Manga): SearchResults()
object NotFound: SearchResults()
object Error: SearchResults()
}
}

View File

@ -11,6 +11,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipToPadding="false" android:clipToPadding="false"
android:id="@+id/recycler" android:id="@+id/recycler"
tools:listitem="@layout/migration_new_process_item" /> tools:listitem="@layout/migration_process_item" />
</FrameLayout> </FrameLayout>

View File

@ -1,270 +1,124 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:foreground="?android:attr/selectableItemBackground" android:background="?selectable_library_drawable">
android:clickable="true"
android:focusable="true">
<androidx.constraintlayout.widget.ConstraintLayout <FrameLayout
android:layout_width="match_parent" android:id="@+id/card"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:clickable="false"> android:layout_height="220dp"
android:background="@drawable/card_background"
app:layout_constraintDimensionRatio="0.75"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_min="100dp"
app:layout_constraintHeight_min="100dp">
<ImageView <ImageView
android:id="@+id/manga_cover" android:id="@+id/thumbnail"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
android:layout_marginStart="16dp" android:background="?android:attr/colorBackground"
android:layout_marginTop="16dp" tools:background="?android:attr/colorBackground"
android:contentDescription="@string/description_cover" tools:ignore="ContentDescription"
app:layout_constraintDimensionRatio="l,2:3" tools:src="@mipmap/ic_launcher" />
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/card_scroll_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_min="100dp"
tools:background="@color/material_grey_700" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/card_scroll_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:paddingBottom="16dp"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintLeft_toRightOf="@+id/manga_cover"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/manga_cover">
<TextView
android:id="@+id/manga_full_title"
style="@style/TextAppearance.Medium.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:maxLines="2"
android:text="@string/manga_info_full_title_label"
android:textIsSelectable="false"
app:autoSizeMaxTextSize="20sp"
app:autoSizeMinTextSize="12sp"
app:autoSizeStepGranularity="2sp"
app:autoSizeTextType="uniform"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/manga_author_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:text="@string/manga_info_author_label"
android:textIsSelectable="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_full_title" />
<TextView
android:id="@+id/manga_author"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:clickable="false"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_author_label"
app:layout_constraintLeft_toRightOf="@+id/manga_author_label"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/manga_artist_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:text="@string/manga_info_artist_label"
android:textIsSelectable="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_author_label" />
<TextView
android:id="@+id/manga_artist"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:clickable="false"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_artist_label"
app:layout_constraintLeft_toRightOf="@+id/manga_artist_label"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/manga_status_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:text="@string/manga_info_status_label"
android:textIsSelectable="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_artist_label" />
<TextView
android:id="@+id/manga_status"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:clickable="false"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_status_label"
app:layout_constraintLeft_toRightOf="@+id/manga_status_label"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/manga_chapters_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:text="@string/manga_info_chapters_label"
android:textIsSelectable="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_status_label" />
<TextView
android:id="@+id/manga_chapters"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:clickable="false"
android:textIsSelectable="false"
app:layout_constraintLeft_toRightOf="@+id/manga_chapters_label"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_status_label" />
<TextView
android:id="@+id/manga_last_chapter_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:text="@string/manga_info_last_chapter_label"
android:textIsSelectable="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_chapters_label" />
<TextView
android:id="@+id/manga_last_chapter"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:clickable="false"
android:textIsSelectable="false"
app:layout_constraintLeft_toRightOf="@+id/manga_last_chapter_label"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_chapters_label" />
<TextView
android:id="@+id/manga_last_update_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:text="@string/manga_info_latest_data_label"
android:textIsSelectable="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_last_chapter_label" />
<TextView
android:id="@+id/manga_last_update"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:clickable="false"
android:textIsSelectable="false"
app:layout_constraintLeft_toRightOf="@+id/manga_last_update_label"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_last_chapter_label" />
<TextView
android:id="@+id/manga_source_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:text="@string/manga_info_source_label"
android:textIsSelectable="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_last_update_label" />
<TextView
android:id="@+id/manga_source"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:clickable="false"
android:textIsSelectable="false"
app:layout_constraintLeft_toRightOf="@+id/manga_source_label"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_last_update_label" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:id="@+id/card_shim" android:id="@+id/gradient"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp"
android:alpha="0.9"
android:background="?attr/background_card"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/search_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_gravity="bottom"
android:layout_marginLeft="8dp" android:background="@drawable/gradient_shape" />
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:text="Searching..."
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintBottom_toTopOf="@+id/search_progress"
app:layout_constraintEnd_toEndOf="@+id/card_shim"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/card_shim" />
<ProgressBar <ProgressBar
android:id="@+id/search_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/card_shim"
app:layout_constraintEnd_toEndOf="@+id/card_shim"
app:layout_constraintStart_toStartOf="parent" />
<androidx.constraintlayout.widget.Group
android:id="@+id/loading_group" android:id="@+id/loading_group"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_gravity="center" />
<TextView
android:id="@+id/manga_chapters"
style="@style/TextAppearance.Regular.Caption.Light"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:constraint_referenced_ids="card_shim,search_status,search_progress" /> android:background="@color/md_teal_500"
</androidx.constraintlayout.widget.ConstraintLayout> android:paddingBottom="1dp"
</androidx.cardview.widget.CardView> android:paddingLeft="3dp"
android:paddingRight="3dp"
android:paddingTop="1dp"
android:visibility="gone"
tools:visibility="visible"
android:text="101"
android:layout_marginStart="4dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="4dp"/>
<eu.kanade.tachiyomi.widget.PTSansTextView
android:id="@+id/title"
style="@style/TextAppearance.Regular.Body1.Light"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:ellipsize="end"
android:lineSpacingExtra="-4dp"
android:maxLines="2"
android:padding="8dp"
android:shadowColor="@color/textColorPrimaryLight"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="4"
app:typeface="ptsansNarrowBold"
tools:text="Sample name" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
<LinearLayout
android:id="@+id/card_scroll_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:paddingBottom="20dp"
android:gravity="start"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/card"
app:layout_constraintStart_toStartOf="@id/card"
app:layout_constraintTop_toBottomOf="@id/card">
<TextView
android:id="@+id/manga_source_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:textIsSelectable="false"
app:layout_constraintLeft_toLeftOf="parent"
android:ellipsize="end"
android:maxLines="1"
tools:layout_editor_absoluteY="57dp" />
<TextView
android:id="@+id/manga_last_chapter_label"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:textIsSelectable="false" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,124 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectable_library_drawable">
<FrameLayout
android:id="@+id/card"
android:layout_width="wrap_content"
android:layout_height="220dp"
android:background="@drawable/card_background"
app:layout_constraintDimensionRatio="0.75"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_min="100dp"
app:layout_constraintHeight_min="100dp">
<ImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
tools:background="?android:attr/colorBackground"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_launcher" />
<View
android:id="@+id/gradient"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/gradient_shape" />
<ProgressBar
android:id="@+id/loading_group"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_gravity="center" />
<TextView
android:id="@+id/manga_chapters"
style="@style/TextAppearance.Regular.Caption.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/md_teal_500"
android:paddingBottom="1dp"
android:paddingLeft="3dp"
android:paddingRight="3dp"
android:paddingTop="1dp"
android:visibility="gone"
tools:visibility="visible"
android:text="101"
android:layout_marginStart="4dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="4dp"/>
<eu.kanade.tachiyomi.widget.PTSansTextView
android:id="@+id/title"
style="@style/TextAppearance.Regular.Body1.Light"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:ellipsize="end"
android:lineSpacingExtra="-4dp"
android:maxLines="2"
android:padding="8dp"
android:shadowColor="@color/textColorPrimaryLight"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="4"
app:typeface="ptsansNarrowBold"
tools:text="Sample name" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
<LinearLayout
android:id="@+id/card_scroll_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:paddingBottom="20dp"
android:gravity="start"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/card"
app:layout_constraintStart_toStartOf="@id/card"
app:layout_constraintTop_toBottomOf="@id/card">
<TextView
android:id="@+id/manga_source_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:textIsSelectable="false"
app:layout_constraintLeft_toLeftOf="parent"
android:ellipsize="end"
android:maxLines="1"
tools:layout_editor_absoluteY="57dp" />
<TextView
android:id="@+id/manga_last_chapter_label"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:textIsSelectable="false" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,72 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<include
android:id="@+id/migration_manga_card_from"
layout="@layout/migration_new_manga_card"
android:layout_width="150dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/imageView"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:adjustViewBounds="true"
android:contentDescription="@string/migrating_to"
android:scaleType="center"
android:layout_marginBottom="45dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/migration_manga_card_to"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/migration_manga_card_from"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_keyboard_arrow_right_black_24dp" />
<include
android:id="@+id/migration_manga_card_to"
layout="@layout/migration_new_manga_card"
android:layout_width="150dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/migration_menu"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/migration_menu"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:contentDescription="@string/description_cover"
android:paddingTop="30dp"
android:paddingBottom="30dp"
android:layout_marginBottom="45dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/migration_manga_card_to"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_more_vert_black_24dp"
android:visibility="invisible"/>
<ImageView
android:id="@+id/skip_manga"
android:layout_width="48dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/migration_menu"
app:layout_constraintEnd_toEndOf="@+id/migration_menu"
app:layout_constraintStart_toStartOf="@+id/migration_menu"
app:layout_constraintTop_toTopOf="@+id/migration_menu"
app:srcCompat="@drawable/baseline_close_24" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

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

View File

@ -1,108 +1,72 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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" 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="wrap_content"
android:animateLayoutChanges="true"> android:gravity="center">
<androidx.constraintlayout.widget.ConstraintLayout <include
android:layout_width="match_parent" android:id="@+id/migration_manga_card_from"
android:layout_height="match_parent"> layout="@layout/migration_manga_card"
android:layout_width="150dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/imageView"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<include <ImageView
android:id="@+id/migration_manga_card_from" android:id="@+id/imageView"
layout="@layout/migration_manga_card" android:layout_width="wrap_content"
android:layout_width="0dp" android:layout_height="25dp"
android:layout_height="wrap_content" android:adjustViewBounds="true"
android:layout_marginStart="16dp" android:contentDescription="@string/migrating_to"
android:layout_marginLeft="16dp" android:scaleType="center"
android:layout_marginTop="16dp" android:layout_marginBottom="45dp"
android:layout_marginEnd="16dp" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginRight="16dp" app:layout_constraintEnd_toStartOf="@+id/migration_manga_card_to"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toEndOf="@+id/migration_manga_card_from"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="450dp" /> app:srcCompat="@drawable/ic_keyboard_arrow_right_black_24dp" />
<ImageView <include
android:id="@+id/imageView" android:id="@+id/migration_manga_card_to"
android:layout_width="wrap_content" layout="@layout/migration_manga_card"
android:layout_height="50dp" android:layout_width="150dp"
android:adjustViewBounds="true" android:layout_height="wrap_content"
android:contentDescription="migrating to" app:layout_constraintBottom_toBottomOf="parent"
android:scaleType="center" app:layout_constraintEnd_toStartOf="@+id/migration_menu"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toBottomOf="@+id/migration_manga_card_from" app:layout_constraintTop_toTopOf="parent" />
app:srcCompat="@drawable/ic_arrow_down_white_32dp" />
<include <ImageView
android:id="@+id/migration_manga_card_to" android:id="@+id/migration_menu"
layout="@layout/migration_manga_card" android:layout_width="48dp"
android:layout_width="0dp" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:contentDescription="@string/description_cover"
android:layout_marginStart="16dp" android:paddingTop="30dp"
android:layout_marginLeft="16dp" android:paddingBottom="30dp"
android:layout_marginEnd="16dp" android:layout_marginBottom="45dp"
android:layout_marginRight="16dp" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintTop_toBottomOf="@+id/imageView" app:layout_constraintStart_toEndOf="@+id/migration_manga_card_to"
app:layout_constraintWidth_max="450dp" /> app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_more_vert_black_24dp"
android:visibility="invisible"/>
<Button
android:id="@+id/skip_migration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:drawableStart="@drawable/ic_clear_grey_24dp_img"
android:drawablePadding="6dp"
android:text="Skip manga"
android:textColor="#ffffff"
app:backgroundTint="#E53935"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/accept_migration"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
<Button <ImageView
android:id="@+id/accept_migration" android:id="@+id/skip_manga"
android:layout_width="wrap_content" android:layout_width="48dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:alpha="0.5" app:layout_constraintBottom_toBottomOf="@+id/migration_menu"
android:drawableStart="@drawable/ic_check_box_24dp" app:layout_constraintEnd_toEndOf="@+id/migration_menu"
android:drawablePadding="6dp" app:layout_constraintStart_toStartOf="@+id/migration_menu"
android:enabled="false" app:layout_constraintTop_toTopOf="@+id/migration_menu"
android:text="Migrate manga" app:srcCompat="@drawable/baseline_close_24" />
android:textColor="#ffffff" </androidx.constraintlayout.widget.ConstraintLayout>
app:backgroundTint="#00C853"
app:layout_constraintBottom_toBottomOf="@+id/skip_migration"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/skip_migration" />
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/migrating_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<View
android:id="@+id/migrating_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#E6FFFFFF" />
<ProgressBar
android:id="@+id/migrating_progress"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
</FrameLayout>