[修复] 偶现的无法退出书源管理的问题

This commit is contained in:
HapeLee
2026-03-11 00:20:55 +08:00
parent ca067578a1
commit 50cd2f56fe
3 changed files with 70 additions and 29 deletions
@@ -5,6 +5,7 @@ import android.content.Context
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.util.size
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -37,6 +38,8 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
private var diffJob: Coroutine<*>? = null
private var isResumed = false
var itemAnimation: ItemAnimation? = null
fun setOnItemClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Unit) {
@@ -54,8 +57,8 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
@Synchronized
fun addHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) {
kotlin.runCatching {
val index = headerItems.size()
headerItems.put(TYPE_HEADER_VIEW + headerItems.size(), header)
val index = headerItems.size
headerItems.put(TYPE_HEADER_VIEW + headerItems.size, header)
notifyItemInserted(index)
}
}
@@ -63,8 +66,8 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
@Synchronized
fun addFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {
kotlin.runCatching {
val index = getActualItemCount() + footerItems.size()
footerItems.put(TYPE_FOOTER_VIEW + footerItems.size(), footer)
val index = getActualItemCount() + footerItems.size
footerItems.put(TYPE_FOOTER_VIEW + footerItems.size, footer)
notifyItemInserted(index)
}
}
@@ -113,6 +116,10 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
skipDiff: Boolean = false
) {
kotlin.runCatching {
if (!isResumed) { //全量标记更新
setItems(items)
return@runCatching
}
val oldItems = this.items.toList()
val itemsSize = items?.size ?: 0
val headerCount = getHeaderCount()
@@ -153,6 +160,10 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
return itemCallback.getChangePayload(oldItem, newItem)
}
}
if (!isResumed) {
setItems(items)
return@runCatching
}
diffJob?.cancel()
diffJob = Coroutine.async {
val diffResult = if (skipDiff) withTimeoutOrNullAsync(500L) {
@@ -162,7 +173,7 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
}
ensureActive()
handler.post {
if (diffResult == null) {
if (isResumed || diffResult == null) {
setItems(items)
return@post
}
@@ -172,6 +183,10 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
if (items != null) {
this@RecyclerAdapter.items.addAll(items)
}
if (!isResumed) {
return@post
}
ensureActive()
diffResult.dispatchUpdatesTo(this@RecyclerAdapter)
onCurrentListChanged()
}
@@ -329,10 +344,10 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
fun getActualItemCount() = items.size
fun getHeaderCount() = headerItems.size()
fun getHeaderCount() = headerItems.size
fun getFooterCount() = footerItems.size()
fun getFooterCount() = footerItems.size
fun getItem(position: Int): ITEM? = items.getOrNull(position)
@@ -431,6 +446,15 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
}
}
fun upResumed(isResumed: Boolean) {
if (!isResumed) {
diffJob?.cancel()
diffJob = null
handler.removeCallbacksAndMessages(null)
}
this.isResumed = isResumed
}
private fun isHeader(position: Int) = position < getHeaderCount()
private fun isFooter(position: Int) = position >= getActualItemCount() + getHeaderCount()
@@ -479,8 +503,4 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
private val handler by lazy { buildMainHandler() }
}
}
}
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.SubMenu
import android.view.WindowManager
import androidx.activity.viewModels
@@ -31,8 +30,7 @@ import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.help.DirectLinkUpload
import io.legado.app.help.config.LocalConfig
import io.legado.app.lib.dialogs.alert
//import io.legado.app.lib.theme.primaryColor
//import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.lib.theme.primaryColor
import io.legado.app.model.CheckSource
import io.legado.app.model.Debug
import io.legado.app.ui.association.ImportBookSourceDialog
@@ -53,13 +51,12 @@ import io.legado.app.utils.cnCompare
import io.legado.app.utils.dpToPx
import io.legado.app.utils.flowWithLifecycleAndDatabaseChange
import io.legado.app.utils.flowWithLifecycleAndDatabaseChangeFirst
import io.legado.app.utils.hideSoftInput
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.launch
import io.legado.app.utils.observeEvent
import io.legado.app.utils.sendToClip
import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.share
import io.legado.app.utils.shouldHideSoftInput
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.showHelp
import io.legado.app.utils.splitNotBlank
@@ -272,7 +269,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
}
private fun initRecyclerView() {
//binding.recyclerView.setEdgeEffectColor(primaryColor)
binding.recyclerView.setEdgeEffectColor(primaryColor)
binding.recyclerView.addItemDecoration(VerticalDivider(this))
binding.recyclerView.adapter = adapter
binding.recyclerView.recycledViewPool.setMaxRecycledViews(0, 15)
@@ -286,7 +283,6 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
}
private fun initSearchView() {
//searchView.applyTint(primaryTextColor)
searchView.queryHint = getString(R.string.search_book_source)
searchView.setOnQueryTextListener(this)
}
@@ -394,6 +390,16 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
}
}
override fun onResume() {
super.onResume()
adapter.upResumed(true)
}
override fun onPause() {
adapter.upResumed(false)
super.onPause()
}
private fun initLiveDataGroup() {
lifecycleScope.launch {
appDb.bookSourceDao.flowGroups()
@@ -472,11 +478,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
searchView.query?.toString(),
sortAscending,
sort
) { file ->
) { file, name ->
exportDir.launch {
mode = HandleFileContract.EXPORT
fileData = HandleFileContract.FileData(
"bookSource.json",
name,
file,
"application/json"
)
@@ -488,8 +494,8 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
searchView.query?.toString(),
sortAscending,
sort
) {
share(it)
) { file, name ->
share(file)
}
R.id.menu_check_selected_interval -> adapter.checkSelectedInterval()
@@ -594,7 +600,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
?.toMutableList() ?: mutableListOf()
alert(titleResource = R.string.import_on_line) {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
editLayout.hint = "URL"
editView.hint = "url"
editView.setFilterValues(cacheUrls)
editView.delCallBack = {
cacheUrls.remove(it)
@@ -12,6 +12,7 @@ import io.legado.app.help.source.SourceHelp
import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.cnCompare
import io.legado.app.utils.normalizeFileName
import io.legado.app.utils.outputStream
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.stackTraceStr
@@ -19,6 +20,8 @@ import io.legado.app.utils.toastOnUi
import io.legado.app.utils.writeToOutputStream
import splitties.init.appCtx
import java.io.File
import java.util.Date
import java.util.Locale
/**
* 书源管理数据修改
@@ -123,7 +126,11 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
}
}
private fun saveToFile(sources: List<BookSource>, success: (file: File) -> Unit) {
private fun saveToFile(
sources: List<BookSource>,
name: String,
success: (file: File, name: String) -> Unit
) {
execute {
val path = "${context.filesDir}/shareBookSource.json"
FileUtils.delete(path)
@@ -133,7 +140,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
}
file
}.onSuccess {
success.invoke(it)
success.invoke(it, name)
}.onError {
context.toastOnUi(it.stackTraceStr)
}
@@ -144,11 +151,12 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
searchKey: String?,
sortAscending: Boolean,
sort: BookSourceSort,
success: (file: File) -> Unit
success: (file: File, name: String) -> Unit
) {
execute {
val selection = adapter.selection
val selectedRate = selection.size.toFloat() / adapter.itemCount.toFloat()
val selectionSize = selection.size
val selectedRate = selectionSize.toFloat() / adapter.itemCount.toFloat()
val sources = if (selectedRate == 1f) {
getBookSources(searchKey, sortAscending, sort)
} else if (selectedRate < 0.3) {
@@ -160,7 +168,14 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
keys.contains(it.bookSourceUrl)
}
}
saveToFile(sources, success)
val name = if (selectionSize == 1) {
"bookSource_${selection.first().bookSourceName.normalizeFileName()}.json"
} else {
val timestamp =
java.text.SimpleDateFormat("yyyyMMddHHmm", Locale.getDefault()).format(Date())
"bookSource_$timestamp.json"
}
saveToFile(sources, name, success)
}
}