diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt index f6cc9e9ec..b6da6d6f6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt @@ -73,6 +73,20 @@ class MangaSearchRepository @Inject constructor( }.orEmpty() } + suspend fun getQueryHintSuggestion( + query: String, + limit: Int, + ): List { + if (query.isEmpty()) { + return emptyList() + } + val titles = db.suggestionDao.getTitles("$query%") + if (titles.isEmpty()) { + return emptyList() + } + return titles.shuffled().take(limit) + } + suspend fun getTagsSuggestion(query: String, limit: Int, source: MangaSource?): List { return when { query.isNotEmpty() && source != null -> db.tagsDao.findTags(source.name, "%$query%", limit) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionFragment.kt index b5436848e..58ec1eac3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionFragment.kt @@ -69,6 +69,11 @@ class SearchSuggestionFragment : viewModel.deleteQuery(query) } + override fun onResume() { + super.onResume() + viewModel.onResume() + } + companion object { fun newInstance() = SearchSuggestionFragment() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt index 46c523bff..8596fbd74 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt @@ -30,6 +30,7 @@ import javax.inject.Inject private const val DEBOUNCE_TIMEOUT = 500L private const val MAX_MANGA_ITEMS = 6 private const val MAX_QUERY_ITEMS = 16 +private const val MAX_HINTS_ITEMS = 3 private const val MAX_TAGS_ITEMS = 8 private const val MAX_SOURCES_ITEMS = 6 @@ -42,6 +43,7 @@ class SearchSuggestionViewModel @Inject constructor( private val query = MutableStateFlow("") private var suggestionJob: Job? = null + private var invalidateOnResume = false val isIncognitoModeEnabled = settings.observeAsStateFlow( scope = viewModelScope + Dispatchers.Default, @@ -60,11 +62,10 @@ class SearchSuggestionViewModel @Inject constructor( } fun saveQuery(query: String) { - launchJob(Dispatchers.Default) { - if (!settings.isIncognitoModeEnabled) { - repository.saveSearchQuery(query) - } + if (!settings.isIncognitoModeEnabled) { + repository.saveSearchQuery(query) } + invalidateOnResume = true } fun clearSearchHistory() { @@ -80,6 +81,13 @@ class SearchSuggestionViewModel @Inject constructor( } } + fun onResume() { + if (invalidateOnResume) { + invalidateOnResume = false + setupSuggestion() + } + } + fun deleteQuery(query: String) { launchJob { repository.deleteSearchQuery(query) @@ -108,6 +116,9 @@ class SearchSuggestionViewModel @Inject constructor( val queriesDeferred = async { repository.getQuerySuggestion(searchQuery, MAX_QUERY_ITEMS) } + val hintsDeferred = async { + repository.getQueryHintSuggestion(searchQuery, MAX_HINTS_ITEMS) + } val tagsDeferred = async { repository.getTagsSuggestion(searchQuery, MAX_TAGS_ITEMS, null) } @@ -119,16 +130,18 @@ class SearchSuggestionViewModel @Inject constructor( val tags = tagsDeferred.await() val mangaList = mangaDeferred.await() val queries = queriesDeferred.await() + val hints = hintsDeferred.await() - buildList(queries.size + sources.size + 2) { + buildList(queries.size + sources.size + hints.size + 2) { if (tags.isNotEmpty()) { add(SearchSuggestionItem.Tags(mapTags(tags))) } if (mangaList.isNotEmpty()) { add(SearchSuggestionItem.MangaList(mangaList)) } - queries.mapTo(this) { SearchSuggestionItem.RecentQuery(it) } sources.mapTo(this) { SearchSuggestionItem.Source(it, it in enabledSources) } + queries.mapTo(this) { SearchSuggestionItem.RecentQuery(it) } + hints.mapTo(this) { SearchSuggestionItem.Hint(it) } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt index bd255e3cd..3140500b2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt @@ -20,5 +20,6 @@ class SearchSuggestionAdapter( .addDelegate(searchSuggestionSourceAD(coil, lifecycleOwner, listener)) .addDelegate(searchSuggestionTagsAD(listener)) .addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener)) + .addDelegate(searchSuggestionQueryHintAD(listener)) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionQueryHintAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionQueryHintAD.kt new file mode 100644 index 000000000..72df703f7 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionQueryHintAD.kt @@ -0,0 +1,24 @@ +package org.koitharu.kotatsu.search.ui.suggestion.adapter + +import android.view.View +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding +import org.koitharu.kotatsu.databinding.ItemSearchSuggestionQueryHintBinding +import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener +import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem + +fun searchSuggestionQueryHintAD( + listener: SearchSuggestionListener, +) = adapterDelegateViewBinding( + { inflater, parent -> ItemSearchSuggestionQueryHintBinding.inflate(inflater, parent, false) }, +) { + + val viewClickListener = View.OnClickListener { v -> + listener.onQueryClick(item.query, true) + } + + binding.root.setOnClickListener(viewClickListener) + + bind { + binding.root.text = item.query + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt index 9bf285e5d..f7b83531c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt @@ -27,6 +27,15 @@ sealed interface SearchSuggestionItem : ListModel { } } + data class Hint( + val query: String, + ) : SearchSuggestionItem { + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is Hint && query == other.query + } + } + data class Source( val source: MangaSource, val isEnabled: Boolean, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/data/SuggestionDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/data/SuggestionDao.kt index 4c55cd299..a71ca9e9b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/data/SuggestionDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/data/SuggestionDao.kt @@ -26,6 +26,9 @@ abstract class SuggestionDao { @Query("SELECT COUNT(*) FROM suggestions") abstract suspend fun count(): Int + @Query("SELECT manga.title FROM suggestions LEFT JOIN manga ON suggestions.manga_id = manga.manga_id WHERE manga.title LIKE :query") + abstract suspend fun getTitles(query: String): List + @Insert(onConflict = OnConflictStrategy.IGNORE) abstract suspend fun insert(entity: SuggestionEntity): Long diff --git a/app/src/main/res/layout/item_search_suggestion_query.xml b/app/src/main/res/layout/item_search_suggestion_query.xml index 6219e06fa..2cb2a1baf 100644 --- a/app/src/main/res/layout/item_search_suggestion_query.xml +++ b/app/src/main/res/layout/item_search_suggestion_query.xml @@ -8,6 +8,7 @@ android:background="?selectableItemBackground" android:gravity="center_vertical" android:minHeight="?listPreferredItemHeightSmall" + android:orientation="horizontal" android:paddingStart="?listPreferredItemPaddingStart" android:paddingEnd="?listPreferredItemPaddingEnd"> @@ -24,6 +25,12 @@ app:drawableStartCompat="@drawable/ic_history" tools:text="@tools:sample/lorem[6]" /> + + - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_search_suggestion_query_hint.xml b/app/src/main/res/layout/item_search_suggestion_query_hint.xml new file mode 100644 index 000000000..e38796b7d --- /dev/null +++ b/app/src/main/res/layout/item_search_suggestion_query_hint.xml @@ -0,0 +1,19 @@ + + diff --git a/app/src/main/res/layout/item_search_suggestion_tags.xml b/app/src/main/res/layout/item_search_suggestion_tags.xml index 43fb47666..4fe329755 100644 --- a/app/src/main/res/layout/item_search_suggestion_tags.xml +++ b/app/src/main/res/layout/item_search_suggestion_tags.xml @@ -4,8 +4,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingVertical="4dp" android:paddingStart="?listPreferredItemPaddingStart" + android:paddingTop="4dp" android:paddingEnd="?listPreferredItemPaddingEnd" + android:paddingBottom="6dp" app:chipSpacingHorizontal="6dp" app:chipSpacingVertical="6dp" />