From e5da28d92b4a32aa39fd8dc9e996fe0115cf1581 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Thu, 23 Mar 2023 16:26:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/base/adapter/RecyclerAdapter.kt | 7 +- .../java/io/legado/app/constant/BookType.kt | 2 +- .../io/legado/app/data/dao/TxtTocRuleDao.kt | 3 + .../java/io/legado/app/model/CheckSource.kt | 3 +- .../legado/app/model/localBook/LocalBook.kt | 15 ++-- .../io/legado/app/model/localBook/TextFile.kt | 2 +- .../app/service/BaseReadAloudService.kt | 3 +- .../legado/app/service/CheckSourceService.kt | 5 +- .../app/service/HttpReadAloudService.kt | 9 ++- .../ui/book/import/BaseImportBookActivity.kt | 6 +- .../book/import/remote/RemoteBookActivity.kt | 8 +++ .../book/import/remote/RemoteBookViewModel.kt | 5 ++ .../book/source/manage/BookSourceAdapter.kt | 37 +++++----- .../app/ui/book/toc/rule/TxtTocRuleDialog.kt | 68 +++++++++++++++---- 14 files changed, 120 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/io/legado/app/base/adapter/RecyclerAdapter.kt b/app/src/main/java/io/legado/app/base/adapter/RecyclerAdapter.kt index 5ef66fbe5..d47781403 100644 --- a/app/src/main/java/io/legado/app/base/adapter/RecyclerAdapter.kt +++ b/app/src/main/java/io/legado/app/base/adapter/RecyclerAdapter.kt @@ -108,6 +108,7 @@ abstract class RecyclerAdapter(protected val context: Co @Synchronized fun setItems(items: List?, itemCallback: DiffUtil.ItemCallback) { kotlin.runCatching { + val oldItems = this.items.toList() val callback = object : DiffUtil.Callback() { override fun getOldListSize(): Int { return itemCount @@ -118,7 +119,7 @@ abstract class RecyclerAdapter(protected val context: Co } override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - val oldItem = getItem(oldItemPosition - getHeaderCount()) + val oldItem = oldItems.getOrNull(oldItemPosition - getHeaderCount()) ?: return true val newItem = items?.getOrNull(newItemPosition - getHeaderCount()) ?: return true @@ -129,7 +130,7 @@ abstract class RecyclerAdapter(protected val context: Co oldItemPosition: Int, newItemPosition: Int ): Boolean { - val oldItem = getItem(oldItemPosition - getHeaderCount()) + val oldItem = oldItems.getOrNull(oldItemPosition - getHeaderCount()) ?: return true val newItem = items?.getOrNull(newItemPosition - getHeaderCount()) ?: return true @@ -137,7 +138,7 @@ abstract class RecyclerAdapter(protected val context: Co } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { - val oldItem = getItem(oldItemPosition - getHeaderCount()) + val oldItem = oldItems.getOrNull(oldItemPosition - getHeaderCount()) ?: return null val newItem = items?.getOrNull(newItemPosition - getHeaderCount()) ?: return null diff --git a/app/src/main/java/io/legado/app/constant/BookType.kt b/app/src/main/java/io/legado/app/constant/BookType.kt index c936a9408..5cf3a3fef 100644 --- a/app/src/main/java/io/legado/app/constant/BookType.kt +++ b/app/src/main/java/io/legado/app/constant/BookType.kt @@ -44,7 +44,7 @@ object BookType { @Target(AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.SOURCE) - @IntDef(text, updateError, audio, image, webFile, local) + @IntDef(text, updateError, audio, image, webFile, local, archive) annotation class Type diff --git a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt index 9e45ad26f..32981d1d4 100644 --- a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt @@ -19,6 +19,9 @@ interface TxtTocRuleDao { @get:Query("select * from txtTocRules where enable != 1 order by serialNumber") val disabled: List + @get:Query("select count(*) from txtTocRules") + val count: Int + @Query("select * from txtTocRules where id = :id") fun get(id: Long): TxtTocRule? diff --git a/app/src/main/java/io/legado/app/model/CheckSource.kt b/app/src/main/java/io/legado/app/model/CheckSource.kt index c43aafe33..8ca2f5bd6 100644 --- a/app/src/main/java/io/legado/app/model/CheckSource.kt +++ b/app/src/main/java/io/legado/app/model/CheckSource.kt @@ -5,6 +5,7 @@ import io.legado.app.R import io.legado.app.constant.IntentAction import io.legado.app.data.entities.BookSource import io.legado.app.help.CacheManager +import io.legado.app.help.IntentData import io.legado.app.service.CheckSourceService import io.legado.app.utils.startService import splitties.init.appCtx @@ -26,9 +27,9 @@ object CheckSource { sources.map { selectedIds.add(it.bookSourceUrl) } + IntentData.put("checkSourceSelectedIds", selectedIds) context.startService { action = IntentAction.start - putExtra("selectIds", selectedIds) } } diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index 08d84124a..69759c70b 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -212,11 +212,8 @@ object LocalBook { val files = ArchiveUtils.deCompress(archiveFileDoc, filter = filter) if (files.isEmpty()) throw NoStackTraceException(appCtx.getString(R.string.unsupport_archivefile_entry)) return files.map { - saveBookFile( - FileInputStream(it), - saveFileName ?: it.name - ).let { - importFile(it).apply { + saveBookFile(FileInputStream(it), saveFileName ?: it.name).let { uri -> + importFile(uri).apply { //附加压缩包名称 以便解压文件被删后再解压 origin = "${BookType.localTag}::${archiveFileDoc.name}" addType(BookType.archive) @@ -255,8 +252,8 @@ object LocalBook { importFile(uri) } }.onFailure { - AppLog.put("ImportFile Error:\nFile ${fileDoc.toString()}\n${it.localizedMessage}", it) - errorCount = errorCount + 1 + AppLog.put("ImportFile Error:\nFile $fileDoc\n${it.localizedMessage}", it) + errorCount += 1 } } if (errorCount == uris.size) throw NoStackTraceException("ImportFiles Error:\nAll input files occur error") @@ -409,8 +406,8 @@ object LocalBook { if (localBook.isArchive) { // 压缩包 val archiveUri = saveBookFile(it, localBook.archiveName) - val newBook = importArchiveFile(archiveUri, localBook.originName) { - it.contains(localBook.originName) + val newBook = importArchiveFile(archiveUri, localBook.originName) { name -> + name.contains(localBook.originName) }.first() localBook.origin = newBook.origin localBook.bookUrl = newBook.bookUrl diff --git a/app/src/main/java/io/legado/app/model/localBook/TextFile.kt b/app/src/main/java/io/legado/app/model/localBook/TextFile.kt index 0ce1a45f6..368709b0c 100644 --- a/app/src/main/java/io/legado/app/model/localBook/TextFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/TextFile.kt @@ -407,7 +407,7 @@ class TextFile(private val book: Book) { */ private fun getTocRules(): List { var rules = appDb.txtTocRuleDao.enabled - if (rules.isEmpty()) { + if (appDb.txtTocRuleDao.count == 0) { rules = DefaultData.txtTocRules.apply { appDb.txtTocRuleDao.insert(*this.toTypedArray()) }.filter { diff --git a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt index 21aa2af21..92f2b6184 100644 --- a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt @@ -78,6 +78,7 @@ abstract class BaseReadAloudService : BaseService(), internal var pageIndex = 0 private var needResumeOnAudioFocusGain = false private var dsJob: Job? = null + var pageChanged = false private val broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -157,7 +158,7 @@ abstract class BaseReadAloudService : BaseService(), contentList.add(text) } } - if (play) play() + if (play) play() else pageChanged = true } } diff --git a/app/src/main/java/io/legado/app/service/CheckSourceService.kt b/app/src/main/java/io/legado/app/service/CheckSourceService.kt index aa7aa924c..abc8cef76 100644 --- a/app/src/main/java/io/legado/app/service/CheckSourceService.kt +++ b/app/src/main/java/io/legado/app/service/CheckSourceService.kt @@ -15,6 +15,7 @@ import io.legado.app.data.entities.BookSource import io.legado.app.exception.ContentEmptyException import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.TocEmptyException +import io.legado.app.help.IntentData import io.legado.app.help.config.AppConfig import io.legado.app.help.source.exploreKinds import io.legado.app.model.CheckSource @@ -65,12 +66,12 @@ class CheckSourceService : BaseService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { - IntentAction.start -> intent.getStringArrayListExtra("selectIds")?.let { + IntentAction.start -> IntentData.get>("checkSourceSelectedIds")?.let { check(it) } IntentAction.resume -> upNotification() - else -> stopSelf() + IntentAction.stop -> stopSelf() } return super.onStartCommand(intent, flags, startId) } diff --git a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt index 23b103832..bb0256b96 100644 --- a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt @@ -62,6 +62,7 @@ class HttpReadAloudService : BaseReadAloudService(), } override fun play() { + pageChanged = false exoPlayer.stop() if (contentList.isEmpty()) { AppLog.putDebug("朗读列表为空") @@ -285,8 +286,12 @@ class HttpReadAloudService : BaseReadAloudService(), override fun resumeReadAloud() { super.resumeReadAloud() kotlin.runCatching { - exoPlayer.play() - upPlayPos() + if (pageChanged) { + play() + } else { + exoPlayer.play() + upPlayPos() + } } } diff --git a/app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt index 796cb077e..50f1b9141 100644 --- a/app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt @@ -32,7 +32,7 @@ abstract class BaseImportBookActivity : VMBaseActivity AppConfig.defaultBookTreeUri = treeUri.toString() localBookTreeSelectListener?.invoke(true) @@ -61,10 +61,12 @@ abstract class BaseImportBookActivity : VMBaseActivity localBookTreeSelectListener = { + localBookTreeSelectListener = null block.resume(it) } //测试书籍保存位置是否设置 if (!AppConfig.defaultBookTreeUri.isNullOrBlank()) { + localBookTreeSelectListener = null block.resume(true) return@suspendCoroutine } @@ -78,9 +80,11 @@ abstract class BaseImportBookActivity : VMBaseActivity(), } } + override fun observeLiveBus() { + viewModel.permissionDenialLiveData.observe(this) { + localBookTreeSelect.launch { + title = getString(R.string.select_book_folder) + } + } + } + private fun initView() { binding.layTop.setBackgroundColor(backgroundColor) binding.recyclerView.layoutManager = LinearLayoutManager(this) diff --git a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookViewModel.kt index eeabc5a5f..b670934d7 100644 --- a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookViewModel.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.book.import.remote import android.app.Application +import androidx.lifecycle.MutableLiveData import io.legado.app.base.BaseViewModel import io.legado.app.constant.AppLog import io.legado.app.constant.BookType @@ -25,6 +26,7 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application) var sortKey = RemoteBookSort.Default var sortAscending = false val dirList = arrayListOf() + val permissionDenialLiveData = MutableLiveData() var dataCallback: DataCallback? = null @@ -141,6 +143,9 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application) }.onError { AppLog.put("导入出错\n${it.localizedMessage}", it) context.toastOnUi("导入出错\n${it.localizedMessage}") + if (it is SecurityException) { + permissionDenialLiveData.postValue(1) + } }.onFinally { finally.invoke() } diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt index d17c19b09..7ae9e1a8f 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt @@ -32,6 +32,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : ItemTouchCallback.Callback { private val selected = linkedSetOf() + private val finalMessageRegex = Regex("成功|失败") val selection: List get() { @@ -94,9 +95,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : cbBookSource.text = item.getDisPlayNameGroup() swtEnabled.isChecked = item.enabled cbBookSource.isChecked = selected.contains(item) - ivDebugText.text = Debug.debugMessageMap[item.bookSourceUrl] ?: "" - ivDebugText.visibility = - if (ivDebugText.text.toString().isNotBlank()) View.VISIBLE else View.GONE + upCheckSourceMessage(binding, item) upShowExplore(ivExplore, item) } else { payload.keySet().map { @@ -105,21 +104,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : "upName" -> cbBookSource.text = item.getDisPlayNameGroup() "upExplore" -> upShowExplore(ivExplore, item) "selected" -> cbBookSource.isChecked = selected.contains(item) - "checkSourceMessage" -> { - ivDebugText.text = Debug.debugMessageMap[item.bookSourceUrl] ?: "" - val isEmpty = ivDebugText.text.toString().isEmpty() - var isFinalMessage = - ivDebugText.text.toString().contains(Regex("成功|失败")) - if (!Debug.isChecking && !isFinalMessage) { - Debug.updateFinalMessage(item.bookSourceUrl, "校验失败") - ivDebugText.text = Debug.debugMessageMap[item.bookSourceUrl] ?: "" - isFinalMessage = true - } - ivDebugText.visibility = - if (!isEmpty) View.VISIBLE else View.GONE - ivProgressBar.visibility = - if (isFinalMessage || isEmpty || !Debug.isChecking) View.GONE else View.VISIBLE - } + "checkSourceMessage" -> upCheckSourceMessage(binding, item) } } } @@ -220,6 +205,22 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : } } + private fun upCheckSourceMessage(binding: ItemBookSourceBinding, item: BookSource) = binding.run { + val msg = Debug.debugMessageMap[item.bookSourceUrl] ?: "" + ivDebugText.text = msg + val isEmpty = msg.isEmpty() + var isFinalMessage = msg.contains(finalMessageRegex) + if (!Debug.isChecking && !isFinalMessage) { + Debug.updateFinalMessage(item.bookSourceUrl, "校验失败") + ivDebugText.text = Debug.debugMessageMap[item.bookSourceUrl] ?: "" + isFinalMessage = true + } + ivDebugText.visibility = + if (!isEmpty) View.VISIBLE else View.GONE + ivProgressBar.visibility = + if (isFinalMessage || isEmpty || !Debug.isChecking) View.GONE else View.VISIBLE + } + fun selectAll() { getItems().forEach { selected.add(it) diff --git a/app/src/main/java/io/legado/app/ui/book/toc/rule/TxtTocRuleDialog.kt b/app/src/main/java/io/legado/app/ui/book/toc/rule/TxtTocRuleDialog.kt index bf9c2cae6..7e6113d96 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/rule/TxtTocRuleDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/rule/TxtTocRuleDialog.kt @@ -7,7 +7,9 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.Toolbar +import androidx.core.os.bundleOf import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import io.legado.app.R @@ -30,7 +32,6 @@ import io.legado.app.ui.widget.recycler.ItemTouchCallback import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.* import io.legado.app.utils.viewbindingdelegate.viewBinding -import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.launch @@ -108,7 +109,7 @@ class TxtTocRuleDialog() : BaseDialogFragment(R.layout.dialog_toc_regex), launch { appDb.txtTocRuleDao.observeAll().conflate().collect { tocRules -> initSelectedName(tocRules) - adapter.setItems(tocRules) + adapter.setItems(tocRules, adapter.diffItemCallBack) } } } @@ -191,6 +192,43 @@ class TxtTocRuleDialog() : BaseDialogFragment(R.layout.dialog_toc_regex), RecyclerAdapter(context), ItemTouchCallback.Callback { + val diffItemCallBack = object : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: TxtTocRule, newItem: TxtTocRule): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: TxtTocRule, newItem: TxtTocRule): Boolean { + if (oldItem.name != newItem.name) { + return false + } + if (oldItem.enable != newItem.enable) { + return false + } + if (oldItem.example != newItem.example) { + return false + } + return true + } + + override fun getChangePayload(oldItem: TxtTocRule, newItem: TxtTocRule): Any? { + val payload = Bundle() + if (oldItem.name != newItem.name) { + payload.putBoolean("upName", true) + } + if (oldItem.enable != newItem.enable) { + payload.putBoolean("enabled", newItem.enable) + } + if (oldItem.example != newItem.example) { + payload.putBoolean("upExample", true) + } + if (payload.isEmpty) { + return null + } + return payload + } + } + override fun getViewBinding(parent: ViewGroup): ItemTocRegexBinding { return ItemTocRegexBinding.inflate(inflater, parent, false) } @@ -202,14 +240,22 @@ class TxtTocRuleDialog() : BaseDialogFragment(R.layout.dialog_toc_regex), payloads: MutableList ) { binding.apply { - if (payloads.isEmpty()) { + val bundle = payloads.getOrNull(0) as? Bundle + if (bundle == null) { root.setBackgroundColor(context.backgroundColor) rbRegexName.text = item.name titleExample.text = item.example rbRegexName.isChecked = item.name == selectedName swtEnabled.isChecked = item.enable } else { - rbRegexName.isChecked = item.name == selectedName + bundle.keySet().map { + when (it) { + "upNmae" -> rbRegexName.text = item.name + "upExample" -> titleExample.text = item.example + "enabled" -> swtEnabled.isChecked = item.enable + "upSelect" -> rbRegexName.isChecked = item.name == selectedName + } + } } } } @@ -219,16 +265,14 @@ class TxtTocRuleDialog() : BaseDialogFragment(R.layout.dialog_toc_regex), rbRegexName.setOnCheckedChangeListener { buttonView, isChecked -> if (buttonView.isPressed && isChecked) { selectedName = getItem(holder.layoutPosition)?.name - updateItems(0, itemCount - 1, true) + updateItems(0, itemCount - 1, bundleOf("upSelect" to null)) } } swtEnabled.setOnCheckedChangeListener { buttonView, isChecked -> if (buttonView.isPressed) { getItem(holder.layoutPosition)?.let { it.enable = isChecked - launch(IO) { - appDb.txtTocRuleDao.update(it) - } + viewModel.update(it) } } } @@ -237,9 +281,7 @@ class TxtTocRuleDialog() : BaseDialogFragment(R.layout.dialog_toc_regex), } ivDelete.setOnClickListener { getItem(holder.layoutPosition)?.let { item -> - launch(IO) { - appDb.txtTocRuleDao.delete(item) - } + viewModel.del(item) } } } @@ -259,9 +301,7 @@ class TxtTocRuleDialog() : BaseDialogFragment(R.layout.dialog_toc_regex), for ((index, item) in getItems().withIndex()) { item.serialNumber = index + 1 } - launch(IO) { - appDb.txtTocRuleDao.update(*getItems().toTypedArray()) - } + viewModel.update(*getItems().toTypedArray()) } isMoved = false }