This commit is contained in:
Horis
2023-03-23 16:26:33 +08:00
parent ea0b28a9d6
commit e5da28d92b
14 changed files with 120 additions and 53 deletions

View File

@@ -108,6 +108,7 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
@Synchronized
fun setItems(items: List<ITEM>?, itemCallback: DiffUtil.ItemCallback<ITEM>) {
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<ITEM, VB : ViewBinding>(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<ITEM, VB : ViewBinding>(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<ITEM, VB : ViewBinding>(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

View File

@@ -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

View File

@@ -19,6 +19,9 @@ interface TxtTocRuleDao {
@get:Query("select * from txtTocRules where enable != 1 order by serialNumber")
val disabled: List<TxtTocRule>
@get:Query("select count(*) from txtTocRules")
val count: Int
@Query("select * from txtTocRules where id = :id")
fun get(id: Long): TxtTocRule?

View File

@@ -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<CheckSourceService> {
action = IntentAction.start
putExtra("selectIds", selectedIds)
}
}

View File

@@ -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

View File

@@ -407,7 +407,7 @@ class TextFile(private val book: Book) {
*/
private fun getTocRules(): List<TxtTocRule> {
var rules = appDb.txtTocRuleDao.enabled
if (rules.isEmpty()) {
if (appDb.txtTocRuleDao.count == 0) {
rules = DefaultData.txtTocRules.apply {
appDb.txtTocRuleDao.insert(*this.toTypedArray())
}.filter {

View File

@@ -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
}
}

View File

@@ -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<ArrayList<String>>("checkSourceSelectedIds")?.let {
check(it)
}
IntentAction.resume -> upNotification()
else -> stopSelf()
IntentAction.stop -> stopSelf()
}
return super.onStartCommand(intent, flags, startId)
}

View File

@@ -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()
}
}
}

View File

@@ -32,7 +32,7 @@ abstract class BaseImportBookActivity<VM : ViewModel> : VMBaseActivity<ActivityI
binding.titleBar.findViewById(R.id.search_view)
}
private val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {
val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {
it.uri?.let { treeUri ->
AppConfig.defaultBookTreeUri = treeUri.toString()
localBookTreeSelectListener?.invoke(true)
@@ -61,10 +61,12 @@ abstract class BaseImportBookActivity<VM : ViewModel> : VMBaseActivity<ActivityI
*/
protected suspend fun setBookStorage() = suspendCoroutine { block ->
localBookTreeSelectListener = {
localBookTreeSelectListener = null
block.resume(it)
}
//测试书籍保存位置是否设置
if (!AppConfig.defaultBookTreeUri.isNullOrBlank()) {
localBookTreeSelectListener = null
block.resume(true)
return@suspendCoroutine
}
@@ -78,9 +80,11 @@ abstract class BaseImportBookActivity<VM : ViewModel> : VMBaseActivity<ActivityI
}
}
noButton {
localBookTreeSelectListener = null
block.resume(false)
}
onCancelled {
localBookTreeSelectListener = null
block.resume(false)
}
}

View File

@@ -64,6 +64,14 @@ class RemoteBookActivity : BaseImportBookActivity<RemoteBookViewModel>(),
}
}
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)

View File

@@ -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<RemoteBook>()
val permissionDenialLiveData = MutableLiveData<Int>()
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()
}

View File

@@ -32,6 +32,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
ItemTouchCallback.Callback {
private val selected = linkedSetOf<BookSource>()
private val finalMessageRegex = Regex("成功|失败")
val selection: List<BookSource>
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)

View File

@@ -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<TxtTocRule, ItemTocRegexBinding>(context),
ItemTouchCallback.Callback {
val diffItemCallBack = object : DiffUtil.ItemCallback<TxtTocRule>() {
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<Any>
) {
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
}