feat: unify reader chrome state management
- Add RDEPUBReaderUIState struct for centralized UI state model - Refactor RDEPUBReaderChromeCoordinator with makeUIState() and applyUIState() - Remove direct UI updates from RDEPUBReaderAnnotationCoordinator - Consolidate updateBookmarkChrome() into updateReaderChrome() - Update RDEPUBReaderContext, Runtime, and LocationCoordinator - Add reader problem fix development checklist documentation Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
d15187b730
commit
ed390da147
@@ -31,7 +31,7 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
totalPages: pageCountOfReaderView(readerView: readerView),
|
||||
configuration: configuration,
|
||||
highlights: textHighlights(for: resolvedPage.page),
|
||||
searchState: searchState
|
||||
searchState: searchState(for: resolvedPage.page)
|
||||
)
|
||||
return contentView
|
||||
}
|
||||
@@ -46,7 +46,7 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
totalPages: textBook.pages.count,
|
||||
configuration: configuration,
|
||||
highlights: textHighlights(for: page),
|
||||
searchState: searchState
|
||||
searchState: searchState(for: page)
|
||||
)
|
||||
return contentView
|
||||
}
|
||||
@@ -91,6 +91,57 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/// 返回当前文本页需要展示的搜索状态。
|
||||
/// 这里会先按页过滤命中结果,并把 currentMatchIndex 重映射到当前页内的局部索引,
|
||||
/// 这样渲染层不需要再关心 href 规范化差异。
|
||||
private func searchState(for page: RDEPUBTextPage) -> RDEPUBSearchState? {
|
||||
guard let globalSearchState = searchState else { return nil }
|
||||
|
||||
let matches = globalSearchState.matches.filter { searchMatch in
|
||||
searchMatchBelongsToPage(searchMatch, page: page)
|
||||
}
|
||||
guard !matches.isEmpty || globalSearchState.currentMatch != nil else {
|
||||
return globalSearchState.matches.isEmpty ? globalSearchState : nil
|
||||
}
|
||||
|
||||
let currentMatchIndex = globalSearchState.currentMatch.flatMap { currentMatch in
|
||||
matches.firstIndex(of: currentMatch)
|
||||
}
|
||||
return RDEPUBSearchState(
|
||||
keyword: globalSearchState.keyword,
|
||||
matches: matches,
|
||||
currentMatchIndex: currentMatchIndex
|
||||
)
|
||||
}
|
||||
|
||||
private func searchMatchBelongsToPage(_ searchMatch: RDEPUBSearchMatch, page: RDEPUBTextPage) -> Bool {
|
||||
if let textBook,
|
||||
let chapterData = textBook.chapterData(for: page.href),
|
||||
let range = chapterData.absoluteRange(for: searchMatch) {
|
||||
return NSIntersectionRange(range, page.contentRange).length > 0
|
||||
}
|
||||
|
||||
let pageHref = normalizedPageHref(for: page)
|
||||
let matchHref = normalizedSearchHref(searchMatch.href)
|
||||
guard pageHref == matchHref else { return false }
|
||||
|
||||
guard let rangeLocation = searchMatch.rangeLocation else {
|
||||
return false
|
||||
}
|
||||
let range = NSRange(location: rangeLocation, length: searchMatch.rangeLength)
|
||||
return NSIntersectionRange(range, page.contentRange).length > 0
|
||||
}
|
||||
|
||||
private func normalizedPageHref(for page: RDEPUBTextPage) -> String {
|
||||
guard let publication else { return page.href }
|
||||
return publication.resourceResolver.normalizedHref(page.href) ?? page.href
|
||||
}
|
||||
|
||||
private func normalizedSearchHref(_ href: String) -> String {
|
||||
guard let publication else { return href }
|
||||
return publication.resourceResolver.normalizedHref(href) ?? href
|
||||
}
|
||||
|
||||
/// 根据规范化 href 获取文本章节数据
|
||||
func textChapterData(forNormalizedHref href: String) -> RDEPUBChapterData? {
|
||||
readerContext.textChapterData(forNormalizedHref: href)
|
||||
|
||||
Reference in New Issue
Block a user