From 6d07c335de23165400023a7c6c9f6a1ab1204bfc Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 15 Jul 2024 17:10:18 +0300 Subject: [PATCH] Show sources pinned icons --- app/build.gradle | 4 ++-- .../kotatsu/core/model/MangaSource.kt | 5 ++++- .../kotatsu/core/model/MangaSourceInfo.kt | 9 ++++++++ .../decor/AbstractSelectionItemDecoration.kt | 2 +- .../explore/data/MangaSourcesRepository.kt | 21 +++++++++++-------- .../kotatsu/explore/ui/ExploreFragment.kt | 11 +++++----- .../kotatsu/explore/ui/ExploreViewModel.kt | 11 ++++++++-- .../ui/adapter/ExploreAdapterDelegates.kt | 6 ++++++ .../explore/ui/model/MangaSourceItem.kt | 4 ++-- .../res/layout/item_explore_source_grid.xml | 6 ++++-- .../res/layout/item_explore_source_list.xml | 2 ++ app/src/main/res/menu/mode_source.xml | 12 +++++------ 12 files changed, 62 insertions(+), 31 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSourceInfo.kt diff --git a/app/build.gradle b/app/build.gradle index cc32b7d0a..bd7498317 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdk = 21 targetSdk = 34 - versionCode = 651 - versionName = '7.3' + versionCode = 652 + versionName = '7.4-a1' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSource.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSource.kt index 6c101ab82..f1c20eedc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSource.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSource.kt @@ -38,7 +38,8 @@ fun MangaSource(name: String?): MangaSource { return UnknownMangaSource } -fun MangaSource.isNsfw() = when (this) { +fun MangaSource.isNsfw(): Boolean = when (this) { + is MangaSourceInfo -> mangaSource.isNsfw() is MangaParserSource -> contentType == ContentType.HENTAI else -> false } @@ -53,6 +54,7 @@ val ContentType.titleResId } fun MangaSource.getSummary(context: Context): String? = when (this) { + is MangaSourceInfo -> mangaSource.getSummary(context) is MangaParserSource -> { val type = context.getString(contentType.titleResId) val locale = locale.toLocale().getDisplayName(context) @@ -63,6 +65,7 @@ fun MangaSource.getSummary(context: Context): String? = when (this) { } fun MangaSource.getTitle(context: Context): String = when (this) { + is MangaSourceInfo -> mangaSource.getTitle(context) is MangaParserSource -> title LocalMangaSource -> context.getString(R.string.local_storage) else -> context.getString(R.string.unknown) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSourceInfo.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSourceInfo.kt new file mode 100644 index 000000000..a14f5b185 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSourceInfo.kt @@ -0,0 +1,9 @@ +package org.koitharu.kotatsu.core.model + +import org.koitharu.kotatsu.parsers.model.MangaSource + +data class MangaSourceInfo( + val mangaSource: MangaSource, + val isEnabled: Boolean, + val isPinned: Boolean, +) : MangaSource by mangaSource diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt index 8216f4aab..80d7c91d8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt @@ -12,7 +12,7 @@ abstract class AbstractSelectionItemDecoration : RecyclerView.ItemDecoration() { private val bounds = Rect() private val boundsF = RectF() - protected val selection = HashSet() + protected val selection = HashSet() // TODO MutableLongSet protected var hasBackground: Boolean = true protected var hasForeground: Boolean = false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt index f31327dc6..727479374 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt @@ -14,6 +14,7 @@ import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.dao.MangaSourcesDao import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity +import org.koitharu.kotatsu.core.model.MangaSourceInfo import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.model.isNsfw import org.koitharu.kotatsu.core.prefs.AppSettings @@ -146,7 +147,7 @@ class MangaSourcesRepository @Inject constructor( }.distinctUntilChanged().onStart { assimilateNewSources() } } - fun observeEnabledSources(): Flow> = combine( + fun observeEnabledSources(): Flow> = combine( observeIsNsfwDisabled(), observeSortOrder(), ) { skipNsfw, order -> @@ -295,23 +296,25 @@ class MangaSourcesRepository @Inject constructor( private fun List.toSources( skipNsfwSources: Boolean, sortOrder: SourcesSortOrder?, - ): MutableList { - val result = ArrayList(size) - val pinned = HashSet() + ): MutableList { + val result = ArrayList(size) for (entity in this) { val source = entity.source.toMangaSourceOrNull() ?: continue if (skipNsfwSources && source.isNsfw()) { continue } if (source in remoteSources) { - result.add(source) - if (entity.isPinned) { - pinned.add(source) - } + result.add( + MangaSourceInfo( + mangaSource = source, + isEnabled = entity.isEnabled, + isPinned = entity.isPinned, + ), + ) } } if (sortOrder == SourcesSortOrder.ALPHABETIC) { - result.sortWith(compareBy { it in pinned }.thenBy { it.getTitle(context) }) + result.sortWith(compareBy { it.isPinned }.thenBy { it.getTitle(context) }) } return result } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt index 84e60f38a..569e9b6de 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt @@ -41,8 +41,6 @@ import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.parsers.model.Manga -import org.koitharu.kotatsu.parsers.model.MangaParserSource -import org.koitharu.kotatsu.parsers.util.mapNotNullToSet import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.settings.sources.catalog.SourcesCatalogActivity @@ -166,16 +164,17 @@ class ExploreFragment : } override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { - val isSingleSelection = controller.count == 1 + val selectedSources = viewModel.sourcesSnapshot(controller.peekCheckedIds()) + val isSingleSelection = selectedSources.size == 1 menu.findItem(R.id.action_settings).isVisible = isSingleSelection menu.findItem(R.id.action_shortcut).isVisible = isSingleSelection + menu.findItem(R.id.action_pin).isVisible = selectedSources.all { !it.isPinned } + menu.findItem(R.id.action_unpin).isVisible = selectedSources.all { it.isPinned } return super.onPrepareActionMode(controller, mode, menu) } override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { - val selectedSources = controller.peekCheckedIds().mapNotNullToSet { id -> - MangaParserSource.entries.getOrNull(id.toInt()) // TODO - } + val selectedSources = viewModel.sourcesSnapshot(controller.peekCheckedIds()) if (selectedSources.isEmpty()) { return false } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt index fad516b34..6f9f681dc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.model.MangaSourceInfo import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.observeAsFlow @@ -108,7 +109,7 @@ class ExploreViewModel @Inject constructor( } } - fun setSourcesPinned(sources: Set, isPinned: Boolean) { + fun setSourcesPinned(sources: Collection, isPinned: Boolean) { launchJob(Dispatchers.Default) { sourcesRepository.setIsPinned(sources, isPinned) val message = if (sources.size == 1) { @@ -125,6 +126,12 @@ class ExploreViewModel @Inject constructor( settings.closeTip(TIP_SUGGESTIONS) } + fun sourcesSnapshot(ids: Set): List { + return content.value.mapNotNull { + (it as? MangaSourceItem)?.takeIf { x -> x.id in ids }?.source + } + } + private fun createContentFlow() = combine( sourcesRepository.observeEnabledSources(), getSuggestionFlow(), @@ -136,7 +143,7 @@ class ExploreViewModel @Inject constructor( }.withErrorHandling() private fun buildList( - sources: List, + sources: List, recommendation: List, isGrid: Boolean, randomLoading: Boolean, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt index 8952ac354..7dc91d9b9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.explore.ui.adapter import android.view.View +import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding @@ -15,6 +16,7 @@ import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders +import org.koitharu.kotatsu.core.util.ext.drawableStart import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.recyclerView @@ -116,6 +118,7 @@ fun exploreSourceListItemAD( ) { val eventListener = AdapterDelegateClickListenerAdapter(this, listener) + val iconPinned = ContextCompat.getDrawable(context, R.drawable.ic_pin_small) binding.root.setOnClickListener(eventListener) binding.root.setOnLongClickListener(eventListener) @@ -123,6 +126,7 @@ fun exploreSourceListItemAD( bind { binding.textViewTitle.text = item.source.getTitle(context) + binding.textViewTitle.drawableStart = if (item.source.isPinned) iconPinned else null binding.textViewSubtitle.text = item.source.getSummary(context) val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { @@ -151,6 +155,7 @@ fun exploreSourceGridItemAD( ) { val eventListener = AdapterDelegateClickListenerAdapter(this, listener) + val iconPinned = ContextCompat.getDrawable(context, R.drawable.ic_pin_small) binding.root.setOnClickListener(eventListener) binding.root.setOnLongClickListener(eventListener) @@ -158,6 +163,7 @@ fun exploreSourceGridItemAD( bind { binding.textViewTitle.text = item.source.getTitle(context) + binding.textViewTitle.drawableStart = if (item.source.isPinned) iconPinned else null val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Large, item.source.name) binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { fallback(fallbackIcon) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/MangaSourceItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/MangaSourceItem.kt index 891535fc9..9c084c80a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/MangaSourceItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/MangaSourceItem.kt @@ -1,11 +1,11 @@ package org.koitharu.kotatsu.explore.ui.model +import org.koitharu.kotatsu.core.model.MangaSourceInfo import org.koitharu.kotatsu.core.util.ext.longHashCode import org.koitharu.kotatsu.list.ui.model.ListModel -import org.koitharu.kotatsu.parsers.model.MangaSource data class MangaSourceItem( - val source: MangaSource, + val source: MangaSourceInfo, val isGrid: Boolean, ) : ListModel { diff --git a/app/src/main/res/layout/item_explore_source_grid.xml b/app/src/main/res/layout/item_explore_source_grid.xml index e1cef0936..27e7a59d2 100644 --- a/app/src/main/res/layout/item_explore_source_grid.xml +++ b/app/src/main/res/layout/item_explore_source_grid.xml @@ -27,15 +27,17 @@ + tools:drawableStart="@drawable/ic_pin_small" + tools:text="@tools:sample/lorem[0]" /> diff --git a/app/src/main/res/layout/item_explore_source_list.xml b/app/src/main/res/layout/item_explore_source_list.xml index 0666e7f8f..2bd02a729 100644 --- a/app/src/main/res/layout/item_explore_source_list.xml +++ b/app/src/main/res/layout/item_explore_source_list.xml @@ -37,9 +37,11 @@ android:id="@+id/textView_title" android:layout_width="match_parent" android:layout_height="wrap_content" + android:drawablePadding="2dp" android:ellipsize="end" android:singleLine="true" android:textAppearance="?attr/textAppearanceTitleSmall" + tools:drawableStart="@drawable/ic_pin_small" tools:text="@tools:sample/lorem[2]" /> - - + +