feat: add in-reader search and restructure documentation

- Add RDEPUBReaderSearchBarView with animated show/hide, keyword
  navigation, and match counting integrated into the reader controller
- Restructure docs: replace scattered design docs with consolidated
  BUSINESS_LOGIC.md and UML_CLASS_DIAGRAMS.md; update ARCHITECTURE.md
- Add SearchTests and FanrenParseTimeTest; enhance LargeBookOnDemandTests
- Add scripts/run_ui_regression.sh and summarize_ui_results.py for
  automated UI test execution and reporting

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-06-05 17:34:50 +08:00
co-authored by Claude Opus 4.7
parent d20196ee34
commit 0e7c0577e3
37 changed files with 4941 additions and 7621 deletions
@@ -19,12 +19,15 @@ final class RDEPUBReaderChromeCoordinator {
context.controller
}
///
///
func makeTopToolView() -> RDEPUBReaderTopToolView {
let toolView = RDEPUBReaderTopToolView()
toolView.onBack = { [weak self] in
self?.handleBackAction()
}
toolView.onSearch = { [weak self] in
self?.toggleSearchBar()
}
toolView.onToggleBookmark = { [weak self] in
_ = self?.context.runtime?.toggleBookmark()
}
@@ -76,6 +79,7 @@ final class RDEPUBReaderChromeCoordinator {
controller.configuration.allowsHighlights && !controller.activeHighlights.isEmpty
)
controller.updateBookmarkChrome()
updateSearchBar()
}
///
@@ -140,6 +144,29 @@ final class RDEPUBReaderChromeCoordinator {
controller.present(navigationController, animated: true)
}
/// /
func toggleSearchBar() {
guard let controller else { return }
if controller.isSearchBarVisible {
controller.hideSearchBar()
} else {
controller.showSearchBar()
}
}
///
func updateSearchBar() {
guard let controller else { return }
controller.searchBarView.apply(theme: controller.configuration.theme)
if let searchState = controller.searchState {
if let index = searchState.currentMatchIndex {
controller.searchBarView.updateMatchCount(current: index + 1, total: searchState.matches.count)
} else if searchState.matches.isEmpty {
controller.searchBarView.showNoResults()
}
}
}
/// pop dismiss
func handleBackAction() {
guard let controller else { return }