diff --git a/app/src/debug/res/drawable-anydpi/ic_copy.xml b/app/src/debug/res/drawable-anydpi/ic_copy.xml
new file mode 100644
index 0000000000..b11c7d4efc
--- /dev/null
+++ b/app/src/debug/res/drawable-anydpi/ic_copy.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/debug/res/drawable-anydpi/ic_done.xml b/app/src/debug/res/drawable-anydpi/ic_done.xml
new file mode 100644
index 0000000000..28bf053692
--- /dev/null
+++ b/app/src/debug/res/drawable-anydpi/ic_done.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/debug/res/drawable-anydpi/ic_done_all.xml b/app/src/debug/res/drawable-anydpi/ic_done_all.xml
new file mode 100644
index 0000000000..85d9ffd199
--- /dev/null
+++ b/app/src/debug/res/drawable-anydpi/ic_done_all.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/debug/res/drawable-hdpi/ic_copy.png b/app/src/debug/res/drawable-hdpi/ic_copy.png
new file mode 100644
index 0000000000..7821875d32
Binary files /dev/null and b/app/src/debug/res/drawable-hdpi/ic_copy.png differ
diff --git a/app/src/debug/res/drawable-hdpi/ic_done.png b/app/src/debug/res/drawable-hdpi/ic_done.png
new file mode 100644
index 0000000000..f1f39724ec
Binary files /dev/null and b/app/src/debug/res/drawable-hdpi/ic_done.png differ
diff --git a/app/src/debug/res/drawable-hdpi/ic_done_all.png b/app/src/debug/res/drawable-hdpi/ic_done_all.png
new file mode 100644
index 0000000000..3d0e27aa98
Binary files /dev/null and b/app/src/debug/res/drawable-hdpi/ic_done_all.png differ
diff --git a/app/src/debug/res/drawable-mdpi/ic_copy.png b/app/src/debug/res/drawable-mdpi/ic_copy.png
new file mode 100644
index 0000000000..912309132c
Binary files /dev/null and b/app/src/debug/res/drawable-mdpi/ic_copy.png differ
diff --git a/app/src/debug/res/drawable-mdpi/ic_done.png b/app/src/debug/res/drawable-mdpi/ic_done.png
new file mode 100644
index 0000000000..4d1d9bdfd3
Binary files /dev/null and b/app/src/debug/res/drawable-mdpi/ic_done.png differ
diff --git a/app/src/debug/res/drawable-mdpi/ic_done_all.png b/app/src/debug/res/drawable-mdpi/ic_done_all.png
new file mode 100644
index 0000000000..c0e88e9fe7
Binary files /dev/null and b/app/src/debug/res/drawable-mdpi/ic_done_all.png differ
diff --git a/app/src/debug/res/drawable-xhdpi/ic_copy.png b/app/src/debug/res/drawable-xhdpi/ic_copy.png
new file mode 100644
index 0000000000..a15eb3da56
Binary files /dev/null and b/app/src/debug/res/drawable-xhdpi/ic_copy.png differ
diff --git a/app/src/debug/res/drawable-xhdpi/ic_done.png b/app/src/debug/res/drawable-xhdpi/ic_done.png
new file mode 100644
index 0000000000..f193eba596
Binary files /dev/null and b/app/src/debug/res/drawable-xhdpi/ic_done.png differ
diff --git a/app/src/debug/res/drawable-xhdpi/ic_done_all.png b/app/src/debug/res/drawable-xhdpi/ic_done_all.png
new file mode 100644
index 0000000000..d6036992c4
Binary files /dev/null and b/app/src/debug/res/drawable-xhdpi/ic_done_all.png differ
diff --git a/app/src/debug/res/drawable-xxhdpi/ic_copy.png b/app/src/debug/res/drawable-xxhdpi/ic_copy.png
new file mode 100644
index 0000000000..bfc0c25885
Binary files /dev/null and b/app/src/debug/res/drawable-xxhdpi/ic_copy.png differ
diff --git a/app/src/debug/res/drawable-xxhdpi/ic_done.png b/app/src/debug/res/drawable-xxhdpi/ic_done.png
new file mode 100644
index 0000000000..612c0a3fd1
Binary files /dev/null and b/app/src/debug/res/drawable-xxhdpi/ic_done.png differ
diff --git a/app/src/debug/res/drawable-xxhdpi/ic_done_all.png b/app/src/debug/res/drawable-xxhdpi/ic_done_all.png
new file mode 100644
index 0000000000..da71795bd6
Binary files /dev/null and b/app/src/debug/res/drawable-xxhdpi/ic_done_all.png differ
diff --git a/app/src/debug/res/drawable/ic_migrate_direction.xml b/app/src/debug/res/drawable/ic_migrate_direction.xml
new file mode 100644
index 0000000000..90e15b6b99
--- /dev/null
+++ b/app/src/debug/res/drawable/ic_migrate_direction.xml
@@ -0,0 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt
new file mode 100644
index 0000000000..ad29b0a015
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt
@@ -0,0 +1,41 @@
+package eu.kanade.tachiyomi.ui.migration
+
+import android.app.Dialog
+import android.os.Bundle
+import com.afollestad.materialdialogs.MaterialDialog
+import com.bluelinelabs.conductor.Controller
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.ui.base.controller.DialogController
+import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
+
+class MigrationMangaDialog(bundle: Bundle? = null) : DialogController(bundle)
+ where T : Controller {
+
+ var copy = false
+ var mangaSet = 0
+ var mangaSkipped = 0
+ constructor(target: T, copy: Boolean, mangaSet: Int, mangaSkipped: Int) : this() {
+ targetController = target
+ this.copy = copy
+ this.mangaSet = mangaSet
+ this.mangaSkipped = mangaSkipped
+ }
+
+ override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+ val confirmRes = if (copy) R.string.confirm_copy else R.string.confirm_migration
+ val confirmString = applicationContext?.getString(confirmRes, mangaSet, (
+ if (mangaSkipped > 0)
+ " " + applicationContext?.getString(R.string.skipping_x, mangaSkipped) ?: ""
+ else "")) ?: ""
+ return MaterialDialog.Builder(activity!!)
+ .content(confirmString)
+ .positiveText(android.R.string.yes)
+ .negativeText(android.R.string.no)
+ .onPositive { _, _ ->
+ if (copy)
+ (targetController as? MigrationListController)?.copyMangas()
+ else
+ (targetController as? MigrationListController)?.migrateMangas()
+ }.show()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt
index 8545922bed..f4710342cb 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt
@@ -5,16 +5,19 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
-import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
+import androidx.appcompat.widget.SearchView
import com.afollestad.materialdialogs.MaterialDialog
+import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter
+import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import uy.kohesive.injekt.injectLazy
class SearchController(
@@ -25,6 +28,14 @@ class SearchController(
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()}"
@@ -49,7 +60,7 @@ class SearchController(
newManga = savedInstanceState.getSerializable(::newManga.name) as? Manga
}
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ /*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
@@ -66,7 +77,7 @@ class SearchController(
}
}
return true
- }
+ }*/
fun migrateManga() {
val target = targetController as? MigrationInterface ?: return
@@ -98,6 +109,14 @@ class SearchController(
}
override fun onMangaClick(manga: Manga) {
+ if (targetController is MigrationListController) {
+ val migrationListController = targetController as? MigrationListController
+ val sourceManager: SourceManager by injectLazy()
+ val source = sourceManager.get(manga.source) ?: return
+ migrationListController?.useMangaForMigration(manga, source)
+ router.popCurrentController()
+ return
+ }
newManga = manga
val dialog = MigrationDialog()
dialog.targetController = this
@@ -142,4 +161,40 @@ class SearchController(
}
+ /**
+ * 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.catalogue_new_list, 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.queryTextChangeEvents()
+ .filter { it.isSubmitted }
+ .subscribeUntilDestroy {
+ presenter.search(it.queryText().toString())
+ searchItem.collapseActionView()
+ setTitle() // Update toolbar title
+ }
+ }
+
+
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationDesignController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationDesignController.kt
index a824e35440..15363dcd08 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationDesignController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationDesignController.kt
@@ -14,7 +14,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
-import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureController
+import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.util.gone
import eu.kanade.tachiyomi.util.visible
import exh.ui.migration.manga.process.MigrationProcedureConfig
@@ -60,10 +60,6 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle)
use_smart_search.toggle()
}
- copy_manga_desc.setOnClickListener {
- copy_manga.toggle()
- }
-
extra_search_param_desc.setOnClickListener {
extra_search_param.toggle()
}
@@ -93,7 +89,7 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle)
if(mig_categories.isChecked) flags = flags or MigrationFlags.TRACK
router.replaceTopController(
- MigrationProcedureController.create(
+ MigrationListController.create(
MigrationProcedureConfig(
config.toList(),
ourAdapter.items.filter {
@@ -102,7 +98,6 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle)
useSourceWithMostChapters = prioritize_chapter_count.isChecked,
enableLenientSearch = use_smart_search.isChecked,
migrationFlags = flags,
- copy = copy_manga.isChecked,
extraSearchParams = if(extra_search_param.isChecked && extra_search_param_text.text.isNotBlank()) {
extra_search_param_text.text.toString()
} else null
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt
index 5f952e7d73..2a98fbb01f 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt
@@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcessItem
import eu.kanade.tachiyomi.util.DeferredField
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@@ -31,4 +32,9 @@ class MigratingManga(private val db: DatabaseHelper,
suspend fun mangaSource(): Source {
return sourceManager.getOrStub(manga()?.source ?: -1)
}
+
+ fun toModal(): MigrationProcessItem {
+ // Create the model object.
+ return MigrationProcessItem(this)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt
new file mode 100644
index 0000000000..a47d350832
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt
@@ -0,0 +1,388 @@
+package eu.kanade.tachiyomi.ui.migration.manga.process
+
+import android.content.pm.ActivityInfo
+import android.graphics.Color
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.graphics.ColorUtils
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+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.Source
+import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.ui.base.controller.BaseController
+import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
+import eu.kanade.tachiyomi.ui.migration.MigrationMangaDialog
+import eu.kanade.tachiyomi.ui.migration.SearchController
+import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener
+import eu.kanade.tachiyomi.util.await
+import eu.kanade.tachiyomi.util.launchUI
+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.chapters_controller.*
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+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
+
+class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
+ MigrationProcessAdapter.MigrationProcessInterface,
+ CoroutineScope {
+
+ init {
+ setHasOptionsMenu(true)
+ }
+
+ private var titleText = "Migrate manga"
+
+ private var adapter: MigrationProcessAdapter? = 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: MutableList? = null
+ private var selectedPosition:Int? = null
+
+ override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
+ return inflater.inflate(R.layout.migration_list_controller, 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.toMutableList()
+ new
+ }
+
+ adapter = MigrationProcessAdapter(this, view.context)
+
+ recycler.adapter = adapter
+ recycler.layoutManager = LinearLayoutManager(view.context)
+ //recycler.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration
+ // .VERTICAL))
+ recycler.setHasFixedSize(true)
+ recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
+ //recycler.isEnabled = false
+
+ adapter?.updateDataSet(newMigratingManga.map { it.toModal() } )
+
+ if(migrationsJob == null) {
+ migrationsJob = launch {
+ runMigrations(newMigratingManga)
+ }
+ }
+ }
+
+ /*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) {
+ 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
+ }
+
+ override fun enableButtons() {
+ activity?.invalidateOptionsMenu()
+ }
+
+ override fun removeManga(position: Int) {
+ val ids = config?.mangaIds?.toMutableList() ?: return
+ ids.removeAt(position)
+ migratingManga?.removeAt(position)
+ config.mangaIds = ids
+ }
+
+ override fun noMigration() {
+ activity?.toast(R.string.no_migrations)
+ router.popCurrentController()
+ }
+
+ override fun onMenuItemClick(position: Int, item: MenuItem) {
+
+ when (item.itemId) {
+ R.id.action_search_manually -> {
+ launchUI {
+ val manga = adapter?.getItem(position) ?: return@launchUI
+ selectedPosition = position
+ val searchController = SearchController(manga.manga.manga())
+ searchController.targetController = this@MigrationListController
+ router.pushController(searchController.withFadeTransaction())
+ }
+ }
+ R.id.action_skip -> adapter?.removeManga(position)
+ R.id.action_migrate_now -> adapter?.migrateManga(position, false)
+ R.id.action_copy_now -> adapter?.migrateManga(position, true)
+ }
+ }
+
+ fun useMangaForMigration(manga: Manga, source: Source) {
+ val firstIndex = selectedPosition ?: return
+ val migratingManga = adapter?.getItem(firstIndex) ?: return
+ migratingManga.showSpinner()
+ launchUI {
+ val result = CoroutineScope(migratingManga.manga.migrationJob).async {
+ val localManga = smartSearchEngine.networkToLocalManga(manga, source.id)
+ val chapters = source.fetchChapterList(localManga).toSingle().await(
+ Schedulers.io()
+ )
+ withContext(Dispatchers.IO) {
+ syncChaptersWithSource(db, chapters, localManga, source)
+ }
+ localManga
+ }.await()
+
+ 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) {
+ }
+
+ migratingManga.manga.searchResult.set(result.id)
+ adapter?.notifyDataSetChanged()
+ }
+ }
+
+ fun migrateMangas() {
+ launchUI {
+ adapter?.performMigrations(false)
+ router.popCurrentController()
+ }
+ }
+
+ fun copyMangas() {
+ launchUI {
+ adapter?.performMigrations(true)
+ router.popCurrentController()
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ inflater.inflate(R.menu.migration_list, menu)
+ }
+
+ override fun onPrepareOptionsMenu(menu: Menu) {
+ // Initialize menu items.
+
+ val allMangasDone = adapter?.allMangasDone() ?: return
+
+ val menuCopy = menu.findItem(R.id.action_copy_manga)
+ val menuMigrate = menu.findItem(R.id.action_migrate_manga)
+
+ if (adapter?.itemCount == 1) {
+ menuMigrate.icon = VectorDrawableCompat.create(
+ resources!!, R.drawable.ic_done, null
+ )
+ }
+ val translucentWhite = ColorUtils.setAlphaComponent(Color.WHITE, 127)
+ menuCopy.icon?.setTint(if (allMangasDone) Color.WHITE else translucentWhite)
+ menuMigrate?.icon?.setTint(if (allMangasDone) Color.WHITE else translucentWhite)
+ menuCopy.isEnabled = allMangasDone
+ menuMigrate.isEnabled = allMangasDone
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ val itemsCount = adapter?.itemCount ?: 0
+ val mangasSkipped = adapter?.mangasSkipped() ?: 0
+ when (item.itemId) {
+ R.id.action_copy_manga -> MigrationMangaDialog(this, true, itemsCount, mangasSkipped)
+ .showDialog(router)
+ R.id.action_migrate_manga -> MigrationMangaDialog(this, false, itemsCount, mangasSkipped)
+ .showDialog(router)
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ companion object {
+ const val CONFIG_EXTRA = "config_extra"
+
+ fun create(config: MigrationProcedureConfig): MigrationListController {
+ return MigrationListController(Bundle().apply {
+ putParcelable(CONFIG_EXTRA, config)
+ })
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureAdapter.kt
index 17a256883c..08df43f241 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureAdapter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureAdapter.kt
@@ -4,7 +4,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.viewpager.widget.PagerAdapter
import com.bumptech.glide.load.engine.DiskCacheStrategy
-import com.google.gson.Gson
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
@@ -40,7 +39,6 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
val migratingManga: List,
override val coroutineContext: CoroutineContext) : PagerAdapter(), CoroutineScope {
private val db: DatabaseHelper by injectLazy()
- private val gson: Gson by injectLazy()
private val sourceManager: SourceManager by injectLazy()
override fun isViewFromObject(p0: View, p1: Any): Boolean {
@@ -55,7 +53,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
container.addView(view)
view.skip_migration.setOnClickListener {
- controller.nextMigration()
+ //controller.nextMigration()
}
val viewTag = ViewTag(coroutineContext)
@@ -81,26 +79,26 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
}
suspend fun performMigration(manga: MigratingManga) {
- if(!manga.searchResult.initialized) {
- return
- }
+ if(!manga.searchResult.initialized) {
+ return
+ }
- val toMangaObj = db.getManga(manga.searchResult.get() ?: return).executeAsBlocking() ?: return
+ val toMangaObj = db.getManga(manga.searchResult.get() ?: return).executeAsBlocking() ?: return
- withContext(Dispatchers.IO) {
- migrateMangaInternal(
+ withContext(Dispatchers.IO) {
+ migrateMangaInternal(
manga.manga() ?: return@withContext,
toMangaObj,
- !(controller.config?.copy ?: false)
- )
- }
+ false
+ )
+ }
}
private fun migrateMangaInternal(prevManga: Manga,
manga: Manga,
replace: Boolean) {
val config = controller.config ?: return
- db.inTransaction {
+ //db.inTransaction {
// Update chapters read
if (MigrationFlags.hasChapters(controller.config.migrationFlags)) {
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
@@ -141,7 +139,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
// 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) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureConfig.kt
index d11b70bcc6..f5235c06a5 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureConfig.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureConfig.kt
@@ -5,11 +5,10 @@ import kotlinx.android.parcel.Parcelize
@Parcelize
data class MigrationProcedureConfig(
- val mangaIds: List,
+ var mangaIds: List,
val targetSourceIds: List,
val useSourceWithMostChapters: Boolean,
val enableLenientSearch: Boolean,
val migrationFlags: Int,
- val copy: Boolean,
val extraSearchParams: String?
): Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureController.kt
index e3f14e96d7..eed24715bb 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureController.kt
@@ -150,8 +150,7 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseController(bund
async {
sourceSemaphore.withPermit {
try {
- val searchResult = if (config?.enableLenientSearch ==
- true) {
+ val searchResult = if (config.enableLenientSearch) {
smartSearchEngine.smartSearch(source, mangaObj.title)
} else {
smartSearchEngine.normalSearch(source, mangaObj.title)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt
new file mode 100644
index 0000000000..888a1e2807
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt
@@ -0,0 +1,144 @@
+package eu.kanade.tachiyomi.ui.migration.manga.process
+
+import android.content.Context
+import android.view.MenuItem
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.data.database.models.MangaCategory
+import eu.kanade.tachiyomi.ui.migration.MigrationFlags
+import eu.kanade.tachiyomi.util.launchUI
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.withContext
+import uy.kohesive.injekt.injectLazy
+
+class MigrationProcessAdapter(
+ val controller: MigrationListController,
+ context: Context
+) : FlexibleAdapter(null, controller, true) {
+
+
+ private val db: DatabaseHelper by injectLazy()
+ var items: List = emptyList()
+
+ val menuItemListener: MigrationProcessInterface = controller
+
+ override fun updateDataSet(items: List?) {
+ this.items = items ?: emptyList()
+ super.updateDataSet(items)
+ }
+
+ fun indexOf(item: MigrationProcessItem): Int {
+ return items.indexOf(item)
+ }
+
+ interface MigrationProcessInterface {
+ fun onMenuItemClick(position: Int, item: MenuItem)
+ fun enableButtons()
+ fun removeManga(position: Int)
+ fun noMigration()
+ }
+
+ fun sourceFinished() {
+ if (mangasSkipped() == itemCount || itemCount == 0) menuItemListener.noMigration()
+ if (allMangasDone()) menuItemListener.enableButtons()
+ }
+
+ fun allMangasDone() = (items.all { it.manga.searchResult.initialized || !it.manga.migrationJob
+ .isActive } && items.any { it.manga
+ .searchResult.content != null })
+
+ fun mangasSkipped() = (items.count { (!it.manga.searchResult.initialized || it.manga
+ .searchResult.content == null) && !it.manga.migrationJob.isActive })
+
+ suspend fun performMigrations(copy: Boolean) {
+ withContext(Dispatchers.IO) {
+ db.inTransaction {
+ currentItems.forEach { migratingManga ->
+ val manga = migratingManga.manga
+ if (manga.searchResult.initialized) {
+ val toMangaObj =
+ db.getManga(manga.searchResult.get() ?: return@forEach).executeAsBlocking()
+ ?: return@forEach
+ migrateMangaInternal(
+ manga.manga() ?: return@forEach,
+ toMangaObj,
+ !copy)
+ }
+ }
+ }
+ }
+ }
+
+ fun migrateManga(position: Int, copy: Boolean) {
+ launchUI {
+ val manga = getItem(position)?.manga ?: return@launchUI
+ db.inTransaction {
+ val toMangaObj = db.getManga(manga.searchResult.get() ?: return@launchUI).executeAsBlocking()
+ ?: return@launchUI
+ migrateMangaInternal(
+ manga.manga() ?: return@launchUI, toMangaObj, !copy
+ )
+ }
+ removeManga(position)
+ }
+ }
+
+ fun removeManga(position: Int) {
+ menuItemListener.removeManga(position)
+ getItem(position)?.manga?.migrationJob?.cancel()
+ removeItem(position)
+ items = currentItems
+ sourceFinished()
+ }
+
+ private fun migrateMangaInternal(prevManga: Manga,
+ manga: Manga,
+ replace: Boolean) {
+ if (controller.config == null) 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()
+ //}
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt
new file mode 100644
index 0000000000..ac3749dadb
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt
@@ -0,0 +1,173 @@
+package eu.kanade.tachiyomi.ui.migration.manga.process
+
+import android.view.View
+import android.widget.PopupMenu
+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.glide.GlideApp
+import eu.kanade.tachiyomi.source.Source
+import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
+import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
+import eu.kanade.tachiyomi.ui.manga.MangaController
+import eu.kanade.tachiyomi.util.getResourceColor
+import eu.kanade.tachiyomi.util.gone
+import eu.kanade.tachiyomi.util.launchUI
+import eu.kanade.tachiyomi.util.setVectorCompat
+import eu.kanade.tachiyomi.util.visible
+import kotlinx.android.synthetic.main.migration_new_manga_card.view.*
+import kotlinx.android.synthetic.main.migration_new_process_item.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import uy.kohesive.injekt.injectLazy
+import java.text.DecimalFormat
+
+class MigrationProcessHolder(
+ private val view: View,
+ private val adapter: MigrationProcessAdapter
+) : BaseFlexibleViewHolder(view, adapter) {
+
+ private val db: DatabaseHelper by injectLazy()
+ private val sourceManager: SourceManager by injectLazy()
+
+ init {
+ // We need to post a Runnable to show the popup to make sure that the PopupMenu is
+ // correctly positioned. The reason being that the view may change position before the
+ // PopupMenu is shown.
+ migration_menu.setOnClickListener { it.post { showPopupMenu(it) } }
+ skip_manga.setOnClickListener { it.post { adapter.removeManga(adapterPosition) } }
+ }
+
+ fun bind(item: MigrationProcessItem) {
+ launchUI {
+ val manga = item.manga.manga()
+ val source = item.manga.mangaSource()
+
+ migration_menu.setVectorCompat(R.drawable.ic_more_vert_black_24dp, view.context.getResourceColor(R.attr.icon_color))
+ skip_manga.setVectorCompat(R.drawable.baseline_close_24, view.context.getResourceColor(R
+ .attr.icon_color))
+ migration_menu.gone()
+ if (manga != null) {
+ withContext(Dispatchers.Main) {
+ migration_manga_card_from.loading_group.gone()
+ attachManga(migration_manga_card_from, manga, source)
+ migration_manga_card_from.setOnClickListener {
+ adapter.controller.router.pushController(
+ MangaController(
+ manga,
+ true
+ ).withFadeTransaction()
+ )
+ }
+ }
+
+ /*launchUI {
+ item.manga.progress.asFlow().collect { (max, progress) ->
+ withContext(Dispatchers.Main) {
+ migration_manga_card_to.search_progress.let { progressBar ->
+ progressBar.max = max
+ progressBar.progress = progress
+ }
+ }
+ }
+ }*/
+
+ val searchResult = item.manga.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()
+ attachManga(migration_manga_card_to, searchResult, resultSource)
+ migration_manga_card_to.setOnClickListener {
+ adapter.controller.router.pushController(
+ MangaController(
+ searchResult, true
+ ).withFadeTransaction()
+ )
+ }
+ } else {
+ migration_manga_card_to.loading_group.gone()
+ migration_manga_card_to.title.text = "No Alternatives Found"
+ }
+ migration_menu.visible()
+ skip_manga.gone()
+ adapter.sourceFinished()
+ }
+ }
+ }
+ }
+
+ fun showSpinner() {
+ migration_manga_card_to.loading_group.visible()
+ }
+
+ fun attachManga(view: View, manga: Manga, source: Source) {
+ view.loading_group.gone()
+ GlideApp.with(view.context.applicationContext)
+ .load(manga)
+ .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
+ .centerCrop()
+ .into(view.thumbnail)
+
+ view.title.text = if (manga.title.isBlank()) {
+ view.context.getString(R.string.unknown)
+ } else {
+ manga.title
+ }
+
+ view.gradient.visible()
+ view.manga_source_label.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()
+ // }
+
+ val mangaChapters = db.getChapters(manga).executeAsBlocking()
+ view.manga_chapters.visible()
+ view.manga_chapters.text = mangaChapters.size.toString()
+ val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
+
+ if (latestChapter > 0f) {
+ view.manga_last_chapter_label.text = view.context.getString(R.string.latest_x,
+ DecimalFormat("#.#").format(latestChapter))
+ } else {
+ view.manga_last_chapter_label.setText(R.string.unknown)
+ }
+ }
+
+ private fun showPopupMenu(view: View) {
+ val item = adapter.getItem(adapterPosition) ?: return
+
+ // Create a PopupMenu, giving it the clicked view for an anchor
+ val popup = PopupMenu(view.context, view)
+
+ // Inflate our menu resource into the PopupMenu's Menu
+ popup.menuInflater.inflate(R.menu.migration_single, popup.menu)
+
+ val mangas = item.manga
+
+ popup.menu.findItem(R.id.action_search_manually).isVisible = true
+ // Hide download and show delete if the chapter is downloaded
+ if (mangas.searchResult.content != null) {
+ popup.menu.findItem(R.id.action_migrate_now).isVisible = true
+ popup.menu.findItem(R.id.action_copy_now).isVisible = true
+ }
+
+ // Set a listener so we are notified if a menu item is clicked
+ popup.setOnMenuItemClickListener { menuItem ->
+ adapter.menuItemListener.onMenuItemClick(adapterPosition, menuItem)
+ true
+ }
+
+ // Finally show the PopupMenu
+ popup.show()
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessItem.kt
new file mode 100644
index 0000000000..45bff997fd
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessItem.kt
@@ -0,0 +1,48 @@
+package eu.kanade.tachiyomi.ui.migration.manga.process
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
+import eu.davidea.flexibleadapter.items.IFlexible
+import eu.kanade.tachiyomi.R
+import exh.ui.migration.manga.process.MigratingManga
+
+class MigrationProcessItem(val manga: MigratingManga) :
+ AbstractFlexibleItem() {
+
+ var holder:MigrationProcessHolder? = null
+ override fun getLayoutRes(): Int {
+ return R.layout.migration_new_process_item
+ }
+
+ override fun createViewHolder(view: View, adapter: FlexibleAdapter>): MigrationProcessHolder {
+ return MigrationProcessHolder(view, adapter as MigrationProcessAdapter)
+ }
+
+ override fun bindViewHolder(adapter: FlexibleAdapter>,
+ holder: MigrationProcessHolder,
+ position: Int,
+ payloads: MutableList?) {
+
+ this.holder = holder
+ holder.bind(this)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other is MigrationProcessItem) {
+ return manga.mangaId == other.manga.mangaId
+ }
+ return false
+ }
+
+ fun showSpinner() {
+ holder?.showSpinner()
+ }
+
+ override fun hashCode(): Int {
+ return manga.mangaId.hashCode()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt b/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt
index 5146787989..4c99e8e8ba 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt
@@ -12,7 +12,7 @@ import kotlinx.coroutines.sync.withLock
class DeferredField {
@Volatile
- private var content: T? = null
+ var content: T? = null
@Volatile
var initialized = false
@@ -32,6 +32,14 @@ class DeferredField {
mutex.unlock()
}
+ fun set(content: T) {
+ mutex.tryLock()
+ this.content = content
+ initialized = true
+ // Notify current listeners
+ mutex.unlock()
+ }
+
/**
* Will only suspend if !initialized.
*/
diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml
new file mode 100644
index 0000000000..58d1a85f9e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/migration_design_controller.xml b/app/src/main/res/layout/migration_design_controller.xml
index 9b531d356f..a97ce39e90 100644
--- a/app/src/main/res/layout/migration_design_controller.xml
+++ b/app/src/main/res/layout/migration_design_controller.xml
@@ -114,30 +114,6 @@
android:gravity="start|center_vertical"
android:text="@string/use_intelligent_search"
android:clickable="true"
- app:layout_constraintBottom_toTopOf="@+id/copy_manga"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count"
- android:focusable="true" />
-
-
-
-
+ app:constraint_referenced_ids="migration_mode,use_smart_search,fuzzy_search,action_copy_manga,extra_search_param_desc,mig_tracking,textView,mig_chapters,copy_manga_desc,textView2,prioritize_chapter_count,mig_categories,extra_search_param" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/migration_list_controller.xml b/app/src/main/res/layout/migration_list_controller.xml
new file mode 100644
index 0000000000..7abb2eba76
--- /dev/null
+++ b/app/src/main/res/layout/migration_list_controller.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/migration_new_manga_card.xml b/app/src/main/res/layout/migration_new_manga_card.xml
new file mode 100644
index 0000000000..82e40e0b6e
--- /dev/null
+++ b/app/src/main/res/layout/migration_new_manga_card.xml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/migration_new_process_item.xml b/app/src/main/res/layout/migration_new_process_item.xml
new file mode 100644
index 0000000000..1905caec63
--- /dev/null
+++ b/app/src/main/res/layout/migration_new_process_item.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/migration_list.xml b/app/src/main/res/menu/migration_list.xml
new file mode 100644
index 0000000000..b19ff0b7bc
--- /dev/null
+++ b/app/src/main/res/menu/migration_list.xml
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/migration_single.xml b/app/src/main/res/menu/migration_single.xml
new file mode 100644
index 0000000000..af3ab4772c
--- /dev/null
+++ b/app/src/main/res/menu/migration_single.xml
@@ -0,0 +1,21 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cc6e22c46b..18dfd5163d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -104,6 +104,9 @@
Back
Forward
Auto-check for updates
+ Search manually
+ Migrate now
+ Copy now
Deleting…
@@ -416,6 +419,10 @@
Download all
Download unread
Are you sure you want to delete selected chapters?
+ Migrate %1$d%2$s mangas?
+ Copy %1$d%2$s mangas?
+ (skipping %1$d)
+ No manga migrated
Tracking
@@ -486,6 +493,7 @@
Migrate
Copy
Migrating…
+ Latest: %1$s
An error occurred while downloading chapters. You can try again in the downloads section