From cf97c8f2a9064e24c6cb42bdfdb93e07b2a243de Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Thu, 8 Aug 2024 20:18:12 +0300 Subject: [PATCH] Use FormattedTextView for annotation text editing --- .../AnnotationPopoverCoordinator.swift | 1 + .../Models/AnnotationEditAction.swift | 2 +- .../Models/AnnotationEditState.swift | 7 +- .../Models/AnnotationPopoverAction.swift | 3 +- .../Models/AnnotationPopoverState.swift | 7 +- .../AnnotationPopoverActionHandler.swift | 6 -- .../Views/AnnotationEditViewController.swift | 10 +-- .../Views/TextContentEditCell.swift | 18 +++-- .../Detail/PDF/Models/PDFReaderAction.swift | 14 +++- .../Detail/PDF/Models/PDFReaderState.swift | 4 +- Zotero/Scenes/Detail/PDF/PDFCoordinator.swift | 15 ++++- .../ViewModels/PDFReaderActionHandler.swift | 65 ++++++++----------- .../AnnotationViewTextView.swift | 2 +- .../Views/PDFAnnotationsViewController.swift | 30 +++++---- .../PDF/Views/PDFDocumentViewController.swift | 24 +++---- .../PDF/Views/PDFReaderViewController.swift | 8 +-- .../General/Views/FormattedTextView.swift | 4 -- 17 files changed, 112 insertions(+), 108 deletions(-) diff --git a/Zotero/Scenes/Detail/Annotation Popover/AnnotationPopoverCoordinator.swift b/Zotero/Scenes/Detail/Annotation Popover/AnnotationPopoverCoordinator.swift index 52ef7ea0a..28a499cdf 100644 --- a/Zotero/Scenes/Detail/Annotation Popover/AnnotationPopoverCoordinator.swift +++ b/Zotero/Scenes/Detail/Annotation Popover/AnnotationPopoverCoordinator.swift @@ -81,6 +81,7 @@ extension AnnotationPopoverCoordinator: AnnotationPopoverAnnotationCoordinatorDe lineWidth: state.lineWidth, pageLabel: state.pageLabel, highlightText: state.highlightText, + highlightFont: state.highlightFont, fontSize: nil ) let state = AnnotationEditState(data: data) diff --git a/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationEditAction.swift b/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationEditAction.swift index 69ab2acdc..c9613364f 100644 --- a/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationEditAction.swift +++ b/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationEditAction.swift @@ -12,6 +12,6 @@ enum AnnotationEditAction { case setColor(String) case setLineWidth(CGFloat) case setPageLabel(String, Bool) - case setHighlight(String) + case setHighlight(NSAttributedString) case setFontSize(UInt) } diff --git a/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationEditState.swift b/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationEditState.swift index f101b94b0..7c667e12f 100644 --- a/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationEditState.swift +++ b/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationEditState.swift @@ -15,7 +15,8 @@ struct AnnotationEditState: ViewModelState { let color: String let lineWidth: CGFloat let pageLabel: String - let highlightText: String + let highlightText: NSAttributedString + let highlightFont: UIFont let fontSize: UInt? } @@ -35,7 +36,8 @@ struct AnnotationEditState: ViewModelState { var lineWidth: CGFloat var pageLabel: String var fontSize: UInt - var highlightText: String + var highlightText: NSAttributedString + var highlightFont: UIFont var updateSubsequentLabels: Bool var changes: Changes @@ -46,6 +48,7 @@ struct AnnotationEditState: ViewModelState { lineWidth = data.lineWidth pageLabel = data.pageLabel highlightText = data.highlightText + highlightFont = data.highlightFont fontSize = data.fontSize ?? 0 updateSubsequentLabels = false changes = [] diff --git a/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationPopoverAction.swift b/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationPopoverAction.swift index aebf6e2ba..e8e94946f 100644 --- a/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationPopoverAction.swift +++ b/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationPopoverAction.swift @@ -12,9 +12,8 @@ enum AnnotationPopoverAction { case setColor(String) case setLineWidth(CGFloat) case setPageLabel(String, Bool) - case setHighlight(String) case setTags([Tag]) case setComment(NSAttributedString) case delete - case setProperties(pageLabel: String, updateSubsequentLabels: Bool, highlightText: String) + case setProperties(pageLabel: String, updateSubsequentLabels: Bool, highlightText: NSAttributedString) } diff --git a/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationPopoverState.swift b/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationPopoverState.swift index cbaa72ce5..b29e9f152 100644 --- a/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationPopoverState.swift +++ b/Zotero/Scenes/Detail/Annotation Popover/Models/AnnotationPopoverState.swift @@ -18,7 +18,8 @@ struct AnnotationPopoverState: ViewModelState { let color: String let lineWidth: CGFloat let pageLabel: String - let highlightText: String + let highlightText: NSAttributedString + let highlightFont: UIFont let tags: [Tag] let showsDeleteButton: Bool } @@ -47,7 +48,8 @@ struct AnnotationPopoverState: ViewModelState { var color: String var lineWidth: CGFloat var pageLabel: String - var highlightText: String + var highlightText: NSAttributedString + var highlightFont: UIFont var updateSubsequentLabels: Bool var tags: [Tag] var changes: Changes @@ -62,6 +64,7 @@ struct AnnotationPopoverState: ViewModelState { self.lineWidth = data.lineWidth self.pageLabel = data.pageLabel self.highlightText = data.highlightText + self.highlightFont = data.highlightFont self.tags = data.tags self.showsDeleteButton = data.showsDeleteButton self.updateSubsequentLabels = false diff --git a/Zotero/Scenes/Detail/Annotation Popover/ViewModels/AnnotationPopoverActionHandler.swift b/Zotero/Scenes/Detail/Annotation Popover/ViewModels/AnnotationPopoverActionHandler.swift index 6b544b498..2f853d372 100644 --- a/Zotero/Scenes/Detail/Annotation Popover/ViewModels/AnnotationPopoverActionHandler.swift +++ b/Zotero/Scenes/Detail/Annotation Popover/ViewModels/AnnotationPopoverActionHandler.swift @@ -33,12 +33,6 @@ struct AnnotationPopoverActionHandler: ViewModelActionHandler { state.changes = .pageLabel } - case .setHighlight(let text): - update(viewModel: viewModel) { state in - state.highlightText = text - state.changes = .highlight - } - case .setComment(let comment): update(viewModel: viewModel) { state in state.comment = comment diff --git a/Zotero/Scenes/Detail/Annotation Popover/Views/AnnotationEditViewController.swift b/Zotero/Scenes/Detail/Annotation Popover/Views/AnnotationEditViewController.swift index ffa446e9b..7f804d6c6 100644 --- a/Zotero/Scenes/Detail/Annotation Popover/Views/AnnotationEditViewController.swift +++ b/Zotero/Scenes/Detail/Annotation Popover/Views/AnnotationEditViewController.swift @@ -10,8 +10,7 @@ import UIKit import RxSwift -// key, color, lineWidth, fontSize, pageLabel, updateSubsequentLabels, highlightText -typealias AnnotationEditSaveAction = (String, CGFloat, UInt, String, Bool, String) -> Void +typealias AnnotationEditSaveAction = (_ key: String, _ lineWidth: CGFloat, _ fontSize: UInt, _ pageLabel: String, _ updateSubsequentLabels: Bool, _ highlightText: NSAttributedString) -> Void typealias AnnotationEditDeleteAction = () -> Void final class AnnotationEditViewController: UIViewController { @@ -133,7 +132,8 @@ final class AnnotationEditViewController: UIViewController { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.minimumLineHeight = AnnotationPopoverLayout.annotationLayout.lineHeight paragraphStyle.maximumLineHeight = AnnotationPopoverLayout.annotationLayout.lineHeight - let attributedText = NSAttributedString(string: self.viewModel.state.highlightText, attributes: [.font: AnnotationPopoverLayout.annotationLayout.font, .paragraphStyle: paragraphStyle]) + let attributedText = NSMutableAttributedString(attributedString: viewModel.state.highlightText) + attributedText.addAttribute(.paragraphStyle, value: paragraphStyle, range: .init(location: 0, length: attributedText.length)) let boundingRect = attributedText.boundingRect(with: CGSize(width: width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil) height += ceil(boundingRect.height) + 58 // 58 for 22 insets and 36 spacer } @@ -231,9 +231,9 @@ extension AnnotationEditViewController: UITableViewDataSource { case .textContent: if let cell = cell as? TextContentEditCell { cell.setup(with: self.viewModel.state.highlightText, color: self.viewModel.state.color) - cell.textAndHeightReloadNeededObservable.subscribe { [weak self] text, needsHeightReload in + cell.attributedTextAndHeightReloadNeededObservable.subscribe { [weak self] attributedText, needsHeightReload in guard let self else { return } - viewModel.process(action: .setHighlight(text)) + viewModel.process(action: .setHighlight(attributedText)) if needsHeightReload { updatePreferredContentSize() reloadHeight() diff --git a/Zotero/Scenes/Detail/Annotation Popover/Views/TextContentEditCell.swift b/Zotero/Scenes/Detail/Annotation Popover/Views/TextContentEditCell.swift index 0a1bff584..3bbacfdc7 100644 --- a/Zotero/Scenes/Detail/Annotation Popover/Views/TextContentEditCell.swift +++ b/Zotero/Scenes/Detail/Annotation Popover/Views/TextContentEditCell.swift @@ -11,13 +11,13 @@ import UIKit import RxSwift final class TextContentEditCell: RxTableViewCell { - let textAndHeightReloadNeededObservable: PublishSubject<(String, Bool)> + let attributedTextAndHeightReloadNeededObservable: PublishSubject<(NSAttributedString, Bool)> private var lineView: UIView? - private var textView: UITextView? + private var textView: FormattedTextView? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - textAndHeightReloadNeededObservable = PublishSubject() + attributedTextAndHeightReloadNeededObservable = PublishSubject() super.init(style: style, reuseIdentifier: reuseIdentifier) setup() selectionStyle = .none @@ -28,7 +28,7 @@ final class TextContentEditCell: RxTableViewCell { contentView.addSubview(lineView) self.lineView = lineView - let textView = TextKit1TextView() + let textView = FormattedTextView(defaultFont: AnnotationPopoverLayout.annotationLayout.font) textView.textContainerInset = UIEdgeInsets() textView.textContainer.lineFragmentPadding = 0 textView.delegate = self @@ -59,14 +59,12 @@ final class TextContentEditCell: RxTableViewCell { return textView.caretRect(for: selectedPosition) } - func setup(with text: String, color: String) { + func setup(with text: NSAttributedString, color: String) { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.minimumLineHeight = AnnotationPopoverLayout.annotationLayout.lineHeight paragraphStyle.maximumLineHeight = AnnotationPopoverLayout.annotationLayout.lineHeight - let attributedText = NSAttributedString( - string: text, - attributes: [.font: AnnotationPopoverLayout.annotationLayout.font, .foregroundColor: Asset.Colors.annotationText.color, .paragraphStyle: paragraphStyle] - ) + let attributedText = NSMutableAttributedString(attributedString: text) + attributedText.addAttributes([.foregroundColor: Asset.Colors.annotationText.color, .paragraphStyle: paragraphStyle], range: .init(location: 0, length: attributedText.length)) lineView?.backgroundColor = UIColor(hex: color) textView?.attributedText = attributedText @@ -77,6 +75,6 @@ extension TextContentEditCell: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { let height = textView.contentSize.height textView.sizeToFit() - textAndHeightReloadNeededObservable.onNext((textView.text, height != textView.contentSize.height)) + attributedTextAndHeightReloadNeededObservable.onNext((textView.attributedText, height != textView.contentSize.height)) } } diff --git a/Zotero/Scenes/Detail/PDF/Models/PDFReaderAction.swift b/Zotero/Scenes/Detail/PDF/Models/PDFReaderAction.swift index 5c4eda187..e3f16626f 100644 --- a/Zotero/Scenes/Detail/PDF/Models/PDFReaderAction.swift +++ b/Zotero/Scenes/Detail/PDF/Models/PDFReaderAction.swift @@ -28,15 +28,23 @@ enum PDFReaderAction { case setTags(key: String, tags: [Tag]) case setColor(key: String, color: String) case setLineWidth(key: String, width: CGFloat) - case setHighlight(key: String, text: String) - case updateAnnotationProperties(key: String, color: String, lineWidth: CGFloat, fontSize: UInt, pageLabel: String, updateSubsequentLabels: Bool, highlightText: String) + case updateAnnotationProperties( + key: String, + color: String, + lineWidth: CGFloat, + fontSize: UInt, + pageLabel: String, + updateSubsequentLabels: Bool, + highlightText: NSAttributedString, + higlightFont: UIFont + ) case userInterfaceStyleChanged(UIUserInterfaceStyle) case updateAnnotationPreviews case setToolOptions(color: String?, size: CGFloat?, tool: PSPDFKit.Annotation.Tool) case createNote(pageIndex: PageIndex, origin: CGPoint) case createImage(pageIndex: PageIndex, origin: CGPoint) case createHighlight(pageIndex: PageIndex, rects: [CGRect]) - case parseAndCacheText(key: String, text: String) + case parseAndCacheText(key: String, text: String, font: UIFont) case parseAndCacheComment(key: String, comment: String) case setComment(key: String, comment: NSAttributedString) case setCommentActive(Bool) diff --git a/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift b/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift index c481d8ba9..19557ef25 100644 --- a/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift +++ b/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift @@ -70,6 +70,7 @@ struct PDFReaderState: ViewModelState { let displayTitle: String? let previewCache: NSCache let textFont: UIFont + let textEditorFont: UIFont let commentFont: UIFont let userId: Int let username: String @@ -83,7 +84,7 @@ struct PDFReaderState: ViewModelState { var itemToken: NotificationToken? var databaseAnnotations: Results! var documentAnnotations: [String: PDFDocumentAnnotation] - var texts: [String: NSAttributedString] + var texts: [String: (String, [UIFont: NSAttributedString])] var comments: [String: NSAttributedString] var searchTerm: String? var filter: AnnotationsFilter? @@ -147,6 +148,7 @@ struct PDFReaderState: ViewModelState { self.displayTitle = displayTitle self.previewCache = NSCache() self.textFont = PDFReaderLayout.annotationLayout.font + self.textEditorFont = AnnotationPopoverLayout.annotationLayout.font self.commentFont = PDFReaderLayout.annotationLayout.font self.userId = userId self.username = username diff --git a/Zotero/Scenes/Detail/PDF/PDFCoordinator.swift b/Zotero/Scenes/Detail/PDF/PDFCoordinator.swift index 119a3ed5d..cda553759 100644 --- a/Zotero/Scenes/Detail/PDF/PDFCoordinator.swift +++ b/Zotero/Scenes/Detail/PDF/PDFCoordinator.swift @@ -45,6 +45,7 @@ protocol PdfAnnotationsCoordinatorDelegate: AnyObject { func showTagPicker(libraryId: LibraryIdentifier, selected: Set, userInterfaceStyle: UIUserInterfaceStyle?, picked: @escaping ([Tag]) -> Void) func showCellOptions( for annotation: PDFAnnotation, + highlightFont: UIFont, userId: Int, library: Library, sender: UIButton, @@ -198,7 +199,10 @@ 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) ?? NSAttributedString() + let comment: NSAttributedString = (self.navigationController?.viewControllers.first as? AnnotationsDelegate)?.parseAndCacheIfNeededAttributedComment(for: annotation) ?? .init(string: "") + let highlightFont = viewModel.state.textEditorFont + let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? AnnotationsDelegate)? + .parseAndCacheIfNeededAttributedText(for: annotation, with: highlightFont) ?? .init(string: "") let editability = annotation.editability(currentUserId: viewModel.state.userId, library: viewModel.state.library) let data = AnnotationPopoverState.Data( @@ -210,7 +214,8 @@ extension PDFCoordinator: PdfReaderCoordinatorDelegate { color: annotation.color, lineWidth: annotation.lineWidth ?? 0, pageLabel: annotation.pageLabel, - highlightText: annotation.text ?? "", + highlightText: highlightText, + highlightFont: highlightFont, tags: annotation.tags, showsDeleteButton: editability != .notEditable ) @@ -603,6 +608,7 @@ extension PDFCoordinator: PdfAnnotationsCoordinatorDelegate { func showCellOptions( for annotation: PDFAnnotation, + highlightFont: UIFont, userId: Int, library: Library, sender: UIButton, @@ -613,6 +619,8 @@ extension PDFCoordinator: PdfAnnotationsCoordinatorDelegate { let navigationController = NavigationViewController() navigationController.overrideUserInterfaceStyle = userInterfaceStyle + let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? AnnotationsDelegate)? + .parseAndCacheIfNeededAttributedText(for: annotation, with: highlightFont) ?? .init(string: "") let coordinator = AnnotationEditCoordinator( data: AnnotationEditState.Data( type: annotation.type, @@ -620,7 +628,8 @@ extension PDFCoordinator: PdfAnnotationsCoordinatorDelegate { color: annotation.color, lineWidth: annotation.lineWidth ?? 0, pageLabel: annotation.pageLabel, - highlightText: annotation.text ?? "", + highlightText: highlightText, + highlightFont: highlightFont, fontSize: annotation.fontSize ), saveAction: saveAction, diff --git a/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift b/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift index d27a0d20f..9b65e1403 100644 --- a/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift +++ b/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift @@ -167,13 +167,8 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi case .requestPreviews(let keys, let notify): loadPreviews(for: keys, notify: notify, in: viewModel) - case .setHighlight(let key, let highlight): - set(highlightText: highlight, key: key, viewModel: viewModel) - - case .parseAndCacheText(let key, let text): - update(viewModel: viewModel, notifyListeners: false) { state in - state.texts[key] = htmlAttributedStringConverter.convert(text: text, baseAttributes: [.font: state.textFont]) - } + case .parseAndCacheText(let key, let text, let font): + updateTextCache(key: key, text: text, font: font, viewModel: viewModel, notifyListeners: false) case .parseAndCacheComment(let key, let comment): update(viewModel: viewModel, notifyListeners: false) { state in @@ -202,7 +197,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi case .setTags(let key, let tags): set(tags: tags, key: key, viewModel: viewModel) - case .updateAnnotationProperties(let key, let color, let lineWidth, let fontSize, let pageLabel, let updateSubsequentLabels, let highlightText): + case .updateAnnotationProperties(let key, let color, let lineWidth, let fontSize, let pageLabel, let updateSubsequentLabels, let highlightText, let highlightFont): set( color: color, lineWidth: lineWidth, @@ -210,6 +205,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi pageLabel: pageLabel, updateSubsequentLabels: updateSubsequentLabels, highlightText: highlightText, + highlightFont: highlightFont, key: key, viewModel: viewModel ) @@ -1272,26 +1268,6 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi update(annotation: annotation, contents: htmlComment, in: viewModel.state.document) } - private func set(highlightText: String, key: String, viewModel: ViewModel) { - let values = [KeyBaseKeyPair(key: FieldKeys.Item.Annotation.text, baseKey: nil): highlightText] - let request = EditItemFieldsDbRequest(key: key, libraryId: viewModel.state.library.identifier, fieldValues: values, dateParser: dateParser) - perform(request: request) { [weak self, weak viewModel] error in - guard let self, let viewModel else { return } - if let error { - DDLogError("PDFReaderActionHandler: can't update annotation \(key) - \(error)") - - update(viewModel: viewModel) { state in - state.error = .cantUpdateAnnotation - } - return - } - let text = htmlAttributedStringConverter.convert(text: highlightText, baseAttributes: [.font: viewModel.state.textFont]) - update(viewModel: viewModel) { state in - state.texts[key] = text - } - } - } - private func set(tags: [Tag], key: String, viewModel: ViewModel) { let request = EditTagsForItemDbRequest(key: key, libraryId: viewModel.state.library.identifier, tags: tags) perform(request: request) { [weak self, weak viewModel] error in @@ -1311,7 +1287,8 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi fontSize: UInt, pageLabel: String, updateSubsequentLabels: Bool, - highlightText: String, + highlightText: NSAttributedString, + highlightFont: UIFont, key: String, viewModel: ViewModel ) { @@ -1320,7 +1297,8 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi update(annotation: annotation, color: (color, viewModel.state.interfaceStyle), lineWidth: lineWidth, fontSize: fontSize, in: viewModel.state.document) // Update remaining values directly - let values = [KeyBaseKeyPair(key: FieldKeys.Item.Annotation.pageLabel, baseKey: nil): pageLabel, KeyBaseKeyPair(key: FieldKeys.Item.Annotation.text, baseKey: nil): highlightText] + 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): text] let request = EditItemFieldsDbRequest(key: key, libraryId: viewModel.state.library.identifier, fieldValues: values, dateParser: dateParser) perform(request: request) { [weak self, weak viewModel] error in guard let self, let viewModel else { return } @@ -1332,10 +1310,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi } return } - let text = htmlAttributedStringConverter.convert(text: highlightText, baseAttributes: [.font: viewModel.state.textFont]) - update(viewModel: viewModel) { state in - state.texts[key] = text - } + updateTextCache(key: key, text: text, font: highlightFont, viewModel: viewModel, notifyListeners: true) } } @@ -2161,17 +2136,20 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi if item.changeType == .sync { // Update text and comment if it's remote sync change DDLogInfo("PDFReaderActionHandler: update text and comment") - let text: NSAttributedString? + + let textCacheTuple: (String, [UIFont: NSAttributedString])? let comment: NSAttributedString? // Annotation text switch annotation.type { case .highlight, .underline: - text = annotation.text.flatMap({ htmlAttributedStringConverter.convert(text: $0, baseAttributes: [.font: viewModel.state.textFont]) }) + textCacheTuple = annotation.text.flatMap({ + ($0, [viewModel.state.textFont: htmlAttributedStringConverter.convert(text: $0, baseAttributes: [.font: viewModel.state.textFont])]) + }) case .note, .image, .ink, .freeText: - text = nil + textCacheTuple = nil } - texts[key.key] = text + texts[key.key] = textCacheTuple // Annotation comment switch annotation.type { case .note, .highlight, .image, .underline: @@ -2459,6 +2437,17 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi userInfo: [PSPDFAnnotationChangedNotificationKeyPathKey: PdfAnnotationChanges.stringValues(from: changes)] ) } + + private func updateTextCache(key: String, text: String, font: UIFont, viewModel: ViewModel, notifyListeners: Bool) { + update(viewModel: viewModel, notifyListeners: notifyListeners) { 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) + } + } } extension PSPDFKit.Annotation { diff --git a/Zotero/Scenes/Detail/PDF/Views/Annotation View/AnnotationViewTextView.swift b/Zotero/Scenes/Detail/PDF/Views/Annotation View/AnnotationViewTextView.swift index c1e3fda7e..9d1f8669e 100644 --- a/Zotero/Scenes/Detail/PDF/Views/Annotation View/AnnotationViewTextView.swift +++ b/Zotero/Scenes/Detail/PDF/Views/Annotation View/AnnotationViewTextView.swift @@ -18,7 +18,7 @@ final class AnnotationViewTextView: UIView { private var textViewDelegate: PlaceholderTextViewDelegate! var textObservable: Observable<(NSAttributedString, Bool)?> { - return textView.textObservable.flatMap { [weak self] _ -> Observable<(NSAttributedString, Bool)?> in + return textView.attributedTextObservable.flatMap { [weak self] _ -> Observable<(NSAttributedString, Bool)?> in guard let self else { return .just(nil) } let height = textView.contentSize.height textView.sizeToFit() diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift index 0053247a6..6d5b73a87 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift @@ -16,7 +16,7 @@ import RxSwift typealias AnnotationsViewControllerAction = (AnnotationView.Action, Annotation, UIButton) -> Void protocol AnnotationsDelegate: AnyObject { - func parseAndCacheIfNeededAttributedText(for annotation: PDFAnnotation) -> NSAttributedString? + func parseAndCacheIfNeededAttributedText(for annotation: PDFAnnotation, with font: UIFont) -> NSAttributedString? func parseAndCacheIfNeededAttributedComment(for annotation: PDFAnnotation) -> NSAttributedString? } @@ -109,24 +109,26 @@ final class PDFAnnotationsViewController: UIViewController { case .options(let sender): guard let sender else { return } let key = annotation.readerKey + let highlightFont = viewModel.state.textEditorFont coordinatorDelegate?.showCellOptions( for: annotation, + highlightFont: highlightFont, userId: viewModel.state.userId, library: viewModel.state.library, sender: sender, userInterfaceStyle: viewModel.state.interfaceStyle, - saveAction: { [weak self] color, lineWidth, fontSize, pageLabel, updateSubsequentLabels, highlightText in - self?.viewModel.process( - action: .updateAnnotationProperties( - key: key.key, - color: color, - lineWidth: lineWidth, - fontSize: fontSize, - pageLabel: pageLabel, - updateSubsequentLabels: updateSubsequentLabels, - highlightText: highlightText - ) - ) + saveAction: { [weak viewModel] color, lineWidth, fontSize, pageLabel, updateSubsequentLabels, highlightText in + guard let viewModel else { return } + viewModel.process(action: .updateAnnotationProperties( + key: key.key, + color: color, + lineWidth: lineWidth, + fontSize: fontSize, + pageLabel: pageLabel, + updateSubsequentLabels: updateSubsequentLabels, + highlightText: highlightText, + higlightFont: highlightFont + )) }, deleteAction: { [weak self] in self?.viewModel.process(action: .removeAnnotation(key)) @@ -286,7 +288,7 @@ final class PDFAnnotationsViewController: UIViewController { // Annotation text switch annotation.type { case .highlight, .underline: - text = parentDelegate?.parseAndCacheIfNeededAttributedText(for: annotation) + text = parentDelegate?.parseAndCacheIfNeededAttributedText(for: annotation, with: state.textFont) case .note, .image, .ink, .freeText: text = nil diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift index e40def378..dd433c269 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift @@ -351,8 +351,8 @@ final class PDFDocumentViewController: UIViewController { ) guard let observable else { return } - observable.subscribe(onNext: { [weak self] state in - guard let self else { return } + observable.subscribe(onNext: { [weak viewModel] state in + guard let viewModel else { return } if state.changes.contains(.color) { viewModel.process(action: .setColor(key: key.key, color: state.color)) } @@ -370,16 +370,16 @@ final class PDFDocumentViewController: UIViewController { } if state.changes.contains(.pageLabel) || state.changes.contains(.highlight) { // TODO: - fix font size - viewModel.process(action: - .updateAnnotationProperties( - key: key.key, - color: state.color, - lineWidth: state.lineWidth, - fontSize: 0, - pageLabel: state.pageLabel, - updateSubsequentLabels: state.updateSubsequentLabels, - highlightText: state.highlightText) - ) + viewModel.process(action: .updateAnnotationProperties( + key: key.key, + color: state.color, + lineWidth: state.lineWidth, + fontSize: 0, + pageLabel: state.pageLabel, + updateSubsequentLabels: state.updateSubsequentLabels, + highlightText: state.highlightText, + higlightFont: state.highlightFont + )) } }) .disposed(by: disposeBag) diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift index 7defdbf7d..c627813fb 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift @@ -856,15 +856,15 @@ extension PDFReaderViewController: SidebarDelegate { } extension PDFReaderViewController: AnnotationsDelegate { - func parseAndCacheIfNeededAttributedText(for annotation: any PDFAnnotation) -> NSAttributedString? { + func parseAndCacheIfNeededAttributedText(for annotation: any PDFAnnotation, with font: UIFont) -> NSAttributedString? { guard let text = annotation.text, !text.isEmpty else { return nil } - if let attributedText = viewModel.state.texts[annotation.key] { + if let attributedText = viewModel.state.texts[annotation.key]?.1[font] { return attributedText } - viewModel.process(action: .parseAndCacheText(key: annotation.key, text: text)) - return viewModel.state.texts[annotation.key] + viewModel.process(action: .parseAndCacheText(key: annotation.key, text: text, font: font)) + return viewModel.state.texts[annotation.key]?.1[font] } func parseAndCacheIfNeededAttributedComment(for annotation: PDFAnnotation) -> NSAttributedString? { diff --git a/Zotero/Scenes/General/Views/FormattedTextView.swift b/Zotero/Scenes/General/Views/FormattedTextView.swift index c07aaf5b8..cdf164d61 100644 --- a/Zotero/Scenes/General/Views/FormattedTextView.swift +++ b/Zotero/Scenes/General/Views/FormattedTextView.swift @@ -10,7 +10,6 @@ import UIKit import RxSwift final class FormattedTextView: TextKit1TextView { - let textObservable: PublishSubject let attributedTextObservable: PublishSubject let didBecomeActiveObservable: PublishSubject @@ -19,7 +18,6 @@ final class FormattedTextView: TextKit1TextView { private let disposeBag: DisposeBag init(defaultFont: UIFont) { - textObservable = PublishSubject() attributedTextObservable = PublishSubject() didBecomeActiveObservable = PublishSubject() self.defaultFont = defaultFont @@ -55,7 +53,6 @@ final class FormattedTextView: TextKit1TextView { .observe(on: MainScheduler.instance) .subscribe(onNext: { [weak self] _ in guard let self else { return } - textObservable.onNext(text) attributedTextObservable.onNext(attributedText) }) .disposed(by: disposeBag) @@ -107,7 +104,6 @@ final class FormattedTextView: TextKit1TextView { attributedStringAction(string, range) attributedText = string selectedRange = range - textObservable.onNext(text) attributedTextObservable.onNext(attributedText) delegate?.textViewDidChange?(self) }