Skip to content

Commit

Permalink
Added improved highlight/underline text customisation to html/epub re…
Browse files Browse the repository at this point in the history
…ader
  • Loading branch information
michalrentka committed Aug 28, 2024
1 parent 361c70a commit c94d639
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 20 deletions.
16 changes: 13 additions & 3 deletions Zotero/Scenes/Detail/HTML:EPUB/HtmlEpubCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ protocol HtmlEpubSidebarCoordinatorDelegate: AnyObject {
for annotation: HtmlEpubAnnotation,
userId: Int,
library: Library,
highlightFont: UIFont,
sender: UIButton,
userInterfaceStyle: UIUserInterfaceStyle,
saveAction: @escaping AnnotationEditSaveAction,
Expand Down Expand Up @@ -188,6 +189,7 @@ extension HtmlEpubCoordinator: HtmlEpubSidebarCoordinatorDelegate {
for annotation: HtmlEpubAnnotation,
userId: Int,
library: Library,
highlightFont: UIFont,
sender: UIButton,
userInterfaceStyle: UIUserInterfaceStyle,
saveAction: @escaping AnnotationEditSaveAction,
Expand All @@ -196,14 +198,17 @@ extension HtmlEpubCoordinator: HtmlEpubSidebarCoordinatorDelegate {
let navigationController = NavigationViewController()
navigationController.overrideUserInterfaceStyle = userInterfaceStyle

let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? HtmlEpubAnnotationsDelegate)?
.parseAndCacheIfNeededAttributedText(for: annotation, with: highlightFont) ?? .init(string: "")
let coordinator = AnnotationEditCoordinator(
data: AnnotationEditState.Data(
type: annotation.type,
isEditable: annotation.editability(currentUserId: userId, library: library) == .editable,
color: annotation.color,
lineWidth: 0,
pageLabel: annotation.pageLabel,
highlightText: annotation.text ?? "",
highlightText: highlightText,
highlightFont: highlightFont,
fontSize: 12
),
saveAction: saveAction,
Expand Down Expand Up @@ -241,7 +246,11 @@ extension HtmlEpubCoordinator: HtmlEpubSidebarCoordinatorDelegate {
let navigationController = NavigationViewController()
navigationController.overrideUserInterfaceStyle = userInterfaceStyle
let author = viewModel.state.library.identifier == .custom(.myLibrary) ? "" : annotation.author
let comment = viewModel.state.comments[annotation.key] ?? NSAttributedString()
let comment: NSAttributedString = (self.navigationController?.viewControllers.first as? HtmlEpubAnnotationsDelegate)?
.parseAndCacheIfNeededAttributedComment(for: annotation) ?? .init(string: "")
let highlightFont = viewModel.state.textFont
let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? HtmlEpubAnnotationsDelegate)?
.parseAndCacheIfNeededAttributedText(for: annotation, with: highlightFont) ?? .init(string: "")
let editability = annotation.editability(currentUserId: viewModel.state.userId, library: viewModel.state.library)
let data = AnnotationPopoverState.Data(
libraryId: viewModel.state.library.identifier,
Expand All @@ -252,7 +261,8 @@ extension HtmlEpubCoordinator: HtmlEpubSidebarCoordinatorDelegate {
color: annotation.color,
lineWidth: 0,
pageLabel: annotation.pageLabel,
highlightText: annotation.text ?? "",
highlightText: highlightText,
highlightFont: highlightFont,
tags: annotation.tags,
showsDeleteButton: editability != .notEditable
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2023 Corporation for Digital Scholarship. All rights reserved.
//

import Foundation
import UIKit

enum HtmlEpubReaderAction {
case changeFilter(AnnotationsFilter?)
Expand All @@ -18,6 +18,7 @@ enum HtmlEpubReaderAction {
case initialiseReader
case loadDocument
case parseAndCacheComment(key: String, comment: String)
case parseAndCacheText(key: String, text: String, font: UIFont)
case removeAnnotation(String)
case removeSelectedAnnotations
case saveAnnotations([String: Any])
Expand All @@ -36,5 +37,5 @@ enum HtmlEpubReaderAction {
case setViewState([String: Any])
case showAnnotationPopover(key: String, rect: CGRect)
case toggleTool(AnnotationTool)
case updateAnnotationProperties(key: String, color: String, lineWidth: CGFloat, pageLabel: String, updateSubsequentLabels: Bool, highlightText: String)
case updateAnnotationProperties(key: String, color: String, lineWidth: CGFloat, pageLabel: String, updateSubsequentLabels: Bool, highlightText: NSAttributedString)
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ struct HtmlEpubReaderState: ViewModelState {
let userId: Int
let username: String
let commentFont: UIFont
let textFont: UIFont

var documentData: DocumentData?
var settings: HtmlEpubSettings
Expand All @@ -83,6 +84,7 @@ struct HtmlEpubReaderState: ViewModelState {
var annotationPopoverRect: CGRect?
var documentSearchTerm: String?
var comments: [String: NSAttributedString]
var texts: [String: (String, [UIFont: NSAttributedString])]
var changes: Changes
var error: Error?
/// Updates that need to be performed on html/epub document
Expand Down Expand Up @@ -110,9 +112,11 @@ struct HtmlEpubReaderState: ViewModelState {
self.userId = userId
self.username = username
commentFont = PDFReaderLayout.annotationLayout.font
textFont = PDFReaderLayout.annotationLayout.font
sortedKeys = []
annotations = [:]
comments = [:]
texts = [:]
sidebarEditingEnabled = false
selectedAnnotationCommentActive = false
toolColors = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
state.comments[key] = self.htmlAttributedStringConverter.convert(text: comment, baseAttributes: [.font: viewModel.state.commentFont])
}

case .parseAndCacheText(let key, let text, let font):
updateTextCache(key: key, text: text, font: font, viewModel: viewModel)

case .updateAnnotationProperties(let key, let color, let lineWidth, let pageLabel, let updateSubsequentLabels, let highlightText):
set(color: color, lineWidth: lineWidth, pageLabel: pageLabel, updateSubsequentLabels: updateSubsequentLabels, highlightText: highlightText, key: key, viewModel: viewModel)

Expand Down Expand Up @@ -148,6 +151,17 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
}
}

private func updateTextCache(key: String, text: String, font: UIFont, viewModel: ViewModel<HtmlEpubReaderActionHandler>) {
update(viewModel: viewModel, notifyListeners: false) { state in
var (cachedText, attributedTextByFont) = state.texts[key, default: (text, [:])]
if cachedText != text {
attributedTextByFont = [:]
}
attributedTextByFont[font] = htmlAttributedStringConverter.convert(text: text, baseAttributes: [.font: font])
state.texts[key] = (text, attributedTextByFont)
}
}

private func changeIdleTimer(disabled: Bool, in viewModel: ViewModel<HtmlEpubReaderActionHandler>) {
guard viewModel.state.settings.idleTimerDisabled != disabled else { return }
var settings = viewModel.state.settings
Expand Down Expand Up @@ -314,10 +328,19 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
}
}

private func set(color: String, lineWidth: CGFloat, pageLabel: String, updateSubsequentLabels: Bool, highlightText: String, key: String, viewModel: ViewModel<HtmlEpubReaderActionHandler>) {
private func set(
color: String,
lineWidth: CGFloat,
pageLabel: String,
updateSubsequentLabels: Bool,
highlightText: NSAttributedString,
key: String,
viewModel: ViewModel<HtmlEpubReaderActionHandler>
) {
let text = htmlAttributedStringConverter.convert(attributedString: highlightText)
let values = [
KeyBaseKeyPair(key: FieldKeys.Item.Annotation.pageLabel, baseKey: nil): pageLabel,
KeyBaseKeyPair(key: FieldKeys.Item.Annotation.text, baseKey: nil): highlightText,
KeyBaseKeyPair(key: FieldKeys.Item.Annotation.text, baseKey: nil): text,
KeyBaseKeyPair(key: FieldKeys.Item.Annotation.color, baseKey: nil): color,
KeyBaseKeyPair(key: FieldKeys.Item.Annotation.Position.lineWidth, baseKey: FieldKeys.Item.Annotation.position): "\(Decimal(lineWidth).rounded(to: 3))"
]
Expand Down Expand Up @@ -697,6 +720,7 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
// Get sorted database keys
var keys = viewModel.state.snapshotKeys ?? viewModel.state.sortedKeys
var annotations: [String: HtmlEpubAnnotation] = viewModel.state.annotations
var texts = viewModel.state.texts
var comments = viewModel.state.comments
var selectionDeleted = false
var popoverWasInserted = false
Expand Down Expand Up @@ -730,7 +754,28 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
if item.changeType == .sync {
// Update comment if it's remote sync change
DDLogInfo("HtmlEpubReaderActionHandler: update comment")
comments[key] = htmlAttributedStringConverter.convert(text: annotation.comment, baseAttributes: [.font: viewModel.state.commentFont])
let textCacheTuple: (String, [UIFont: NSAttributedString])?
let comment: NSAttributedString?
// Annotation text
switch annotation.type {
case .highlight, .underline:
textCacheTuple = annotation.text.flatMap({
($0, [viewModel.state.textFont: htmlAttributedStringConverter.convert(text: $0, baseAttributes: [.font: viewModel.state.textFont])])
})

case .note, .image, .ink, .freeText:
textCacheTuple = nil
}
texts[key] = textCacheTuple
// Annotation comment
switch annotation.type {
case .note, .highlight, .image, .underline:
comment = htmlAttributedStringConverter.convert(text: annotation.comment, baseAttributes: [.font: viewModel.state.commentFont])

case .ink, .freeText:
comment = nil
}
comments[key] = comment
}
}
}
Expand Down Expand Up @@ -810,6 +855,7 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
state.annotations = annotations
state.documentUpdate = HtmlEpubReaderState.DocumentUpdate(deletions: deletedPdfAnnotations, insertions: insertedPdfAnnotations, modifications: updatedPdfAnnotations)
state.comments = comments
state.texts = texts
// Filter updated keys to include only keys that are actually available in `sortedKeys`. If filter/search is turned on and an item is edited so that it disappears from the filter/search,
// `updatedKeys` will try to update it while the key will be deleted from data source at the same time.
state.updatedAnnotationKeys = updatedKeys.filter({ state.sortedKeys.contains($0) })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,3 +510,28 @@ extension HtmlEpubReaderViewController: UIPopoverPresentationControllerDelegate
}

extension HtmlEpubReaderViewController: HtmlEpubReaderContainerDelegate {}

extension HtmlEpubReaderViewController: HtmlEpubAnnotationsDelegate {
func parseAndCacheIfNeededAttributedText(for annotation: HtmlEpubAnnotation, with font: UIFont) -> NSAttributedString? {
guard let text = annotation.text, !text.isEmpty else { return nil }

if let attributedText = viewModel.state.texts[annotation.key]?.1[font] {
return attributedText
}

viewModel.process(action: .parseAndCacheText(key: annotation.key, text: text, font: font))
return viewModel.state.texts[annotation.key]?.1[font]
}

func parseAndCacheIfNeededAttributedComment(for annotation: HtmlEpubAnnotation) -> NSAttributedString? {
let comment = annotation.comment
guard !comment.isEmpty else { return nil }

if let attributedComment = viewModel.state.comments[annotation.key] {
return attributedComment
}

viewModel.process(action: .parseAndCacheComment(key: annotation.key, comment: comment))
return viewModel.state.comments[annotation.key]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import UIKit

import RxSwift

protocol HtmlEpubAnnotationsDelegate: AnyObject {
func parseAndCacheIfNeededAttributedText(for annotation: HtmlEpubAnnotation, with font: UIFont) -> NSAttributedString?
func parseAndCacheIfNeededAttributedComment(for annotation: HtmlEpubAnnotation) -> NSAttributedString?
}

class HtmlEpubSidebarViewController: UIViewController {
private static let cellId = "AnnotationCell"
private let viewModel: ViewModel<HtmlEpubReaderActionHandler>
Expand Down Expand Up @@ -274,17 +279,18 @@ class HtmlEpubSidebarViewController: UIViewController {
for: annotation,
userId: viewModel.state.userId,
library: viewModel.state.library,
highlightFont: viewModel.state.textFont,
sender: sender,
userInterfaceStyle: viewModel.state.settings.appearance.userInterfaceStyle,
saveAction: { [weak self] color, lineWidth, _, pageLabel, updateSubsequentLabels, highlightText in
saveAction: { [weak self] data, updateSubsequentLabels in
self?.viewModel.process(
action: .updateAnnotationProperties(
key: key,
color: color,
lineWidth: lineWidth,
pageLabel: pageLabel,
color: data.color,
lineWidth: data.lineWidth,
pageLabel: data.pageLabel,
updateSubsequentLabels: updateSubsequentLabels,
highlightText: highlightText
highlightText: data.highlightText
)
)
},
Expand Down
6 changes: 3 additions & 3 deletions Zotero/Scenes/Detail/PDF/PDFCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ extension PDFCoordinator: PdfReaderCoordinatorDelegate {
navigationController.overrideUserInterfaceStyle = userInterfaceStyle

let author = viewModel.state.library.identifier == .custom(.myLibrary) ? "" : annotation.author(displayName: viewModel.state.displayName, username: viewModel.state.username)
let comment: NSAttributedString = (self.navigationController?.viewControllers.first as? AnnotationsDelegate)?.parseAndCacheIfNeededAttributedComment(for: annotation) ?? .init(string: "")
let comment: NSAttributedString = (self.navigationController?.viewControllers.first as? PDFAnnotationsDelegate)?.parseAndCacheIfNeededAttributedComment(for: annotation) ?? .init(string: "")
let highlightFont = viewModel.state.textEditorFont
let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? AnnotationsDelegate)?
let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? PDFAnnotationsDelegate)?
.parseAndCacheIfNeededAttributedText(for: annotation, with: highlightFont) ?? .init(string: "")
let editability = annotation.editability(currentUserId: viewModel.state.userId, library: viewModel.state.library)

Expand Down Expand Up @@ -621,7 +621,7 @@ extension PDFCoordinator: PdfAnnotationsCoordinatorDelegate {
let navigationController = NavigationViewController()
navigationController.overrideUserInterfaceStyle = userInterfaceStyle

let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? AnnotationsDelegate)?
let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? PDFAnnotationsDelegate)?
.parseAndCacheIfNeededAttributedText(for: annotation, with: highlightFont) ?? .init(string: "")
let coordinator = AnnotationEditCoordinator(
data: AnnotationEditState.Data(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import RxSwift

typealias AnnotationsViewControllerAction = (AnnotationView.Action, Annotation, UIButton) -> Void

protocol AnnotationsDelegate: AnyObject {
protocol PDFAnnotationsDelegate: AnyObject {
func parseAndCacheIfNeededAttributedText(for annotation: PDFAnnotation, with font: UIFont) -> NSAttributedString?
func parseAndCacheIfNeededAttributedComment(for annotation: PDFAnnotation) -> NSAttributedString?
}
Expand All @@ -36,7 +36,7 @@ final class PDFAnnotationsViewController: UIViewController {
private var dataSource: TableViewDiffableDataSource<Int, PDFReaderState.AnnotationKey>!
private var searchController: UISearchController!

weak var parentDelegate: (PDFReaderContainerDelegate & SidebarDelegate & AnnotationsDelegate)?
weak var parentDelegate: (PDFReaderContainerDelegate & SidebarDelegate & PDFAnnotationsDelegate)?
weak var coordinatorDelegate: PdfAnnotationsCoordinatorDelegate?
weak var boundingBoxConverter: AnnotationBoundingBoxConverter?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,7 @@ extension PDFReaderViewController: SidebarDelegate {
}
}

extension PDFReaderViewController: AnnotationsDelegate {
extension PDFReaderViewController: PDFAnnotationsDelegate {
func parseAndCacheIfNeededAttributedText(for annotation: any PDFAnnotation, with font: UIFont) -> NSAttributedString? {
guard let text = annotation.text, !text.isEmpty else { return nil }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class PDFSidebarViewController: UIViewController {
private weak var controllerContainer: UIView!
private weak var currentController: UIViewController?
private var controllerDisposeBag: DisposeBag?
weak var parentDelegate: (PDFReaderContainerDelegate & SidebarDelegate & AnnotationsDelegate)?
weak var parentDelegate: (PDFReaderContainerDelegate & SidebarDelegate & PDFAnnotationsDelegate)?
weak var coordinatorDelegate: PdfAnnotationsCoordinatorDelegate?
weak var boundingBoxConverter: AnnotationBoundingBoxConverter?

Expand Down

0 comments on commit c94d639

Please sign in to comment.