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

This commit is contained in:
HapeLee
2026-06-19 21:30:50 +08:00
parent 5569bf9a62
commit c45e4355ff
5 changed files with 84 additions and 9 deletions
@@ -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
@@ -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
}
@@ -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) {
@@ -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,
)
}
@@ -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) {