Add recent sources to search suggestions
This commit is contained in:
@@ -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>>
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user