[修复] 偶现的无法退出书源管理的问题
This commit is contained in:
@@ -5,6 +5,7 @@ import android.content.Context
|
|||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.util.size
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
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 diffJob: Coroutine<*>? = null
|
||||||
|
|
||||||
|
private var isResumed = false
|
||||||
|
|
||||||
var itemAnimation: ItemAnimation? = null
|
var itemAnimation: ItemAnimation? = null
|
||||||
|
|
||||||
fun setOnItemClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Unit) {
|
fun setOnItemClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Unit) {
|
||||||
@@ -54,8 +57,8 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun addHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) {
|
fun addHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val index = headerItems.size()
|
val index = headerItems.size
|
||||||
headerItems.put(TYPE_HEADER_VIEW + headerItems.size(), header)
|
headerItems.put(TYPE_HEADER_VIEW + headerItems.size, header)
|
||||||
notifyItemInserted(index)
|
notifyItemInserted(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,8 +66,8 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun addFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {
|
fun addFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val index = getActualItemCount() + footerItems.size()
|
val index = getActualItemCount() + footerItems.size
|
||||||
footerItems.put(TYPE_FOOTER_VIEW + footerItems.size(), footer)
|
footerItems.put(TYPE_FOOTER_VIEW + footerItems.size, footer)
|
||||||
notifyItemInserted(index)
|
notifyItemInserted(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,6 +116,10 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
|
|||||||
skipDiff: Boolean = false
|
skipDiff: Boolean = false
|
||||||
) {
|
) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
|
if (!isResumed) { //全量标记更新
|
||||||
|
setItems(items)
|
||||||
|
return@runCatching
|
||||||
|
}
|
||||||
val oldItems = this.items.toList()
|
val oldItems = this.items.toList()
|
||||||
val itemsSize = items?.size ?: 0
|
val itemsSize = items?.size ?: 0
|
||||||
val headerCount = getHeaderCount()
|
val headerCount = getHeaderCount()
|
||||||
@@ -153,6 +160,10 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
|
|||||||
return itemCallback.getChangePayload(oldItem, newItem)
|
return itemCallback.getChangePayload(oldItem, newItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!isResumed) {
|
||||||
|
setItems(items)
|
||||||
|
return@runCatching
|
||||||
|
}
|
||||||
diffJob?.cancel()
|
diffJob?.cancel()
|
||||||
diffJob = Coroutine.async {
|
diffJob = Coroutine.async {
|
||||||
val diffResult = if (skipDiff) withTimeoutOrNullAsync(500L) {
|
val diffResult = if (skipDiff) withTimeoutOrNullAsync(500L) {
|
||||||
@@ -162,7 +173,7 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
|
|||||||
}
|
}
|
||||||
ensureActive()
|
ensureActive()
|
||||||
handler.post {
|
handler.post {
|
||||||
if (diffResult == null) {
|
if (isResumed || diffResult == null) {
|
||||||
setItems(items)
|
setItems(items)
|
||||||
return@post
|
return@post
|
||||||
}
|
}
|
||||||
@@ -172,6 +183,10 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
|
|||||||
if (items != null) {
|
if (items != null) {
|
||||||
this@RecyclerAdapter.items.addAll(items)
|
this@RecyclerAdapter.items.addAll(items)
|
||||||
}
|
}
|
||||||
|
if (!isResumed) {
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
ensureActive()
|
||||||
diffResult.dispatchUpdatesTo(this@RecyclerAdapter)
|
diffResult.dispatchUpdatesTo(this@RecyclerAdapter)
|
||||||
onCurrentListChanged()
|
onCurrentListChanged()
|
||||||
}
|
}
|
||||||
@@ -329,10 +344,10 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
|
|||||||
fun getActualItemCount() = items.size
|
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)
|
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 isHeader(position: Int) = position < getHeaderCount()
|
||||||
|
|
||||||
private fun isFooter(position: Int) = position >= getActualItemCount() + 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() }
|
private val handler by lazy { buildMainHandler() }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.SubMenu
|
import android.view.SubMenu
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.activity.viewModels
|
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.DirectLinkUpload
|
||||||
import io.legado.app.help.config.LocalConfig
|
import io.legado.app.help.config.LocalConfig
|
||||||
import io.legado.app.lib.dialogs.alert
|
import io.legado.app.lib.dialogs.alert
|
||||||
//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.model.CheckSource
|
import io.legado.app.model.CheckSource
|
||||||
import io.legado.app.model.Debug
|
import io.legado.app.model.Debug
|
||||||
import io.legado.app.ui.association.ImportBookSourceDialog
|
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.dpToPx
|
||||||
import io.legado.app.utils.flowWithLifecycleAndDatabaseChange
|
import io.legado.app.utils.flowWithLifecycleAndDatabaseChange
|
||||||
import io.legado.app.utils.flowWithLifecycleAndDatabaseChangeFirst
|
import io.legado.app.utils.flowWithLifecycleAndDatabaseChangeFirst
|
||||||
import io.legado.app.utils.hideSoftInput
|
|
||||||
import io.legado.app.utils.isAbsUrl
|
import io.legado.app.utils.isAbsUrl
|
||||||
import io.legado.app.utils.launch
|
import io.legado.app.utils.launch
|
||||||
import io.legado.app.utils.observeEvent
|
import io.legado.app.utils.observeEvent
|
||||||
import io.legado.app.utils.sendToClip
|
import io.legado.app.utils.sendToClip
|
||||||
|
import io.legado.app.utils.setEdgeEffectColor
|
||||||
import io.legado.app.utils.share
|
import io.legado.app.utils.share
|
||||||
import io.legado.app.utils.shouldHideSoftInput
|
|
||||||
import io.legado.app.utils.showDialogFragment
|
import io.legado.app.utils.showDialogFragment
|
||||||
import io.legado.app.utils.showHelp
|
import io.legado.app.utils.showHelp
|
||||||
import io.legado.app.utils.splitNotBlank
|
import io.legado.app.utils.splitNotBlank
|
||||||
@@ -272,7 +269,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initRecyclerView() {
|
private fun initRecyclerView() {
|
||||||
//binding.recyclerView.setEdgeEffectColor(primaryColor)
|
binding.recyclerView.setEdgeEffectColor(primaryColor)
|
||||||
binding.recyclerView.addItemDecoration(VerticalDivider(this))
|
binding.recyclerView.addItemDecoration(VerticalDivider(this))
|
||||||
binding.recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
binding.recyclerView.recycledViewPool.setMaxRecycledViews(0, 15)
|
binding.recyclerView.recycledViewPool.setMaxRecycledViews(0, 15)
|
||||||
@@ -286,7 +283,6 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initSearchView() {
|
private fun initSearchView() {
|
||||||
//searchView.applyTint(primaryTextColor)
|
|
||||||
searchView.queryHint = getString(R.string.search_book_source)
|
searchView.queryHint = getString(R.string.search_book_source)
|
||||||
searchView.setOnQueryTextListener(this)
|
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() {
|
private fun initLiveDataGroup() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
appDb.bookSourceDao.flowGroups()
|
appDb.bookSourceDao.flowGroups()
|
||||||
@@ -472,11 +478,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
searchView.query?.toString(),
|
searchView.query?.toString(),
|
||||||
sortAscending,
|
sortAscending,
|
||||||
sort
|
sort
|
||||||
) { file ->
|
) { file, name ->
|
||||||
exportDir.launch {
|
exportDir.launch {
|
||||||
mode = HandleFileContract.EXPORT
|
mode = HandleFileContract.EXPORT
|
||||||
fileData = HandleFileContract.FileData(
|
fileData = HandleFileContract.FileData(
|
||||||
"bookSource.json",
|
name,
|
||||||
file,
|
file,
|
||||||
"application/json"
|
"application/json"
|
||||||
)
|
)
|
||||||
@@ -488,8 +494,8 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
searchView.query?.toString(),
|
searchView.query?.toString(),
|
||||||
sortAscending,
|
sortAscending,
|
||||||
sort
|
sort
|
||||||
) {
|
) { file, name ->
|
||||||
share(it)
|
share(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_check_selected_interval -> adapter.checkSelectedInterval()
|
R.id.menu_check_selected_interval -> adapter.checkSelectedInterval()
|
||||||
@@ -594,7 +600,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
?.toMutableList() ?: mutableListOf()
|
?.toMutableList() ?: mutableListOf()
|
||||||
alert(titleResource = R.string.import_on_line) {
|
alert(titleResource = R.string.import_on_line) {
|
||||||
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
|
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
|
||||||
editLayout.hint = "URL"
|
editView.hint = "url"
|
||||||
editView.setFilterValues(cacheUrls)
|
editView.setFilterValues(cacheUrls)
|
||||||
editView.delCallBack = {
|
editView.delCallBack = {
|
||||||
cacheUrls.remove(it)
|
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.FileUtils
|
||||||
import io.legado.app.utils.GSON
|
import io.legado.app.utils.GSON
|
||||||
import io.legado.app.utils.cnCompare
|
import io.legado.app.utils.cnCompare
|
||||||
|
import io.legado.app.utils.normalizeFileName
|
||||||
import io.legado.app.utils.outputStream
|
import io.legado.app.utils.outputStream
|
||||||
import io.legado.app.utils.splitNotBlank
|
import io.legado.app.utils.splitNotBlank
|
||||||
import io.legado.app.utils.stackTraceStr
|
import io.legado.app.utils.stackTraceStr
|
||||||
@@ -19,6 +20,8 @@ import io.legado.app.utils.toastOnUi
|
|||||||
import io.legado.app.utils.writeToOutputStream
|
import io.legado.app.utils.writeToOutputStream
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.File
|
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 {
|
execute {
|
||||||
val path = "${context.filesDir}/shareBookSource.json"
|
val path = "${context.filesDir}/shareBookSource.json"
|
||||||
FileUtils.delete(path)
|
FileUtils.delete(path)
|
||||||
@@ -133,7 +140,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
|
|||||||
}
|
}
|
||||||
file
|
file
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
success.invoke(it)
|
success.invoke(it, name)
|
||||||
}.onError {
|
}.onError {
|
||||||
context.toastOnUi(it.stackTraceStr)
|
context.toastOnUi(it.stackTraceStr)
|
||||||
}
|
}
|
||||||
@@ -144,11 +151,12 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
|
|||||||
searchKey: String?,
|
searchKey: String?,
|
||||||
sortAscending: Boolean,
|
sortAscending: Boolean,
|
||||||
sort: BookSourceSort,
|
sort: BookSourceSort,
|
||||||
success: (file: File) -> Unit
|
success: (file: File, name: String) -> Unit
|
||||||
) {
|
) {
|
||||||
execute {
|
execute {
|
||||||
val selection = adapter.selection
|
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) {
|
val sources = if (selectedRate == 1f) {
|
||||||
getBookSources(searchKey, sortAscending, sort)
|
getBookSources(searchKey, sortAscending, sort)
|
||||||
} else if (selectedRate < 0.3) {
|
} else if (selectedRate < 0.3) {
|
||||||
@@ -160,7 +168,14 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
|
|||||||
keys.contains(it.bookSourceUrl)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user