feat: improve epub reader controls and annotations
This commit is contained in:
@@ -14,14 +14,14 @@ final class RDEPUBReaderHighlightsViewController: UITableViewController {
|
||||
|
||||
private let sectionTitleProvider: (RDEPUBHighlight) -> String?
|
||||
|
||||
private let filterControl = UISegmentedControl(items: ["全部", "批注", "高亮"])
|
||||
private let filterControl = UISegmentedControl(items: ["全部", "注释", "划线"])
|
||||
|
||||
private var filteredHighlights: [RDEPUBHighlight] {
|
||||
switch filterControl.selectedSegmentIndex {
|
||||
case 1:
|
||||
return highlights.filter(\.hasNote)
|
||||
case 2:
|
||||
return highlights.filter { $0.style == .highlight }
|
||||
return highlights.filter { !$0.hasNote }
|
||||
default:
|
||||
return highlights
|
||||
}
|
||||
@@ -48,7 +48,11 @@ final class RDEPUBReaderHighlightsViewController: UITableViewController {
|
||||
view.accessibilityIdentifier = "epub.reader.highlights.panel"
|
||||
tableView.accessibilityIdentifier = "epub.reader.highlights.table"
|
||||
tableView.tableFooterView = UIView(frame: .zero)
|
||||
tableView.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
|
||||
tableView.separatorStyle = .none
|
||||
tableView.contentInset = UIEdgeInsets(top: 12, left: 0, bottom: 20, right: 0)
|
||||
navigationItem.rightBarButtonItem = editButtonItem
|
||||
editButtonItem.title = "编辑"
|
||||
tableView.allowsMultipleSelectionDuringEditing = false
|
||||
configureFilterControl()
|
||||
applyTheme()
|
||||
updateEmptyState()
|
||||
@@ -63,23 +67,42 @@ final class RDEPUBReaderHighlightsViewController: UITableViewController {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
|
||||
let highlight = filteredHighlights[indexPath.row]
|
||||
|
||||
cell.backgroundColor = theme.contentBackgroundColor
|
||||
cell.textLabel?.textColor = theme.contentTextColor
|
||||
cell.backgroundColor = .clear
|
||||
cell.contentView.backgroundColor = UIColor.white.withAlphaComponent(0.76)
|
||||
cell.contentView.layer.cornerRadius = 14
|
||||
cell.contentView.layer.masksToBounds = true
|
||||
cell.textLabel?.textColor = UIColor(red: 0.08, green: 0.10, blue: 0.14, alpha: 1)
|
||||
cell.textLabel?.font = UIFont.systemFont(ofSize: 15, weight: .medium)
|
||||
cell.textLabel?.numberOfLines = 2
|
||||
cell.textLabel?.text = titleText(for: highlight)
|
||||
|
||||
cell.detailTextLabel?.textColor = theme.contentTextColor.withAlphaComponent(0.7)
|
||||
cell.detailTextLabel?.textColor = UIColor(red: 0.34, green: 0.36, blue: 0.40, alpha: 1)
|
||||
cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 12)
|
||||
cell.detailTextLabel?.numberOfLines = 3
|
||||
cell.detailTextLabel?.text = detailText(for: highlight)
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
cell.accessoryType = .none
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 112 }
|
||||
|
||||
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
cell.contentView.frame = cell.bounds.inset(by: UIEdgeInsets(top: 6, left: 18, bottom: 6, right: 18))
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard !tableView.isEditing else { return }
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
presentActions(for: filteredHighlights[indexPath.row], sourceIndexPath: indexPath)
|
||||
onSelectHighlight?(filteredHighlights[indexPath.row])
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
guard editingStyle == .delete else { return }
|
||||
deleteHighlight(filteredHighlights[indexPath.row])
|
||||
}
|
||||
|
||||
private func configureFilterControl() {
|
||||
@@ -90,10 +113,10 @@ final class RDEPUBReaderHighlightsViewController: UITableViewController {
|
||||
}
|
||||
|
||||
private func applyTheme() {
|
||||
tableView.backgroundColor = theme.contentBackgroundColor
|
||||
navigationController?.navigationBar.tintColor = theme.toolControlTextColor
|
||||
navigationController?.navigationBar.barTintColor = theme.toolBackgroundColor
|
||||
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: theme.toolControlTextColor]
|
||||
tableView.backgroundColor = UIColor(red: 0.975, green: 0.965, blue: 0.935, alpha: 1)
|
||||
navigationController?.navigationBar.tintColor = UIColor(red: 0.10, green: 0.34, blue: 0.78, alpha: 1)
|
||||
navigationController?.navigationBar.barTintColor = tableView.backgroundColor
|
||||
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor(red: 0.08, green: 0.10, blue: 0.14, alpha: 1)]
|
||||
navigationController?.navigationBar.accessibilityIdentifier = "epub.reader.highlights.navbar"
|
||||
}
|
||||
|
||||
@@ -120,7 +143,7 @@ final class RDEPUBReaderHighlightsViewController: UITableViewController {
|
||||
private func emptyStateText() -> String {
|
||||
switch filterControl.selectedSegmentIndex {
|
||||
case 1:
|
||||
return "暂无批注"
|
||||
return "暂无注释"
|
||||
case 2:
|
||||
return "暂无划线"
|
||||
default:
|
||||
@@ -142,7 +165,7 @@ final class RDEPUBReaderHighlightsViewController: UITableViewController {
|
||||
private func titleText(for highlight: RDEPUBHighlight) -> String {
|
||||
let text = highlight.text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if highlight.hasNote {
|
||||
return "批注: \(text)"
|
||||
return "注释:\(text)"
|
||||
}
|
||||
return text
|
||||
}
|
||||
@@ -150,58 +173,12 @@ final class RDEPUBReaderHighlightsViewController: UITableViewController {
|
||||
private func styleDescription(for highlight: RDEPUBHighlight) -> String {
|
||||
switch highlight.style {
|
||||
case .highlight:
|
||||
return highlight.hasNote ? "高亮批注" : "高亮"
|
||||
return highlight.hasNote ? "划线注释" : "划线"
|
||||
case .underline:
|
||||
return highlight.hasNote ? "划线批注" : "划线"
|
||||
return highlight.hasNote ? "划线注释" : "划线"
|
||||
}
|
||||
}
|
||||
|
||||
private func presentActions(for highlight: RDEPUBHighlight, sourceIndexPath: IndexPath) {
|
||||
let alert = UIAlertController(title: "标注管理", message: nil, preferredStyle: .actionSheet)
|
||||
alert.addAction(UIAlertAction(title: "跳转到位置", style: .default) { [weak self] _ in
|
||||
self?.onSelectHighlight?(highlight)
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "编辑批注", style: .default) { [weak self] _ in
|
||||
self?.presentNoteEditor(for: highlight)
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "删除标注", style: .destructive) { [weak self] _ in
|
||||
self?.deleteHighlight(highlight)
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
|
||||
if let popover = alert.popoverPresentationController,
|
||||
let cell = tableView.cellForRow(at: sourceIndexPath) {
|
||||
popover.sourceView = cell
|
||||
popover.sourceRect = cell.bounds
|
||||
}
|
||||
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func presentNoteEditor(for highlight: RDEPUBHighlight) {
|
||||
let alert = UIAlertController(title: "编辑批注", message: nil, preferredStyle: .alert)
|
||||
alert.addTextField { textField in
|
||||
textField.placeholder = "输入批注内容"
|
||||
textField.text = highlight.note
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: "保存", style: .default) { [weak self, weak alert] _ in
|
||||
guard let self,
|
||||
let note = alert?.textFields?.first?.text,
|
||||
let index = self.highlights.firstIndex(where: { $0.id == highlight.id }) else {
|
||||
return
|
||||
}
|
||||
|
||||
var updated = highlight
|
||||
updated.note = self.normalizedNote(note)
|
||||
self.highlights[index] = updated
|
||||
self.tableView.reloadData()
|
||||
self.onUpdateHighlight?(updated)
|
||||
self.updateEmptyState()
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func deleteHighlight(_ highlight: RDEPUBHighlight) {
|
||||
guard let index = highlights.firstIndex(where: { $0.id == highlight.id }) else { return }
|
||||
highlights.remove(at: index)
|
||||
@@ -248,7 +225,10 @@ final class RDEPUBReaderBookmarksViewController: UITableViewController {
|
||||
view.accessibilityIdentifier = "epub.reader.bookmarks.panel"
|
||||
tableView.accessibilityIdentifier = "epub.reader.bookmarks.table"
|
||||
tableView.tableFooterView = UIView(frame: .zero)
|
||||
tableView.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
|
||||
tableView.separatorStyle = .none
|
||||
tableView.contentInset = UIEdgeInsets(top: 12, left: 0, bottom: 20, right: 0)
|
||||
navigationItem.rightBarButtonItem = editButtonItem
|
||||
editButtonItem.title = "编辑"
|
||||
applyTheme()
|
||||
updateEmptyState()
|
||||
}
|
||||
@@ -262,30 +242,40 @@ final class RDEPUBReaderBookmarksViewController: UITableViewController {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
|
||||
let bookmark = bookmarks[indexPath.row]
|
||||
|
||||
cell.backgroundColor = theme.contentBackgroundColor
|
||||
cell.textLabel?.textColor = theme.contentTextColor
|
||||
cell.backgroundColor = .clear
|
||||
cell.contentView.backgroundColor = UIColor.white.withAlphaComponent(0.76)
|
||||
cell.contentView.layer.cornerRadius = 14
|
||||
cell.contentView.layer.masksToBounds = true
|
||||
cell.textLabel?.textColor = UIColor(red: 0.08, green: 0.10, blue: 0.14, alpha: 1)
|
||||
cell.textLabel?.font = UIFont.systemFont(ofSize: 15, weight: .medium)
|
||||
cell.textLabel?.numberOfLines = 2
|
||||
cell.textLabel?.text = titleText(for: bookmark)
|
||||
|
||||
cell.detailTextLabel?.textColor = theme.contentTextColor.withAlphaComponent(0.7)
|
||||
cell.detailTextLabel?.textColor = UIColor(red: 0.34, green: 0.36, blue: 0.40, alpha: 1)
|
||||
cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 12)
|
||||
cell.detailTextLabel?.numberOfLines = 3
|
||||
cell.detailTextLabel?.text = detailText(for: bookmark)
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
cell.accessoryType = .none
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 104 }
|
||||
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
guard editingStyle == .delete else { return }
|
||||
deleteBookmark(bookmarks[indexPath.row])
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
presentActions(for: bookmarks[indexPath.row], sourceIndexPath: indexPath)
|
||||
onSelectBookmark?(bookmarks[indexPath.row])
|
||||
}
|
||||
|
||||
private func applyTheme() {
|
||||
tableView.backgroundColor = theme.contentBackgroundColor
|
||||
navigationController?.navigationBar.tintColor = theme.toolControlTextColor
|
||||
navigationController?.navigationBar.barTintColor = theme.toolBackgroundColor
|
||||
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: theme.toolControlTextColor]
|
||||
tableView.backgroundColor = UIColor(red: 0.975, green: 0.965, blue: 0.935, alpha: 1)
|
||||
navigationController?.navigationBar.tintColor = UIColor(red: 0.10, green: 0.34, blue: 0.78, alpha: 1)
|
||||
navigationController?.navigationBar.barTintColor = tableView.backgroundColor
|
||||
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor(red: 0.08, green: 0.10, blue: 0.14, alpha: 1)]
|
||||
navigationController?.navigationBar.accessibilityIdentifier = "epub.reader.bookmarks.navbar"
|
||||
}
|
||||
|
||||
@@ -322,25 +312,6 @@ final class RDEPUBReaderBookmarksViewController: UITableViewController {
|
||||
return parts.joined(separator: "\n")
|
||||
}
|
||||
|
||||
private func presentActions(for bookmark: RDEPUBBookmark, sourceIndexPath: IndexPath) {
|
||||
let alert = UIAlertController(title: "书签管理", message: nil, preferredStyle: .actionSheet)
|
||||
alert.addAction(UIAlertAction(title: "跳转到位置", style: .default) { [weak self] _ in
|
||||
self?.onSelectBookmark?(bookmark)
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "删除书签", style: .destructive) { [weak self] _ in
|
||||
self?.deleteBookmark(bookmark)
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
|
||||
if let popover = alert.popoverPresentationController,
|
||||
let cell = tableView.cellForRow(at: sourceIndexPath) {
|
||||
popover.sourceView = cell
|
||||
popover.sourceRect = cell.bounds
|
||||
}
|
||||
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func deleteBookmark(_ bookmark: RDEPUBBookmark) {
|
||||
guard let index = bookmarks.firstIndex(where: { $0.id == bookmark.id }) else { return }
|
||||
bookmarks.remove(at: index)
|
||||
|
||||
Reference in New Issue
Block a user