mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
Various updates to migration list
* Migration list now shows language + number of items in each source * Option to sort alphabetically (default), by most entries, or by obsolete/uninstalled sources * Show if a source is obsolete or uninstalled in the extension list * Refactor main migration controller to reduce similar code + use flows
This commit is contained in:
parent
201c334b13
commit
6b1725fd3b
15 changed files with 328 additions and 215 deletions
|
@ -1,5 +1,6 @@
|
|||
package eu.kanade.tachiyomi.data.preference
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
// Library
|
||||
|
@ -25,4 +26,16 @@ object PreferenceValues {
|
|||
LOW(R.string.pref_low, 31),
|
||||
LOWEST(R.string.pref_lowest, 47),
|
||||
}
|
||||
|
||||
enum class MigrationSourceOrder(val value: Int, @StringRes val titleResId: Int) {
|
||||
Alphabetically(0, R.string.alphabetically),
|
||||
MostEntries(1, R.string.most_entries),
|
||||
Obsolete(2, R.string.obsolete),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromValue(preference: Int) = values().find { it.value == preference } ?: Alphabetically
|
||||
fun fromPreference(pref: PreferencesHelper) = fromValue(pref.migrationSourceOrder().get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -298,6 +298,8 @@ class PreferencesHelper(val context: Context) {
|
|||
|
||||
fun installedExtensionsOrder() = flowPrefs.getInt(Keys.installedExtensionsOrder, InstalledExtensionsOrder.Name.value)
|
||||
|
||||
fun migrationSourceOrder() = flowPrefs.getInt("migration_source_order", Values.MigrationSourceOrder.Alphabetically.value)
|
||||
|
||||
fun collapsedCategories() = flowPrefs.getStringSet("collapsed_categories", mutableSetOf())
|
||||
|
||||
fun collapsedDynamicCategories() = flowPrefs.getStringSet("collapsed_dynamic_categories", mutableSetOf())
|
||||
|
|
|
@ -3,23 +3,13 @@ package eu.kanade.tachiyomi.ui.extension
|
|||
import android.content.pm.PackageInstaller
|
||||
import androidx.core.content.ContextCompat
|
||||
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.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.extension.ExtensionInstallService
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter
|
||||
import eu.kanade.tachiyomi.ui.migration.MangaItem
|
||||
import eu.kanade.tachiyomi.ui.migration.SelectionHeader
|
||||
import eu.kanade.tachiyomi.ui.migration.SourceItem
|
||||
import eu.kanade.tachiyomi.ui.migration.BaseMigrationPresenter
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||
import eu.kanade.tachiyomi.util.system.withUIContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
|
@ -28,7 +18,6 @@ import kotlinx.coroutines.flow.collect
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
typealias ExtensionTuple =
|
||||
|
@ -38,26 +27,13 @@ typealias ExtensionIntallInfo = Pair<InstallStep, PackageInstaller.SessionInfo?>
|
|||
/**
|
||||
* Presenter of [ExtensionBottomSheet].
|
||||
*/
|
||||
class ExtensionBottomPresenter(
|
||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||
val preferences: PreferencesHelper = Injekt.get(),
|
||||
) : BaseCoroutinePresenter<ExtensionBottomSheet>() {
|
||||
class ExtensionBottomPresenter() : BaseMigrationPresenter<ExtensionBottomSheet>() {
|
||||
|
||||
private var extensions = emptyList<ExtensionItem>()
|
||||
|
||||
var sourceItems = emptyList<SourceItem>()
|
||||
private set
|
||||
|
||||
var mangaItems = hashMapOf<Long, List<MangaItem>>()
|
||||
private set
|
||||
|
||||
private var currentDownloads = hashMapOf<String, ExtensionIntallInfo>()
|
||||
|
||||
private val sourceManager: SourceManager = Injekt.get()
|
||||
|
||||
private var selectedSource: Long? = null
|
||||
private var firstLoad = true
|
||||
private val db: DatabaseHelper = Injekt.get()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
@ -73,25 +49,7 @@ class ExtensionBottomPresenter(
|
|||
)
|
||||
withContext(Dispatchers.Main) { controller?.setExtensions(extensions, false) }
|
||||
}
|
||||
val migrationJob = async {
|
||||
val favs = db.getFavoriteMangas().executeOnIO()
|
||||
sourceItems = findSourcesWithManga(favs)
|
||||
mangaItems = HashMap(
|
||||
sourceItems.associate {
|
||||
it.source.id to this@ExtensionBottomPresenter.libraryToMigrationItem(
|
||||
favs,
|
||||
it.source.id,
|
||||
)
|
||||
},
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (selectedSource != null) {
|
||||
controller?.setMigrationManga(mangaItems[selectedSource])
|
||||
} else {
|
||||
controller?.setMigrationSources(sourceItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
val migrationJob = async { firstTimeMigration() }
|
||||
listOf(migrationJob, extensionJob).awaitAll()
|
||||
}
|
||||
presenterScope.launch {
|
||||
|
@ -129,18 +87,6 @@ class ExtensionBottomPresenter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun findSourcesWithManga(library: List<Manga>): List<SourceItem> {
|
||||
val header = SelectionHeader()
|
||||
return library.map { it.source }.toSet()
|
||||
.mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
|
||||
.sortedBy { it.name }
|
||||
.map { SourceItem(it, header) }
|
||||
}
|
||||
|
||||
private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> {
|
||||
return library.filter { it.source == sourceId }.map(::MangaItem)
|
||||
}
|
||||
|
||||
fun refreshExtensions() {
|
||||
presenterScope.launch {
|
||||
extensions = toItems(
|
||||
|
@ -154,25 +100,6 @@ class ExtensionBottomPresenter(
|
|||
}
|
||||
}
|
||||
|
||||
fun refreshMigrations() {
|
||||
presenterScope.launch {
|
||||
val favs = db.getFavoriteMangas().executeOnIO()
|
||||
sourceItems = findSourcesWithManga(favs)
|
||||
mangaItems = HashMap(
|
||||
sourceItems.associate {
|
||||
it.source.id to this@ExtensionBottomPresenter.libraryToMigrationItem(favs, it.source.id)
|
||||
},
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (selectedSource != null) {
|
||||
controller?.setMigrationManga(mangaItems[selectedSource])
|
||||
} else {
|
||||
controller?.setMigrationSources(sourceItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun toItems(tuple: ExtensionTuple): List<ExtensionItem> {
|
||||
val context = controller?.context ?: return emptyList()
|
||||
|
@ -355,18 +282,4 @@ class ExtensionBottomPresenter(
|
|||
fun trustSignature(signatureHash: String) {
|
||||
extensionManager.trustSignature(signatureHash)
|
||||
}
|
||||
|
||||
fun setSelectedSource(source: Source) {
|
||||
selectedSource = source.id
|
||||
presenterScope.launch {
|
||||
withContext(Dispatchers.Main) { controller?.setMigrationManga(mangaItems[source.id]) }
|
||||
}
|
||||
}
|
||||
|
||||
fun deselectSource() {
|
||||
selectedSource = null
|
||||
presenterScope.launch {
|
||||
withContext(Dispatchers.Main) { controller?.setMigrationSources(sourceItems) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import eu.kanade.tachiyomi.databinding.RecyclerWithScrollerBinding
|
|||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
|
||||
import eu.kanade.tachiyomi.ui.extension.details.ExtensionDetailsController
|
||||
import eu.kanade.tachiyomi.ui.migration.BaseMigrationInterface
|
||||
import eu.kanade.tachiyomi.ui.migration.MangaAdapter
|
||||
import eu.kanade.tachiyomi.ui.migration.MangaItem
|
||||
import eu.kanade.tachiyomi.ui.migration.SourceAdapter
|
||||
|
@ -46,7 +47,8 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
|
|||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnItemLongClickListener,
|
||||
ExtensionTrustDialog.Listener,
|
||||
SourceAdapter.OnAllClickListener {
|
||||
SourceAdapter.OnAllClickListener,
|
||||
BaseMigrationInterface {
|
||||
|
||||
var sheetBehavior: BottomSheetBehavior<*>? = null
|
||||
|
||||
|
@ -62,6 +64,7 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
|
|||
get() = listOf(extAdapter, migAdapter)
|
||||
|
||||
val presenter = ExtensionBottomPresenter()
|
||||
var currentSourceTitle: String? = null
|
||||
|
||||
private var extensions: List<ExtensionItem> = emptyList()
|
||||
var canExpand = false
|
||||
|
@ -323,22 +326,28 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
|
|||
drawExtensions()
|
||||
}
|
||||
|
||||
fun setMigrationSources(sources: List<SourceItem>) {
|
||||
override fun setMigrationSources(sources: List<SourceItem>) {
|
||||
currentSourceTitle = null
|
||||
val changingAdapters = migAdapter !is SourceAdapter
|
||||
if (migAdapter !is SourceAdapter) {
|
||||
migAdapter = SourceAdapter(this)
|
||||
migrationFrameLayout?.onBind(migAdapter!!)
|
||||
migAdapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
|
||||
}
|
||||
migAdapter?.updateDataSet(sources, true)
|
||||
migAdapter?.updateDataSet(sources, changingAdapters)
|
||||
controller.updateTitleAndMenu()
|
||||
}
|
||||
|
||||
fun setMigrationManga(manga: List<MangaItem>?) {
|
||||
override fun setMigrationManga(title: String, manga: List<MangaItem>?) {
|
||||
currentSourceTitle = title
|
||||
val changingAdapters = migAdapter !is MangaAdapter
|
||||
if (migAdapter !is MangaAdapter) {
|
||||
migAdapter = MangaAdapter(this, presenter.preferences.outlineOnCovers().get())
|
||||
migrationFrameLayout?.onBind(migAdapter!!)
|
||||
migAdapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
|
||||
}
|
||||
migAdapter?.updateDataSet(manga, true)
|
||||
migAdapter?.updateDataSet(manga, changingAdapters)
|
||||
controller.updateTitleAndMenu()
|
||||
}
|
||||
|
||||
fun drawExtensions() {
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
package eu.kanade.tachiyomi.ui.migration
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter
|
||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||
import eu.kanade.tachiyomi.util.system.withUIContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
abstract class BaseMigrationPresenter<T : BaseMigrationInterface>(
|
||||
protected val sourceManager: SourceManager = Injekt.get(),
|
||||
protected val db: DatabaseHelper = Injekt.get(),
|
||||
val preferences: PreferencesHelper = Injekt.get(),
|
||||
) : BaseCoroutinePresenter<T>() {
|
||||
private var selectedSource: Pair<String, Long>? = null
|
||||
var sourceItems = emptyList<SourceItem>()
|
||||
protected set
|
||||
|
||||
var mangaItems = hashMapOf<Long, List<MangaItem>>()
|
||||
protected set
|
||||
protected val extensionManager: ExtensionManager by injectLazy()
|
||||
|
||||
fun refreshMigrations() {
|
||||
presenterScope.launch {
|
||||
val favs = db.getFavoriteMangas().executeOnIO()
|
||||
sourceItems = findSourcesWithManga(favs)
|
||||
mangaItems = HashMap(
|
||||
sourceItems.associate {
|
||||
it.source.id to libraryToMigrationItem(favs, it.source.id)
|
||||
},
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (selectedSource != null) {
|
||||
controller?.setMigrationManga(selectedSource!!.first, mangaItems[selectedSource!!.second])
|
||||
} else {
|
||||
controller?.setMigrationSources(sourceItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findSourcesWithManga(library: List<Manga>): List<SourceItem> {
|
||||
val header = SelectionHeader()
|
||||
val sourceGroup = library.groupBy { it.source }
|
||||
val sortOrder = PreferenceValues.MigrationSourceOrder.fromPreference(preferences)
|
||||
val extensions = extensionManager.installedExtensions
|
||||
val obsoleteSources =
|
||||
extensions.filter { it.isObsolete }.map { it.sources }.flatten().map { it.id }
|
||||
|
||||
return sourceGroup
|
||||
.mapNotNull { if (it.key != LocalSource.ID) sourceManager.getOrStub(it.key) to it.value.size else null }
|
||||
.sortedWith(
|
||||
compareBy(
|
||||
{
|
||||
when (sortOrder) {
|
||||
PreferenceValues.MigrationSourceOrder.Alphabetically -> it.first.name
|
||||
PreferenceValues.MigrationSourceOrder.MostEntries -> Long.MAX_VALUE - it.second
|
||||
PreferenceValues.MigrationSourceOrder.Obsolete ->
|
||||
it.first !is SourceManager.StubSource &&
|
||||
it.first.id !in obsoleteSources
|
||||
}
|
||||
},
|
||||
{ it.first.name },
|
||||
),
|
||||
)
|
||||
.map {
|
||||
SourceItem(
|
||||
it.first,
|
||||
header,
|
||||
it.second,
|
||||
it.first is SourceManager.StubSource,
|
||||
it.first.id in obsoleteSources,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> {
|
||||
return library.filter { it.source == sourceId }.map(::MangaItem)
|
||||
}
|
||||
|
||||
protected suspend fun firstTimeMigration() {
|
||||
val favs = db.getFavoriteMangas().executeOnIO()
|
||||
sourceItems = findSourcesWithManga(favs)
|
||||
mangaItems = HashMap(
|
||||
sourceItems.associate {
|
||||
it.source.id to libraryToMigrationItem(
|
||||
favs,
|
||||
it.source.id,
|
||||
)
|
||||
},
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (selectedSource != null) {
|
||||
controller?.setMigrationManga(selectedSource!!.first, mangaItems[selectedSource!!.second])
|
||||
} else {
|
||||
controller?.setMigrationSources(sourceItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSelectedSource(source: Source) {
|
||||
selectedSource = source.name to source.id
|
||||
presenterScope.launch {
|
||||
withUIContext { controller?.setMigrationManga(source.name, mangaItems[source.id]) }
|
||||
}
|
||||
}
|
||||
|
||||
fun deselectSource() {
|
||||
selectedSource = null
|
||||
presenterScope.launch {
|
||||
withUIContext { controller?.setMigrationSources(sourceItems) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface BaseMigrationInterface {
|
||||
fun setMigrationManga(title: String, manga: List<MangaItem>?)
|
||||
fun setMigrationSources(sources: List<SourceItem>)
|
||||
}
|
|
@ -1,18 +1,24 @@
|
|||
package eu.kanade.tachiyomi.ui.migration
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.databinding.MigrationControllerBinding
|
||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseCoroutineController
|
||||
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
|
||||
import eu.kanade.tachiyomi.ui.source.BrowseController
|
||||
import eu.kanade.tachiyomi.util.system.await
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
import eu.kanade.tachiyomi.util.view.activityBinding
|
||||
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
||||
import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset
|
||||
|
@ -23,9 +29,10 @@ import uy.kohesive.injekt.Injekt
|
|||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class MigrationController :
|
||||
NucleusController<MigrationControllerBinding, MigrationPresenter>(),
|
||||
BaseCoroutineController<MigrationControllerBinding, MigrationPresenter>(),
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
SourceAdapter.OnAllClickListener {
|
||||
SourceAdapter.OnAllClickListener,
|
||||
BaseMigrationInterface {
|
||||
|
||||
private var adapter: FlexibleAdapter<IFlexible<*>>? = null
|
||||
|
||||
|
@ -35,9 +42,7 @@ class MigrationController :
|
|||
setTitle()
|
||||
}
|
||||
|
||||
override fun createPresenter(): MigrationPresenter {
|
||||
return MigrationPresenter()
|
||||
}
|
||||
override val presenter = MigrationPresenter()
|
||||
|
||||
override fun createBinding(inflater: LayoutInflater) = MigrationControllerBinding.inflate(inflater)
|
||||
|
||||
|
@ -59,10 +64,10 @@ class MigrationController :
|
|||
return title
|
||||
}
|
||||
|
||||
override fun canStillGoBack(): Boolean = presenter.state.selectedSource != null
|
||||
override fun canStillGoBack(): Boolean = adapter is MangaAdapter
|
||||
|
||||
override fun handleBack(): Boolean {
|
||||
return if (presenter.state.selectedSource != null) {
|
||||
return if (adapter is MangaAdapter) {
|
||||
presenter.deselectSource()
|
||||
true
|
||||
} else {
|
||||
|
@ -70,27 +75,6 @@ class MigrationController :
|
|||
}
|
||||
}
|
||||
|
||||
fun render(state: ViewState) {
|
||||
if (state.selectedSource == null) {
|
||||
title = resources?.getString(R.string.source_migration)
|
||||
if (adapter !is SourceAdapter) {
|
||||
adapter = SourceAdapter(this)
|
||||
binding.migrationRecycler.adapter = adapter
|
||||
}
|
||||
adapter?.updateDataSet(state.sourcesWithManga)
|
||||
} else {
|
||||
title = state.selectedSource.toString()
|
||||
if (adapter !is MangaAdapter) {
|
||||
adapter = MangaAdapter(this, presenter.preferences.outlineOnCovers().get())
|
||||
binding.migrationRecycler.adapter = adapter
|
||||
}
|
||||
adapter?.updateDataSet(state.mangaForSource, true)
|
||||
activityBinding?.appBar?.doOnNextLayout {
|
||||
binding.migrationRecycler.requestApplyInsets()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemClick(view: View?, position: Int): Boolean {
|
||||
val item = adapter?.getItem(position) ?: return false
|
||||
|
||||
|
@ -124,4 +108,56 @@ class MigrationController :
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.migration_main, menu)
|
||||
menu.findItem(R.id.action_sources_settings).isVisible = false
|
||||
val id = when (PreferenceValues.MigrationSourceOrder.fromPreference(presenter.preferences)) {
|
||||
PreferenceValues.MigrationSourceOrder.Alphabetically -> R.id.action_sort_alpha
|
||||
PreferenceValues.MigrationSourceOrder.MostEntries -> R.id.action_sort_largest
|
||||
PreferenceValues.MigrationSourceOrder.Obsolete -> R.id.action_sort_obsolete
|
||||
}
|
||||
menu.findItem(id).isChecked = true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val sorting = when (item.itemId) {
|
||||
R.id.action_sort_alpha -> PreferenceValues.MigrationSourceOrder.Alphabetically
|
||||
R.id.action_sort_largest -> PreferenceValues.MigrationSourceOrder.MostEntries
|
||||
R.id.action_sort_obsolete -> PreferenceValues.MigrationSourceOrder.Obsolete
|
||||
else -> null
|
||||
}
|
||||
if (sorting != null) {
|
||||
presenter.preferences.migrationSourceOrder().set(sorting.value)
|
||||
presenter.refreshMigrations()
|
||||
item.isChecked = true
|
||||
}
|
||||
when (item.itemId) {
|
||||
R.id.action_migration_guide -> {
|
||||
activity?.openInBrowser(BrowseController.HELP_URL)
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun setMigrationManga(title: String, manga: List<MangaItem>?) {
|
||||
this.title = title
|
||||
if (adapter !is MangaAdapter) {
|
||||
adapter = MangaAdapter(this, presenter.preferences.outlineOnCovers().get())
|
||||
binding.migrationRecycler.adapter = adapter
|
||||
}
|
||||
adapter?.updateDataSet(manga, true)
|
||||
activityBinding?.appBar?.doOnNextLayout {
|
||||
binding.migrationRecycler.requestApplyInsets()
|
||||
}
|
||||
}
|
||||
|
||||
override fun setMigrationSources(sources: List<SourceItem>) {
|
||||
title = resources?.getString(R.string.source_migration)
|
||||
if (adapter !is SourceAdapter) {
|
||||
adapter = SourceAdapter(this)
|
||||
binding.migrationRecycler.adapter = adapter
|
||||
}
|
||||
adapter?.updateDataSet(sources)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +1,10 @@
|
|||
package eu.kanade.tachiyomi.ui.migration
|
||||
|
||||
import android.os.Bundle
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.util.lang.combineLatest
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MigrationPresenter(
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val db: DatabaseHelper = Injekt.get(),
|
||||
val preferences: PreferencesHelper = Injekt.get(),
|
||||
) : BasePresenter<MigrationController>() {
|
||||
|
||||
var state = ViewState()
|
||||
private set(value) {
|
||||
field = value
|
||||
stateRelay.call(value)
|
||||
}
|
||||
|
||||
private val stateRelay = BehaviorRelay.create(state)
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
|
||||
db.getFavoriteMangas().asRxObservable().observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { state = state.copy(sourcesWithManga = findSourcesWithManga(it)) }
|
||||
.combineLatest(
|
||||
stateRelay.map { it.selectedSource }
|
||||
.distinctUntilChanged(),
|
||||
) { library, source -> library to source }
|
||||
.filter { (_, source) -> source != null }.observeOn(Schedulers.io())
|
||||
.map { (library, source) -> libraryToMigrationItem(library, source!!.id) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { state = state.copy(mangaForSource = it) }.subscribe()
|
||||
|
||||
stateRelay
|
||||
// Render the view when any field other than isReplacingManga changes
|
||||
.distinctUntilChanged { t1, t2 -> t1.isReplacingManga != t2.isReplacingManga }
|
||||
.subscribeLatestCache(MigrationController::render)
|
||||
}
|
||||
|
||||
fun setSelectedSource(source: Source) {
|
||||
state = state.copy(selectedSource = source, mangaForSource = emptyList())
|
||||
}
|
||||
|
||||
fun deselectSource() {
|
||||
state = state.copy(selectedSource = null, mangaForSource = emptyList())
|
||||
}
|
||||
|
||||
private fun findSourcesWithManga(library: List<Manga>): List<SourceItem> {
|
||||
val header = SelectionHeader()
|
||||
return library.asSequence().map { it.source }.toSet()
|
||||
.mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
|
||||
.sortedBy { it.name }
|
||||
.map { SourceItem(it, header) }.toList()
|
||||
}
|
||||
|
||||
private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> {
|
||||
return library.filter { it.source == sourceId }.map(::MangaItem)
|
||||
class MigrationPresenter : BaseMigrationPresenter<MigrationController>() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
presenterScope.launch { firstTimeMigration() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,11 +29,4 @@ class SourceAdapter(val allClickListener: OnAllClickListener) :
|
|||
interface OnAllClickListener {
|
||||
fun onAllClick(position: Int)
|
||||
}
|
||||
|
||||
override fun updateDataSet(items: MutableList<IFlexible<*>>?) {
|
||||
if (this.items !== items) {
|
||||
this.items = items
|
||||
super.updateDataSet(items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
package eu.kanade.tachiyomi.ui.migration
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.color
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.MigrationCardItemBinding
|
||||
import eu.kanade.tachiyomi.source.icon
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.util.lang.withColor
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import java.util.Locale
|
||||
|
||||
class SourceHolder(view: View, val adapter: SourceAdapter) :
|
||||
|
@ -23,8 +29,20 @@ class SourceHolder(view: View, val adapter: SourceAdapter) :
|
|||
val sourceName =
|
||||
if (adapter.isMultiLanguage) source.toString() else source.name.replaceFirstChar {
|
||||
it.titlecase(Locale.getDefault())
|
||||
}
|
||||
} + " (${item.numberOfItems})"
|
||||
binding.title.text = sourceName
|
||||
binding.lang.text = when {
|
||||
item.isUninstalled -> itemView.context.getString(R.string.source_not_installed)
|
||||
.withColor(itemView.context.getResourceColor(R.attr.colorError))
|
||||
item.isObsolete -> buildSpannedString {
|
||||
append(LocaleHelper.getSourceDisplayName(source.lang, itemView.context))
|
||||
append(" ")
|
||||
color(itemView.context.getResourceColor(R.attr.colorError)) {
|
||||
append(itemView.context.getString(R.string.obsolete).uppercase())
|
||||
}
|
||||
}
|
||||
else -> LocaleHelper.getSourceDisplayName(source.lang, itemView.context)
|
||||
}
|
||||
|
||||
// Set circle letter image.
|
||||
itemView.post {
|
||||
|
|
|
@ -14,7 +14,13 @@ import eu.kanade.tachiyomi.source.Source
|
|||
* @param source Instance of [Source] containing source information.
|
||||
* @param header The header for this item.
|
||||
*/
|
||||
data class SourceItem(val source: Source, val header: SelectionHeader? = null) :
|
||||
data class SourceItem(
|
||||
val source: Source,
|
||||
val header: SelectionHeader? = null,
|
||||
val numberOfItems: Int,
|
||||
val isUninstalled: Boolean,
|
||||
val isObsolete: Boolean,
|
||||
) :
|
||||
AbstractSectionableItem<SourceHolder, SelectionHeader>(header) {
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.migration
|
||||
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
|
||||
data class ViewState(
|
||||
val selectedSource: Source? = null,
|
||||
val mangaForSource: List<MangaItem> = emptyList(),
|
||||
val sourcesWithManga: List<SourceItem> = emptyList(),
|
||||
val isReplacingManga: Boolean = false,
|
||||
)
|
|
@ -27,6 +27,7 @@ import com.google.android.material.snackbar.Snackbar
|
|||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.databinding.BrowseControllerBinding
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
|
@ -238,10 +239,12 @@ class BrowseController :
|
|||
|
||||
private fun updateSheetMenu() {
|
||||
binding.bottomSheet.sheetToolbar.title =
|
||||
view?.context?.getString(
|
||||
if (binding.bottomSheet.tabs.selectedTabPosition == 0) R.string.extensions
|
||||
else R.string.source_migration,
|
||||
)
|
||||
if (binding.bottomSheet.tabs.selectedTabPosition != 0) {
|
||||
binding.bottomSheet.root.currentSourceTitle
|
||||
?: view?.context?.getString(R.string.source_migration)
|
||||
} else {
|
||||
view?.context?.getString(R.string.extensions)
|
||||
}
|
||||
val onExtensionTab = binding.bottomSheet.tabs.selectedTabPosition == 0
|
||||
if (binding.bottomSheet.sheetToolbar.menu.findItem(if (onExtensionTab) R.id.action_search else R.id.action_migration_guide) != null) {
|
||||
return
|
||||
|
@ -254,6 +257,13 @@ class BrowseController :
|
|||
else R.menu.migration_main,
|
||||
)
|
||||
|
||||
val id = when (PreferenceValues.MigrationSourceOrder.fromPreference(preferences)) {
|
||||
PreferenceValues.MigrationSourceOrder.Alphabetically -> R.id.action_sort_alpha
|
||||
PreferenceValues.MigrationSourceOrder.MostEntries -> R.id.action_sort_largest
|
||||
PreferenceValues.MigrationSourceOrder.Obsolete -> R.id.action_sort_obsolete
|
||||
}
|
||||
binding.bottomSheet.sheetToolbar.menu.findItem(id)?.isChecked = true
|
||||
|
||||
// Initialize search option.
|
||||
binding.bottomSheet.sheetToolbar.menu.findItem(R.id.action_search)?.let { searchItem ->
|
||||
val searchView = searchItem.actionView as SearchView
|
||||
|
@ -279,6 +289,18 @@ class BrowseController :
|
|||
|
||||
private fun setSheetToolbar() {
|
||||
binding.bottomSheet.sheetToolbar.setOnMenuItemClickListener { item ->
|
||||
val sorting = when (item.itemId) {
|
||||
R.id.action_sort_alpha -> PreferenceValues.MigrationSourceOrder.Alphabetically
|
||||
R.id.action_sort_largest -> PreferenceValues.MigrationSourceOrder.MostEntries
|
||||
R.id.action_sort_obsolete -> PreferenceValues.MigrationSourceOrder.Obsolete
|
||||
else -> null
|
||||
}
|
||||
if (sorting != null) {
|
||||
preferences.migrationSourceOrder().set(sorting.value)
|
||||
binding.bottomSheet.root.presenter.refreshMigrations()
|
||||
item.isChecked = true
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
when (item.itemId) {
|
||||
// Initialize option to open catalogue settings.
|
||||
R.id.action_filter -> {
|
||||
|
|
|
@ -152,6 +152,9 @@ fun String.indexesOf(substr: String, ignoreCase: Boolean = true): List<Int> {
|
|||
}
|
||||
}
|
||||
|
||||
fun String.withColor(@ColorInt colorInt: Int) =
|
||||
buildSpannedString { color(colorInt) { append(this@withColor) } }
|
||||
|
||||
fun String.withSubtitle(context: Context, @StringRes subtitleRes: Int) =
|
||||
withSubtitle(context, context.getString(subtitleRes))
|
||||
|
||||
|
|
|
@ -32,13 +32,31 @@
|
|||
android:paddingStart="0dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
style="?textAppearanceBodyLarge"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
style="?textAppearanceBodyMedium"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_constraintBottom_toTopOf="@id/lang"
|
||||
app:layout_constraintStart_toEndOf="@id/source_image"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/migration_all"
|
||||
tools:text="Source title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/lang"
|
||||
style="?textAppearanceBodySmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintStart_toStartOf="@id/title"
|
||||
app:layout_constraintEnd_toEndOf="@id/title"
|
||||
tools:text="English"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/migration_all"
|
||||
style="@style/Widget.Tachiyomi.Button.Small"
|
||||
|
|
|
@ -1,6 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_migration_sort"
|
||||
android:icon="@drawable/ic_sort_24dp"
|
||||
android:title="@string/sort_by"
|
||||
app:showAsAction="ifRoom" >
|
||||
<menu>
|
||||
<group android:checkableBehavior="single"
|
||||
android:id="@+id/action_sort_group">
|
||||
<item
|
||||
android:id="@+id/action_sort_alpha"
|
||||
android:title="@string/alphabetically"/>
|
||||
<item
|
||||
android:id="@+id/action_sort_largest"
|
||||
android:title="@string/most_entries"/>
|
||||
<item
|
||||
android:id="@+id/action_sort_obsolete"
|
||||
android:title="@string/obsolete"/>
|
||||
</group>
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_sources_settings"
|
||||
android:icon="@drawable/ic_tune_24dp"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue