import UIKit /// EPUB 阅读器 Chrome 协调器:负责顶部/底部工具栏的创建、更新和交互处理。 /// /// 职责: /// - 创建并配置顶部工具栏(返回、书签按钮) /// - 创建并配置底部工具栏(目录、书签、高亮、设置按钮) /// - 同步工具栏的主题和状态 /// - 弹出设置面板和目录面板 /// - 处理返回按钮的关闭逻辑 final class RDEPUBReaderChromeCoordinator { private unowned let context: RDEPUBReaderContext init(context: RDEPUBReaderContext) { self.context = context } private var controller: RDEPUBReaderController? { 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() } return toolView } /// 创建底部工具栏视图,绑定目录、书签、高亮、设置等回调。 func makeBottomToolView() -> RDEPUBReaderBottomToolView { let toolView = RDEPUBReaderBottomToolView() toolView.onShowTableOfContents = { [weak self] in self?.presentTableOfContents() } toolView.onShowBookmarks = { [weak self] in self?.context.runtime?.presentBookmarksManager() } toolView.onShowHighlights = { [weak self] in self?.context.runtime?.presentHighlightsManager() } toolView.onAddHighlight = { [weak self] in self?.context.runtime?.presentAnnotationCreation() } toolView.onShowSettings = { [weak self] in self?.presentSettings() } return toolView } /// 同步更新顶部和底部工具栏的主题、标题、按钮可用性等状态。 func updateReaderChrome() { guard let controller else { return } controller.topToolView.apply(theme: controller.configuration.theme) controller.topToolView.setTitle( controller.title ?? controller.parser?.metadata.title ?? controller.epubURL.deletingPathExtension().lastPathComponent ) controller.topToolView.setBookmarkEnabled(controller.currentBookIdentifier != nil) controller.bottomToolView.apply(theme: controller.configuration.theme) controller.bottomToolView.updateVisibility( showsTableOfContents: controller.configuration.showsTableOfContents, allowsHighlights: controller.configuration.allowsHighlights, showsSettingsPanel: controller.configuration.showsSettingsPanel ) controller.bottomToolView.setBookmarksEnabled(!controller.activeBookmarks.isEmpty) controller.bottomToolView.setAddHighlightEnabled( controller.configuration.allowsHighlights && controller.currentSelection != nil ) controller.bottomToolView.setHighlightsEnabled( controller.configuration.allowsHighlights && !controller.activeHighlights.isEmpty ) controller.updateBookmarkChrome() updateSearchBar() } /// 弹出阅读设置面板(字号、字体、行距、分栏、主题、亮度等)。 func presentSettings() { guard let controller else { return } guard controller.configuration.showsSettingsPanel else { return } let settingsController = RDEPUBReaderSettingsViewController( configuration: controller.configuration, brightness: controller.currentBrightness ) settingsController.onBrightnessChange = { [weak controller] brightness in controller?.setScreenBrightness(brightness) } settingsController.onFontSizeChange = { [weak controller] fontSize in controller?.updateConfiguration { $0.fontSize = fontSize } } settingsController.onFontChoiceChange = { [weak controller] fontChoice in controller?.updateConfiguration { $0.fontChoice = fontChoice } } settingsController.onLineHeightChange = { [weak controller] lineHeightMultiple in controller?.updateConfiguration { $0.lineHeightMultiple = lineHeightMultiple } } settingsController.onColumnCountChange = { [weak controller] numberOfColumns in controller?.updateConfiguration { $0.numberOfColumns = numberOfColumns } } settingsController.onDisplayTypeChange = { [weak controller] displayType in controller?.updateConfiguration { $0.displayType = displayType } } settingsController.onThemeChange = { [weak controller] theme in controller?.updateConfiguration { $0.theme = theme } } let navigationController = UINavigationController(rootViewController: settingsController) navigationController.modalPresentationStyle = .pageSheet controller.present(navigationController, animated: true) } /// 弹出目录列表面板,支持点击跳转到指定章节。 func presentTableOfContents() { guard let controller else { return } guard controller.configuration.showsTableOfContents else { return } let items = controller.flattenedTableOfContentsItems( from: controller.publication?.tableOfContents ?? [], includePageNumbers: false ) guard !items.isEmpty else { return } let chapterController = RDEPUBReaderChapterListController( items: items, currentItem: controller.currentTableOfContentsItem, theme: controller.configuration.theme ) chapterController.onSelectItem = { [weak controller] item in guard let controller else { return } chapterController.dismiss(animated: true) { _ = controller.go(toTableOfContentsItem: item, animated: true) } } let navigationController = UINavigationController(rootViewController: chapterController) navigationController.modalPresentationStyle = .pageSheet 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 } close(controller) } private func close(_ controller: UIViewController) { let target = closestDismissTarget(from: controller) if let navigationController = target.navigationController, navigationController.viewControllers.first !== target { navigationController.popViewController(animated: true) return } if let navigationController = target.navigationController, navigationController.presentingViewController != nil { navigationController.dismiss(animated: true) return } if target.presentingViewController != nil { target.dismiss(animated: true) return } controller.dismiss(animated: true) } private func closestDismissTarget(from controller: UIViewController) -> UIViewController { var candidate: UIViewController = controller var current = controller.parent while let parent = current { if let navigationController = parent.navigationController, navigationController.viewControllers.contains(parent) { return parent } if parent.presentingViewController != nil || parent.navigationController?.presentingViewController != nil { return parent } candidate = parent current = parent.parent } return candidate } }