diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cb22f131e..d3762ec2a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -50,6 +50,8 @@
+
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt
index f7b90fa01..68b1ffe42 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt
@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.base.ui
-import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -53,15 +52,6 @@ abstract class BaseFragment : Fragment(), OnApplyWindowInsetsLi
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 {
val newInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
if (newInsets != lastInsets) {
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt
index 7d8730c33..c20b615cf 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt
@@ -80,6 +80,7 @@ class ChipsView @JvmOverloads constructor(
chip.setOnCloseIconClickListener(chipOnCloseListener)
chip.setEnsureMinTouchTargetSize(false)
chip.setOnClickListener(chipOnClickListener)
+ chip.isCheckable = false
addView(chip)
return chip
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt
index 7f9655d19..c31307a24 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt
@@ -9,6 +9,45 @@ abstract class TagsDao {
@Query("SELECT * FROM tags WHERE source = :source")
abstract suspend fun findTags(source: String): List
+ @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
+
+ @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
+
+ @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
+
+ @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
+
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract suspend fun insert(tag: TagEntity): Long
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt
index 0b271ef64..f0da9fbd3 100644
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt
@@ -16,27 +16,26 @@ import androidx.core.view.updatePadding
import coil.ImageLoader
import coil.request.ImageRequest
import coil.util.CoilUtils
+import com.google.android.material.chip.Chip
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
-import org.koitharu.kotatsu.core.model.Manga
-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.core.model.*
import org.koitharu.kotatsu.databinding.FragmentDetailsBinding
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesDialog
import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.reader.ui.ReaderActivity
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.utils.FileSize
import org.koitharu.kotatsu.utils.ext.*
class DetailsFragment : BaseFragment(), View.OnClickListener,
- View.OnLongClickListener {
+ View.OnLongClickListener, ChipsView.OnChipClickListener {
private val viewModel by sharedViewModel()
private val coil by inject(mode = LazyThreadSafetyMode.NONE)
@@ -54,6 +53,7 @@ class DetailsFragment : BaseFragment(), View.OnClickList
binding.buttonRead.setOnLongClickListener(this)
binding.imageViewCover.setOnClickListener(this)
binding.textViewDescription.movementMethod = LinkMovementMethod.getInstance()
+ binding.chipsTags.onChipClickListener = this
viewModel.manga.observe(viewLifecycleOwner, ::onMangaUpdated)
viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged)
viewModel.favouriteCategories.observe(viewLifecycleOwner, ::onFavouriteChanged)
@@ -231,6 +231,11 @@ class DetailsFragment : BaseFragment(), 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) {
binding.root.updatePadding(
bottom = insets.bottom,
@@ -242,7 +247,8 @@ class DetailsFragment : BaseFragment(), View.OnClickList
manga.tags.map { tag ->
ChipsView.ChipModel(
title = tag.title,
- icon = 0
+ icon = 0,
+ data = tag,
)
}
)
diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt
index 096d26cfd..edc8c8519 100644
--- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt
@@ -103,10 +103,6 @@ class FavouritesContainerFragment : BaseFragment(),
else -> super.onOptionsItemSelected(item)
}
- override fun getTitle(): CharSequence? {
- return context?.getString(R.string.favourites)
- }
-
private fun onError(e: Throwable) {
Snackbar.make(binding.pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt
index 550fab1a5..8b5aaa9ec 100644
--- a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt
@@ -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) {
super.onCreatePopupMenu(inflater, menu, data)
inflater.inflate(R.menu.popup_history, menu)
diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt
index 8e6075362..bbcd57ad5 100644
--- a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt
@@ -59,6 +59,12 @@ class FilterCoordinator(
}
}
+ fun setTags(tags: Set) {
+ currentState.update { oldValue ->
+ FilterState(oldValue.sortOrder, tags)
+ }
+ }
+
fun reset() {
currentState.update { oldValue ->
FilterState(oldValue.sortOrder, emptySet())
diff --git a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt
index e30120259..35c8e8986 100644
--- a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt
@@ -92,10 +92,6 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback) {
if (result.isEmpty()) return
viewModel.importFiles(result)
diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt
index b8d8cb999..b3ea38df9 100644
--- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt
@@ -33,6 +33,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.Manga
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.AppSettings
import org.koitharu.kotatsu.databinding.ActivityMainBinding
@@ -43,6 +44,7 @@ import org.koitharu.kotatsu.history.ui.HistoryListFragment
import org.koitharu.kotatsu.local.ui.LocalListFragment
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
+import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
@@ -261,6 +263,12 @@ class MainActivity : BaseActivity(),
}
}
+ override fun onTagClick(tag: MangaTag) {
+ startActivity(
+ MangaListActivity.newIntent(this, tag)
+ )
+ }
+
override fun onQueryChanged(query: String) {
searchSuggestionViewModel.onQueryChanged(query)
}
@@ -353,17 +361,19 @@ class MainActivity : BaseActivity(),
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment, TAG_PRIMARY)
.commit()
- if (fragment is HistoryListFragment) binding.fab.show() else binding.fab.hide()
+ adjustFabVisibility(topFragment = fragment)
}
private fun onSearchOpened() {
drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
drawerToggle?.isDrawerIndicatorEnabled = false
+ adjustFabVisibility(isSearchOpened = true)
}
private fun onSearchClosed() {
drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
drawerToggle?.isDrawerIndicatorEnabled = true
+ adjustFabVisibility(isSearchOpened = false)
}
private fun onFirstStart() {
@@ -378,4 +388,11 @@ class MainActivity : BaseActivity(),
}
}
}
+
+ private fun adjustFabVisibility(
+ topFragment: Fragment? = supportFragmentManager.findFragmentByTag(TAG_PRIMARY),
+ isSearchOpened: Boolean = supportFragmentManager.findFragmentByTag(TAG_SEARCH)?.isVisible == true,
+ ) {
+ if (!isSearchOpened && topFragment is HistoryListFragment) binding.fab.show() else binding.fab.hide()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt
index b1b2867e6..ef918db8e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt
@@ -25,10 +25,6 @@ class RemoteListFragment : MangaListFragment() {
viewModel.loadNextPage()
}
- override fun getTitle(): CharSequence {
- return source.title
- }
-
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.opt_list_remote, menu)
diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
index 025dfd515..36f1bdcca 100644
--- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
@@ -110,6 +110,10 @@ class RemoteListViewModel(
fun resetFilter() = filter.reset()
+ fun applyFilter(tags: Set) {
+ filter.setTags(tags)
+ }
+
private fun loadList(filterState: FilterState, append: Boolean) {
if (loadingJob?.isActive == true) {
return
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt
index fc1f293c9..8027505f3 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt
@@ -12,6 +12,7 @@ import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.model.Manga
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.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -79,6 +80,17 @@ class MangaSearchRepository(
}.orEmpty()
}
+ suspend fun getTagsSuggestion(query: String, limit: Int, source: MangaSource?): List {
+ 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) {
recentSuggestions.saveRecentQuery(query, null)
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt
new file mode 100644
index 000000000..eb85bc179
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt
@@ -0,0 +1,77 @@
+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.remotelist.ui.RemoteListFragment
+import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel
+
+class MangaListActivity : BaseActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(ActivitySearchGlobalBinding.inflate(layoutInflater))
+ val tag = intent.getParcelableExtra(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 {
+ 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 {
+ parametersOf(tag.source)
+ }
+ viewModel.applyFilter(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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt
index 6f2621475..b74bd5d12 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt
@@ -21,8 +21,6 @@ class SearchFragment : MangaListFragment() {
viewModel.loadNextPage()
}
- override fun getTitle() = query
-
companion object {
private const val ARG_QUERY = "query"
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt
index 2f6ca1ae3..81fc4491a 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt
@@ -17,10 +17,6 @@ class GlobalSearchFragment : MangaListFragment() {
override fun onScrolledToEnd() = Unit
- override fun getTitle(): CharSequence? {
- return query
- }
-
companion object {
private const val ARG_QUERY = "query"
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt
index e983be92b..64e53dc6b 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.search.ui.suggestion
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
-import org.koitharu.kotatsu.search.ui.suggestion.adapter.SearchSuggestionAdapter
+import org.koitharu.kotatsu.search.ui.suggestion.adapter.SEARCH_SUGGESTION_ITEM_TYPE_QUERY
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
import org.koitharu.kotatsu.utils.ext.getItem
@@ -18,7 +18,7 @@ class SearchSuggestionItemCallback(
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
- ): Int = if (viewHolder.itemViewType == SearchSuggestionAdapter.ITEM_TYPE_QUERY) {
+ ): Int = if (viewHolder.itemViewType == SEARCH_SUGGESTION_ITEM_TYPE_QUERY) {
movementFlags
} else {
0
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt
index d82a47bb0..f6b0c718d 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt
@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.search.ui.suggestion
import org.koitharu.kotatsu.core.model.Manga
+import org.koitharu.kotatsu.core.model.MangaTag
interface SearchSuggestionListener {
@@ -11,4 +12,6 @@ interface SearchSuggestionListener {
fun onQueryChanged(query: String)
fun onClearSearchHistory()
+
+ fun onTagClick(tag: MangaTag)
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt
index 4317d5bdb..02e7f2856 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt
@@ -2,20 +2,19 @@ package org.koitharu.kotatsu.search.ui.suggestion
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
+import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
-import kotlinx.coroutines.plus
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.MangaTag
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
private const val DEBOUNCE_TIMEOUT = 500L
-private const val SEARCH_THRESHOLD = 3
-private const val MAX_MANGA_ITEMS = 3
-private const val MAX_QUERY_ITEMS = 120
-private const val MAX_SUGGESTION_ITEMS = MAX_MANGA_ITEMS + MAX_QUERY_ITEMS + 1
+private const val MAX_MANGA_ITEMS = 6
+private const val MAX_QUERY_ITEMS = 16
+private const val MAX_TAGS_ITEMS = 8
class SearchSuggestionViewModel(
private val repository: MangaSearchRepository,
@@ -65,28 +64,56 @@ class SearchSuggestionViewModel(
private fun setupSuggestion() {
suggestionJob?.cancel()
suggestionJob = combine(
- query
- .debounce(DEBOUNCE_TIMEOUT)
- .mapLatest { q ->
- q to repository.getQuerySuggestion(q, MAX_QUERY_ITEMS)
- },
+ query.debounce(DEBOUNCE_TIMEOUT),
source,
- isLocalSearch
- ) { (q, queries), src, srcOnly ->
- val result = ArrayList(MAX_SUGGESTION_ITEMS)
+ isLocalSearch,
+ ::Triple,
+ ).mapLatest { (searchQuery, src, srcOnly) ->
+ buildSearchSuggestion(searchQuery, src, srcOnly)
+ }.distinctUntilChanged()
+ .onEach {
+ suggestion.postValue(it)
+ }.launchIn(viewModelScope + Dispatchers.Default)
+ }
+
+ private suspend fun buildSearchSuggestion(
+ searchQuery: String,
+ src: MangaSource?,
+ srcOnly: Boolean,
+ ): List = coroutineScope {
+ val queriesDeferred = async {
+ repository.getQuerySuggestion(searchQuery, MAX_QUERY_ITEMS)
+ }
+ val tagsDeferred = async {
+ repository.getTagsSuggestion(searchQuery, MAX_TAGS_ITEMS, src.takeIf { srcOnly })
+ }
+ val mangaDeferred = async {
+ repository.getMangaSuggestion(searchQuery, MAX_MANGA_ITEMS, src.takeIf { srcOnly })
+ }
+
+ val tags = tagsDeferred.await()
+ val mangaList = mangaDeferred.await()
+ val queries = queriesDeferred.await()
+
+ buildList(queries.size + 3) {
if (src != null) {
- result += SearchSuggestionItem.Header(src, isLocalSearch)
+ add(SearchSuggestionItem.Header(src, isLocalSearch))
}
- if (q.length >= SEARCH_THRESHOLD) {
- repository.getMangaSuggestion(q, MAX_MANGA_ITEMS, src.takeIf { srcOnly })
- .mapTo(result) {
- SearchSuggestionItem.MangaItem(it)
- }
+ if (tags.isNotEmpty()) {
+ add(SearchSuggestionItem.Tags(mapTags(tags)))
}
- queries.mapTo(result) { SearchSuggestionItem.RecentQuery(it) }
- result
- }.onEach {
- suggestion.postValue(it)
- }.launchIn(viewModelScope + Dispatchers.Default)
+ if (mangaList.isNotEmpty()) {
+ add(SearchSuggestionItem.MangaList(mangaList))
+ }
+ queries.mapTo(this) { SearchSuggestionItem.RecentQuery(it) }
+ }
+ }
+
+ private fun mapTags(tags: List): List = tags.map { tag ->
+ ChipsView.ChipModel(
+ icon = 0,
+ title = tag.title,
+ data = tag,
+ )
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt
index 06e6af858..86a8d5283 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt
@@ -8,6 +8,8 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
import kotlin.jvm.internal.Intrinsics
+const val SEARCH_SUGGESTION_ITEM_TYPE_QUERY = 0
+
class SearchSuggestionAdapter(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
@@ -15,9 +17,11 @@ class SearchSuggestionAdapter(
) : AsyncListDifferDelegationAdapter(DiffCallback()) {
init {
- delegatesManager.addDelegate(ITEM_TYPE_MANGA, searchSuggestionMangaAD(coil, lifecycleOwner, listener))
- .addDelegate(ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener))
- .addDelegate(ITEM_TYPE_HEADER, searchSuggestionHeaderAD(listener))
+ delegatesManager
+ .addDelegate(SEARCH_SUGGESTION_ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener))
+ .addDelegate(searchSuggestionHeaderAD(listener))
+ .addDelegate(searchSuggestionTagsAD(listener))
+ .addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener))
}
private class DiffCallback : DiffUtil.ItemCallback() {
@@ -26,14 +30,10 @@ class SearchSuggestionAdapter(
oldItem: SearchSuggestionItem,
newItem: SearchSuggestionItem,
): Boolean = when {
- oldItem is SearchSuggestionItem.MangaItem && newItem is SearchSuggestionItem.MangaItem -> {
- oldItem.manga.id == newItem.manga.id
- }
oldItem is SearchSuggestionItem.RecentQuery && newItem is SearchSuggestionItem.RecentQuery -> {
oldItem.query == newItem.query
}
- oldItem is SearchSuggestionItem.Header && newItem is SearchSuggestionItem.Header -> true
- else -> false
+ else -> oldItem.javaClass == newItem.javaClass
}
override fun areContentsTheSame(
@@ -41,11 +41,4 @@ class SearchSuggestionAdapter(
newItem: SearchSuggestionItem,
): Boolean = Intrinsics.areEqual(oldItem, newItem)
}
-
- companion object {
-
- const val ITEM_TYPE_MANGA = 0
- const val ITEM_TYPE_QUERY = 1
- const val ITEM_TYPE_HEADER = 2
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionMangaAD.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionMangaAD.kt
deleted file mode 100644
index 2eed6b932..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionMangaAD.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.koitharu.kotatsu.search.ui.suggestion.adapter
-
-import androidx.lifecycle.LifecycleOwner
-import coil.ImageLoader
-import coil.request.Disposable
-import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.databinding.ItemSearchSuggestionMangaBinding
-import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
-import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
-import org.koitharu.kotatsu.utils.ext.enqueueWith
-import org.koitharu.kotatsu.utils.ext.newImageRequest
-import org.koitharu.kotatsu.utils.ext.textAndVisible
-
-fun searchSuggestionMangaAD(
- coil: ImageLoader,
- lifecycleOwner: LifecycleOwner,
- listener: SearchSuggestionListener,
-) = adapterDelegateViewBinding(
- { inflater, parent -> ItemSearchSuggestionMangaBinding.inflate(inflater, parent, false) }
-) {
-
- var imageRequest: Disposable? = null
-
- itemView.setOnClickListener {
- listener.onMangaClick(item.manga)
- }
-
- bind {
- imageRequest?.dispose()
- imageRequest = binding.imageViewCover.newImageRequest(item.manga.coverUrl)
- .placeholder(R.drawable.ic_placeholder)
- .fallback(R.drawable.ic_placeholder)
- .error(R.drawable.ic_placeholder)
- .allowRgb565(true)
- .lifecycle(lifecycleOwner)
- .enqueueWith(coil)
- binding.textViewTitle.text = item.manga.title
- binding.textViewSubtitle.textAndVisible = item.manga.altTitle
- }
-
- onViewRecycled {
- imageRequest?.dispose()
- binding.imageViewCover.setImageDrawable(null)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTagsAD.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTagsAD.kt
new file mode 100644
index 000000000..3c070edad
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTagsAD.kt
@@ -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(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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt
new file mode 100644
index 000000000..9c3eb37b1
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt
@@ -0,0 +1,89 @@
+package org.koitharu.kotatsu.search.ui.suggestion.adapter
+
+import androidx.core.view.updatePadding
+import androidx.lifecycle.LifecycleOwner
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.RecyclerView
+import coil.ImageLoader
+import coil.request.Disposable
+import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
+import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate
+import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
+import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
+import org.koitharu.kotatsu.core.model.Manga
+import org.koitharu.kotatsu.databinding.ItemSearchSuggestionMangaGridBinding
+import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
+import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
+import org.koitharu.kotatsu.utils.ScrollResetCallback
+import org.koitharu.kotatsu.utils.ext.enqueueWith
+import org.koitharu.kotatsu.utils.ext.newImageRequest
+
+fun searchSuggestionMangaListAD(
+ coil: ImageLoader,
+ lifecycleOwner: LifecycleOwner,
+ listener: SearchSuggestionListener,
+) = adapterDelegate(R.layout.item_search_suggestion_manga_list) {
+
+ val adapter = AsyncListDifferDelegationAdapter(
+ SuggestionMangaDiffCallback(),
+ searchSuggestionMangaGridAD(coil, lifecycleOwner, listener),
+ )
+ val recyclerView = itemView as RecyclerView
+ recyclerView.adapter = adapter
+ val spacing = context.resources.getDimensionPixelOffset(R.dimen.search_suggestions_manga_spacing)
+ recyclerView.updatePadding(
+ left = recyclerView.paddingLeft - spacing,
+ right = recyclerView.paddingRight - spacing,
+ )
+ recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
+ val scrollResetCallback = ScrollResetCallback(recyclerView)
+
+ bind {
+ adapter.setItems(item.items, scrollResetCallback)
+ }
+}
+
+private fun searchSuggestionMangaGridAD(
+ coil: ImageLoader,
+ lifecycleOwner: LifecycleOwner,
+ listener: SearchSuggestionListener,
+) = adapterDelegateViewBinding(
+ { layoutInflater, parent -> ItemSearchSuggestionMangaGridBinding.inflate(layoutInflater, parent, false) }
+) {
+
+ var imageRequest: Disposable? = null
+
+ itemView.setOnClickListener {
+ listener.onMangaClick(item)
+ }
+
+ bind {
+ imageRequest?.dispose()
+ imageRequest = binding.imageViewCover.newImageRequest(item.coverUrl)
+ .placeholder(R.drawable.ic_placeholder)
+ .fallback(R.drawable.ic_placeholder)
+ .error(R.drawable.ic_placeholder)
+ .allowRgb565(true)
+ .lifecycle(lifecycleOwner)
+ .enqueueWith(coil)
+ binding.textViewTitle.text = item.title
+ }
+
+ onViewRecycled {
+ imageRequest?.dispose()
+ binding.imageViewCover.setImageDrawable(null)
+ }
+}
+
+private class SuggestionMangaDiffCallback : DiffUtil.ItemCallback() {
+
+ override fun areItemsTheSame(oldItem: Manga, newItem: Manga): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(oldItem: Manga, newItem: Manga): Boolean {
+ return oldItem.title == newItem.title && oldItem.coverUrl == newItem.coverUrl
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt
index d2d9d04d7..369639462 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt
@@ -1,21 +1,98 @@
package org.koitharu.kotatsu.search.ui.suggestion.model
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.MangaSource
+import org.koitharu.kotatsu.utils.ext.areItemsEquals
-sealed class SearchSuggestionItem {
+sealed interface SearchSuggestionItem {
- data class MangaItem(
- val manga: Manga,
- ) : SearchSuggestionItem()
+ class MangaList(
+ val items: List,
+ ) : SearchSuggestionItem {
- data class RecentQuery(
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as MangaList
+
+ return items.areItemsEquals(other.items) { a, b ->
+ a.title == b.title && a.coverUrl == b.coverUrl
+ }
+ }
+
+ override fun hashCode(): Int {
+ return items.fold(0) { acc, t ->
+ var r = 31 * acc + t.title.hashCode()
+ r = 31 * r + t.coverUrl.hashCode()
+ r
+ }
+ }
+ }
+
+ class RecentQuery(
val query: String,
- ) : SearchSuggestionItem()
+ ) : SearchSuggestionItem {
- data class Header(
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as RecentQuery
+
+ if (query != other.query) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return query.hashCode()
+ }
+ }
+
+ class Header(
val source: MangaSource,
val isChecked: MutableStateFlow,
- ) : SearchSuggestionItem()
+ ) : SearchSuggestionItem {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Header
+
+ if (source != other.source) return false
+ if (isChecked !== other.isChecked) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = source.hashCode()
+ result = 31 * result + isChecked.hashCode()
+ return result
+ }
+ }
+
+ class Tags(
+ val tags: List,
+ ) : SearchSuggestionItem {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Tags
+
+ if (tags != other.tags) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return tags.hashCode()
+ }
+ }
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt
index 350b82aab..02a10e453 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt
@@ -1,14 +1,19 @@
package org.koitharu.kotatsu.search.ui.widget
+import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.KeyEvent
+import android.view.MotionEvent
import android.view.inputmethod.EditorInfo
import androidx.annotation.AttrRes
import androidx.appcompat.widget.AppCompatEditText
+import androidx.core.content.ContextCompat
import com.google.android.material.R
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
+private const val DRAWABLE_END = 2
+
class SearchEditText @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@@ -16,6 +21,7 @@ class SearchEditText @JvmOverloads constructor(
) : AppCompatEditText(context, attrs, defStyleAttr) {
var searchSuggestionListener: SearchSuggestionListener? = null
+ private val clearIcon = ContextCompat.getDrawable(context, R.drawable.abc_ic_clear_material)
var query: String
get() = text?.trim()?.toString().orEmpty()
@@ -50,9 +56,32 @@ class SearchEditText @JvmOverloads constructor(
lengthAfter: Int,
) {
super.onTextChanged(text, start, lengthBefore, lengthAfter)
+ setCompoundDrawablesRelativeWithIntrinsicBounds(
+ null,
+ null,
+ if (text.isNullOrEmpty()) null else clearIcon,
+ null,
+ )
searchSuggestionListener?.onQueryChanged(query)
}
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ if (event.action == MotionEvent.ACTION_UP) {
+ val drawable = compoundDrawablesRelative[DRAWABLE_END] ?: return super.onTouchEvent(event)
+ val isOnDrawable = drawable.isVisible && if (layoutDirection == LAYOUT_DIRECTION_RTL) {
+ event.x.toInt() in paddingLeft..(drawable.bounds.width() + paddingLeft)
+ } else {
+ event.x.toInt() in (width - drawable.bounds.width() - paddingRight)..(width - paddingRight)
+ }
+ if (isOnDrawable) {
+ text?.clear()
+ return true
+ }
+ }
+ return super.onTouchEvent(event)
+ }
+
override fun clearFocus() {
super.clearFocus()
text?.clear()
diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt
index c70793405..507c2a142 100644
--- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt
@@ -46,10 +46,6 @@ class SuggestionsFragment : MangaListFragment() {
override fun onScrolledToEnd() = Unit
- override fun getTitle(): CharSequence? {
- return context?.getString(R.string.suggestions)
- }
-
companion object {
fun newInstance() = SuggestionsFragment()
diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt
index 350979484..47a2168ee 100644
--- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt
@@ -35,8 +35,6 @@ class FeedFragment : BaseFragment(), PaginationScrollListen
private var paddingVertical = 0
private var paddingHorizontal = 0
- override fun getTitle() = context?.getString(R.string.updates)
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ScrollResetCallback.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ScrollResetCallback.kt
new file mode 100644
index 000000000..5a279f4ec
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ScrollResetCallback.kt
@@ -0,0 +1,13 @@
+package org.koitharu.kotatsu.utils
+
+import androidx.recyclerview.widget.RecyclerView
+import java.lang.ref.WeakReference
+
+class ScrollResetCallback(recyclerView: RecyclerView) : Runnable {
+
+ private val recyclerViewRef = WeakReference(recyclerView)
+
+ override fun run() {
+ recyclerViewRef.get()?.scrollToPosition(0)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt
index 441b87c31..58797833c 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt
@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.utils.ext
-import android.util.SparseArray
import androidx.collection.ArrayMap
import androidx.collection.ArraySet
import androidx.collection.LongSparseArray
@@ -82,4 +81,18 @@ fun MutableList.move(sourceIndex: Int, targetIndex: Int) {
} else {
Collections.rotate(subList(targetIndex, sourceIndex + 1), 1)
}
+}
+
+inline fun List.areItemsEquals(other: List, equals: (T, T) -> Boolean): Boolean {
+ if (size != other.size) {
+ return false
+ }
+ for (i in indices) {
+ val a = this[i]
+ val b = other[i]
+ if (!equals(a, b)) {
+ return false
+ }
+ }
+ return true
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index fe35e0918..e4ed68397 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -48,12 +48,15 @@
style="@style/Widget.Kotatsu.SearchView"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:layout_marginEnd="2dp"
android:background="@null"
android:gravity="center_vertical"
android:hint="@string/search_manga"
android:imeOptions="actionSearch"
android:importantForAutofill="no"
- android:singleLine="true" />
+ android:paddingBottom="1dp"
+ android:singleLine="true"
+ tools:drawableEnd="@drawable/abc_ic_clear_material" />
diff --git a/app/src/main/res/layout/item_search_suggestion_manga.xml b/app/src/main/res/layout/item_search_suggestion_manga.xml
deleted file mode 100644
index c0cf277fd..000000000
--- a/app/src/main/res/layout/item_search_suggestion_manga.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_search_suggestion_manga_grid.xml b/app/src/main/res/layout/item_search_suggestion_manga_grid.xml
new file mode 100644
index 000000000..dad0dddd8
--- /dev/null
+++ b/app/src/main/res/layout/item_search_suggestion_manga_grid.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_search_suggestion_manga_list.xml b/app/src/main/res/layout/item_search_suggestion_manga_list.xml
new file mode 100644
index 000000000..88bcf54fb
--- /dev/null
+++ b/app/src/main/res/layout/item_search_suggestion_manga_list.xml
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_search_suggestion_tags.xml b/app/src/main/res/layout/item_search_suggestion_tags.xml
new file mode 100644
index 000000000..3a6d64787
--- /dev/null
+++ b/app/src/main/res/layout/item_search_suggestion_tags.xml
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 5003ad1fc..f4b747b4a 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -18,4 +18,7 @@
36dp
48dp
16dp
+
+ 124dp
+ 4dp
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index f65fca080..08ab43dec 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,4 +1,4 @@
-
+
@@ -76,6 +76,10 @@
- @bool/elevation_overlay_enabled
+
+
+
+