Show sources pinned icons
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user