diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt index 22d3fdfa07..c3abba01ea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt @@ -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()) + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 9b9c47853f..9a233eeef8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -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()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt index 6f6befa278..43d6bb3afb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt @@ -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 /** * Presenter of [ExtensionBottomSheet]. */ -class ExtensionBottomPresenter( - private val extensionManager: ExtensionManager = Injekt.get(), - val preferences: PreferencesHelper = Injekt.get(), -) : BaseCoroutinePresenter() { +class ExtensionBottomPresenter() : BaseMigrationPresenter() { private var extensions = emptyList() - var sourceItems = emptyList() - private set - - var mangaItems = hashMapOf>() - private set - private var currentDownloads = hashMapOf() - 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): List { - 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, sourceId: Long): List { - 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 { 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) } - } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt index 1c7e2fff22..7bb66322c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt @@ -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 = emptyList() var canExpand = false @@ -323,22 +326,28 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At drawExtensions() } - fun setMigrationSources(sources: List) { + override fun setMigrationSources(sources: List) { + 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?) { + override fun setMigrationManga(title: String, manga: List?) { + 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() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/BaseMigrationPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/BaseMigrationPresenter.kt new file mode 100644 index 0000000000..d185471094 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/BaseMigrationPresenter.kt @@ -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( + protected val sourceManager: SourceManager = Injekt.get(), + protected val db: DatabaseHelper = Injekt.get(), + val preferences: PreferencesHelper = Injekt.get(), +) : BaseCoroutinePresenter() { + private var selectedSource: Pair? = null + var sourceItems = emptyList() + protected set + + var mangaItems = hashMapOf>() + 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): List { + 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, sourceId: Long): List { + 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?) + fun setMigrationSources(sources: List) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt index b1426ef95b..c2180a86f8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt @@ -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(), + BaseCoroutineController(), FlexibleAdapter.OnItemClickListener, - SourceAdapter.OnAllClickListener { + SourceAdapter.OnAllClickListener, + BaseMigrationInterface { private var adapter: FlexibleAdapter>? = 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?) { + 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) { + title = resources?.getString(R.string.source_migration) + if (adapter !is SourceAdapter) { + adapter = SourceAdapter(this) + binding.migrationRecycler.adapter = adapter + } + adapter?.updateDataSet(sources) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt index 97aad95639..ae58750405 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt @@ -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() { - - 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): List { - 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, sourceId: Long): List { - return library.filter { it.source == sourceId }.map(::MangaItem) +class MigrationPresenter : BaseMigrationPresenter() { + override fun onCreate() { + super.onCreate() + presenterScope.launch { firstTimeMigration() } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceAdapter.kt index 7976eaa933..91e44f7706 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceAdapter.kt @@ -29,11 +29,4 @@ class SourceAdapter(val allClickListener: OnAllClickListener) : interface OnAllClickListener { fun onAllClick(position: Int) } - - override fun updateDataSet(items: MutableList>?) { - if (this.items !== items) { - this.items = items - super.updateDataSet(items) - } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceHolder.kt index 97d72525d9..f6d5072e6d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceHolder.kt @@ -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 { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt index 8a934afb57..9758c5ac1c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt @@ -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(header) { /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/ViewState.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/ViewState.kt deleted file mode 100644 index 8f0dd4e1d0..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/ViewState.kt +++ /dev/null @@ -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 = emptyList(), - val sourcesWithManga: List = emptyList(), - val isReplacingManga: Boolean = false, -) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt index f52aef0210..5ca8d3717b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt @@ -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 -> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt index 4cf03603db..352e58d566 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt @@ -152,6 +152,9 @@ fun String.indexesOf(substr: String, ignoreCase: Boolean = true): List { } } +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)) diff --git a/app/src/main/res/layout/migration_card_item.xml b/app/src/main/res/layout/migration_card_item.xml index a8d3fd539e..7c39481282 100644 --- a/app/src/main/res/layout/migration_card_item.xml +++ b/app/src/main/res/layout/migration_card_item.xml @@ -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"/> + + + + + + + + + + + +