diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt index 7aaafbadd..0fe5e2049 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt +++ b/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 diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt index 1b5600a19..24c97594f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt +++ b/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 } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt index dd09fe588..f4ece372d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt +++ b/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) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 360b7a697..d4c6f13c1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/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, ) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/sheet/ContentEditSheet.kt b/app/src/main/java/io/legado/app/ui/book/read/sheet/ContentEditSheet.kt index cd2d2341d..b1f18a9b5 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/sheet/ContentEditSheet.kt +++ b/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(null) } + var pendingLocateOffset by remember { mutableStateOf(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) {