Suggest tags on search

This commit is contained in:
Koitharu
2022-03-07 15:04:03 +02:00
parent 445ff89392
commit 3802bc146f
22 changed files with 218 additions and 50 deletions

View File

@@ -50,6 +50,8 @@
<activity <activity
android:name="org.koitharu.kotatsu.search.ui.SearchActivity" android:name="org.koitharu.kotatsu.search.ui.SearchActivity"
android:label="@string/search" /> android:label="@string/search" />
<activity android:name="org.koitharu.kotatsu.search.ui.MangaListActivity"
android:label="@string/search_manga" />
<activity <activity
android:name="org.koitharu.kotatsu.settings.SettingsActivity" android:name="org.koitharu.kotatsu.settings.SettingsActivity"
android:label="@string/settings" /> android:label="@string/settings" />

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.base.ui package org.koitharu.kotatsu.base.ui
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@@ -46,15 +45,6 @@ abstract class BaseFragment<B : ViewBinding> : Fragment(), OnApplyWindowInsetsLi
super.onDestroyView() super.onDestroyView()
} }
open fun getTitle(): CharSequence? = null
override fun onAttach(context: Context) {
super.onAttach(context)
getTitle()?.let {
activity?.title = it
}
}
override fun onApplyWindowInsets(v: View?, insets: WindowInsetsCompat): WindowInsetsCompat { override fun onApplyWindowInsets(v: View?, insets: WindowInsetsCompat): WindowInsetsCompat {
val newInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val newInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
if (newInsets != lastInsets) { if (newInsets != lastInsets) {

View File

@@ -80,6 +80,7 @@ class ChipsView @JvmOverloads constructor(
chip.setOnCloseIconClickListener(chipOnCloseListener) chip.setOnCloseIconClickListener(chipOnCloseListener)
chip.setEnsureMinTouchTargetSize(false) chip.setEnsureMinTouchTargetSize(false)
chip.setOnClickListener(chipOnClickListener) chip.setOnClickListener(chipOnClickListener)
chip.isCheckable = false
addView(chip) addView(chip)
return chip return chip
} }

View File

@@ -9,6 +9,45 @@ abstract class TagsDao {
@Query("SELECT * FROM tags WHERE source = :source") @Query("SELECT * FROM tags WHERE source = :source")
abstract suspend fun findTags(source: String): List<TagEntity> abstract suspend fun findTags(source: String): List<TagEntity>
@Query(
"""SELECT tags.* FROM tags
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id
GROUP BY manga_tags.tag_id
ORDER BY COUNT(manga_id) DESC
LIMIT :limit"""
)
abstract suspend fun findPopularTags(limit: Int): List<TagEntity>
@Query(
"""SELECT tags.* FROM tags
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id
WHERE tags.source = :source
GROUP BY manga_tags.tag_id
ORDER BY COUNT(manga_id) DESC
LIMIT :limit"""
)
abstract suspend fun findPopularTags(source: String, limit: Int): List<TagEntity>
@Query(
"""SELECT tags.* FROM tags
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id
WHERE tags.source = :source AND title LIKE :query
GROUP BY manga_tags.tag_id
ORDER BY COUNT(manga_id) DESC
LIMIT :limit"""
)
abstract suspend fun findTags(source: String, query: String, limit: Int): List<TagEntity>
@Query(
"""SELECT tags.* FROM tags
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id
WHERE title LIKE :query
GROUP BY manga_tags.tag_id
ORDER BY COUNT(manga_id) DESC
LIMIT :limit"""
)
abstract suspend fun findTags(query: String, limit: Int): List<TagEntity>
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
abstract suspend fun insert(tag: TagEntity): Long abstract suspend fun insert(tag: TagEntity): Long

View File

@@ -16,27 +16,26 @@ import androidx.core.view.updatePadding
import coil.ImageLoader import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.util.CoilUtils import coil.util.CoilUtils
import com.google.android.material.chip.Chip
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.MangaState
import org.koitharu.kotatsu.databinding.FragmentDetailsBinding import org.koitharu.kotatsu.databinding.FragmentDetailsBinding
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesDialog import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesDialog
import org.koitharu.kotatsu.image.ui.ImageActivity import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.utils.FileSize import org.koitharu.kotatsu.utils.FileSize
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickListener, class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickListener,
View.OnLongClickListener { View.OnLongClickListener, ChipsView.OnChipClickListener {
private val viewModel by sharedViewModel<DetailsViewModel>() private val viewModel by sharedViewModel<DetailsViewModel>()
private val coil by inject<ImageLoader>(mode = LazyThreadSafetyMode.NONE) private val coil by inject<ImageLoader>(mode = LazyThreadSafetyMode.NONE)
@@ -54,6 +53,7 @@ class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickList
binding.buttonRead.setOnLongClickListener(this) binding.buttonRead.setOnLongClickListener(this)
binding.imageViewCover.setOnClickListener(this) binding.imageViewCover.setOnClickListener(this)
binding.textViewDescription.movementMethod = LinkMovementMethod.getInstance() binding.textViewDescription.movementMethod = LinkMovementMethod.getInstance()
binding.chipsTags.onChipClickListener = this
viewModel.manga.observe(viewLifecycleOwner, ::onMangaUpdated) viewModel.manga.observe(viewLifecycleOwner, ::onMangaUpdated)
viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged)
viewModel.favouriteCategories.observe(viewLifecycleOwner, ::onFavouriteChanged) viewModel.favouriteCategories.observe(viewLifecycleOwner, ::onFavouriteChanged)
@@ -231,6 +231,11 @@ class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickList
} }
} }
override fun onChipClick(chip: Chip, data: Any?) {
val tag = data as? MangaTag ?: return
startActivity(MangaListActivity.newIntent(requireContext(), tag))
}
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
binding.root.updatePadding( binding.root.updatePadding(
left = insets.left, left = insets.left,
@@ -244,7 +249,8 @@ class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickList
manga.tags.map { tag -> manga.tags.map { tag ->
ChipsView.ChipModel( ChipsView.ChipModel(
title = tag.title, title = tag.title,
icon = 0 icon = 0,
data = tag,
) )
} }
) )

View File

@@ -103,10 +103,6 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun getTitle(): CharSequence? {
return context?.getString(R.string.favourites)
}
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
Snackbar.make(binding.pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show() Snackbar.make(binding.pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
} }

View File

@@ -59,10 +59,6 @@ class HistoryListFragment : MangaListFragment() {
} }
} }
override fun getTitle(): CharSequence? {
return context?.getString(R.string.history)
}
override fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) { override fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) {
super.onCreatePopupMenu(inflater, menu, data) super.onCreatePopupMenu(inflater, menu, data)
inflater.inflate(R.menu.popup_history, menu) inflater.inflate(R.menu.popup_history, menu)

View File

@@ -92,10 +92,6 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback<List<@JvmS
} }
} }
override fun getTitle(): CharSequence? {
return context?.getString(R.string.local_storage)
}
override fun onActivityResult(result: List<@JvmSuppressWildcards Uri>) { override fun onActivityResult(result: List<@JvmSuppressWildcards Uri>) {
if (result.isEmpty()) return if (result.isEmpty()) return
viewModel.importFiles(result) viewModel.importFiles(result)

View File

@@ -31,6 +31,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.prefs.AppSection import org.koitharu.kotatsu.core.prefs.AppSection
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ActivityMainBinding import org.koitharu.kotatsu.databinding.ActivityMainBinding
@@ -42,6 +43,7 @@ import org.koitharu.kotatsu.local.ui.LocalListFragment
import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
@@ -258,6 +260,12 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
} }
} }
override fun onTagClick(tag: MangaTag) {
startActivity(
MangaListActivity.newIntent(this, tag)
)
}
override fun onQueryChanged(query: String) { override fun onQueryChanged(query: String) {
searchSuggestionViewModel.onQueryChanged(query) searchSuggestionViewModel.onQueryChanged(query)
} }

View File

@@ -25,10 +25,6 @@ class RemoteListFragment : MangaListFragment() {
viewModel.loadNextPage() viewModel.loadNextPage()
} }
override fun getTitle(): CharSequence {
return source.title
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.opt_list_remote, menu) inflater.inflate(R.menu.opt_list_remote, menu)

View File

@@ -12,6 +12,7 @@ import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -79,6 +80,17 @@ class MangaSearchRepository(
}.orEmpty() }.orEmpty()
} }
suspend fun getTagsSuggestion(query: String, limit: Int, source: MangaSource?): List<MangaTag> {
return when {
query.isNotEmpty() && source != null -> db.tagsDao.findTags(source.name, "%$query%", limit)
query.isNotEmpty() -> db.tagsDao.findTags("%$query%", limit)
source != null -> db.tagsDao.findTags(source.name, limit)
else -> db.tagsDao.findPopularTags(limit)
}.map {
it.toMangaTag()
}
}
fun saveSearchQuery(query: String) { fun saveSearchQuery(query: String) {
recentSuggestions.saveRecentQuery(query, null) recentSuggestions.saveRecentQuery(query, null)
} }

View File

@@ -0,0 +1,78 @@
package org.koitharu.kotatsu.search.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.ViewGroup
import androidx.core.graphics.Insets
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import org.koin.androidx.viewmodel.ext.android.getViewModel
import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.databinding.ActivitySearchGlobalBinding
import org.koitharu.kotatsu.list.ui.filter.FilterState
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel
class MangaListActivity : BaseActivity<ActivitySearchGlobalBinding>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivitySearchGlobalBinding.inflate(layoutInflater))
val tag = intent.getParcelableExtra<MangaTag>(EXTRA_TAG) ?: run {
finishAfterTransition()
return
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val fm = supportFragmentManager
if (fm.findFragmentById(R.id.container) == null) {
fm.commit {
val fragment = RemoteListFragment.newInstance(tag.source)
replace(R.id.container, fragment)
runOnCommit(ApplyFilterRunnable(fragment, tag))
}
}
}
override fun onWindowInsetsChanged(insets: Insets) {
with(binding.toolbar) {
updatePadding(
left = insets.left,
right = insets.right
)
updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = insets.top
}
}
binding.container.updatePadding(
bottom = insets.bottom
)
}
private class ApplyFilterRunnable(
private val fragment: Fragment,
private val tag: MangaTag,
) : Runnable {
override fun run() {
val viewModel = fragment.getViewModel<RemoteListViewModel> {
parametersOf(tag.source)
}
viewModel.applyFilter(FilterState(viewModel.filter.sortOrder, setOf(tag)))
}
}
companion object {
private const val EXTRA_TAG = "tag"
fun newIntent(context: Context, tag: MangaTag) =
Intent(context, MangaListActivity::class.java)
.putExtra(EXTRA_TAG, tag)
}
}

View File

@@ -21,8 +21,6 @@ class SearchFragment : MangaListFragment() {
viewModel.loadNextPage() viewModel.loadNextPage()
} }
override fun getTitle() = query
companion object { companion object {
private const val ARG_QUERY = "query" private const val ARG_QUERY = "query"

View File

@@ -17,10 +17,6 @@ class GlobalSearchFragment : MangaListFragment() {
override fun onScrolledToEnd() = Unit override fun onScrolledToEnd() = Unit
override fun getTitle(): CharSequence? {
return query
}
companion object { companion object {
private const val ARG_QUERY = "query" private const val ARG_QUERY = "query"

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.search.ui.suggestion package org.koitharu.kotatsu.search.ui.suggestion
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaTag
interface SearchSuggestionListener { interface SearchSuggestionListener {
@@ -11,4 +12,6 @@ interface SearchSuggestionListener {
fun onQueryChanged(query: String) fun onQueryChanged(query: String)
fun onClearSearchHistory() fun onClearSearchHistory()
fun onTagClick(tag: MangaTag)
} }

View File

@@ -7,15 +7,18 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.search.domain.MangaSearchRepository import org.koitharu.kotatsu.search.domain.MangaSearchRepository
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
private const val DEBOUNCE_TIMEOUT = 500L private const val DEBOUNCE_TIMEOUT = 500L
private const val SEARCH_THRESHOLD = 3 private const val SEARCH_THRESHOLD = 3
private const val MAX_MANGA_ITEMS = 3 private const val MAX_MANGA_ITEMS = 3
private const val MAX_QUERY_ITEMS = 120 private const val MAX_QUERY_ITEMS = 16
private const val MAX_SUGGESTION_ITEMS = MAX_MANGA_ITEMS + MAX_QUERY_ITEMS + 1 private const val MAX_TAGS_ITEMS = 8
private const val MAX_SUGGESTION_ITEMS = MAX_MANGA_ITEMS + MAX_QUERY_ITEMS + 2
class SearchSuggestionViewModel( class SearchSuggestionViewModel(
private val repository: MangaSearchRepository, private val repository: MangaSearchRepository,
@@ -77,6 +80,10 @@ class SearchSuggestionViewModel(
if (src != null) { if (src != null) {
result += SearchSuggestionItem.Header(src, isLocalSearch) result += SearchSuggestionItem.Header(src, isLocalSearch)
} }
val tags = repository.getTagsSuggestion(q, MAX_TAGS_ITEMS, src.takeIf { srcOnly })
if (tags.isNotEmpty()) {
result.add(SearchSuggestionItem.Tags(mapTags(tags)))
}
if (q.length >= SEARCH_THRESHOLD) { if (q.length >= SEARCH_THRESHOLD) {
repository.getMangaSuggestion(q, MAX_MANGA_ITEMS, src.takeIf { srcOnly }) repository.getMangaSuggestion(q, MAX_MANGA_ITEMS, src.takeIf { srcOnly })
.mapTo(result) { .mapTo(result) {
@@ -89,4 +96,12 @@ class SearchSuggestionViewModel(
suggestion.postValue(it) suggestion.postValue(it)
}.launchIn(viewModelScope + Dispatchers.Default) }.launchIn(viewModelScope + Dispatchers.Default)
} }
private fun mapTags(tags: List<MangaTag>): List<ChipsView.ChipModel> = tags.map { tag ->
ChipsView.ChipModel(
icon = 0,
title = tag.title,
data = tag,
)
}
} }

View File

@@ -18,6 +18,7 @@ class SearchSuggestionAdapter(
delegatesManager.addDelegate(ITEM_TYPE_MANGA, searchSuggestionMangaAD(coil, lifecycleOwner, listener)) delegatesManager.addDelegate(ITEM_TYPE_MANGA, searchSuggestionMangaAD(coil, lifecycleOwner, listener))
.addDelegate(ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener)) .addDelegate(ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener))
.addDelegate(ITEM_TYPE_HEADER, searchSuggestionHeaderAD(listener)) .addDelegate(ITEM_TYPE_HEADER, searchSuggestionHeaderAD(listener))
.addDelegate(ITEM_TYPE_TAGS, searchSuggestionTagsAD(listener))
} }
private class DiffCallback : DiffUtil.ItemCallback<SearchSuggestionItem>() { private class DiffCallback : DiffUtil.ItemCallback<SearchSuggestionItem>() {
@@ -33,6 +34,7 @@ class SearchSuggestionAdapter(
oldItem.query == newItem.query oldItem.query == newItem.query
} }
oldItem is SearchSuggestionItem.Header && newItem is SearchSuggestionItem.Header -> true oldItem is SearchSuggestionItem.Header && newItem is SearchSuggestionItem.Header -> true
oldItem is SearchSuggestionItem.Tags && newItem is SearchSuggestionItem.Tags -> true
else -> false else -> false
} }
@@ -47,5 +49,6 @@ class SearchSuggestionAdapter(
const val ITEM_TYPE_MANGA = 0 const val ITEM_TYPE_MANGA = 0
const val ITEM_TYPE_QUERY = 1 const val ITEM_TYPE_QUERY = 1
const val ITEM_TYPE_HEADER = 2 const val ITEM_TYPE_HEADER = 2
const val ITEM_TYPE_TAGS = 3
} }
} }

View File

@@ -0,0 +1,23 @@
package org.koitharu.kotatsu.search.ui.suggestion.adapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
fun searchSuggestionTagsAD(
listener: SearchSuggestionListener,
) = adapterDelegate<SearchSuggestionItem.Tags, SearchSuggestionItem>(R.layout.item_search_suggestion_tags) {
val chipGroup = itemView as ChipsView
chipGroup.onChipClickListener = ChipsView.OnChipClickListener { _, data ->
listener.onTagClick(data as? MangaTag ?: return@OnChipClickListener)
}
bind {
chipGroup.setChips(item.tags)
}
}

View File

@@ -1,21 +1,26 @@
package org.koitharu.kotatsu.search.ui.suggestion.model package org.koitharu.kotatsu.search.ui.suggestion.model
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
sealed class SearchSuggestionItem { sealed interface SearchSuggestionItem {
data class MangaItem( data class MangaItem(
val manga: Manga, val manga: Manga,
) : SearchSuggestionItem() ) : SearchSuggestionItem
data class RecentQuery( data class RecentQuery(
val query: String, val query: String,
) : SearchSuggestionItem() ) : SearchSuggestionItem
data class Header( data class Header(
val source: MangaSource, val source: MangaSource,
val isChecked: MutableStateFlow<Boolean>, val isChecked: MutableStateFlow<Boolean>,
) : SearchSuggestionItem() ) : SearchSuggestionItem
data class Tags(
val tags: List<ChipsView.ChipModel>,
) : SearchSuggestionItem
} }

View File

@@ -46,10 +46,6 @@ class SuggestionsFragment : MangaListFragment() {
override fun onScrolledToEnd() = Unit override fun onScrolledToEnd() = Unit
override fun getTitle(): CharSequence? {
return context?.getString(R.string.suggestions)
}
companion object { companion object {
fun newInstance() = SuggestionsFragment() fun newInstance() = SuggestionsFragment()

View File

@@ -35,8 +35,6 @@ class FeedFragment : BaseFragment<FragmentFeedBinding>(), PaginationScrollListen
private var paddingVertical = 0 private var paddingVertical = 0
private var paddingHorizontal = 0 private var paddingHorizontal = 0
override fun getTitle() = context?.getString(R.string.updates)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd"
android:paddingVertical="4dp"
app:chipSpacingHorizontal="6dp"
app:chipSpacingVertical="6dp" />