Finished Part 1 of new auto source migration
11
app/src/debug/res/drawable-anydpi/ic_copy.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="#FFFFFF"
|
||||||
|
android:alpha="0.8">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
|
||||||
|
</vector>
|
11
app/src/debug/res/drawable-anydpi/ic_done.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="#FFFFFF"
|
||||||
|
android:alpha="0.8">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
|
||||||
|
</vector>
|
11
app/src/debug/res/drawable-anydpi/ic_done_all.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="#FFFFFF"
|
||||||
|
android:alpha="0.8">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>
|
||||||
|
</vector>
|
BIN
app/src/debug/res/drawable-hdpi/ic_copy.png
Normal file
After Width: | Height: | Size: 220 B |
BIN
app/src/debug/res/drawable-hdpi/ic_done.png
Normal file
After Width: | Height: | Size: 197 B |
BIN
app/src/debug/res/drawable-hdpi/ic_done_all.png
Normal file
After Width: | Height: | Size: 289 B |
BIN
app/src/debug/res/drawable-mdpi/ic_copy.png
Normal file
After Width: | Height: | Size: 146 B |
BIN
app/src/debug/res/drawable-mdpi/ic_done.png
Normal file
After Width: | Height: | Size: 151 B |
BIN
app/src/debug/res/drawable-mdpi/ic_done_all.png
Normal file
After Width: | Height: | Size: 213 B |
BIN
app/src/debug/res/drawable-xhdpi/ic_copy.png
Normal file
After Width: | Height: | Size: 209 B |
BIN
app/src/debug/res/drawable-xhdpi/ic_done.png
Normal file
After Width: | Height: | Size: 219 B |
BIN
app/src/debug/res/drawable-xhdpi/ic_done_all.png
Normal file
After Width: | Height: | Size: 323 B |
BIN
app/src/debug/res/drawable-xxhdpi/ic_copy.png
Normal file
After Width: | Height: | Size: 303 B |
BIN
app/src/debug/res/drawable-xxhdpi/ic_done.png
Normal file
After Width: | Height: | Size: 279 B |
BIN
app/src/debug/res/drawable-xxhdpi/ic_done_all.png
Normal file
After Width: | Height: | Size: 416 B |
6
app/src/debug/res/drawable/ic_migrate_direction.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<vector android:height="100dp"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="100dp" xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 -5,5z"/>
|
||||||
|
</vector>
|
@ -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<T>(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()
|
||||||
|
}
|
||||||
|
}
|
@ -5,16 +5,19 @@ import android.os.Bundle
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
|
import androidx.appcompat.widget.SearchView
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
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.DialogController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
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.CatalogueSearchController
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter
|
||||||
|
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class SearchController(
|
class SearchController(
|
||||||
@ -25,6 +28,14 @@ class SearchController(
|
|||||||
private var progress = 1
|
private var progress = 1
|
||||||
var totalProgress = 0
|
var totalProgress = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when controller is initialized.
|
||||||
|
*/
|
||||||
|
init {
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
override fun getTitle(): String? {
|
||||||
if (totalProgress > 1) {
|
if (totalProgress > 1) {
|
||||||
return "($progress/$totalProgress) ${super.getTitle()}"
|
return "($progress/$totalProgress) ${super.getTitle()}"
|
||||||
@ -49,7 +60,7 @@ class SearchController(
|
|||||||
newManga = savedInstanceState.getSerializable(::newManga.name) as? Manga
|
newManga = savedInstanceState.getSerializable(::newManga.name) as? Manga
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
/*override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
if (totalProgress > 1) {
|
if (totalProgress > 1) {
|
||||||
val menuItem = menu.add(Menu.NONE, 1, Menu.NONE, R.string.action_skip_manga)
|
val menuItem = menu.add(Menu.NONE, 1, Menu.NONE, R.string.action_skip_manga)
|
||||||
menuItem.icon = VectorDrawableCompat.create(resources!!, R.drawable
|
menuItem.icon = VectorDrawableCompat.create(resources!!, R.drawable
|
||||||
@ -66,7 +77,7 @@ class SearchController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}*/
|
||||||
|
|
||||||
fun migrateManga() {
|
fun migrateManga() {
|
||||||
val target = targetController as? MigrationInterface ?: return
|
val target = targetController as? MigrationInterface ?: return
|
||||||
@ -98,6 +109,14 @@ class SearchController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onMangaClick(manga: Manga) {
|
override fun onMangaClick(manga: Manga) {
|
||||||
|
if (targetController is MigrationListController) {
|
||||||
|
val migrationListController = targetController as? MigrationListController
|
||||||
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
val source = sourceManager.get(manga.source) ?: return
|
||||||
|
migrationListController?.useMangaForMigration(manga, source)
|
||||||
|
router.popCurrentController()
|
||||||
|
return
|
||||||
|
}
|
||||||
newManga = manga
|
newManga = manga
|
||||||
val dialog = MigrationDialog()
|
val dialog = MigrationDialog()
|
||||||
dialog.targetController = this
|
dialog.targetController = this
|
||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -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.BaseController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
|
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
|
||||||
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureController
|
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
|
||||||
import eu.kanade.tachiyomi.util.gone
|
import eu.kanade.tachiyomi.util.gone
|
||||||
import eu.kanade.tachiyomi.util.visible
|
import eu.kanade.tachiyomi.util.visible
|
||||||
import exh.ui.migration.manga.process.MigrationProcedureConfig
|
import exh.ui.migration.manga.process.MigrationProcedureConfig
|
||||||
@ -60,10 +60,6 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle)
|
|||||||
use_smart_search.toggle()
|
use_smart_search.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
copy_manga_desc.setOnClickListener {
|
|
||||||
copy_manga.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
extra_search_param_desc.setOnClickListener {
|
extra_search_param_desc.setOnClickListener {
|
||||||
extra_search_param.toggle()
|
extra_search_param.toggle()
|
||||||
}
|
}
|
||||||
@ -93,7 +89,7 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle)
|
|||||||
if(mig_categories.isChecked) flags = flags or MigrationFlags.TRACK
|
if(mig_categories.isChecked) flags = flags or MigrationFlags.TRACK
|
||||||
|
|
||||||
router.replaceTopController(
|
router.replaceTopController(
|
||||||
MigrationProcedureController.create(
|
MigrationListController.create(
|
||||||
MigrationProcedureConfig(
|
MigrationProcedureConfig(
|
||||||
config.toList(),
|
config.toList(),
|
||||||
ourAdapter.items.filter {
|
ourAdapter.items.filter {
|
||||||
@ -102,7 +98,6 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle)
|
|||||||
useSourceWithMostChapters = prioritize_chapter_count.isChecked,
|
useSourceWithMostChapters = prioritize_chapter_count.isChecked,
|
||||||
enableLenientSearch = use_smart_search.isChecked,
|
enableLenientSearch = use_smart_search.isChecked,
|
||||||
migrationFlags = flags,
|
migrationFlags = flags,
|
||||||
copy = copy_manga.isChecked,
|
|
||||||
extraSearchParams = if(extra_search_param.isChecked && extra_search_param_text.text.isNotBlank()) {
|
extraSearchParams = if(extra_search_param.isChecked && extra_search_param_text.text.isNotBlank()) {
|
||||||
extra_search_param_text.text.toString()
|
extra_search_param_text.text.toString()
|
||||||
} else null
|
} else null
|
||||||
|
@ -4,6 +4,7 @@ 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
|
||||||
@ -31,4 +32,9 @@ class MigratingManga(private val db: DatabaseHelper,
|
|||||||
suspend fun mangaSource(): Source {
|
suspend fun mangaSource(): Source {
|
||||||
return sourceManager.getOrStub(manga()?.source ?: -1)
|
return sourceManager.getOrStub(manga()?.source ?: -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toModal(): MigrationProcessItem {
|
||||||
|
// Create the model object.
|
||||||
|
return MigrationProcessItem(this)
|
||||||
|
}
|
||||||
}
|
}
|
@ -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<MigratingManga>? = 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<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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.viewpager.widget.PagerAdapter
|
import androidx.viewpager.widget.PagerAdapter
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.google.gson.Gson
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
@ -40,7 +39,6 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
|
|||||||
val migratingManga: List<MigratingManga>,
|
val migratingManga: List<MigratingManga>,
|
||||||
override val coroutineContext: CoroutineContext) : PagerAdapter(), CoroutineScope {
|
override val coroutineContext: CoroutineContext) : PagerAdapter(), CoroutineScope {
|
||||||
private val db: DatabaseHelper by injectLazy()
|
private val db: DatabaseHelper by injectLazy()
|
||||||
private val gson: Gson by injectLazy()
|
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
override fun isViewFromObject(p0: View, p1: Any): Boolean {
|
override fun isViewFromObject(p0: View, p1: Any): Boolean {
|
||||||
@ -55,7 +53,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
|
|||||||
container.addView(view)
|
container.addView(view)
|
||||||
|
|
||||||
view.skip_migration.setOnClickListener {
|
view.skip_migration.setOnClickListener {
|
||||||
controller.nextMigration()
|
//controller.nextMigration()
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewTag = ViewTag(coroutineContext)
|
val viewTag = ViewTag(coroutineContext)
|
||||||
@ -91,7 +89,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
|
|||||||
migrateMangaInternal(
|
migrateMangaInternal(
|
||||||
manga.manga() ?: return@withContext,
|
manga.manga() ?: return@withContext,
|
||||||
toMangaObj,
|
toMangaObj,
|
||||||
!(controller.config?.copy ?: false)
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +98,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
|
|||||||
manga: Manga,
|
manga: Manga,
|
||||||
replace: Boolean) {
|
replace: Boolean) {
|
||||||
val config = controller.config ?: return
|
val config = controller.config ?: return
|
||||||
db.inTransaction {
|
//db.inTransaction {
|
||||||
// Update chapters read
|
// Update chapters read
|
||||||
if (MigrationFlags.hasChapters(controller.config.migrationFlags)) {
|
if (MigrationFlags.hasChapters(controller.config.migrationFlags)) {
|
||||||
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
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
|
// SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title
|
||||||
db.updateMangaTitle(manga).executeAsBlocking()
|
db.updateMangaTitle(manga).executeAsBlocking()
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun View.setupView(tag: ViewTag, migratingManga: MigratingManga) {
|
fun View.setupView(tag: ViewTag, migratingManga: MigratingManga) {
|
||||||
|
@ -5,11 +5,10 @@ import kotlinx.android.parcel.Parcelize
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class MigrationProcedureConfig(
|
data class MigrationProcedureConfig(
|
||||||
val mangaIds: List<Long>,
|
var mangaIds: List<Long>,
|
||||||
val targetSourceIds: List<Long>,
|
val targetSourceIds: List<Long>,
|
||||||
val useSourceWithMostChapters: Boolean,
|
val useSourceWithMostChapters: Boolean,
|
||||||
val enableLenientSearch: Boolean,
|
val enableLenientSearch: Boolean,
|
||||||
val migrationFlags: Int,
|
val migrationFlags: Int,
|
||||||
val copy: Boolean,
|
|
||||||
val extraSearchParams: String?
|
val extraSearchParams: String?
|
||||||
): Parcelable
|
): Parcelable
|
@ -150,8 +150,7 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseController(bund
|
|||||||
async {
|
async {
|
||||||
sourceSemaphore.withPermit {
|
sourceSemaphore.withPermit {
|
||||||
try {
|
try {
|
||||||
val searchResult = if (config?.enableLenientSearch ==
|
val searchResult = if (config.enableLenientSearch) {
|
||||||
true) {
|
|
||||||
smartSearchEngine.smartSearch(source, mangaObj.title)
|
smartSearchEngine.smartSearch(source, mangaObj.title)
|
||||||
} else {
|
} else {
|
||||||
smartSearchEngine.normalSearch(source, mangaObj.title)
|
smartSearchEngine.normalSearch(source, mangaObj.title)
|
||||||
|
@ -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<MigrationProcessItem>(null, controller, true) {
|
||||||
|
|
||||||
|
|
||||||
|
private val db: DatabaseHelper by injectLazy()
|
||||||
|
var items: List<MigrationProcessItem> = emptyList()
|
||||||
|
|
||||||
|
val menuItemListener: MigrationProcessInterface = controller
|
||||||
|
|
||||||
|
override fun updateDataSet(items: List<MigrationProcessItem>?) {
|
||||||
|
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()
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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<MigrationProcessHolder>() {
|
||||||
|
|
||||||
|
var holder:MigrationProcessHolder? = null
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.migration_new_process_item
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MigrationProcessHolder {
|
||||||
|
return MigrationProcessHolder(view, adapter as MigrationProcessAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||||
|
holder: MigrationProcessHolder,
|
||||||
|
position: Int,
|
||||||
|
payloads: MutableList<Any?>?) {
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -12,7 +12,7 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
class DeferredField<T> {
|
class DeferredField<T> {
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var content: T? = null
|
var content: T? = null
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
var initialized = false
|
var initialized = false
|
||||||
@ -32,6 +32,14 @@ class DeferredField<T> {
|
|||||||
mutex.unlock()
|
mutex.unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun set(content: T) {
|
||||||
|
mutex.tryLock()
|
||||||
|
this.content = content
|
||||||
|
initialized = true
|
||||||
|
// Notify current listeners
|
||||||
|
mutex.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will only suspend if !initialized.
|
* Will only suspend if !initialized.
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z"/>
|
||||||
|
</vector>
|
@ -114,30 +114,6 @@
|
|||||||
android:gravity="start|center_vertical"
|
android:gravity="start|center_vertical"
|
||||||
android:text="@string/use_intelligent_search"
|
android:text="@string/use_intelligent_search"
|
||||||
android:clickable="true"
|
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" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.SwitchCompat
|
|
||||||
android:id="@+id/copy_manga"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintStart_toStartOf="@+id/textView"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/copy_manga_desc" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/copy_manga_desc"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:gravity="start|center_vertical"
|
|
||||||
android:text="@string/keep_old_manga"
|
|
||||||
android:clickable="true"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/extra_search_param"
|
app:layout_constraintBottom_toTopOf="@+id/extra_search_param"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count"
|
app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count"
|
||||||
@ -202,6 +178,6 @@
|
|||||||
android:id="@+id/options_group"
|
android:id="@+id/options_group"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:constraint_referenced_ids="migration_mode,use_smart_search,fuzzy_search,copy_manga,extra_search_param_desc,mig_tracking,textView,mig_chapters,copy_manga_desc,textView2,prioritize_chapter_count,mig_categories,extra_search_param" />
|
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" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
16
app/src/main/res/layout/migration_list_controller.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:animateLayoutChanges="true">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:id="@+id/recycler"
|
||||||
|
tools:listitem="@layout/migration_new_process_item" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
124
app/src/main/res/layout/migration_new_manga_card.xml
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?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>
|
79
app/src/main/res/layout/migration_new_process_item.xml
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout 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|start">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/migration_manga_card_from"
|
||||||
|
layout="@layout/migration_new_manga_card"
|
||||||
|
android:layout_width="150dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintWidth_max="450dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:layout_marginStart="-10dp"
|
||||||
|
android:layout_marginEnd="-10dp"
|
||||||
|
android:layout_marginBottom="30dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:contentDescription="migrating to"
|
||||||
|
android:scaleType="center"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/migration_manga_card_from"
|
||||||
|
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"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||||
|
app:layout_constraintWidth_max="450dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/migration_menu"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="30dp"
|
||||||
|
android:contentDescription="@string/description_cover"
|
||||||
|
android:paddingTop="30dp"
|
||||||
|
android:paddingBottom="30dp"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_more_vert_black_24dp" />
|
||||||
|
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/skip_manga"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="30dp"
|
||||||
|
android:contentDescription="@string/description_cover"
|
||||||
|
android:paddingTop="30dp"
|
||||||
|
android:paddingBottom="30dp"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/baseline_close_24"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
</LinearLayout>
|
15
app/src/main/res/menu/migration_list.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_copy_manga"
|
||||||
|
android:icon="@drawable/ic_copy"
|
||||||
|
android:title="@string/copy"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_migrate_manga"
|
||||||
|
android:icon="@drawable/ic_done_all"
|
||||||
|
android:title="@string/migrate"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
</menu>
|
21
app/src/main/res/menu/migration_single.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item android:id="@+id/action_search_manually"
|
||||||
|
android:title="@string/action_search_manually"
|
||||||
|
android:visible="false" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_skip"
|
||||||
|
android:title="@string/action_skip_manga"
|
||||||
|
android:visible="true"/>
|
||||||
|
|
||||||
|
<item android:id="@+id/action_migrate_now"
|
||||||
|
android:title="@string/action_migrate_now"
|
||||||
|
android:visible="false" />
|
||||||
|
|
||||||
|
<item android:id="@+id/action_copy_now"
|
||||||
|
android:title="@string/action_copy_now"
|
||||||
|
android:visible="false" />
|
||||||
|
</menu>
|
@ -104,6 +104,9 @@
|
|||||||
<string name="action_webview_back">Back</string>
|
<string name="action_webview_back">Back</string>
|
||||||
<string name="action_webview_forward">Forward</string>
|
<string name="action_webview_forward">Forward</string>
|
||||||
<string name="action_auto_check_extensions">Auto-check for updates</string>
|
<string name="action_auto_check_extensions">Auto-check for updates</string>
|
||||||
|
<string name="action_search_manually">Search manually</string>
|
||||||
|
<string name="action_migrate_now">Migrate now</string>
|
||||||
|
<string name="action_copy_now">Copy now</string>
|
||||||
|
|
||||||
<!-- Operations -->
|
<!-- Operations -->
|
||||||
<string name="deleting">Deleting…</string>
|
<string name="deleting">Deleting…</string>
|
||||||
@ -416,6 +419,10 @@
|
|||||||
<string name="download_all">Download all</string>
|
<string name="download_all">Download all</string>
|
||||||
<string name="download_unread">Download unread</string>
|
<string name="download_unread">Download unread</string>
|
||||||
<string name="confirm_delete_chapters">Are you sure you want to delete selected chapters?</string>
|
<string name="confirm_delete_chapters">Are you sure you want to delete selected chapters?</string>
|
||||||
|
<string name="confirm_migration">Migrate %1$d%2$s mangas?</string>
|
||||||
|
<string name="confirm_copy">Copy %1$d%2$s mangas?</string>
|
||||||
|
<string name="skipping_x">(skipping %1$d)</string>
|
||||||
|
<string name="no_migrations">No manga migrated</string>
|
||||||
|
|
||||||
<!-- Tracking Screen -->
|
<!-- Tracking Screen -->
|
||||||
<string name="manga_tracking_tab">Tracking</string>
|
<string name="manga_tracking_tab">Tracking</string>
|
||||||
@ -486,6 +493,7 @@
|
|||||||
<string name="migrate">Migrate</string>
|
<string name="migrate">Migrate</string>
|
||||||
<string name="copy">Copy</string>
|
<string name="copy">Copy</string>
|
||||||
<string name="migrating">Migrating…</string>
|
<string name="migrating">Migrating…</string>
|
||||||
|
<string name="latest_x">Latest: %1$s</string>
|
||||||
|
|
||||||
<!-- Downloads activity and service -->
|
<!-- Downloads activity and service -->
|
||||||
<string name="download_queue_error">An error occurred while downloading chapters. You can try again in the downloads section</string>
|
<string name="download_queue_error">An error occurred while downloading chapters. You can try again in the downloads section</string>
|
||||||
|