fix: 修复内容编辑定位崩溃
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user