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")
|
@Query("SELECT * FROM sources WHERE added_in >= :version")
|
||||||
abstract suspend fun findAllFromVersion(version: Int): List<MangaSourceEntity>
|
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")
|
@Query("SELECT * FROM sources ORDER BY pinned DESC, sort_key")
|
||||||
abstract fun observeAll(): Flow<List<MangaSourceEntity>>
|
abstract fun observeAll(): Flow<List<MangaSourceEntity>>
|
||||||
|
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ enum class SearchSuggestionType(
|
|||||||
QUERIES_SUGGEST(R.string.suggested_queries),
|
QUERIES_SUGGEST(R.string.suggested_queries),
|
||||||
MANGA(R.string.content_type_manga),
|
MANGA(R.string.content_type_manga),
|
||||||
SOURCES(R.string.remote_sources),
|
SOURCES(R.string.remote_sources),
|
||||||
|
RECENT_SOURCES(R.string.recent_sources),
|
||||||
AUTHORS(R.string.authors),
|
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> {
|
suspend fun getDisabledSources(): Set<MangaSource> {
|
||||||
assimilateNewSources()
|
assimilateNewSources()
|
||||||
val result = EnumSet.copyOf(remoteSources)
|
val result = EnumSet.copyOf(remoteSources)
|
||||||
@@ -242,7 +247,9 @@ class MangaSourcesRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun trackUsage(source: MangaSource) {
|
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) {
|
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()
|
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> {
|
fun getSourcesSuggestion(query: String, limit: Int): List<MangaSource> {
|
||||||
if (query.length < 3) {
|
if (query.length < 3) {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ private const val MAX_HINTS_ITEMS = 3
|
|||||||
private const val MAX_AUTHORS_ITEMS = 2
|
private const val MAX_AUTHORS_ITEMS = 2
|
||||||
private const val MAX_TAGS_ITEMS = 8
|
private const val MAX_TAGS_ITEMS = 8
|
||||||
private const val MAX_SOURCES_ITEMS = 6
|
private const val MAX_SOURCES_ITEMS = 6
|
||||||
|
private const val MAX_SOURCES_TIPS_ITEMS = 2
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SearchSuggestionViewModel @Inject constructor(
|
class SearchSuggestionViewModel @Inject constructor(
|
||||||
@@ -149,12 +150,18 @@ class SearchSuggestionViewModel @Inject constructor(
|
|||||||
} else {
|
} else {
|
||||||
null
|
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 tags = tagsDeferred?.await()
|
||||||
val mangaList = mangaDeferred?.await()
|
val mangaList = mangaDeferred?.await()
|
||||||
val queries = queriesDeferred?.await()
|
val queries = queriesDeferred?.await()
|
||||||
val hints = hintsDeferred?.await()
|
val hints = hintsDeferred?.await()
|
||||||
val authors = authorsDeferred?.await()
|
val authors = authorsDeferred?.await()
|
||||||
|
val sourcesTips = sourcesTipsDeferred?.await()
|
||||||
|
|
||||||
buildList(queries.sizeOrZero() + sources.sizeOrZero() + authors.sizeOrZero() + hints.sizeOrZero() + 2) {
|
buildList(queries.sizeOrZero() + sources.sizeOrZero() + authors.sizeOrZero() + hints.sizeOrZero() + 2) {
|
||||||
if (!tags.isNullOrEmpty()) {
|
if (!tags.isNullOrEmpty()) {
|
||||||
@@ -167,6 +174,7 @@ class SearchSuggestionViewModel @Inject constructor(
|
|||||||
queries?.mapTo(this) { SearchSuggestionItem.RecentQuery(it) }
|
queries?.mapTo(this) { SearchSuggestionItem.RecentQuery(it) }
|
||||||
authors?.mapTo(this) { SearchSuggestionItem.Author(it) }
|
authors?.mapTo(this) { SearchSuggestionItem.Author(it) }
|
||||||
hints?.mapTo(this) { SearchSuggestionItem.Hint(it) }
|
hints?.mapTo(this) { SearchSuggestionItem.Hint(it) }
|
||||||
|
sourcesTips?.mapTo(this) { SearchSuggestionItem.SourceTip(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class SearchSuggestionAdapter(
|
|||||||
delegatesManager
|
delegatesManager
|
||||||
.addDelegate(SEARCH_SUGGESTION_ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener))
|
.addDelegate(SEARCH_SUGGESTION_ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener))
|
||||||
.addDelegate(searchSuggestionSourceAD(coil, lifecycleOwner, listener))
|
.addDelegate(searchSuggestionSourceAD(coil, lifecycleOwner, listener))
|
||||||
|
.addDelegate(searchSuggestionSourceTipAD(coil, lifecycleOwner, listener))
|
||||||
.addDelegate(searchSuggestionTagsAD(listener))
|
.addDelegate(searchSuggestionTagsAD(listener))
|
||||||
.addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener))
|
.addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener))
|
||||||
.addDelegate(searchSuggestionQueryHintAD(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
|
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.core.ui.widgets.ChipsView
|
||||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
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(
|
data class Tags(
|
||||||
val tags: List<ChipsView.ChipModel>,
|
val tags: List<ChipsView.ChipModel>,
|
||||||
) : SearchSuggestionItem {
|
) : 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="source_unpinned">Source unpinned</string>
|
||||||
<string name="sources_unpinned">Sources unpinned</string>
|
<string name="sources_unpinned">Sources unpinned</string>
|
||||||
<string name="sources_pinned">Sources pinned</string>
|
<string name="sources_pinned">Sources pinned</string>
|
||||||
|
<string name="recent_sources">Recent sources</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user