发现与订阅优化

This commit is contained in:
HapeLee committed 2025-11-03 19:37:47 +08:00
1 parent bb978639da
commit 0f4e2beb94
4 files changed
+55 -108

No files matched your search

@@ -3,11 +3,13 @@ package io.legado.app.ui.main.explore
//import io.legado.app.lib.theme.primaryColor //import io.legado.app.lib.theme.primaryColor
//import io.legado.app.lib.theme.primaryTextColor //import io.legado.app.lib.theme.primaryTextColor
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.SubMenu import android.view.SubMenu
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.appcompat.widget.SearchView
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
@@ -17,8 +19,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseFragment import io.legado.app.base.VMBaseFragment
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
@@ -40,7 +40,6 @@ import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
@@ -68,12 +67,12 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
ExploreAdapter(requireContext(), this) ExploreAdapter(requireContext(), this)
} }
private val linearLayoutManager by lazy { LinearLayoutManager(context) } private val linearLayoutManager by lazy { LinearLayoutManager(context) }
private val searchBar: SearchBar by lazy { binding.searchBar } private val searchView: SearchView by lazy { binding.searchBar }
private val searchView: SearchView by lazy { binding.searchView }
private val diffItemCallBack = ExploreDiffItemCallBack() private val diffItemCallBack = ExploreDiffItemCallBack()
private val groups = linkedSetOf<String>() private val groups = linkedSetOf<String>()
private var exploreFlowJob: Job? = null private var exploreFlowJob: Job? = null
private var groupsMenu: SubMenu? = null private var groupsMenu: SubMenu? = null
private var currentGroup: String? = null
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
setSupportToolbar(binding.topBar) setSupportToolbar(binding.topBar)
@@ -89,44 +88,20 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
} }
private fun initSearchView() { private fun initSearchView() {
searchView.setupWithSearchBar(searchBar) searchView.queryHint = getString(R.string.screen_find)
searchView.hint = getString(R.string.screen_find) searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
searchBar.setOnClickListener { override fun onQueryTextSubmit(query: String): Boolean {
searchView.show()
if (searchView.text.isEmpty()) {
searchView.hint = getString(R.string.screen_find)
}
}
searchView.addTransitionListener { _, _, newState ->
if (newState == SearchView.TransitionState.HIDDEN) {
searchBar.setText(searchView.text)
}
}
searchView.editText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
val query = searchView.text.toString()
upExploreData(query) upExploreData(query)
searchBar.hint = query.ifEmpty { searchView.clearFocus()
getString(R.string.screen_find) return true
}
searchView.hide()
return@setOnEditorActionListener true
} }
false
}
searchView.editText.doAfterTextChanged { editable -> override fun onQueryTextChange(newText: String): Boolean {
editable?.let { upExploreData(newText)
upExploreData(it.toString()) return true
if (it.isEmpty()) {
searchBar.hint = getString(R.string.screen_find)
}
} }
} })
searchView.setupWithSearchBar(searchBar)
} }
private fun initRecyclerView() { private fun initRecyclerView() {
@@ -158,7 +133,6 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
groups.clear() groups.clear()
groups.addAll(it) groups.addAll(it)
upGroupsMenu() upGroupsMenu()
delay(500)
} }
} }
} }
@@ -166,18 +140,18 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
private fun upExploreData(searchKey: String? = null) { private fun upExploreData(searchKey: String? = null) {
exploreFlowJob?.cancel() exploreFlowJob?.cancel()
exploreFlowJob = viewLifecycleOwner.lifecycleScope.launch { exploreFlowJob = viewLifecycleOwner.lifecycleScope.launch {
val query = searchKey ?: searchView.query?.toString() ?: ""
when { when {
searchKey.isNullOrBlank() -> { query.isBlank() -> {
appDb.bookSourceDao.flowExplore() appDb.bookSourceDao.flowExplore()
} }
query.startsWith("group:") -> {
searchKey.startsWith("group:") -> { val key = query.substringAfter("group:")
val key = searchKey.substringAfter("group:")
appDb.bookSourceDao.flowGroupExplore(key) appDb.bookSourceDao.flowGroupExplore(key)
} }
else -> { else -> {
appDb.bookSourceDao.flowExplore(searchKey) appDb.bookSourceDao.flowExplore(query)
} }
}.flowWithLifecycleAndDatabaseChange( }.flowWithLifecycleAndDatabaseChange(
viewLifecycleOwner.lifecycle, viewLifecycleOwner.lifecycle,
@@ -186,9 +160,8 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
).catch { ).catch {
AppLog.put("发现界面更新数据出错", it) AppLog.put("发现界面更新数据出错", it)
}.conflate().flowOn(IO).collect { }.conflate().flowOn(IO).collect {
binding.emptyView.isGone = it.isNotEmpty() || searchView.text.isNotEmpty() == true binding.emptyView.isGone = it.isNotEmpty() || query.isNotEmpty()
adapter.setItems(it, diffItemCallBack) adapter.setItems(it, diffItemCallBack)
delay(500)
} }
} }
} }
@@ -216,17 +189,20 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
if (item.groupId == R.id.menu_group_text) { if (item.groupId == R.id.menu_group_text) {
val title = item.title.toString() val title = item.title.toString()
if (title == getString(R.string.all)) { if (title == getString(R.string.all)) {
searchView.setText("") searchView.setQuery("", false)
upExploreData(null) upExploreData("")
} else { } else {
val query = "group:$title" searchView.setQuery(title, false)
searchView.setText(query)
upExploreData(query)
} }
} else if (item.itemId == R.id.menu_exp_search) { } else if (item.itemId == R.id.menu_exp_search) {
TransitionManager.beginDelayedTransition(binding.rootView) TransitionManager.beginDelayedTransition(binding.rootView)
binding.searchBar.visibility = if (searchView.isVisible) {
if (binding.searchBar.isVisible) View.GONE else View.VISIBLE searchView.isVisible = false
searchView.clearFocus()
} else {
searchView.isVisible = true
searchView.requestFocus()
}
} }
} }
@@ -7,13 +7,12 @@ import android.view.MenuItem
import android.view.SubMenu import android.view.SubMenu
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseFragment import io.legado.app.base.VMBaseFragment
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
@@ -65,8 +64,7 @@ class RssFragment() : VMBaseFragment<RssViewModel>(R.layout.fragment_rss),
private val adapter by lazy { private val adapter by lazy {
RssAdapter(requireContext(), this, this, viewLifecycleOwner.lifecycle) RssAdapter(requireContext(), this, this, viewLifecycleOwner.lifecycle)
} }
private val searchBar: SearchBar by lazy { binding.searchBar } private val searchView: SearchView by lazy { binding.searchBar }
private val searchView: SearchView by lazy { binding.searchView }
private var groupsFlowJob: Job? = null private var groupsFlowJob: Job? = null
private var rssFlowJob: Job? = null private var rssFlowJob: Job? = null
private val groups = linkedSetOf<String>() private val groups = linkedSetOf<String>()
@@ -99,9 +97,9 @@ class RssFragment() : VMBaseFragment<RssViewModel>(R.layout.fragment_rss),
else -> if (item.groupId == R.id.menu_group_text) { else -> if (item.groupId == R.id.menu_group_text) {
if (item.title == getString(R.string.all)) { if (item.title == getString(R.string.all)) {
upRssFlowJob() upRssFlowJob()
searchView.setText("") searchView.setQuery("", false)
} else { } else {
searchView.setText("group:${item.title}") searchView.setQuery(item.title, false)
upRssFlowJob("group:${item.title}") upRssFlowJob("group:${item.title}")
} }
} }
@@ -123,39 +121,19 @@ class RssFragment() : VMBaseFragment<RssViewModel>(R.layout.fragment_rss),
} }
private fun initSearchView() { private fun initSearchView() {
searchView.hint = getString(R.string.search_rss_source) searchView.queryHint = getString(R.string.search_rss_source)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
searchBar.setOnClickListener { override fun onQueryTextSubmit(query: String): Boolean {
searchView.show()
if (searchView.text.isEmpty()) {
searchView.hint = getString(R.string.search_rss_source)
}
}
searchView.editText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
val query = searchView.text.toString()
upRssFlowJob(query) upRssFlowJob(query)
searchBar.hint = query.ifEmpty { searchView.clearFocus()
getString(R.string.search_rss_source) return true
}
searchView.hide()
return@setOnEditorActionListener true
} }
false
}
searchView.editText.doAfterTextChanged { editable -> override fun onQueryTextChange(newText: String): Boolean {
editable?.let { upRssFlowJob(newText)
upRssFlowJob(it.toString()) return true
if (it.isEmpty()) {
searchBar.hint = getString(R.string.search_rss_source)
}
} }
} })
searchView.setupWithSearchBar(searchBar)
} }
private fun initRecyclerView() { private fun initRecyclerView() {
+6 -8
View File
@@ -31,23 +31,21 @@
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
<com.google.android.material.search.SearchBar <androidx.appcompat.widget.SearchView
android:id="@+id/search_bar" android:id="@+id/search_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
app:queryBackground="@null"
app:iconifiedByDefault="false"
app:searchIcon="@null"
android:background="@drawable/bg_searchview"
android:visibility="gone" android:visibility="gone"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:hint="@string/screen_find" /> android:hint="@string/screen_find" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.search.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/screen_find"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_find" android:id="@+id/rv_find"
android:layout_width="match_parent" android:layout_width="match_parent"
+6 -11
View File
@@ -31,24 +31,21 @@
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
<com.google.android.material.search.SearchBar <androidx.appcompat.widget.SearchView
android:id="@+id/search_bar" android:id="@+id/search_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
app:queryBackground="@null"
app:iconifiedByDefault="false"
app:searchIcon="@null"
android:background="@drawable/bg_searchview"
android:visibility="gone" android:visibility="gone"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:hint="@string/search_rss_source" /> android:hint="@string/search_rss_source" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.search.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -58,8 +55,6 @@
app:spanCount="4" app:spanCount="4"
tools:listitem="@layout/item_rss" /> tools:listitem="@layout/item_rss" />
<TextView <TextView
android:id="@+id/tv_empty_msg" android:id="@+id/tv_empty_msg"
android:layout_width="wrap_content" android:layout_width="wrap_content"