Horis 3 jaren geleden
bovenliggende
commit
63e1334de9

+ 4 - 3
app/src/main/java/io/legado/app/base/adapter/RecyclerAdapter.kt

@@ -108,6 +108,7 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
     @Synchronized
     @Synchronized
     fun setItems(items: List<ITEM>?, itemCallback: DiffUtil.ItemCallback<ITEM>) {
     fun setItems(items: List<ITEM>?, itemCallback: DiffUtil.ItemCallback<ITEM>) {
         kotlin.runCatching {
         kotlin.runCatching {
+            val oldItems = this.items.toList()
             val callback = object : DiffUtil.Callback() {
             val callback = object : DiffUtil.Callback() {
                 override fun getOldListSize(): Int {
                 override fun getOldListSize(): Int {
                     return itemCount
                     return itemCount
@@ -118,7 +119,7 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
                 }
                 }
 
 
                 override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                 override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
-                    val oldItem = getItem(oldItemPosition - getHeaderCount())
+                    val oldItem = oldItems.getOrNull(oldItemPosition - getHeaderCount())
                         ?: return true
                         ?: return true
                     val newItem = items?.getOrNull(newItemPosition - getHeaderCount())
                     val newItem = items?.getOrNull(newItemPosition - getHeaderCount())
                         ?: return true
                         ?: return true
@@ -129,7 +130,7 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
                     oldItemPosition: Int,
                     oldItemPosition: Int,
                     newItemPosition: Int
                     newItemPosition: Int
                 ): Boolean {
                 ): Boolean {
-                    val oldItem = getItem(oldItemPosition - getHeaderCount())
+                    val oldItem = oldItems.getOrNull(oldItemPosition - getHeaderCount())
                         ?: return true
                         ?: return true
                     val newItem = items?.getOrNull(newItemPosition - getHeaderCount())
                     val newItem = items?.getOrNull(newItemPosition - getHeaderCount())
                         ?: return true
                         ?: return true
@@ -137,7 +138,7 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
                 }
                 }
 
 
                 override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
                 override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
-                    val oldItem = getItem(oldItemPosition - getHeaderCount())
+                    val oldItem = oldItems.getOrNull(oldItemPosition - getHeaderCount())
                         ?: return null
                         ?: return null
                     val newItem = items?.getOrNull(newItemPosition - getHeaderCount())
                     val newItem = items?.getOrNull(newItemPosition - getHeaderCount())
                         ?: return null
                         ?: return null

+ 1 - 1
app/src/main/java/io/legado/app/constant/BookType.kt

@@ -44,7 +44,7 @@ object BookType {
 
 
     @Target(AnnotationTarget.VALUE_PARAMETER)
     @Target(AnnotationTarget.VALUE_PARAMETER)
     @Retention(AnnotationRetention.SOURCE)
     @Retention(AnnotationRetention.SOURCE)
-    @IntDef(text, updateError, audio, image, webFile, local)
+    @IntDef(text, updateError, audio, image, webFile, local, archive)
     annotation class Type
     annotation class Type
 
 
 
 

+ 3 - 0
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")
     @get:Query("select * from txtTocRules where enable != 1 order by serialNumber")
     val disabled: List<TxtTocRule>
     val disabled: List<TxtTocRule>
 
 
+    @get:Query("select count(*) from txtTocRules")
+    val count: Int
+
     @Query("select * from txtTocRules where id = :id")
     @Query("select * from txtTocRules where id = :id")
     fun get(id: Long): TxtTocRule?
     fun get(id: Long): TxtTocRule?
 
 

+ 2 - 1
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.constant.IntentAction
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.help.CacheManager
 import io.legado.app.help.CacheManager
+import io.legado.app.help.IntentData
 import io.legado.app.service.CheckSourceService
 import io.legado.app.service.CheckSourceService
 import io.legado.app.utils.startService
 import io.legado.app.utils.startService
 import splitties.init.appCtx
 import splitties.init.appCtx
@@ -26,9 +27,9 @@ object CheckSource {
         sources.map {
         sources.map {
             selectedIds.add(it.bookSourceUrl)
             selectedIds.add(it.bookSourceUrl)
         }
         }
+        IntentData.put("checkSourceSelectedIds", selectedIds)
         context.startService<CheckSourceService> {
         context.startService<CheckSourceService> {
             action = IntentAction.start
             action = IntentAction.start
-            putExtra("selectIds", selectedIds)
         }
         }
     }
     }
 
 

+ 6 - 9
app/src/main/java/io/legado/app/model/localBook/LocalBook.kt

@@ -212,11 +212,8 @@ object LocalBook {
         val files = ArchiveUtils.deCompress(archiveFileDoc, filter = filter)
         val files = ArchiveUtils.deCompress(archiveFileDoc, filter = filter)
         if (files.isEmpty()) throw NoStackTraceException(appCtx.getString(R.string.unsupport_archivefile_entry))
         if (files.isEmpty()) throw NoStackTraceException(appCtx.getString(R.string.unsupport_archivefile_entry))
         return files.map {
         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}"
                     origin = "${BookType.localTag}::${archiveFileDoc.name}"
                     addType(BookType.archive)
                     addType(BookType.archive)
@@ -255,8 +252,8 @@ object LocalBook {
                     importFile(uri)
                     importFile(uri)
                 }
                 }
             }.onFailure {
             }.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")
         if (errorCount == uris.size) throw NoStackTraceException("ImportFiles Error:\nAll input files occur error")
@@ -409,8 +406,8 @@ object LocalBook {
                 if (localBook.isArchive) {
                 if (localBook.isArchive) {
                     // 压缩包
                     // 压缩包
                     val archiveUri = saveBookFile(it, localBook.archiveName)
                     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()
                     }.first()
                     localBook.origin = newBook.origin
                     localBook.origin = newBook.origin
                     localBook.bookUrl = newBook.bookUrl
                     localBook.bookUrl = newBook.bookUrl

+ 1 - 1
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<TxtTocRule> {
     private fun getTocRules(): List<TxtTocRule> {
         var rules = appDb.txtTocRuleDao.enabled
         var rules = appDb.txtTocRuleDao.enabled
-        if (rules.isEmpty()) {
+        if (appDb.txtTocRuleDao.count == 0) {
             rules = DefaultData.txtTocRules.apply {
             rules = DefaultData.txtTocRules.apply {
                 appDb.txtTocRuleDao.insert(*this.toTypedArray())
                 appDb.txtTocRuleDao.insert(*this.toTypedArray())
             }.filter {
             }.filter {

+ 2 - 1
app/src/main/java/io/legado/app/service/BaseReadAloudService.kt

@@ -78,6 +78,7 @@ abstract class BaseReadAloudService : BaseService(),
     internal var pageIndex = 0
     internal var pageIndex = 0
     private var needResumeOnAudioFocusGain = false
     private var needResumeOnAudioFocusGain = false
     private var dsJob: Job? = null
     private var dsJob: Job? = null
+    var pageChanged = false
 
 
     private val broadcastReceiver = object : BroadcastReceiver() {
     private val broadcastReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
         override fun onReceive(context: Context, intent: Intent) {
@@ -157,7 +158,7 @@ abstract class BaseReadAloudService : BaseService(),
                         contentList.add(text)
                         contentList.add(text)
                     }
                     }
                 }
                 }
-            if (play) play()
+            if (play) play() else pageChanged = true
         }
         }
     }
     }
 
 

+ 3 - 2
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.ContentEmptyException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.TocEmptyException
 import io.legado.app.exception.TocEmptyException
+import io.legado.app.help.IntentData
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.source.exploreKinds
 import io.legado.app.help.source.exploreKinds
 import io.legado.app.model.CheckSource
 import io.legado.app.model.CheckSource
@@ -65,12 +66,12 @@ class CheckSourceService : BaseService() {
 
 
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
         when (intent?.action) {
         when (intent?.action) {
-            IntentAction.start -> intent.getStringArrayListExtra("selectIds")?.let {
+            IntentAction.start -> IntentData.get<ArrayList<String>>("checkSourceSelectedIds")?.let {
                 check(it)
                 check(it)
             }
             }
 
 
             IntentAction.resume -> upNotification()
             IntentAction.resume -> upNotification()
-            else -> stopSelf()
+            IntentAction.stop -> stopSelf()
         }
         }
         return super.onStartCommand(intent, flags, startId)
         return super.onStartCommand(intent, flags, startId)
     }
     }

+ 7 - 2
app/src/main/java/io/legado/app/service/HttpReadAloudService.kt

@@ -62,6 +62,7 @@ class HttpReadAloudService : BaseReadAloudService(),
     }
     }
 
 
     override fun play() {
     override fun play() {
+        pageChanged = false
         exoPlayer.stop()
         exoPlayer.stop()
         if (contentList.isEmpty()) {
         if (contentList.isEmpty()) {
             AppLog.putDebug("朗读列表为空")
             AppLog.putDebug("朗读列表为空")
@@ -285,8 +286,12 @@ class HttpReadAloudService : BaseReadAloudService(),
     override fun resumeReadAloud() {
     override fun resumeReadAloud() {
         super.resumeReadAloud()
         super.resumeReadAloud()
         kotlin.runCatching {
         kotlin.runCatching {
-            exoPlayer.play()
-            upPlayPos()
+            if (pageChanged) {
+                play()
+            } else {
+                exoPlayer.play()
+                upPlayPos()
+            }
         }
         }
     }
     }
 
 

+ 5 - 1
app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt

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

+ 8 - 0
app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookActivity.kt

@@ -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() {
     private fun initView() {
         binding.layTop.setBackgroundColor(backgroundColor)
         binding.layTop.setBackgroundColor(backgroundColor)
         binding.recyclerView.layoutManager = LinearLayoutManager(this)
         binding.recyclerView.layoutManager = LinearLayoutManager(this)

+ 5 - 0
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
 package io.legado.app.ui.book.import.remote
 
 
 import android.app.Application
 import android.app.Application
+import androidx.lifecycle.MutableLiveData
 import io.legado.app.base.BaseViewModel
 import io.legado.app.base.BaseViewModel
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.BookType
 import io.legado.app.constant.BookType
@@ -25,6 +26,7 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application)
     var sortKey = RemoteBookSort.Default
     var sortKey = RemoteBookSort.Default
     var sortAscending = false
     var sortAscending = false
     val dirList = arrayListOf<RemoteBook>()
     val dirList = arrayListOf<RemoteBook>()
+    val permissionDenialLiveData = MutableLiveData<Int>()
 
 
     var dataCallback: DataCallback? = null
     var dataCallback: DataCallback? = null
 
 
@@ -141,6 +143,9 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application)
         }.onError {
         }.onError {
             AppLog.put("导入出错\n${it.localizedMessage}", it)
             AppLog.put("导入出错\n${it.localizedMessage}", it)
             context.toastOnUi("导入出错\n${it.localizedMessage}")
             context.toastOnUi("导入出错\n${it.localizedMessage}")
+            if (it is SecurityException) {
+                permissionDenialLiveData.postValue(1)
+            }
         }.onFinally {
         }.onFinally {
             finally.invoke()
             finally.invoke()
         }
         }

+ 19 - 18
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 {
     ItemTouchCallback.Callback {
 
 
     private val selected = linkedSetOf<BookSource>()
     private val selected = linkedSetOf<BookSource>()
+    private val finalMessageRegex = Regex("成功|失败")
 
 
     val selection: List<BookSource>
     val selection: List<BookSource>
         get() {
         get() {
@@ -94,9 +95,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
                 cbBookSource.text = item.getDisPlayNameGroup()
                 cbBookSource.text = item.getDisPlayNameGroup()
                 swtEnabled.isChecked = item.enabled
                 swtEnabled.isChecked = item.enabled
                 cbBookSource.isChecked = selected.contains(item)
                 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)
                 upShowExplore(ivExplore, item)
             } else {
             } else {
                 payload.keySet().map {
                 payload.keySet().map {
@@ -105,21 +104,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
                         "upName" -> cbBookSource.text = item.getDisPlayNameGroup()
                         "upName" -> cbBookSource.text = item.getDisPlayNameGroup()
                         "upExplore" -> upShowExplore(ivExplore, item)
                         "upExplore" -> upShowExplore(ivExplore, item)
                         "selected" -> cbBookSource.isChecked = selected.contains(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() {
     fun selectAll() {
         getItems().forEach {
         getItems().forEach {
             selected.add(it)
             selected.add(it)

+ 54 - 14
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.View
 import android.view.ViewGroup
 import android.view.ViewGroup
 import androidx.appcompat.widget.Toolbar
 import androidx.appcompat.widget.Toolbar
+import androidx.core.os.bundleOf
 import androidx.fragment.app.viewModels
 import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView
 import io.legado.app.R
 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.ui.widget.recycler.VerticalDivider
 import io.legado.app.utils.*
 import io.legado.app.utils.*
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import io.legado.app.utils.viewbindingdelegate.viewBinding
-import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
 
 
@@ -108,7 +109,7 @@ class TxtTocRuleDialog() : BaseDialogFragment(R.layout.dialog_toc_regex),
         launch {
         launch {
             appDb.txtTocRuleDao.observeAll().conflate().collect { tocRules ->
             appDb.txtTocRuleDao.observeAll().conflate().collect { tocRules ->
                 initSelectedName(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),
         RecyclerAdapter<TxtTocRule, ItemTocRegexBinding>(context),
         ItemTouchCallback.Callback {
         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 {
         override fun getViewBinding(parent: ViewGroup): ItemTocRegexBinding {
             return ItemTocRegexBinding.inflate(inflater, parent, false)
             return ItemTocRegexBinding.inflate(inflater, parent, false)
         }
         }
@@ -202,14 +240,22 @@ class TxtTocRuleDialog() : BaseDialogFragment(R.layout.dialog_toc_regex),
             payloads: MutableList<Any>
             payloads: MutableList<Any>
         ) {
         ) {
             binding.apply {
             binding.apply {
-                if (payloads.isEmpty()) {
+                val bundle = payloads.getOrNull(0) as? Bundle
+                if (bundle == null) {
                     root.setBackgroundColor(context.backgroundColor)
                     root.setBackgroundColor(context.backgroundColor)
                     rbRegexName.text = item.name
                     rbRegexName.text = item.name
                     titleExample.text = item.example
                     titleExample.text = item.example
                     rbRegexName.isChecked = item.name == selectedName
                     rbRegexName.isChecked = item.name == selectedName
                     swtEnabled.isChecked = item.enable
                     swtEnabled.isChecked = item.enable
                 } else {
                 } 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 ->
                 rbRegexName.setOnCheckedChangeListener { buttonView, isChecked ->
                     if (buttonView.isPressed && isChecked) {
                     if (buttonView.isPressed && isChecked) {
                         selectedName = getItem(holder.layoutPosition)?.name
                         selectedName = getItem(holder.layoutPosition)?.name
-                        updateItems(0, itemCount - 1, true)
+                        updateItems(0, itemCount - 1, bundleOf("upSelect" to null))
                     }
                     }
                 }
                 }
                 swtEnabled.setOnCheckedChangeListener { buttonView, isChecked ->
                 swtEnabled.setOnCheckedChangeListener { buttonView, isChecked ->
                     if (buttonView.isPressed) {
                     if (buttonView.isPressed) {
                         getItem(holder.layoutPosition)?.let {
                         getItem(holder.layoutPosition)?.let {
                             it.enable = isChecked
                             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 {
                 ivDelete.setOnClickListener {
                     getItem(holder.layoutPosition)?.let { item ->
                     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()) {
                 for ((index, item) in getItems().withIndex()) {
                     item.serialNumber = index + 1
                     item.serialNumber = index + 1
                 }
                 }
-                launch(IO) {
-                    appDb.txtTocRuleDao.update(*getItems().toTypedArray())
-                }
+                viewModel.update(*getItems().toTypedArray())
             }
             }
             isMoved = false
             isMoved = false
         }
         }