Convert SearchBarScene SuggestRV to LazyColumn
This commit is contained in:
1 parent
0ff9b03596
commit
5a5a5da2d9
5 files changed
+99
-213
No files matched your search
@@ -21,9 +21,6 @@ import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.text.InputType
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.ImageSpan
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
@@ -961,66 +958,43 @@ class GalleryListScene : SearchBarScene() {
|
||||
}
|
||||
|
||||
private abstract inner class UrlSuggestion : Suggestion() {
|
||||
override fun getText(textView: TextView): CharSequence? {
|
||||
return if (textView.id == android.R.id.text1) {
|
||||
val bookImage =
|
||||
ContextCompat.getDrawable(textView.context, R.drawable.v_book_open_x24)
|
||||
val ssb = SpannableStringBuilder(" ")
|
||||
ssb.append(getString(R.string.gallery_list_search_bar_open_gallery))
|
||||
val imageSize = (textView.textSize * 1.25).toInt()
|
||||
if (bookImage != null) {
|
||||
bookImage.setBounds(0, 0, imageSize, imageSize)
|
||||
ssb.setSpan(ImageSpan(bookImage), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
ssb
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override val keyword = getString(R.string.gallery_list_search_bar_open_gallery)
|
||||
override val canOpenDirectly = true
|
||||
override fun onClick() {
|
||||
navAnimated(getDestination(), getArgs())
|
||||
navAnimated(destination, args)
|
||||
if (mState == State.SIMPLE_SEARCH) {
|
||||
setState(State.NORMAL)
|
||||
} else if (mState == State.SEARCH_SHOW_LIST) {
|
||||
setState(State.SEARCH)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun getDestination(): Int
|
||||
|
||||
abstract fun getArgs(): Bundle
|
||||
abstract val destination: Int
|
||||
abstract val args: Bundle
|
||||
}
|
||||
|
||||
private inner class GalleryDetailUrlSuggestion(
|
||||
private val mGid: Long,
|
||||
private val mToken: String,
|
||||
gid: Long,
|
||||
token: String,
|
||||
) : UrlSuggestion() {
|
||||
override fun getDestination(): Int {
|
||||
return R.id.galleryDetailScene
|
||||
}
|
||||
|
||||
override fun getArgs() = bundleOf(
|
||||
override val destination = R.id.galleryDetailScene
|
||||
override val args = bundleOf(
|
||||
GalleryDetailScene.KEY_ACTION to GalleryDetailScene.ACTION_GID_TOKEN,
|
||||
GalleryDetailScene.KEY_GID to mGid,
|
||||
GalleryDetailScene.KEY_TOKEN to mToken,
|
||||
GalleryDetailScene.KEY_GID to gid,
|
||||
GalleryDetailScene.KEY_TOKEN to token,
|
||||
)
|
||||
}
|
||||
|
||||
private inner class GalleryPageUrlSuggestion(
|
||||
private val mGid: Long,
|
||||
private val mPToken: String,
|
||||
private val mPage: Int,
|
||||
gid: Long,
|
||||
pToken: String,
|
||||
page: Int,
|
||||
) : UrlSuggestion() {
|
||||
override fun getDestination(): Int {
|
||||
return R.id.progressScene
|
||||
}
|
||||
|
||||
override fun getArgs() = bundleOf(
|
||||
override val destination = R.id.progressScene
|
||||
override val args = bundleOf(
|
||||
ProgressScene.KEY_ACTION to ProgressScene.ACTION_GALLERY_TOKEN,
|
||||
ProgressScene.KEY_GID to mGid,
|
||||
ProgressScene.KEY_PTOKEN to mPToken,
|
||||
ProgressScene.KEY_PAGE to mPage,
|
||||
ProgressScene.KEY_GID to gid,
|
||||
ProgressScene.KEY_PTOKEN to pToken,
|
||||
ProgressScene.KEY_PAGE to page,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,15 +8,28 @@ import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.MenuBook
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.search.SearchView
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.hippo.ehviewer.EhApplication.Companion.searchDatabase
|
||||
@@ -25,9 +38,11 @@ import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.dao.Search
|
||||
import com.hippo.ehviewer.dao.SearchDao
|
||||
import com.hippo.ehviewer.databinding.ItemSimpleList2Binding
|
||||
import com.hippo.ehviewer.databinding.SceneSearchbarBinding
|
||||
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
||||
import com.hippo.ehviewer.ui.setMD3Content
|
||||
import com.jamal.composeprefs3.ui.ifNotNullThen
|
||||
import com.jamal.composeprefs3.ui.ifTrueThen
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -36,8 +51,7 @@ import kotlinx.coroutines.flow.flow
|
||||
abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
private var _binding: SceneSearchbarBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private var mSuggestionList: List<Suggestion>? = null
|
||||
private var mSuggestionAdapter: SuggestionAdapter? = null
|
||||
private var mSuggestionList by mutableStateOf(emptyList<Suggestion>())
|
||||
private var mSuggestionProvider: SuggestionProvider? = null
|
||||
private var mAllowEmptySearch = true
|
||||
private val mSearchDatabase = searchDatabase.searchDao()
|
||||
@@ -50,8 +64,7 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
_binding = SceneSearchbarBinding.inflate(inflater, container, false)
|
||||
binding.appbar.statusBarForeground =
|
||||
MaterialShapeDrawable.createWithElevationOverlay(context)
|
||||
binding.appbar.statusBarForeground = MaterialShapeDrawable.createWithElevationOverlay(context)
|
||||
binding.searchview.editText.addTextChangedListener {
|
||||
updateSuggestions()
|
||||
}
|
||||
@@ -59,11 +72,32 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
onApplySearch()
|
||||
true
|
||||
}
|
||||
mSuggestionList = ArrayList()
|
||||
mSuggestionAdapter = SuggestionAdapter(LayoutInflater.from(context))
|
||||
binding.searchBarList.adapter = mSuggestionAdapter
|
||||
val layoutManager = LinearLayoutManager(context)
|
||||
binding.searchBarList.layoutManager = layoutManager
|
||||
binding.searchBarList.setMD3Content {
|
||||
LazyColumn {
|
||||
items(mSuggestionList) {
|
||||
ListItem(
|
||||
headlineContent = { Text(text = it.keyword) },
|
||||
supportingContent = it.hint.ifNotNullThen { Text(text = it.hint!!) },
|
||||
leadingContent = it.canOpenDirectly.ifTrueThen {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MenuBook,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
trailingContent = it.canDelete.ifTrueThen {
|
||||
IconButton(onClick = { deleteKeyword(it.keyword) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh),
|
||||
modifier = Modifier.clickable { it.onClick() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.searchview.addTransitionListener { _, _, newState ->
|
||||
if (newState == SearchView.TransitionState.SHOWING) {
|
||||
onSearchViewExpanded()
|
||||
@@ -96,7 +130,6 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
super.onDestroyView()
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) mSearchViewOnBackPressedCallback.remove()
|
||||
_binding = null
|
||||
mSuggestionAdapter = null
|
||||
}
|
||||
|
||||
private var privLockModeStart: Int? = null
|
||||
@@ -186,94 +219,42 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
}
|
||||
|
||||
interface SuggestionProvider {
|
||||
|
||||
fun providerSuggestions(text: String): List<Suggestion>?
|
||||
}
|
||||
|
||||
private fun deleteKeyword(keyword: String) {
|
||||
BaseDialogBuilder(requireContext())
|
||||
.setMessage(requireContext().getString(R.string.delete_search_history, keyword))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
lifecycleScope.launchIO {
|
||||
mSearchDatabase.deleteQuery(keyword)
|
||||
withUIContext {
|
||||
updateSuggestions(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
abstract class Suggestion {
|
||||
|
||||
abstract fun getText(textView: TextView): CharSequence?
|
||||
|
||||
abstract val keyword: String
|
||||
open val hint: String? = null
|
||||
abstract fun onClick()
|
||||
|
||||
open fun onLongClick(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private class SuggestionHolder(private val binding: ItemSimpleList2Binding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(suggestion: Suggestion?) {
|
||||
val text1 = suggestion?.getText(binding.text1)
|
||||
val text2 = suggestion?.getText(binding.text2)
|
||||
binding.text1.text = text1
|
||||
if (text2 == null) {
|
||||
binding.text2.visibility = View.GONE
|
||||
binding.text2.text = ""
|
||||
} else {
|
||||
binding.text2.visibility = View.VISIBLE
|
||||
binding.text2.text = text2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class SuggestionAdapter(private val mInflater: LayoutInflater) :
|
||||
RecyclerView.Adapter<SuggestionHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SuggestionHolder {
|
||||
return SuggestionHolder(ItemSimpleList2Binding.inflate(mInflater, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SuggestionHolder, position: Int) {
|
||||
val suggestion = mSuggestionList?.get(position)
|
||||
holder.bind(suggestion)
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
mSuggestionList?.run {
|
||||
if (position < size) {
|
||||
this[position].onClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
holder.itemView.setOnLongClickListener {
|
||||
mSuggestionList?.run {
|
||||
if (position < size) {
|
||||
return@setOnLongClickListener this[position].onLongClick()
|
||||
}
|
||||
}
|
||||
return@setOnLongClickListener false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return position.toLong()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return mSuggestionList?.size ?: 0
|
||||
}
|
||||
open val canDelete: Boolean = false
|
||||
open val canOpenDirectly: Boolean = false
|
||||
}
|
||||
|
||||
inner class TagSuggestion(
|
||||
private var mHint: String?,
|
||||
private var mKeyword: String,
|
||||
) :
|
||||
Suggestion() {
|
||||
|
||||
override fun getText(textView: TextView): CharSequence? {
|
||||
return if (textView.id == android.R.id.text1) {
|
||||
mKeyword
|
||||
} else {
|
||||
mHint
|
||||
}
|
||||
}
|
||||
|
||||
override val hint: String?,
|
||||
override val keyword: String,
|
||||
) : Suggestion() {
|
||||
override fun onClick() {
|
||||
val edittext = binding.searchview.editText
|
||||
edittext.let {
|
||||
var keywords = it.text.toString().substringBeforeLast(' ', "")
|
||||
if (keywords.isNotEmpty()) keywords += ' '
|
||||
keywords += wrapTagKeyword(mKeyword)
|
||||
keywords += wrapTagKeyword(keyword)
|
||||
if (!keywords.endsWith(':')) keywords += ' '
|
||||
it.setText(keywords)
|
||||
it.setSelection(keywords.length)
|
||||
@@ -281,38 +262,16 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
}
|
||||
}
|
||||
|
||||
inner class KeywordSuggestion(private val mKeyword: String) : Suggestion() {
|
||||
|
||||
override fun getText(textView: TextView): CharSequence? {
|
||||
return if (textView.id == android.R.id.text1) {
|
||||
mKeyword
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
inner class KeywordSuggestion(
|
||||
override val keyword: String,
|
||||
) : Suggestion() {
|
||||
override val canDelete = true
|
||||
override fun onClick() {
|
||||
binding.searchview.editText.run {
|
||||
setText(mKeyword)
|
||||
setText(keyword)
|
||||
setSelection(length())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClick(): Boolean {
|
||||
BaseDialogBuilder(requireContext())
|
||||
.setMessage(requireContext().getString(R.string.delete_search_history, mKeyword))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
lifecycleScope.launchIO {
|
||||
mSearchDatabase.deleteQuery(mKeyword)
|
||||
withUIContext {
|
||||
updateSuggestions(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
.show()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@@ -323,13 +282,7 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
mergedSuggestionFlow().collect {
|
||||
suggestions.add(it)
|
||||
}
|
||||
withUIContext {
|
||||
mSuggestionList = suggestions
|
||||
mSuggestionAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
if (scrollToTop) {
|
||||
binding.searchBarList.scrollToPosition(0)
|
||||
mSuggestionList = suggestions
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ import java.math.RoundingMode
|
||||
fun Any?.ifNotNullThen(content: @Composable () -> Unit): (@Composable () -> Unit)? =
|
||||
if (this != null) content else null
|
||||
|
||||
fun Boolean.ifTrueThen(content: @Composable () -> Unit): (@Composable () -> Unit)? =
|
||||
if (this) content else null
|
||||
|
||||
fun roundToDP(value: Double, places: Int): Double {
|
||||
return BigDecimal(value).setScale(places, RoundingMode.HALF_EVEN).toDouble()
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingRight"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textAppearance="?attr/textAppearanceListItemSmall"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/text_little_small" />
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/text2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceListItemSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_small" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -40,7 +40,7 @@
|
||||
app:layout_anchor="@id/toolbar"
|
||||
app:useDrawerArrowDrawable="true">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/search_bar_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
Reference in new issue
Block a user