Add recent sources to search suggestions

This commit is contained in:
Koitharu
2024-07-07 11:38:35 +03:00
parent 9b3ce4d849
commit dcee5542c5
10 changed files with 132 additions and 1 deletions

View File

@@ -27,6 +27,9 @@ abstract class MangaSourcesDao {
@Query("SELECT * FROM sources WHERE added_in >= :version")
abstract suspend fun findAllFromVersion(version: Int): List<MangaSourceEntity>
@Query("SELECT * FROM sources ORDER BY used_at DESC LIMIT :limit")
abstract suspend fun findLastUsed(limit: Int): List<MangaSourceEntity>
@Query("SELECT * FROM sources ORDER BY pinned DESC, sort_key")
abstract fun observeAll(): Flow<List<MangaSourceEntity>>

View File

@@ -12,5 +12,6 @@ enum class SearchSuggestionType(
QUERIES_SUGGEST(R.string.suggested_queries),
MANGA(R.string.content_type_manga),
SOURCES(R.string.remote_sources),
RECENT_SOURCES(R.string.recent_sources),
AUTHORS(R.string.authors),
}

View File

@@ -59,6 +59,11 @@ class MangaSourcesRepository @Inject constructor(
}
}
suspend fun getTopSources(limit: Int): List<MangaSource> {
assimilateNewSources()
return dao.findLastUsed(limit).toSources(settings.isNsfwContentDisabled, null)
}
suspend fun getDisabledSources(): Set<MangaSource> {
assimilateNewSources()
val result = EnumSet.copyOf(remoteSources)
@@ -242,7 +247,9 @@ class MangaSourcesRepository @Inject constructor(
}
suspend fun trackUsage(source: MangaSource) {
dao.setLastUsed(source.name, System.currentTimeMillis())
if (!settings.isIncognitoModeEnabled && !(settings.isHistoryExcludeNsfw && source.isNsfw())) {
dao.setLastUsed(source.name, System.currentTimeMillis())
}
}
private suspend fun setSourcesEnabledImpl(sources: Collection<MangaSource>, isEnabled: Boolean) {

View File

@@ -125,6 +125,8 @@ class MangaSearchRepository @Inject constructor(
return db.getTagsDao().findRareTags(source.name, limit).toMangaTagsList()
}
suspend fun getSourcesSuggestion(limit: Int): List<MangaSource> = sourcesRepository.getTopSources(limit)
fun getSourcesSuggestion(query: String, limit: Int): List<MangaSource> {
if (query.length < 3) {
return emptyList()

View File

@@ -37,6 +37,7 @@ private const val MAX_HINTS_ITEMS = 3
private const val MAX_AUTHORS_ITEMS = 2
private const val MAX_TAGS_ITEMS = 8
private const val MAX_SOURCES_ITEMS = 6
private const val MAX_SOURCES_TIPS_ITEMS = 2
@HiltViewModel
class SearchSuggestionViewModel @Inject constructor(
@@ -149,12 +150,18 @@ class SearchSuggestionViewModel @Inject constructor(
} else {
null
}
val sourcesTipsDeferred = if (searchQuery.isEmpty() && SearchSuggestionType.RECENT_SOURCES in types) {
async { repository.getSourcesSuggestion(MAX_SOURCES_TIPS_ITEMS) }
} else {
null
}
val tags = tagsDeferred?.await()
val mangaList = mangaDeferred?.await()
val queries = queriesDeferred?.await()
val hints = hintsDeferred?.await()
val authors = authorsDeferred?.await()
val sourcesTips = sourcesTipsDeferred?.await()
buildList(queries.sizeOrZero() + sources.sizeOrZero() + authors.sizeOrZero() + hints.sizeOrZero() + 2) {
if (!tags.isNullOrEmpty()) {
@@ -167,6 +174,7 @@ class SearchSuggestionViewModel @Inject constructor(
queries?.mapTo(this) { SearchSuggestionItem.RecentQuery(it) }
authors?.mapTo(this) { SearchSuggestionItem.Author(it) }
hints?.mapTo(this) { SearchSuggestionItem.Hint(it) }
sourcesTips?.mapTo(this) { SearchSuggestionItem.SourceTip(it) }
}
}

View File

@@ -18,6 +18,7 @@ class SearchSuggestionAdapter(
delegatesManager
.addDelegate(SEARCH_SUGGESTION_ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener))
.addDelegate(searchSuggestionSourceAD(coil, lifecycleOwner, listener))
.addDelegate(searchSuggestionSourceTipAD(coil, lifecycleOwner, listener))
.addDelegate(searchSuggestionTagsAD(listener))
.addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener))
.addDelegate(searchSuggestionQueryHintAD(listener))

View File

@@ -0,0 +1,43 @@
package org.koitharu.kotatsu.search.ui.suggestion.adapter
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.getSummary
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemSearchSuggestionSourceTipBinding
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
fun searchSuggestionSourceTipAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
listener: SearchSuggestionListener,
) =
adapterDelegateViewBinding<SearchSuggestionItem.SourceTip, SearchSuggestionItem, ItemSearchSuggestionSourceTipBinding>(
{ inflater, parent -> ItemSearchSuggestionSourceTipBinding.inflate(inflater, parent, false) },
) {
binding.root.setOnClickListener {
listener.onSourceClick(item.source)
}
bind {
binding.textViewTitle.text = item.source.getTitle(context)
binding.textViewSubtitle.text = item.source.getSummary(context)
val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)
binding.imageViewCover.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run {
fallback(fallbackIcon)
placeholder(fallbackIcon)
error(fallbackIcon)
source(item.source)
enqueueWith(coil)
}
}
}

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.search.ui.suggestion.model
import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -69,6 +70,18 @@ sealed interface SearchSuggestionItem : ListModel {
}
}
data class SourceTip(
val source: MangaSource,
) : SearchSuggestionItem {
val isNsfw: Boolean
get() = source.isNsfw()
override fun areItemsTheSame(other: ListModel): Boolean {
return other is Source && other.source == source
}
}
data class Tags(
val tags: List<ChipsView.ChipModel>,
) : SearchSuggestionItem {

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:orientation="horizontal"
android:paddingVertical="@dimen/margin_small">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="?listPreferredItemPaddingStart"
android:scaleType="centerCrop"
app:shapeAppearance="?shapeAppearanceCornerSmall"
tools:src="@tools:sample/backgrounds/scenic" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="?listPreferredItemPaddingEnd"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/textView_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceTitleSmall"
tools:text="@tools:sample/lorem[2]" />
<TextView
android:id="@+id/textView_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="@tools:sample/lorem[2]" />
</LinearLayout>
</LinearLayout>

View File

@@ -663,4 +663,5 @@
<string name="source_unpinned">Source unpinned</string>
<string name="sources_unpinned">Sources unpinned</string>
<string name="sources_pinned">Sources pinned</string>
<string name="recent_sources">Recent sources</string>
</resources>