Convert SearchBarScene SuggestRV to LazyColumn

This commit is contained in:
tarsin committed 2023-08-01 08:42:15 +08:00
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>
+1 -1
View File
@@ -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" />