Przeglądaj źródła

fix: 修复内容编辑定位崩溃

HapeLee 4 miesięcy temu
rodzic
commit
c45e4355ff

+ 1 - 0
app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt

@@ -340,6 +340,7 @@ sealed interface ReadBookIntent {
     data object ToggleReadUrlInBrowser : ReadBookIntent
 
     // Content edit
+    data object OpenContentEdit : ReadBookIntent
     data object LoadContentEdit : ReadBookIntent
     data class SaveContentEdit(val content: String, val saveToSource: Boolean) : ReadBookIntent
     data object ResetContentEdit : ReadBookIntent

+ 2 - 2
app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt

@@ -300,7 +300,7 @@ class ReadBookController(
     }
 
     override fun openContentEdit() {
-        viewModel.onIntent(ReadBookIntent.ShowSheet(ReadBookSheet.ContentEdit))
+        viewModel.onIntent(ReadBookIntent.OpenContentEdit)
     }
 
     override fun addBookmark() {
@@ -552,7 +552,7 @@ class ReadBookController(
             }
 
             R.id.menu_edit -> {
-                viewModel.onIntent(ReadBookIntent.ShowSheet(ReadBookSheet.ContentEdit))
+                viewModel.onIntent(ReadBookIntent.OpenContentEdit)
                 return true
             }
 

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt

@@ -1668,7 +1668,7 @@ private fun OverflowDropdownMenu(
             text = stringResource(R.string.edit_content),
             onClick = {
                 dismiss()
-                onIntent(ReadBookIntent.ShowSheet(ReadBookSheet.ContentEdit))
+                onIntent(ReadBookIntent.OpenContentEdit)
             },
         )
         if (!state.isLocalBook) {

+ 63 - 2
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -168,6 +168,8 @@ class ReadBookViewModel(
 
     private var changeSourceCoroutine: Coroutine<*>? = null
     private var pendingBooksDirReloadChapterList: Boolean = false
+    private var pendingContentEditCursorOffset: Int? = null
+    private var pendingContentEditAnchor: String? = null
 
     val isInitFinish: Boolean get() = _uiState.value.isInitFinish
 
@@ -445,6 +447,7 @@ class ReadBookViewModel(
             }
             is ReadBookIntent.OpenChapterUrl -> openChapterUrl()
             is ReadBookIntent.ToggleReadUrlInBrowser -> toggleReadUrlInBrowser()
+            is ReadBookIntent.OpenContentEdit -> openContentEdit()
             is ReadBookIntent.LoadContentEdit -> loadContentEdit()
             is ReadBookIntent.SaveContentEdit -> saveContentEdit(intent.content, intent.saveToSource)
             is ReadBookIntent.ResetContentEdit -> resetContentEdit()
@@ -2439,6 +2442,62 @@ class ReadBookViewModel(
         }
     }
 
+    private fun openContentEdit() {
+        pendingContentEditCursorOffset = currentContentEditOffset()
+        pendingContentEditAnchor = currentContentEditAnchor()
+        _uiState.update { it.copy(activeSheet = ReadBookSheet.ContentEdit) }
+    }
+
+    private fun currentContentEditPage(): TextPage? {
+        return ReadBook.curTextChapter?.getPage(ReadBook.durPageIndex)
+    }
+
+    private fun currentContentEditOffset(): Int {
+        val page = currentContentEditPage()
+        return page?.lines
+            ?.firstOrNull { !it.isTitle && it.text.isNotBlank() }
+            ?.chapterPosition
+            ?: page?.lines
+                ?.firstOrNull { !it.isTitle }
+                ?.chapterPosition
+            ?: ReadBook.durChapterPos
+    }
+
+    private fun currentContentEditAnchor(): String? {
+        return currentContentEditPage()
+            ?.lines
+            ?.firstOrNull { !it.isTitle && it.text.isNotBlank() }
+            ?.text
+            ?.trim()
+            ?.takeIf { it.isNotEmpty() }
+    }
+
+    private fun resolveContentEditCursorOffset(text: String): Int {
+        if (text.isEmpty()) {
+            clearPendingContentEditLocation()
+            return 0
+        }
+        val preferred = (pendingContentEditCursorOffset ?: currentContentEditOffset())
+            .coerceIn(0, text.length)
+        val anchor = pendingContentEditAnchor ?: currentContentEditAnchor()
+        clearPendingContentEditLocation()
+        if (anchor.isNullOrBlank()) {
+            return preferred
+        }
+        val startIndex = (preferred - 200).coerceAtLeast(0)
+        val nearIndex = text.indexOf(anchor, startIndex = startIndex)
+        if (nearIndex >= 0) {
+            return nearIndex
+        }
+        val anyIndex = text.indexOf(anchor)
+        return if (anyIndex >= 0) anyIndex else preferred
+    }
+
+    private fun clearPendingContentEditLocation() {
+        pendingContentEditCursorOffset = null
+        pendingContentEditAnchor = null
+    }
+
     private fun loadContentEdit() {
         _uiState.update { it.copy(contentEditLoading = true, contentEditText = "") }
         execute {
@@ -2451,11 +2510,12 @@ class ReadBookViewModel(
             val rawContent = BookHelp.getContent(book, chapter) ?: return@execute
             val text = contentProcessor.getContent(book, chapter, rawContent, includeTitle = false)
                 .toString()
+            val cursorOffset = resolveContentEditCursorOffset(text)
             _uiState.update {
                 it.copy(
                     contentEditText = text,
                     contentEditTitle = title,
-                    contentEditCursorOffset = ReadBook.durChapterPos.coerceIn(0, text.length),
+                    contentEditCursorOffset = cursorOffset,
                     contentEditIsLocalTxt = book.isLocalTxt,
                 )
             }
@@ -2496,10 +2556,11 @@ class ReadBookViewModel(
             } else {
                 ""
             }
+            val cursorOffset = resolveContentEditCursorOffset(text)
             _uiState.update {
                 it.copy(
                     contentEditText = text,
-                    contentEditCursorOffset = ReadBook.durChapterPos.coerceIn(0, text.length),
+                    contentEditCursorOffset = cursorOffset,
                     contentEditLoading = false,
                 )
             }

+ 17 - 4
app/src/main/java/io/legado/app/ui/book/read/sheet/ContentEditSheet.kt

@@ -47,6 +47,7 @@ fun ContentEditSheet(
     val editorState = remember { TextFieldState() }
     val editorScrollState = rememberScrollState()
     var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
+    var pendingLocateOffset by remember { mutableStateOf<Int?>(null) }
 
     LaunchedEffect(show) {
         if (!show) return@LaunchedEffect
@@ -57,19 +58,31 @@ fun ContentEditSheet(
         if (editorState.text.toString() != state.contentEditText) {
             editorState.setTextAndSelectAll(state.contentEditText)
             editorState.edit {
-                selection = TextRange(state.contentEditCursorOffset.coerceIn(0, length))
+                val offset = state.contentEditCursorOffset.coerceIn(0, length)
+                selection = TextRange(offset)
+                pendingLocateOffset = offset
             }
         }
     }
 
-    LaunchedEffect(show, state.contentEditLoading, state.contentEditCursorOffset, textLayoutResult) {
+    LaunchedEffect(show, state.contentEditLoading, pendingLocateOffset, textLayoutResult) {
         val layoutResult = textLayoutResult
-        if (!show || state.contentEditLoading || layoutResult == null) {
+        val locateOffset = pendingLocateOffset
+        if (!show || state.contentEditLoading || layoutResult == null || locateOffset == null) {
             return@LaunchedEffect
         }
-        val offset = state.contentEditCursorOffset.coerceIn(0, editorState.text.length)
+        val layoutTextLength = layoutResult.layoutInput.text.length
+        if (layoutTextLength != editorState.text.length) {
+            return@LaunchedEffect
+        }
+        if (layoutTextLength == 0) {
+            pendingLocateOffset = null
+            return@LaunchedEffect
+        }
+        val offset = locateOffset.coerceIn(0, layoutTextLength)
         val cursorTop = layoutResult.getCursorRect(offset).top.toInt()
         editorScrollState.scrollTo((cursorTop - 120).coerceIn(0, editorScrollState.maxValue))
+        pendingLocateOffset = null
     }
 
     LaunchedEffect(editorState) {