This commit is contained in:
Horis
2025-01-20 18:32:59 +08:00
parent 227ea73a72
commit c0399abd5b
5 changed files with 21 additions and 21 deletions
@@ -73,6 +73,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
private fun startSearch() { private fun startSearch() {
val precision = appCtx.getPrefBoolean(PreferKey.precisionSearch) val precision = appCtx.getPrefBoolean(PreferKey.precisionSearch)
var hasMore = false
searchJob = scope.launch(searchPool!!) { searchJob = scope.launch(searchPool!!) {
flow { flow {
for (bs in bookSourceParts) { for (bs in bookSourceParts) {
@@ -90,12 +91,13 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
for (book in items) { for (book in items) {
book.releaseHtmlData() book.releaseHtmlData()
} }
hasMore = hasMore || items.isNotEmpty()
appDb.searchBookDao.insert(*items.toTypedArray()) appDb.searchBookDao.insert(*items.toTypedArray())
mergeItems(items, precision) mergeItems(items, precision)
currentCoroutineContext().ensureActive() currentCoroutineContext().ensureActive()
callBack.onSearchSuccess(searchBooks) callBack.onSearchSuccess(searchBooks)
}.onCompletion { }.onCompletion {
if (it == null) callBack.onSearchFinish(searchBooks.isEmpty()) if (it == null) callBack.onSearchFinish(searchBooks.isEmpty(), hasMore)
}.catch { }.catch {
AppLog.put("书源搜索出错\n${it.localizedMessage}", it) AppLog.put("书源搜索出错\n${it.localizedMessage}", it)
}.collect() }.collect()
@@ -185,7 +187,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
fun getSearchScope(): SearchScope fun getSearchScope(): SearchScope
fun onSearchStart() fun onSearchStart()
fun onSearchSuccess(searchBooks: List<SearchBook>) fun onSearchSuccess(searchBooks: List<SearchBook>)
fun onSearchFinish(isEmpty: Boolean) fun onSearchFinish(isEmpty: Boolean, hasMore: Boolean)
fun onSearchCancel(exception: Throwable? = null) fun onSearchCancel(exception: Throwable? = null)
} }
@@ -54,7 +54,6 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import splitties.init.appCtx import splitties.init.appCtx
import kotlin.math.abs
class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel>(), class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel>(),
BookAdapter.CallBack, BookAdapter.CallBack,
@@ -240,20 +239,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
if (!recyclerView.canScrollVertically(1)) { if (!recyclerView.canScrollVertically(1)) {
val layoutManager = recyclerView.layoutManager as LinearLayoutManager scrollToBottom()
val lastPosition = layoutManager.findLastCompletelyVisibleItemPosition()
if (lastPosition == RecyclerView.NO_POSITION) {
return
}
val lastView = layoutManager.findViewByPosition(lastPosition)
if (lastView == null) {
scrollToBottom()
return
}
val bottom = abs(lastView.bottom - recyclerView.height)
if (bottom <= 1) {
scrollToBottom()
}
} }
} }
}) })
@@ -324,6 +310,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
} }
if (viewModel.isSearchLiveData.value == false if (viewModel.isSearchLiveData.value == false
&& viewModel.searchKey.isNotEmpty() && viewModel.searchKey.isNotEmpty()
&& viewModel.hasMore
) { ) {
viewModel.search("") viewModel.search("")
} }
@@ -407,8 +394,9 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
if (!isEmpty || viewModel.searchScope.isAll()) return@observe if (!isEmpty || viewModel.searchScope.isAll()) return@observe
alert("搜索结果为空") { alert("搜索结果为空") {
val precisionSearch = appCtx.getPrefBoolean(PreferKey.precisionSearch) val precisionSearch = appCtx.getPrefBoolean(PreferKey.precisionSearch)
val displayScope = viewModel.searchScope.display
if (precisionSearch) { if (precisionSearch) {
setMessage("${viewModel.searchScope.display}分组搜索结果为空,是否关闭精准搜索?") setMessage("${displayScope}分组搜索结果为空,是否关闭精准搜索?")
yesButton { yesButton {
appCtx.putPrefBoolean(PreferKey.precisionSearch, false) appCtx.putPrefBoolean(PreferKey.precisionSearch, false)
precisionSearchMenuItem?.isChecked = false precisionSearchMenuItem?.isChecked = false
@@ -416,7 +404,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
viewModel.search(searchView.query.toString()) viewModel.search(searchView.query.toString())
} }
} else { } else {
setMessage("${viewModel.searchScope.display}分组搜索结果为空,是否切换到全部分组?") setMessage("${displayScope}分组搜索结果为空,是否切换到全部分组?")
yesButton { yesButton {
viewModel.searchScope.update("") viewModel.searchScope.update("")
} }
@@ -30,6 +30,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
var searchFinishLiveData = MutableLiveData<Boolean>() var searchFinishLiveData = MutableLiveData<Boolean>()
var isSearchLiveData = MutableLiveData<Boolean>() var isSearchLiveData = MutableLiveData<Boolean>()
var searchKey: String = "" var searchKey: String = ""
var hasMore = true
private var searchID = 0L private var searchID = 0L
private val searchModel = SearchModel(viewModelScope, object : SearchModel.CallBack { private val searchModel = SearchModel(viewModelScope, object : SearchModel.CallBack {
@@ -45,9 +46,10 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
searchBookLiveData.postValue(searchBooks) searchBookLiveData.postValue(searchBooks)
} }
override fun onSearchFinish(isEmpty: Boolean) { override fun onSearchFinish(isEmpty: Boolean, hasMore: Boolean) {
isSearchLiveData.postValue(false) isSearchLiveData.postValue(false)
searchFinishLiveData.postValue(isEmpty) searchFinishLiveData.postValue(isEmpty)
this@SearchViewModel.hasMore = hasMore
} }
override fun onSearchCancel(exception: Throwable?) { override fun onSearchCancel(exception: Throwable?) {
@@ -99,6 +101,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
searchID = System.currentTimeMillis() searchID = System.currentTimeMillis()
searchBookLiveData.postValue(emptyList()) searchBookLiveData.postValue(emptyList())
searchKey = key searchKey = key
hasMore = true
} }
if (searchKey.isEmpty()) { if (searchKey.isEmpty()) {
return@execute return@execute
@@ -1,12 +1,19 @@
package io.legado.app.ui.widget.text package io.legado.app.ui.widget.text
import android.content.Context import android.content.Context
import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
class MultilineTextView(context: Context, attrs: AttributeSet?) : class MultilineTextView(context: Context, attrs: AttributeSet?) :
AppCompatTextView(context, attrs) { AppCompatTextView(context, attrs) {
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
isFallbackLineSpacing = false
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val heightSize = MeasureSpec.getSize(heightMeasureSpec) val heightSize = MeasureSpec.getSize(heightMeasureSpec)
calculateLines(heightSize) calculateLines(heightSize)
@@ -90,7 +90,7 @@ class BookSearchWebSocket(handshakeRequest: NanoHTTPD.IHTTPSession) :
send(GSON.toJson(searchBooks)) send(GSON.toJson(searchBooks))
} }
override fun onSearchFinish(isEmpty: Boolean) = close(normalClosure, SEARCH_FINISH, false) override fun onSearchFinish(isEmpty: Boolean, hasMore: Boolean) = close(normalClosure, SEARCH_FINISH, false)
override fun onSearchCancel(exception: Throwable?) = close(normalClosure, exception?.toString() ?: SEARCH_FINISH, false) override fun onSearchCancel(exception: Throwable?) = close(normalClosure, exception?.toString() ?: SEARCH_FINISH, false)