Show sources pinned icons

This commit is contained in:
Koitharu
2024-07-15 17:10:18 +03:00
parent eba1679761
commit 6d07c335de
12 changed files with 62 additions and 31 deletions

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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

View File

@@ -12,7 +12,7 @@ abstract class AbstractSelectionItemDecoration : RecyclerView.ItemDecoration() {
private val bounds = Rect()
private val boundsF = RectF()
protected val selection = HashSet<Long>()
protected val selection = HashSet<Long>() // TODO MutableLongSet
protected var hasBackground: Boolean = true
protected var hasForeground: Boolean = false

View File

@@ -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<List<MangaSource>> = combine(
fun observeEnabledSources(): Flow<List<MangaSourceInfo>> = combine(
observeIsNsfwDisabled(),
observeSortOrder(),
) { skipNsfw, order ->
@@ -295,23 +296,25 @@ class MangaSourcesRepository @Inject constructor(
private fun List<MangaSourceEntity>.toSources(
skipNsfwSources: Boolean,
sortOrder: SourcesSortOrder?,
): MutableList<MangaSource> {
val result = ArrayList<MangaSource>(size)
val pinned = HashSet<MangaSource>()
): MutableList<MangaSourceInfo> {
val result = ArrayList<MangaSourceInfo>(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<MangaSource> { it in pinned }.thenBy { it.getTitle(context) })
result.sortWith(compareBy<MangaSourceInfo> { it.isPinned }.thenBy { it.getTitle(context) })
}
return result
}

View File

@@ -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
}

View File

@@ -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<MangaSource>, isPinned: Boolean) {
fun setSourcesPinned(sources: Collection<MangaSource>, 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<Long>): List<MangaSourceInfo> {
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<MangaSource>,
sources: List<MangaSourceInfo>,
recommendation: List<Manga>,
isGrid: Boolean,
randomLoading: Boolean,

View File

@@ -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)

View File

@@ -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 {

View File

@@ -27,15 +27,17 @@
<TextView
android:id="@+id/textView_title"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_spacing"
android:drawablePadding="1dp"
android:elegantTextHeight="false"
android:ellipsize="end"
android:gravity="center_horizontal"
android:singleLine="true"
android:textAlignment="center"
android:textAppearance="?attr/textAppearanceBodyMedium"
tools:text="@tools:sample/lorem[2]" />
tools:drawableStart="@drawable/ic_pin_small"
tools:text="@tools:sample/lorem[0]" />
</LinearLayout>

View File

@@ -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]" />
<TextView

View File

@@ -9,12 +9,6 @@
android:title="@string/disable"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_shortcut"
android:icon="@drawable/ic_shortcut"
android:title="@string/create_shortcut"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_pin"
android:icon="@drawable/ic_pin"
@@ -27,6 +21,12 @@
android:title="@string/unpin"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_shortcut"
android:icon="@drawable/ic_shortcut"
android:title="@string/create_shortcut"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_settings"