From 99959355448d8c91e17645000f028b6208312195 Mon Sep 17 00:00:00 2001 From: Michal Rentka Date: Thu, 13 Feb 2025 11:40:44 +0100 Subject: [PATCH 1/3] Added sepia mode to PDF reader --- Zotero/Assets/en.lproj/Localizable.strings | 1 + Zotero/Extensions/Localizable.swift | 2 ++ .../Scenes/Detail/PDF/Models/PDFReaderState.swift | 1 + .../PDF/ViewModels/PDFReaderActionHandler.swift | 7 +++++-- .../PDF/Views/PDFDocumentViewController.swift | 7 ++++++- .../PDF/Views/PDFReaderViewController.swift | 2 +- .../General/Models/ReaderSettingsState.swift | 15 ++++++++++++--- .../Views/ReaderSettingsViewController.swift | 1 + 8 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Zotero/Assets/en.lproj/Localizable.strings b/Zotero/Assets/en.lproj/Localizable.strings index 9de246b58..dcd2e3a22 100644 --- a/Zotero/Assets/en.lproj/Localizable.strings +++ b/Zotero/Assets/en.lproj/Localizable.strings @@ -262,6 +262,7 @@ "pdf.settings.appearance.auto" = "Automatic"; "pdf.settings.appearance.light_mode" = "Light"; "pdf.settings.appearance.dark_mode" = "Dark"; +"pdf.settings.appearance.sepia_mode" = "Sepia"; "pdf.settings.page_mode.title" = "Page Mode"; "pdf.settings.page_mode.single" = "Single"; "pdf.settings.page_mode.double" = "Double"; diff --git a/Zotero/Extensions/Localizable.swift b/Zotero/Extensions/Localizable.swift index fc14c0643..ccc0aca1e 100644 --- a/Zotero/Extensions/Localizable.swift +++ b/Zotero/Extensions/Localizable.swift @@ -1047,6 +1047,8 @@ internal enum L10n { internal static let darkMode = L10n.tr("Localizable", "pdf.settings.appearance.dark_mode", fallback: "Dark") /// Light internal static let lightMode = L10n.tr("Localizable", "pdf.settings.appearance.light_mode", fallback: "Light") + /// Sepia + internal static let sepiaMode = L10n.tr("Localizable", "pdf.settings.appearance.sepia_mode", fallback: "Sepia") /// Appearance internal static let title = L10n.tr("Localizable", "pdf.settings.appearance.title", fallback: "Appearance") } diff --git a/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift b/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift index daa385d3a..5a7772ef7 100644 --- a/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift +++ b/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift @@ -52,6 +52,7 @@ struct PDFReaderState: ViewModelState { static let activeFontSize = Changes(rawValue: 1 << 15) static let library = Changes(rawValue: 1 << 16) static let md5 = Changes(rawValue: 1 << 17) + static let appearanceMode = Changes(rawValue: 1 << 18) } enum Error: Swift.Error { diff --git a/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift b/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift index b50f0beb5..871b64121 100644 --- a/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift +++ b/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift @@ -483,8 +483,11 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi private func update(settings: PDFSettings, parentInterfaceStyle: UIUserInterfaceStyle, in viewModel: ViewModel) { // Update local state update(viewModel: viewModel) { state in + if state.settings.appearanceMode != settings.appearanceMode { + state.changes = .appearanceMode + } state.settings = settings - state.changes = .settings + state.changes.insert(.settings) } // Store new settings to defaults Defaults.shared.pdfSettings = settings @@ -495,7 +498,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi case .dark: settingsInterfaceStyle = .dark - case .light: + case .light, .sepia: settingsInterfaceStyle = .light case .automatic: diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift index 7e1398cc6..0b79703d6 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift @@ -216,7 +216,7 @@ final class PDFDocumentViewController: UIViewController { } private func update(state: PDFReaderState, pdfController: PDFViewController) { - if state.changes.contains(.interfaceStyle) { + if state.changes.contains(.appearanceMode) { updateInterface(to: state.settings.appearanceMode, userInterfaceStyle: state.interfaceStyle) } @@ -351,6 +351,11 @@ final class PDFDocumentViewController: UIViewController { self.pdfController?.overrideUserInterfaceStyle = .light self.unlockController?.overrideUserInterfaceStyle = .light + case .sepia: + self.pdfController?.appearanceModeManager.appearanceMode = .sepia + self.pdfController?.overrideUserInterfaceStyle = .light + self.unlockController?.overrideUserInterfaceStyle = .light + case .dark: self.pdfController?.appearanceModeManager.appearanceMode = .night self.pdfController?.overrideUserInterfaceStyle = .dark diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift index 7c6857593..0868da0e1 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift @@ -526,7 +526,7 @@ class PDFReaderViewController: UIViewController { case .automatic: navigationController?.overrideUserInterfaceStyle = .unspecified - case .light: + case .light, .sepia: navigationController?.overrideUserInterfaceStyle = .light case .dark: diff --git a/Zotero/Scenes/General/Models/ReaderSettingsState.swift b/Zotero/Scenes/General/Models/ReaderSettingsState.swift index 29e842426..64157bc12 100644 --- a/Zotero/Scenes/General/Models/ReaderSettingsState.swift +++ b/Zotero/Scenes/General/Models/ReaderSettingsState.swift @@ -14,13 +14,22 @@ struct ReaderSettingsState: ViewModelState { enum Appearance: UInt { case light case dark + case sepia case automatic var userInterfaceStyle: UIUserInterfaceStyle { switch self { - case .automatic: return .unspecified - case .dark: return .dark - case .light: return .light + case .automatic: + return .unspecified + + case .dark: + return .dark + + case .light: + return .light + + case .sepia: + return .light } } } diff --git a/Zotero/Scenes/General/Views/ReaderSettingsViewController.swift b/Zotero/Scenes/General/Views/ReaderSettingsViewController.swift index ca8381db6..87ebb5a43 100644 --- a/Zotero/Scenes/General/Views/ReaderSettingsViewController.swift +++ b/Zotero/Scenes/General/Views/ReaderSettingsViewController.swift @@ -148,6 +148,7 @@ final class ReaderSettingsViewController: UICollectionViewController { } actions = [UIAction(title: L10n.Pdf.Settings.Appearance.lightMode, handler: { [weak self] _ in self?.viewModel.process(action: .setAppearance(.light)) }), UIAction(title: L10n.Pdf.Settings.Appearance.darkMode, handler: { [weak self] _ in self?.viewModel.process(action: .setAppearance(.dark)) }), + UIAction(title: L10n.Pdf.Settings.Appearance.sepiaMode, handler: { [weak self] _ in self?.viewModel.process(action: .setAppearance(.sepia)) }), UIAction(title: L10n.Pdf.Settings.Appearance.auto, handler: { [weak self] _ in self?.viewModel.process(action: .setAppearance(.automatic)) })] } From b8d11083df1dd4e0ba263a92319255b22e4361a3 Mon Sep 17 00:00:00 2001 From: Michal Rentka Date: Fri, 14 Feb 2025 09:55:37 +0100 Subject: [PATCH 2/3] Update Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift Co-authored-by: Miltiadis Vasilakis --- Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift index 0b79703d6..b256bbe0b 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift @@ -216,7 +216,7 @@ final class PDFDocumentViewController: UIViewController { } private func update(state: PDFReaderState, pdfController: PDFViewController) { - if state.changes.contains(.appearanceMode) { + if state.changes.contains(.interfaceStyle) || state.changes.contains(.appearanceMode) { updateInterface(to: state.settings.appearanceMode, userInterfaceStyle: state.interfaceStyle) } From 436260ec866340c4abaee4b00412c54f7f044947 Mon Sep 17 00:00:00 2001 From: Michal Rentka Date: Fri, 14 Feb 2025 15:23:00 +0100 Subject: [PATCH 3/3] Fixed appearance mode switching, reloading thumbnails --- Zotero.xcodeproj/project.pbxproj | 10 ++ .../AnnotationColorGenerator.swift | 22 +-- Zotero/Controllers/AnnotationConverter.swift | 8 +- .../AnnotationPreviewController.swift | 53 ++++--- .../Controllers/PDFThumbnailController.swift | 33 +++-- Zotero/Models/AnnotationsConfig.swift | 6 +- Zotero/Models/Appearance.swift | 37 +++++ Zotero/Models/DeletableObject.swift | 9 +- Zotero/Models/Files.swift | 30 +++- .../Detail/PDF/Models/PDFReaderState.swift | 33 +++-- .../PDF/Models/PDFThumbnailsAction.swift | 2 +- .../PDF/Models/PDFThumbnailsState.swift | 11 +- .../ViewModels/PDFReaderActionHandler.swift | 133 +++++++++--------- .../PDFThumbnailsActionHandler.swift | 20 +-- .../Views/PDFAnnotationsViewController.swift | 2 +- .../PDF/Views/PDFDocumentViewController.swift | 12 +- .../PDF/Views/PDFReaderViewController.swift | 4 +- .../PDF/Views/PDFSidebarViewController.swift | 5 +- .../Views/PDFThumbnailsViewController.swift | 14 +- 19 files changed, 269 insertions(+), 175 deletions(-) create mode 100644 Zotero/Models/Appearance.swift diff --git a/Zotero.xcodeproj/project.pbxproj b/Zotero.xcodeproj/project.pbxproj index ad1989d68..c70693680 100644 --- a/Zotero.xcodeproj/project.pbxproj +++ b/Zotero.xcodeproj/project.pbxproj @@ -756,6 +756,10 @@ B36FD9AF2A78FB13002D77E8 /* EditAnnotationFontSizeDbRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B36FD9AE2A78FB13002D77E8 /* EditAnnotationFontSizeDbRequest.swift */; }; B36FD9B12A7924CB002D77E8 /* FontSizePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B36FD9B02A7924CB002D77E8 /* FontSizePickerViewController.swift */; }; B36FD9B32A7929C8002D77E8 /* FontSizeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B36FD9B22A7929C8002D77E8 /* FontSizeCell.swift */; }; + B370625E2D5F407100E1722B /* ReaderSettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3F9A4C22B04D0D800684030 /* ReaderSettingsState.swift */; }; + B370625F2D5F595600E1722B /* PDFSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B35B473B25F11BCD0023B6F4 /* PDFSettings.swift */; }; + B37062612D5F75E200E1722B /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B37062602D5F75DF00E1722B /* Appearance.swift */; }; + B37062622D5F75E200E1722B /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B37062602D5F75DF00E1722B /* Appearance.swift */; }; B37080532AA7216E006F56B9 /* Localizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B37080512AA72135006F56B9 /* Localizable.swift */; }; B371494C25D585EA00D6391E /* RestoreDeletionsSyncAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B340868825D574D6000F4446 /* RestoreDeletionsSyncAction.swift */; }; B371494F25D585EF00D6391E /* MarkObjectsAsChangedByUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B340868C25D579C9000F4446 /* MarkObjectsAsChangedByUser.swift */; }; @@ -1805,6 +1809,7 @@ B36FD9AE2A78FB13002D77E8 /* EditAnnotationFontSizeDbRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAnnotationFontSizeDbRequest.swift; sourceTree = ""; }; B36FD9B02A7924CB002D77E8 /* FontSizePickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSizePickerViewController.swift; sourceTree = ""; }; B36FD9B22A7929C8002D77E8 /* FontSizeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSizeCell.swift; sourceTree = ""; }; + B37062602D5F75DF00E1722B /* Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appearance.swift; sourceTree = ""; }; B37080512AA72135006F56B9 /* Localizable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localizable.swift; sourceTree = ""; }; B372CEDF2486504600B423AE /* GroupVersionsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupVersionsRequest.swift; sourceTree = ""; }; B372CEE22486512500B423AE /* GroupRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupRequest.swift; sourceTree = ""; }; @@ -2692,6 +2697,7 @@ B3B557122C884B7D00BD6325 /* AnnotationPreview.swift */, B3F6AA392AB30663005BC22E /* AnnotationTool.swift */, B311737A2AFCF004007D9741 /* AnnotationType.swift */, + B37062602D5F75DF00E1722B /* Appearance.swift */, B305656123FC051E003304F2 /* AppGroup.swift */, B305653A23FC051E003304F2 /* Attachment.swift */, B339459026DE6C2900E59A02 /* AttachmentFileDeletedNotification.swift */, @@ -5180,6 +5186,7 @@ B3D427D62CB67EFC0058453A /* AutoEmptyTrashController.swift in Sources */, B30565EB23FC051E003304F2 /* Controllers.swift in Sources */, B3A351E12715784A002E597A /* WebDavDownloadRequest.swift in Sources */, + B37062612D5F75E200E1722B /* Appearance.swift in Sources */, B305CEBB29E6E67600B9E2B4 /* AssignItemsToTagDbRequest.swift in Sources */, B39AF554290033CD001F400F /* TableOfContentsViewController.swift in Sources */, B3593F78241A76E600760E20 /* CollectionEditError.swift in Sources */, @@ -5736,6 +5743,7 @@ B3868541270DC3AA0068A022 /* WebDavScheme.swift in Sources */, B352FFCC2CBE944400D0887B /* AutoEmptyTrashDbRequest.swift in Sources */, B331F9AF2653CEA00099F6A6 /* ReadGroupDbRequest.swift in Sources */, + B370625F2D5F595600E1722B /* PDFSettings.swift in Sources */, B33E8A4B27B6A39100CBC7DE /* CollectionCell.swift in Sources */, B3DDC0CC2667825E00B2DFD1 /* RegularExpression+Extensions.swift in Sources */, B30566EB23FC082A003304F2 /* ObjectsRequest.swift in Sources */, @@ -5754,6 +5762,7 @@ B30566F123FC0845003304F2 /* CheckItemIsChangedDbRequest.swift in Sources */, B3AFB83D2A0CF1EE008C2374 /* EmptyDecodable.swift in Sources */, B36459E42644120D00A0C2C0 /* Color+Extension.swift in Sources */, + B370625E2D5F407100E1722B /* ReaderSettingsState.swift in Sources */, B34ACC7F2514EB6100040C17 /* AnnotationColorGenerator.swift in Sources */, B3FE79E42B333030009FBDBD /* DeleteDownloadDbRequest.swift in Sources */, B305670423FC08A6003304F2 /* ReadAllCustomLibrariesDbRequest.swift in Sources */, @@ -5902,6 +5911,7 @@ B3B557182C8860E600BD6325 /* CitationMetadata.swift in Sources */, B305677023FC0A58003304F2 /* Alamofire+RxSwift.swift in Sources */, B330261225DEAD9A00742025 /* RPageIndex.swift in Sources */, + B37062622D5F75E200E1722B /* Appearance.swift in Sources */, B305670023FC086B003304F2 /* MarkObjectsAsDeletedDbRequest.swift in Sources */, B34341B7260A49C300093E63 /* ReadCollectionAndLibraryDbRequest.swift in Sources */, B305671023FC08C3003304F2 /* ReadSearchesDbRequest.swift in Sources */, diff --git a/Zotero/Controllers/AnnotationColorGenerator.swift b/Zotero/Controllers/AnnotationColorGenerator.swift index 64a77dc50..570a0fa24 100644 --- a/Zotero/Controllers/AnnotationColorGenerator.swift +++ b/Zotero/Controllers/AnnotationColorGenerator.swift @@ -14,33 +14,33 @@ struct AnnotationColorGenerator { private static let underlineOpacity: CGFloat = 1 private static let underlineDarkOpacity: CGFloat = 1 - static func color(from color: UIColor, type: AnnotationType?, userInterfaceStyle: UIUserInterfaceStyle) -> (color: UIColor, alpha: CGFloat, blendMode: CGBlendMode?) { + static func color(from color: UIColor, type: AnnotationType?, appearance: Appearance) -> (color: UIColor, alpha: CGFloat, blendMode: CGBlendMode?) { let opacity: CGFloat switch type { case .none, .note, .image, .ink, .freeText: return (color, 1, nil) case .highlight: - switch userInterfaceStyle { + switch appearance { case .dark: opacity = Self.highlightDarkOpacity - default: + case .light, .sepia: opacity = Self.highlightOpacity } case .underline: - switch userInterfaceStyle { + switch appearance { case .dark: opacity = Self.underlineDarkOpacity - default: + case .light, .sepia: opacity = Self.underlineOpacity } } let adjustedColor: UIColor - switch userInterfaceStyle { + switch appearance { case .dark: var hue: CGFloat = 0 var sat: CGFloat = 0 @@ -52,24 +52,24 @@ struct AnnotationColorGenerator { let adjustedSat = min(1, (sat * 1.2)) adjustedColor = UIColor(hue: hue, saturation: adjustedSat, brightness: brg, alpha: opacity) - default: + case .light, .sepia: adjustedColor = color.withAlphaComponent(opacity) } - return (adjustedColor, opacity, Self.blendMode(for: userInterfaceStyle, type: type)) + return (adjustedColor, opacity, Self.blendMode(for: appearance, type: type)) } - static func blendMode(for userInterfaceStyle: UIUserInterfaceStyle, type: AnnotationType?) -> CGBlendMode? { + static func blendMode(for appearance: Appearance, type: AnnotationType?) -> CGBlendMode? { switch type { case .none, .note, .image, .ink, .freeText: return nil case .highlight, .underline: - switch userInterfaceStyle { + switch appearance { case .dark: return .lighten - default: + case .light, .sepia: return .multiply } } diff --git a/Zotero/Controllers/AnnotationConverter.swift b/Zotero/Controllers/AnnotationConverter.swift index 87fd2b90d..537d23f2a 100644 --- a/Zotero/Controllers/AnnotationConverter.swift +++ b/Zotero/Controllers/AnnotationConverter.swift @@ -204,7 +204,7 @@ struct AnnotationConverter { static func annotations( from items: Results, type: Kind = .zotero, - interfaceStyle: UIUserInterfaceStyle, + appearance: Appearance, currentUserId: Int, library: Library, displayName: String, @@ -221,7 +221,7 @@ struct AnnotationConverter { return annotation( from: dbAnnotation, type: type, - interfaceStyle: interfaceStyle, + appearance: appearance, currentUserId: currentUserId, library: library, displayName: displayName, @@ -234,7 +234,7 @@ struct AnnotationConverter { static func annotation( from zoteroAnnotation: PDFDatabaseAnnotation, type: Kind, - interfaceStyle: UIUserInterfaceStyle, + appearance: Appearance, currentUserId: Int, library: Library, displayName: String, @@ -244,7 +244,7 @@ struct AnnotationConverter { let (color, alpha, blendMode) = AnnotationColorGenerator.color( from: UIColor(hex: zoteroAnnotation.color), type: zoteroAnnotation.type, - userInterfaceStyle: interfaceStyle + appearance: appearance ) let annotation: PSPDFKit.Annotation diff --git a/Zotero/Controllers/AnnotationPreviewController.swift b/Zotero/Controllers/AnnotationPreviewController.swift index 1db837c3e..c448fbaba 100644 --- a/Zotero/Controllers/AnnotationPreviewController.swift +++ b/Zotero/Controllers/AnnotationPreviewController.swift @@ -24,7 +24,7 @@ final class AnnotationPreviewController: NSObject { let imageSize: CGSize let imageScale: CGFloat let includeAnnotation: Bool - let isDark: Bool + let appearance: Appearance let type: PreviewType init( @@ -37,7 +37,7 @@ final class AnnotationPreviewController: NSObject { parentKey: String, libraryId: LibraryIdentifier, includeAnnotation: Bool = false, - isDark: Bool = false, + appearance: Appearance, type: PreviewType ) { self.key = key @@ -49,11 +49,11 @@ final class AnnotationPreviewController: NSObject { self.imageSize = imageSize self.imageScale = imageScale self.includeAnnotation = includeAnnotation - self.isDark = isDark + self.appearance = appearance self.type = type } - init?(annotation: PSPDFKit.Annotation, parentKey: String, libraryId: LibraryIdentifier, imageSize: CGSize, imageScale: CGFloat, isDark: Bool, type: PreviewType) { + init?(annotation: PSPDFKit.Annotation, parentKey: String, libraryId: LibraryIdentifier, imageSize: CGSize, imageScale: CGFloat, appearance: Appearance, type: PreviewType) { guard annotation.shouldRenderPreview, let document = annotation.document else { return nil } key = annotation.previewId self.parentKey = parentKey @@ -64,7 +64,7 @@ final class AnnotationPreviewController: NSObject { self.imageSize = imageSize self.imageScale = imageScale includeAnnotation = annotation is PSPDFKit.InkAnnotation || annotation is PSPDFKit.FreeTextAnnotation - self.isDark = isDark + self.appearance = appearance self.type = type } } @@ -141,6 +141,7 @@ extension AnnotationPreviewController { key: key, parentKey: parentKey, libraryId: libraryId, + appearance: .light, type: .temporary(subscriberKey: subscriberKey) ) ) @@ -155,22 +156,22 @@ extension AnnotationPreviewController { /// - parameter parentKey: Key of PDF item. /// - parameter libraryId: Library identifier of item. /// - parameter isDark: `true` if dark mode is on, `false` otherwise. - func store(for annotation: PSPDFKit.Annotation, parentKey: String, libraryId: LibraryIdentifier, isDark: Bool) { + func store(for annotation: PSPDFKit.Annotation, parentKey: String, libraryId: LibraryIdentifier, appearance: Appearance) { queue.async { [weak self] in guard let self, - let data = EnqueuedData(annotation: annotation, parentKey: parentKey, libraryId: libraryId, imageSize: previewSize, imageScale: 0, isDark: isDark, type: .cachedAndReported) + let data = EnqueuedData(annotation: annotation, parentKey: parentKey, libraryId: libraryId, imageSize: previewSize, imageScale: 0, appearance: appearance, type: .cachedAndReported) else { return } enqueue(data: data) } } - func store(annotations: [PSPDFKit.Annotation], parentKey: String, libraryId: LibraryIdentifier, isDark: Bool) { + func store(annotations: [PSPDFKit.Annotation], parentKey: String, libraryId: LibraryIdentifier, appearance: Appearance) { queue.async { [weak self] in guard let self else { return } for annotation in annotations { guard - let data = EnqueuedData(annotation: annotation, parentKey: parentKey, libraryId: libraryId, imageSize: previewSize, imageScale: 0, isDark: isDark, type: .cachedOnly) + let data = EnqueuedData(annotation: annotation, parentKey: parentKey, libraryId: libraryId, imageSize: previewSize, imageScale: 0, appearance: appearance, type: .cachedOnly) else { continue } enqueue(data: data) } @@ -193,8 +194,9 @@ extension AnnotationPreviewController { queue.async { [weak self] in guard let self else { return } for key in keys { - try? fileStorage.remove(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, isDark: true)) - try? fileStorage.remove(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, isDark: false)) + try? fileStorage.remove(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: .dark)) + try? fileStorage.remove(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: .light)) + try? fileStorage.remove(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: .sepia)) } } } @@ -211,8 +213,8 @@ extension AnnotationPreviewController { /// - parameter libraryId: Library identifier of item. /// - parameter isDark: `true` if dark mode is on, `false` otherwise. /// - returns: `true` if preview is available, `false` otherwise. - func hasPreview(for key: String, parentKey: String, libraryId: LibraryIdentifier, isDark: Bool) -> Bool { - return fileStorage.has(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, isDark: isDark)) + func hasPreview(for key: String, parentKey: String, libraryId: LibraryIdentifier, appearance: Appearance) -> Bool { + return fileStorage.has(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: appearance)) } /// Loads cached preview for given annotation. @@ -221,12 +223,12 @@ extension AnnotationPreviewController { /// - parameter libraryId: Library identifier of item. /// - parameter isDark: `true` if dark mode is on, `false` otherwise. /// - parameter completed: Completion handler which contains loaded preview or `nil` if loading wasn't successful. - func preview(for key: String, parentKey: String, libraryId: LibraryIdentifier, isDark: Bool, completed: @escaping (UIImage?) -> Void) { + func preview(for key: String, parentKey: String, libraryId: LibraryIdentifier, appearance: Appearance, completed: @escaping (UIImage?) -> Void) { queue.async { [weak self] in guard let self else { return } do { - let data = try fileStorage.read(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, isDark: isDark)) + let data = try fileStorage.read(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: appearance)) let image = UIImage(data: data) DispatchQueue.main.async { completed(image) @@ -242,9 +244,16 @@ extension AnnotationPreviewController { /// Creates and enqueues a render request for PSPDFKit rendering engine. private func enqueue(data: EnqueuedData) { let options = RenderOptions() - if data.isDark { + switch data.appearance { + case .dark: options.invertRenderColor = true options.filters = [.colorCorrectInverted] + + case .sepia: + options.filters = [.sepia] + + case .light: + break } let request = MutableRenderRequest(document: data.document) @@ -261,7 +270,7 @@ extension AnnotationPreviewController { task.completionHandler = { [weak self] image, error in let result: Result = image.flatMap({ .success($0) }) ?? .failure(error ?? Error.imageNotAvailable) self?.queue.async { - self?.completeRequest(with: result, key: data.key, parentKey: data.parentKey, libraryId: data.libraryId, isDark: data.isDark, type: data.type) + self?.completeRequest(with: result, key: data.key, parentKey: data.parentKey, libraryId: data.libraryId, appearance: data.appearance, type: data.type) } } @@ -271,7 +280,7 @@ extension AnnotationPreviewController { } } - private func completeRequest(with result: Result, key: String, parentKey: String, libraryId: LibraryIdentifier, isDark: Bool, type: PreviewType) { + private func completeRequest(with result: Result, key: String, parentKey: String, libraryId: LibraryIdentifier, appearance: Appearance, type: PreviewType) { switch result { case .success(let image): switch type { @@ -279,10 +288,10 @@ extension AnnotationPreviewController { perform(event: .success(image), subscriberKey: subscriberKey) case .cachedOnly: - cache(image: image, key: key, pdfKey: parentKey, libraryId: libraryId, isDark: isDark) + cache(image: image, key: key, pdfKey: parentKey, libraryId: libraryId, appearance: appearance) case .cachedAndReported: - cache(image: image, key: key, pdfKey: parentKey, libraryId: libraryId, isDark: isDark) + cache(image: image, key: key, pdfKey: parentKey, libraryId: libraryId, appearance: appearance) observable.on(.next((key, parentKey, image))) } @@ -305,7 +314,7 @@ extension AnnotationPreviewController { subscribers[subscriberKey] = nil } - private func cache(image: UIImage, key: String, pdfKey: String, libraryId: LibraryIdentifier, isDark: Bool) { + private func cache(image: UIImage, key: String, pdfKey: String, libraryId: LibraryIdentifier, appearance: Appearance) { autoreleasepool { guard let data = image.pngData() else { DDLogError("AnnotationPreviewController: can't create data from image") @@ -313,7 +322,7 @@ extension AnnotationPreviewController { } do { - try fileStorage.write(data, to: Files.annotationPreview(annotationKey: key, pdfKey: pdfKey, libraryId: libraryId, isDark: isDark), options: .atomicWrite) + try fileStorage.write(data, to: Files.annotationPreview(annotationKey: key, pdfKey: pdfKey, libraryId: libraryId, appearance: appearance), options: .atomicWrite) } catch let error { DDLogError("AnnotationPreviewController: can't store preview - \(error)") } diff --git a/Zotero/Controllers/PDFThumbnailController.swift b/Zotero/Controllers/PDFThumbnailController.swift index 1dba69ceb..b3b76731f 100644 --- a/Zotero/Controllers/PDFThumbnailController.swift +++ b/Zotero/Controllers/PDFThumbnailController.swift @@ -22,7 +22,7 @@ final class PDFThumbnailController: NSObject { let libraryId: LibraryIdentifier let page: UInt let size: CGSize - let isDark: Bool + let appearance: Appearance } private let queue: DispatchQueue @@ -47,18 +47,18 @@ extension PDFThumbnailController { /// Start rendering process of multiple thumbnails per document. /// - parameter pages: Page indices which should be rendered. /// - parameter - func cache(pages: [UInt], key: String, libraryId: LibraryIdentifier, document: Document, imageSize: CGSize, isDark: Bool) -> Observable<()> { + func cache(pages: [UInt], key: String, libraryId: LibraryIdentifier, document: Document, imageSize: CGSize, appearance: Appearance) -> Observable<()> { let observables = pages.map({ - cache(page: $0, key: key, libraryId: libraryId, document: document, imageSize: imageSize, isDark: isDark).flatMap({ _ in return Single.just(()) }).asObservable() + cache(page: $0, key: key, libraryId: libraryId, document: document, imageSize: imageSize, appearance: appearance).flatMap({ _ in return Single.just(()) }).asObservable() }) return Observable.merge(observables).subscribe(on: scheduler) } - func cache(page: UInt, key: String, libraryId: LibraryIdentifier, document: Document, imageSize: CGSize, isDark: Bool) -> Single { + func cache(page: UInt, key: String, libraryId: LibraryIdentifier, document: Document, imageSize: CGSize, appearance: Appearance) -> Single { return Single.create { [weak self] subscriber -> Disposable in guard let self else { return Disposables.create() } dispatchPrecondition(condition: .onQueue(queue)) - let subscriberKey = SubscriberKey(key: key, libraryId: libraryId, page: page, size: imageSize, isDark: isDark) + let subscriberKey = SubscriberKey(key: key, libraryId: libraryId, page: page, size: imageSize, appearance: appearance) subscribers[subscriberKey] = subscriber enqueue(subscriberKey: subscriberKey, document: document, imageSize: imageSize) return Disposables.create() @@ -81,8 +81,8 @@ extension PDFThumbnailController { /// - parameter libraryId: Library identifier of item. /// - parameter isDark: `true` if dark mode is on, `false` otherwise. /// - returns: `true` if thumbnail is available, `false` otherwise. - func hasThumbnail(page: UInt, key: String, libraryId: LibraryIdentifier, isDark: Bool) -> Bool { - return fileStorage.has(Files.pageThumbnail(pageIndex: page, key: key, libraryId: libraryId, isDark: isDark)) + func hasThumbnail(page: UInt, key: String, libraryId: LibraryIdentifier, appearance: Appearance) -> Bool { + return fileStorage.has(Files.pageThumbnail(pageIndex: page, key: key, libraryId: libraryId, appearance: appearance)) } /// Loads thumbnail from cached file @@ -91,9 +91,9 @@ extension PDFThumbnailController { /// - parameter libraryId: Library identifier of item. /// - parameter isDark: `true` if dark mode is on, `false` otherwise. /// - returns: UIImage of given page thumbnail. - func thumbnail(page: UInt, key: String, libraryId: LibraryIdentifier, isDark: Bool) -> UIImage? { + func thumbnail(page: UInt, key: String, libraryId: LibraryIdentifier, appearance: Appearance) -> UIImage? { do { - let data = try fileStorage.read(Files.pageThumbnail(pageIndex: page, key: key, libraryId: libraryId, isDark: isDark)) + let data = try fileStorage.read(Files.pageThumbnail(pageIndex: page, key: key, libraryId: libraryId, appearance: appearance)) return try UIImage(imageData: data) } catch let error { DDLogError("PdfThumbnailController: can't load thumbnail - \(error)") @@ -107,9 +107,16 @@ extension PDFThumbnailController { /// - parameter imageSize: Size of rendered image. private func enqueue(subscriberKey: SubscriberKey, document: Document, imageSize: CGSize) { let options = RenderOptions() - if subscriberKey.isDark { + switch subscriberKey.appearance { + case .dark: options.invertRenderColor = true options.filters = [.colorCorrectInverted] + + case .sepia: + options.filters = [.sepia] + + case .light: + break } let request = MutableRenderRequest(document: document) @@ -136,7 +143,7 @@ extension PDFThumbnailController { switch result { case .success(let image): perform(event: .success(image), subscriberKey: subscriberKey) - cache(image: image, page: subscriberKey.page, key: subscriberKey.key, libraryId: subscriberKey.libraryId, isDark: subscriberKey.isDark) + cache(image: image, page: subscriberKey.page, key: subscriberKey.key, libraryId: subscriberKey.libraryId, appearance: subscriberKey.appearance) case .failure(let error): DDLogError("PdfThumbnailController: could not generate image - \(error)") @@ -148,14 +155,14 @@ extension PDFThumbnailController { subscribers[subscriberKey] = nil } - func cache(image: UIImage, page: UInt, key: String, libraryId: LibraryIdentifier, isDark: Bool) { + func cache(image: UIImage, page: UInt, key: String, libraryId: LibraryIdentifier, appearance: Appearance) { autoreleasepool { guard let data = image.pngData() else { DDLogError("PdfThumbnailController: can't create data from image") return } do { - try fileStorage.write(data, to: Files.pageThumbnail(pageIndex: page, key: key, libraryId: libraryId, isDark: isDark), options: .atomicWrite) + try fileStorage.write(data, to: Files.pageThumbnail(pageIndex: page, key: key, libraryId: libraryId, appearance: appearance), options: .atomicWrite) } catch let error { DDLogError("PdfThumbnailController: can't store preview - \(error)") } diff --git a/Zotero/Models/AnnotationsConfig.swift b/Zotero/Models/AnnotationsConfig.swift index ae0765b56..32407cb80 100644 --- a/Zotero/Models/AnnotationsConfig.swift +++ b/Zotero/Models/AnnotationsConfig.swift @@ -14,7 +14,7 @@ struct AnnotationsConfig { static let defaultActiveColor = "#ffd400" static let allColors: [String] = ["#ffd400", "#ff6666", "#5fb236", "#2ea8e5", "#a28ae5", "#e56eee", "#f19837", "#aaaaaa", "#000000"] static let typesWithColorVariation: [AnnotationType?] = [.none, .highlight, .underline] - static let userInterfaceStylesWithVarition: [UIUserInterfaceStyle] = [.light, .dark] + static let appearancesWithVarition: [Appearance] = [.light, .dark, .sepia] static let colorNames: [String: String] = [ "#ffd400": "Yellow", "#ff6666": "Red", @@ -58,8 +58,8 @@ struct AnnotationsConfig { for hexBaseColor in allColors { let baseColor = UIColor(hex: hexBaseColor) for type in typesWithColorVariation { - for userInterfaceStyle in userInterfaceStylesWithVarition { - let variation = AnnotationColorGenerator.color(from: baseColor, type: type, userInterfaceStyle: userInterfaceStyle).color + for appearance in appearancesWithVarition { + let variation = AnnotationColorGenerator.color(from: baseColor, type: type, appearance: appearance).color map[variation.hexString] = hexBaseColor } } diff --git a/Zotero/Models/Appearance.swift b/Zotero/Models/Appearance.swift new file mode 100644 index 000000000..6b6a29ff7 --- /dev/null +++ b/Zotero/Models/Appearance.swift @@ -0,0 +1,37 @@ +// +// Appearance.swift +// Zotero +// +// Created by Michal Rentka on 14.02.2025. +// Copyright © 2025 Corporation for Digital Scholarship. All rights reserved. +// + +import UIKit + +enum Appearance { + case light + case dark + case sepia + + static func from(appearanceMode: ReaderSettingsState.Appearance, interfaceStyle: UIUserInterfaceStyle) -> Appearance { + switch appearanceMode { + case .automatic: + switch interfaceStyle { + case .dark: + return .dark + + default: + return .light + } + + case .light: + return .light + + case .dark: + return .dark + + case .sepia: + return .sepia + } + } +} diff --git a/Zotero/Models/DeletableObject.swift b/Zotero/Models/DeletableObject.swift index 52e4b9c21..6a108e9fb 100644 --- a/Zotero/Models/DeletableObject.swift +++ b/Zotero/Models/DeletableObject.swift @@ -90,14 +90,15 @@ extension RItem: Deletable { } private func cleanupAnnotationFiles() { - guard let parentKey = self.parent?.key, - let libraryId = self.libraryId else { return } + guard let parentKey = parent?.key, let libraryId = libraryId else { return } - let light = Files.annotationPreview(annotationKey: self.key, pdfKey: parentKey, libraryId: libraryId, isDark: false) - let dark = Files.annotationPreview(annotationKey: self.key, pdfKey: parentKey, libraryId: libraryId, isDark: true) + let light = Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: .light) + let dark = Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: .dark) + let sepia = Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: .sepia) NotificationCenter.default.post(name: .attachmentDeleted, object: light) NotificationCenter.default.post(name: .attachmentDeleted, object: dark) + NotificationCenter.default.post(name: .attachmentDeleted, object: sepia) } private func cleanupAttachmentFiles() { diff --git a/Zotero/Models/Files.swift b/Zotero/Models/Files.swift index 08a7e5b81..16f3ec77b 100644 --- a/Zotero/Models/Files.swift +++ b/Zotero/Models/Files.swift @@ -125,8 +125,19 @@ struct Files { return FileData(rootPath: Files.cachesRootPath, relativeComponents: ["Zotero", "sharing", key], name: name, ext: ext) } - static func pageThumbnail(pageIndex: UInt, key: String, libraryId: LibraryIdentifier, isDark: Bool) -> File { - return FileData(rootPath: Files.appGroupPath, relativeComponents: ["thumbnails", libraryId.folderName, key], name: "\(pageIndex)" + (isDark ? "_dark" : ""), contentType: "png") + static func pageThumbnail(pageIndex: UInt, key: String, libraryId: LibraryIdentifier, appearance: Appearance) -> File { + let nameSuffix: String + switch appearance { + case .dark: + nameSuffix = "_dark" + + case .sepia: + nameSuffix = "_sepia" + + case .light: + nameSuffix = "" + } + return FileData(rootPath: Files.appGroupPath, relativeComponents: ["thumbnails", libraryId.folderName, key], name: "\(pageIndex)" + nameSuffix, contentType: "png") } static func pageThumbnails(for key: String, libraryId: LibraryIdentifier) -> File { @@ -143,11 +154,22 @@ struct Files { // MARK: - Annotations - static func annotationPreview(annotationKey: String, pdfKey: String, libraryId: LibraryIdentifier, isDark: Bool) -> File { + static func annotationPreview(annotationKey: String, pdfKey: String, libraryId: LibraryIdentifier, appearance: Appearance) -> File { + let nameSuffix: String + switch appearance { + case .dark: + nameSuffix = "_dark" + + case .sepia: + nameSuffix = "_sepia" + + case .light: + nameSuffix = "" + } return FileData( rootPath: Files.appGroupPath, relativeComponents: ["annotations", libraryId.folderName, pdfKey], - name: annotationKey + (isDark ? "_dark" : ""), + name: annotationKey + nameSuffix, ext: "png" ) } diff --git a/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift b/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift index 5a7772ef7..9dad592a9 100644 --- a/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift +++ b/Zotero/Scenes/Detail/PDF/Models/PDFReaderState.swift @@ -36,23 +36,22 @@ struct PDFReaderState: ViewModelState { static let annotations = Changes(rawValue: 1 << 0) static let selection = Changes(rawValue: 1 << 1) - static let interfaceStyle = Changes(rawValue: 1 << 2) - static let settings = Changes(rawValue: 1 << 3) - static let activeComment = Changes(rawValue: 1 << 4) - static let export = Changes(rawValue: 1 << 5) - static let activeLineWidth = Changes(rawValue: 1 << 6) - static let sidebarEditing = Changes(rawValue: 1 << 7) - static let sidebarEditingSelection = Changes(rawValue: 1 << 8) - static let filter = Changes(rawValue: 1 << 9) - static let activeEraserSize = Changes(rawValue: 1 << 10) - static let initialDataLoaded = Changes(rawValue: 1 << 11) - static let visiblePageFromDocument = Changes(rawValue: 1 << 12) - static let visiblePageFromThumbnailList = Changes(rawValue: 1 << 13) - static let selectionDeletion = Changes(rawValue: 1 << 14) - static let activeFontSize = Changes(rawValue: 1 << 15) - static let library = Changes(rawValue: 1 << 16) - static let md5 = Changes(rawValue: 1 << 17) - static let appearanceMode = Changes(rawValue: 1 << 18) + static let settings = Changes(rawValue: 1 << 2) + static let activeComment = Changes(rawValue: 1 << 3) + static let export = Changes(rawValue: 1 << 4) + static let activeLineWidth = Changes(rawValue: 1 << 5) + static let sidebarEditing = Changes(rawValue: 1 << 6) + static let sidebarEditingSelection = Changes(rawValue: 1 << 7) + static let filter = Changes(rawValue: 1 << 8) + static let activeEraserSize = Changes(rawValue: 1 << 9) + static let initialDataLoaded = Changes(rawValue: 1 << 10) + static let visiblePageFromDocument = Changes(rawValue: 1 << 11) + static let visiblePageFromThumbnailList = Changes(rawValue: 1 << 12) + static let selectionDeletion = Changes(rawValue: 1 << 13) + static let activeFontSize = Changes(rawValue: 1 << 14) + static let library = Changes(rawValue: 1 << 15) + static let md5 = Changes(rawValue: 1 << 16) + static let appearance = Changes(rawValue: 1 << 17) } enum Error: Swift.Error { diff --git a/Zotero/Scenes/Detail/PDF/Models/PDFThumbnailsAction.swift b/Zotero/Scenes/Detail/PDF/Models/PDFThumbnailsAction.swift index e5b05d308..a0a82e8b0 100644 --- a/Zotero/Scenes/Detail/PDF/Models/PDFThumbnailsAction.swift +++ b/Zotero/Scenes/Detail/PDF/Models/PDFThumbnailsAction.swift @@ -11,7 +11,7 @@ import Foundation enum PDFThumbnailsAction { case prefetch([UInt]) case load(UInt) - case setUserInterface(isDark: Bool) + case setAppearance(Appearance) case loadPages case setSelectedPage(pageIndex: Int, type: PDFThumbnailsState.SelectionType) case reloadThumbnails diff --git a/Zotero/Scenes/Detail/PDF/Models/PDFThumbnailsState.swift b/Zotero/Scenes/Detail/PDF/Models/PDFThumbnailsState.swift index af57ac6c1..63d52fa54 100644 --- a/Zotero/Scenes/Detail/PDF/Models/PDFThumbnailsState.swift +++ b/Zotero/Scenes/Detail/PDF/Models/PDFThumbnailsState.swift @@ -16,7 +16,7 @@ struct PDFThumbnailsState: ViewModelState { let rawValue: UInt8 - static let userInterface = Changes(rawValue: 1 << 0) + static let appearance = Changes(rawValue: 1 << 0) static let pages = Changes(rawValue: 1 << 1) static let selection = Changes(rawValue: 1 << 2) static let scrollToSelection = Changes(rawValue: 1 << 3) @@ -45,12 +45,12 @@ struct PDFThumbnailsState: ViewModelState { let cache: NSCache var pages: [Page] - var isDark: Bool + var appearance: Appearance var loadedThumbnail: Int? var selectedPageIndex: Int var changes: Changes - init(key: String, libraryId: LibraryIdentifier, document: Document, selectedPageIndex: Int, isDark: Bool) { + init(key: String, libraryId: LibraryIdentifier, document: Document, selectedPageIndex: Int, appearance: Appearance) { let cache = NSCache() cache.totalCostLimit = 1024 * 1024 * 5 // Cache object limit - 5 MB self.cache = cache @@ -59,12 +59,13 @@ struct PDFThumbnailsState: ViewModelState { self.libraryId = libraryId self.document = document self.selectedPageIndex = selectedPageIndex - self.isDark = isDark + self.appearance = appearance self.changes = [] self.pages = [] } mutating func cleanup() { - self.changes = [] + changes = [] + loadedThumbnail = nil } } diff --git a/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift b/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift index 871b64121..e10fb052a 100644 --- a/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift +++ b/Zotero/Scenes/Detail/PDF/ViewModels/PDFReaderActionHandler.swift @@ -215,7 +215,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi userInterfaceChanged(interfaceStyle: interfaceStyle, in: viewModel) case .updateAnnotationPreviews: - storeAnnotationPreviewsIfNeeded(isDark: viewModel.state.interfaceStyle == .dark, in: viewModel) + storeAnnotationPreviewsIfNeeded(appearance: .from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle), in: viewModel) case .setToolOptions(let hex, let size, let tool): setToolOptions(hex: hex, size: size, tool: tool, in: viewModel) @@ -270,18 +270,33 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi } } - // MARK: - Dark mode changes + // MARK: - Appearance changes private func userInterfaceChanged(interfaceStyle: UIUserInterfaceStyle, in viewModel: ViewModel) { - viewModel.state.previewCache.removeAllObjects() + guard viewModel.state.settings.appearanceMode == .automatic else { return } + updateAnnotations(to: .from(appearanceMode: .automatic, interfaceStyle: interfaceStyle), in: viewModel) + update(viewModel: viewModel) { state in + state.interfaceStyle = interfaceStyle + state.changes = .appearance + } + } + private func appearanceChanged(appearanceMode: ReaderSettingsState.Appearance, in viewModel: ViewModel) { + updateAnnotations(to: .from(appearanceMode: appearanceMode, interfaceStyle: viewModel.state.interfaceStyle), in: viewModel) + update(viewModel: viewModel) { state in + state.changes = .appearance + } + } + + private func updateAnnotations(to appearance: Appearance, in viewModel: ViewModel) { + viewModel.state.previewCache.removeAllObjects() for (_, annotations) in viewModel.state.document.allAnnotations(of: AnnotationsConfig.supported) { for annotation in annotations { let baseColor = annotation.baseColor let (color, alpha, blendMode) = AnnotationColorGenerator.color( from: UIColor(hex: baseColor), type: annotation.type.annotationType, - userInterfaceStyle: interfaceStyle + appearance: appearance ) annotation.color = color annotation.alpha = alpha @@ -290,23 +305,16 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi } } } - - storeAnnotationPreviewsIfNeeded(isDark: interfaceStyle == .dark, in: viewModel) - - update(viewModel: viewModel) { state in - state.interfaceStyle = interfaceStyle - state.changes = .interfaceStyle - } + storeAnnotationPreviewsIfNeeded(appearance: appearance, in: viewModel) } - private func storeAnnotationPreviewsIfNeeded(isDark: Bool, in viewModel: ViewModel) { + private func storeAnnotationPreviewsIfNeeded(appearance: Appearance, in viewModel: ViewModel) { let libraryId = viewModel.state.library.identifier - // Load annotation previews if needed. for (_, annotations) in viewModel.state.document.allAnnotations(of: [.square, .ink, .freeText]) { for annotation in annotations { - guard !annotationPreviewController.hasPreview(for: annotation.previewId, parentKey: viewModel.state.key, libraryId: libraryId, isDark: isDark) else { continue } - annotationPreviewController.store(for: annotation, parentKey: viewModel.state.key, libraryId: libraryId, isDark: isDark) + guard !annotationPreviewController.hasPreview(for: annotation.previewId, parentKey: viewModel.state.key, libraryId: libraryId, appearance: appearance) else { continue } + annotationPreviewController.store(for: annotation, parentKey: viewModel.state.key, libraryId: libraryId, appearance: appearance) } } } @@ -481,32 +489,16 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi } private func update(settings: PDFSettings, parentInterfaceStyle: UIUserInterfaceStyle, in viewModel: ViewModel) { + let appearanceDidChange = settings.appearanceMode != viewModel.state.settings.appearanceMode // Update local state update(viewModel: viewModel) { state in - if state.settings.appearanceMode != settings.appearanceMode { - state.changes = .appearanceMode - } state.settings = settings - state.changes.insert(.settings) + state.changes = .settings } // Store new settings to defaults Defaults.shared.pdfSettings = settings - - // Check whether interfaceStyle changed and update if needed - let settingsInterfaceStyle: UIUserInterfaceStyle - switch settings.appearanceMode { - case .dark: - settingsInterfaceStyle = .dark - - case .light, .sepia: - settingsInterfaceStyle = .light - - case .automatic: - settingsInterfaceStyle = parentInterfaceStyle - } - - guard settingsInterfaceStyle != viewModel.state.interfaceStyle else { return } - userInterfaceChanged(interfaceStyle: settingsInterfaceStyle, in: viewModel) + guard appearanceDidChange else { return } + appearanceChanged(appearanceMode: settings.appearanceMode, in: viewModel) } private func set(page: Int, userActionFromDocument: Bool, fromThumbnailList: Bool, in viewModel: ViewModel) { @@ -559,7 +551,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi annotations = AnnotationConverter.annotations( from: viewModel.state.databaseAnnotations, type: .export, - interfaceStyle: .light, + appearance: .light, currentUserId: viewModel.state.userId, library: viewModel.state.library, displayName: viewModel.state.displayName, @@ -1085,7 +1077,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi guard !keys.isEmpty else { return } let group = DispatchGroup() - let isDark = viewModel.state.interfaceStyle == .dark + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) let libraryId = viewModel.state.library.identifier var loadedKeys: Set = [] @@ -1095,7 +1087,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi guard viewModel.state.previewCache.object(forKey: nsKey) == nil else { continue } group.enter() - annotationPreviewController.preview(for: key, parentKey: viewModel.state.key, libraryId: libraryId, isDark: isDark) { [weak viewModel] image in + annotationPreviewController.preview(for: key, parentKey: viewModel.state.key, libraryId: libraryId, appearance: appearance) { [weak viewModel] image in if let image = image { viewModel?.state.previewCache.setObject(image, forKey: nsKey) loadedKeys.insert(key) @@ -1140,7 +1132,8 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi private func addImage(onPage pageIndex: PageIndex, origin: CGPoint, in viewModel: ViewModel) { guard let activeColor = viewModel.state.toolColors[tool(from: .image)] else { return } - let color = AnnotationColorGenerator.color(from: activeColor, type: .image, userInterfaceStyle: viewModel.state.interfaceStyle).color + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) + let color = AnnotationColorGenerator.color(from: activeColor, type: .image, appearance: appearance).color let rect = CGRect(origin: origin, size: CGSize(width: 100, height: 100)) let square = SquareAnnotation() @@ -1156,7 +1149,8 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi private func addNote(onPage pageIndex: PageIndex, origin: CGPoint, in viewModel: ViewModel) { guard let activeColor = viewModel.state.toolColors[tool(from: .note)] else { return } - let color = AnnotationColorGenerator.color(from: activeColor, type: .note, userInterfaceStyle: viewModel.state.interfaceStyle).color + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) + let color = AnnotationColorGenerator.color(from: activeColor, type: .note, appearance: appearance).color let rect = CGRect(origin: origin, size: AnnotationsConfig.noteAnnotationSize) let note = NoteAnnotation(contents: "") @@ -1172,7 +1166,8 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi private func addHighlightOrUnderline(isHighlight: Bool, onPage pageIndex: PageIndex, rects: [CGRect], in viewModel: ViewModel) { guard let activeColor = viewModel.state.toolColors[tool(from: isHighlight ? .highlight : .underline)] else { return } - let (color, alpha, blendMode) = AnnotationColorGenerator.color(from: activeColor, type: isHighlight ? .highlight : .underline, userInterfaceStyle: viewModel.state.interfaceStyle) + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) + let (color, alpha, blendMode) = AnnotationColorGenerator.color(from: activeColor, type: isHighlight ? .highlight : .underline, appearance: appearance) let annotation = isHighlight ? HighlightAnnotation() : UnderlineAnnotation() annotation.rects = rects @@ -1241,7 +1236,8 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi private func set(color: String, key: String, viewModel: ViewModel) { guard let annotation = viewModel.state.annotation(for: PDFReaderState.AnnotationKey(key: key, type: .database)) else { return } - update(annotation: annotation, color: (color, viewModel.state.interfaceStyle), in: viewModel) + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) + update(annotation: annotation, color: (color, appearance), in: viewModel) } private func set(comment: NSAttributedString, key: String, viewModel: ViewModel) { @@ -1283,7 +1279,8 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi ) { // `type`, `lineWidth`, `fontSize` and `color` is stored in `Document`, update document, which will trigger a notification wich will update the DB guard let annotation = viewModel.state.annotation(for: PDFReaderState.AnnotationKey(key: key, type: .database)) else { return } - update(annotation: annotation, type: type, color: (color, viewModel.state.interfaceStyle), lineWidth: lineWidth, fontSize: fontSize, in: viewModel) + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) + update(annotation: annotation, type: type, color: (color, appearance), lineWidth: lineWidth, fontSize: fontSize, in: viewModel) // Update remaining values directly let text = htmlAttributedStringConverter.convert(attributedString: highlightText) @@ -1309,7 +1306,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi private func update( annotation: PDFAnnotation, type: AnnotationType? = nil, - color: (String, UIUserInterfaceStyle)? = nil, + color: (String, Appearance)? = nil, lineWidth: CGFloat? = nil, fontSize: CGFloat? = nil, contents: String? = nil, @@ -1338,8 +1335,8 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi return } - if let (color, interfaceStyle) = color, color != annotation.color { - let (_color, alpha, blendMode) = AnnotationColorGenerator.color(from: UIColor(hex: color), type: type, userInterfaceStyle: interfaceStyle) + if let (color, appearance) = color, color != annotation.color { + let (_color, alpha, blendMode) = AnnotationColorGenerator.color(from: UIColor(hex: color), type: type, appearance: appearance) newAnnotation.color = _color newAnnotation.alpha = alpha if let blendMode { @@ -1390,8 +1387,8 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi inkAnnotation.lineWidth = lineWidth.rounded(to: 3) } - if changes.contains(.color), let (color, interfaceStyle) = color { - let (_color, alpha, blendMode) = AnnotationColorGenerator.color(from: UIColor(hex: color), type: annotation.type, userInterfaceStyle: interfaceStyle) + if changes.contains(.color), let (color, appearance) = color { + let (_color, alpha, blendMode) = AnnotationColorGenerator.color(from: UIColor(hex: color), type: annotation.type, appearance: appearance) pdfAnnotation.color = _color pdfAnnotation.alpha = alpha if let blendMode { @@ -1462,6 +1459,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi } guard !finalAnnotations.isEmpty else { return } + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) let documentAnnotations: [PDFDocumentAnnotation] = finalAnnotations.compactMap { annotation in let documentAnnotation = AnnotationConverter.annotation( from: annotation, @@ -1473,7 +1471,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi ) guard let documentAnnotation else { return nil } // Only create preview for annotations that will be added in the database. - annotationPreviewController.store(for: annotation, parentKey: viewModel.state.key, libraryId: viewModel.state.library.identifier, isDark: (viewModel.state.interfaceStyle == .dark)) + annotationPreviewController.store(for: annotation, parentKey: viewModel.state.key, libraryId: viewModel.state.library.identifier, appearance: appearance) return documentAnnotation } @@ -1499,11 +1497,12 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi var keptAsIs: [PSPDFKit.Annotation] = [] var toRemove: [PSPDFKit.Annotation] = [] var toAdd: [PSPDFKit.Annotation] = [] + let appearance = Appearance.from(appearanceMode: state.settings.appearanceMode, interfaceStyle: state.interfaceStyle) for annotation in annotations { guard let tool = tool(from: annotation), let activeColor = state.toolColors[tool] else { continue } // `AnnotationStateManager` doesn't apply the `blendMode` to created annotations, so it needs to be applied to newly created annotations here. - let (_, _, blendMode) = AnnotationColorGenerator.color(from: activeColor, type: annotation.type.annotationType, userInterfaceStyle: state.interfaceStyle) + let (_, _, blendMode) = AnnotationColorGenerator.color(from: activeColor, type: annotation.type.annotationType, appearance: appearance) annotation.blendMode = blendMode ?? .normal // Either annotation is new (key not assigned) or the user used undo/redo and we check whether the annotation exists in DB @@ -1680,7 +1679,8 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi private func change(annotation: PSPDFKit.Annotation, with changes: [String], in viewModel: ViewModel) { guard !changes.isEmpty, let key = annotation.key, let boundingBoxConverter = delegate else { return } - annotationPreviewController.store(for: annotation, parentKey: viewModel.state.key, libraryId: viewModel.state.library.identifier, isDark: (viewModel.state.interfaceStyle == .dark)) + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) + annotationPreviewController.store(for: annotation, parentKey: viewModel.state.key, libraryId: viewModel.state.library.identifier, appearance: appearance) let hasChanges: (PdfAnnotationChanges) -> Bool = { pdfChanges in let rawPdfChanges = PdfAnnotationChanges.stringValues(from: pdfChanges) @@ -1789,7 +1789,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi let startTime = CFAbsoluteTimeGetCurrent() let key = viewModel.state.key - let isDark = viewModel.state.interfaceStyle == .dark + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) let (item, liveAnnotations, storedPage) = try loadItemAnnotationsAndPage(for: key, libraryId: viewModel.state.library.identifier) if checkWhetherMd5Changed(forItem: item, andUpdateViewModel: viewModel, handler: self) { @@ -1811,13 +1811,13 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi library: library, username: viewModel.state.username, displayName: viewModel.state.displayName, - isDark: isDark + appearance: appearance ) let convertDbAnnotationsStartTime = CFAbsoluteTimeGetCurrent() let dbToPdfAnnotations = AnnotationConverter.annotations( from: databaseAnnotations, - interfaceStyle: viewModel.state.interfaceStyle, + appearance: appearance, currentUserId: viewModel.state.userId, library: library, displayName: viewModel.state.displayName, @@ -1834,7 +1834,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi viewModel.state.document.add(annotations: dbToPdfAnnotations, options: [.suppressNotifications: true]) let endTime = CFAbsoluteTimeGetCurrent() - annotationPreviewController.store(annotations: dbToPdfAnnotations, parentKey: key, libraryId: library.identifier, isDark: isDark) + annotationPreviewController.store(annotations: dbToPdfAnnotations, parentKey: key, libraryId: library.identifier, appearance: appearance) update(viewModel: viewModel) { state in state.library = library @@ -2119,10 +2119,10 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi library: Library, username: String, displayName: String, - isDark: Bool + appearance: Appearance ) -> [String: PDFDocumentAnnotation] { let documentAnnotations = document.allAnnotations(of: PSPDFKit.Annotation.Kind.all).values.flatMap({ $0 }) - annotationPreviewController.store(annotations: documentAnnotations, parentKey: key, libraryId: library.identifier, isDark: isDark) + annotationPreviewController.store(annotations: documentAnnotations, parentKey: key, libraryId: library.identifier, appearance: appearance) var annotations: [String: PDFDocumentAnnotation] = [:] for pdfAnnotation in documentAnnotations { @@ -2164,6 +2164,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi var comments = viewModel.state.comments var selectKey: PDFReaderState.AnnotationKey? var selectionDeleted = false + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) // Update database keys based on realm notification var updatedKeys: [PDFReaderState.AnnotationKey] = [] // Collect modified, deleted and inserted annotations to update the `Document` @@ -2287,7 +2288,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi let pdfAnnotation = AnnotationConverter.annotation( from: annotation, type: .zotero, - interfaceStyle: viewModel.state.interfaceStyle, + appearance: appearance, currentUserId: viewModel.state.userId, library: viewModel.state.library, displayName: viewModel.state.displayName, @@ -2310,7 +2311,13 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi pdfDisposeBag = DisposeBag() // Update annotations in `Document` for (pdfAnnotation, annotation) in updatedPdfAnnotations { - update(pdfAnnotation: pdfAnnotation, with: annotation, parentKey: viewModel.state.key, libraryId: viewModel.state.library.identifier, interfaceStyle: viewModel.state.interfaceStyle) + update( + pdfAnnotation: pdfAnnotation, + with: annotation, + parentKey: viewModel.state.key, + libraryId: viewModel.state.library.identifier, + appearance: appearance + ) } // Remove annotations from `Document` if !deletedPdfAnnotations.isEmpty { @@ -2329,7 +2336,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi annotations: insertedPdfAnnotations, parentKey: viewModel.state.key, libraryId: viewModel.state.library.identifier, - isDark: (viewModel.state.interfaceStyle == .dark) + appearance: appearance ) } observeDocument(in: viewModel) @@ -2393,7 +2400,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi } } - private func update(pdfAnnotation: PSPDFKit.Annotation, with annotation: PDFDatabaseAnnotation, parentKey: String, libraryId: LibraryIdentifier, interfaceStyle: UIUserInterfaceStyle) { + private func update(pdfAnnotation: PSPDFKit.Annotation, with annotation: PDFDatabaseAnnotation, parentKey: String, libraryId: LibraryIdentifier, appearance: Appearance) { guard let boundingBoxConverter = delegate else { return } var changes: PdfAnnotationChanges = [] @@ -2401,7 +2408,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi if pdfAnnotation.baseColor != annotation.color { let hexColor = annotation.color - let (color, alpha, blendMode) = AnnotationColorGenerator.color(from: UIColor(hex: hexColor), type: annotation.type, userInterfaceStyle: interfaceStyle) + let (color, alpha, blendMode) = AnnotationColorGenerator.color(from: UIColor(hex: hexColor), type: annotation.type, appearance: appearance) pdfAnnotation.color = color pdfAnnotation.alpha = alpha if let blendMode { @@ -2465,7 +2472,7 @@ final class PDFReaderActionHandler: ViewModelActionHandler, BackgroundDbProcessi guard !changes.isEmpty else { return } - annotationPreviewController.store(for: pdfAnnotation, parentKey: parentKey, libraryId: libraryId, isDark: (interfaceStyle == .dark)) + annotationPreviewController.store(for: pdfAnnotation, parentKey: parentKey, libraryId: libraryId, appearance: appearance) NotificationCenter.default.post( name: NSNotification.Name.PSPDFAnnotationChanged, diff --git a/Zotero/Scenes/Detail/PDF/ViewModels/PDFThumbnailsActionHandler.swift b/Zotero/Scenes/Detail/PDF/ViewModels/PDFThumbnailsActionHandler.swift index 6d02af2e6..24915685e 100644 --- a/Zotero/Scenes/Detail/PDF/ViewModels/PDFThumbnailsActionHandler.swift +++ b/Zotero/Scenes/Detail/PDF/ViewModels/PDFThumbnailsActionHandler.swift @@ -33,8 +33,8 @@ struct PDFThumbnailsActionHandler: ViewModelActionHandler { case .prefetch(let pageIndices): prefetch(pageIndices: pageIndices, in: viewModel) - case .setUserInterface(let isDark): - setUserInteraface(isDark: isDark, in: viewModel) + case .setAppearance(let appearance): + setAppearance(appearance: appearance, in: viewModel) case .loadPages: loadPages(viewModel: viewModel) @@ -84,7 +84,7 @@ struct PDFThumbnailsActionHandler: ViewModelActionHandler { private func prefetch(pageIndices: [UInt], in viewModel: ViewModel) { let toFetch = pageIndices.compactMap { pageIndex in - let hasImage = thumbnailController.hasThumbnail(page: pageIndex, key: viewModel.state.key, libraryId: viewModel.state.libraryId, isDark: viewModel.state.isDark) + let hasImage = thumbnailController.hasThumbnail(page: pageIndex, key: viewModel.state.key, libraryId: viewModel.state.libraryId, appearance: viewModel.state.appearance) return hasImage ? nil : pageIndex } thumbnailController.cache( @@ -93,14 +93,14 @@ struct PDFThumbnailsActionHandler: ViewModelActionHandler { libraryId: viewModel.state.libraryId, document: viewModel.state.document, imageSize: viewModel.state.thumbnailSize, - isDark: viewModel.state.isDark + appearance: viewModel.state.appearance ) .subscribe() .disposed(by: disposeBag) } private func load(pageIndex: UInt, in viewModel: ViewModel) { - if thumbnailController.hasThumbnail(page: pageIndex, key: viewModel.state.key, libraryId: viewModel.state.libraryId, isDark: viewModel.state.isDark) { + if thumbnailController.hasThumbnail(page: pageIndex, key: viewModel.state.key, libraryId: viewModel.state.libraryId, appearance: viewModel.state.appearance) { loadCachedThumbnailAsync() } else { loadAndCacheThumbnailAsync() @@ -110,7 +110,7 @@ struct PDFThumbnailsActionHandler: ViewModelActionHandler { queue.async { [weak thumbnailController, weak viewModel] in guard let thumbnailController, let viewModel, - let image = thumbnailController.thumbnail(page: pageIndex, key: viewModel.state.key, libraryId: viewModel.state.libraryId, isDark: viewModel.state.isDark) + let image = thumbnailController.thumbnail(page: pageIndex, key: viewModel.state.key, libraryId: viewModel.state.libraryId, appearance: viewModel.state.appearance) else { return } DispatchQueue.main.async { [weak viewModel] in guard let viewModel else { return } @@ -126,7 +126,7 @@ struct PDFThumbnailsActionHandler: ViewModelActionHandler { libraryId: viewModel.state.libraryId, document: viewModel.state.document, imageSize: viewModel.state.thumbnailSize, - isDark: viewModel.state.isDark + appearance: viewModel.state.appearance ) .observe(on: MainScheduler.instance) .subscribe(with: viewModel) { viewModel, image in @@ -143,11 +143,11 @@ struct PDFThumbnailsActionHandler: ViewModelActionHandler { } } - private func setUserInteraface(isDark: Bool, in viewModel: ViewModel) { + private func setAppearance(appearance: Appearance, in viewModel: ViewModel) { viewModel.state.cache.removeAllObjects() update(viewModel: viewModel) { state in - state.isDark = isDark - state.changes = .userInterface + state.appearance = appearance + state.changes = .appearance } } } diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift index 0b325a959..312fda549 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift @@ -229,7 +229,7 @@ final class PDFAnnotationsViewController: UIViewController { return } - if state.changes.contains(.interfaceStyle) && dataSource.snapshot().numberOfItems > 0 { + if state.changes.contains(.appearance) && dataSource.snapshot().numberOfItems > 0 { updateQueue.async { [weak self] in guard let self else { return } var snapshot = dataSource.snapshot() diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift index b256bbe0b..ed071c388 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFDocumentViewController.swift @@ -174,7 +174,8 @@ final class PDFDocumentViewController: UIViewController { default: type = nil } - let (_color, _, blendMode) = AnnotationColorGenerator.color(from: color, type: type, userInterfaceStyle: viewModel.state.interfaceStyle) + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) + let (_color, _, blendMode) = AnnotationColorGenerator.color(from: color, type: type, appearance: appearance) stateManager.drawColor = _color stateManager.blendMode = blendMode ?? .normal } @@ -216,7 +217,7 @@ final class PDFDocumentViewController: UIViewController { } private func update(state: PDFReaderState, pdfController: PDFViewController) { - if state.changes.contains(.interfaceStyle) || state.changes.contains(.appearanceMode) { + if state.changes.contains(.appearance) { updateInterface(to: state.settings.appearanceMode, userInterfaceStyle: state.interfaceStyle) } @@ -343,8 +344,8 @@ final class PDFDocumentViewController: UIViewController { switch appearanceMode { case .automatic: self.pdfController?.appearanceModeManager.appearanceMode = userInterfaceStyle == .dark ? .night : [] - self.pdfController?.overrideUserInterfaceStyle = .unspecified - self.unlockController?.overrideUserInterfaceStyle = .unspecified + self.pdfController?.overrideUserInterfaceStyle = userInterfaceStyle + self.unlockController?.overrideUserInterfaceStyle = userInterfaceStyle case .light: self.pdfController?.appearanceModeManager.appearanceMode = [] @@ -462,7 +463,8 @@ final class PDFDocumentViewController: UIViewController { default: type = nil } - let toolColor = AnnotationColorGenerator.color(from: color, type: type, userInterfaceStyle: viewModel.state.interfaceStyle).color + let appearance = Appearance.from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) + let toolColor = AnnotationColorGenerator.color(from: color, type: type, appearance: appearance).color stateManager.setLastUsedColor(toolColor, annotationString: tool) if stateManager.state == tool { stateManager.drawColor = toolColor diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift index 0868da0e1..47795105d 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift @@ -453,7 +453,7 @@ class PDFReaderViewController: UIViewController { } } - if state.changes.contains(.interfaceStyle) { + if state.changes.contains(.appearance) { updateInterface(to: state.settings) } @@ -624,7 +624,7 @@ class PDFReaderViewController: UIViewController { } private func updateUserInterfaceStyleIfNeeded(previousTraitCollection: UITraitCollection?) { - guard traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) && viewModel.state.settings.appearanceMode == .automatic else { return } + guard traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) else { return } viewModel.process(action: .userInterfaceStyleChanged(traitCollection.userInterfaceStyle)) } diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFSidebarViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFSidebarViewController.swift index 5c42b7fab..4fd5487d6 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFSidebarViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFSidebarViewController.swift @@ -137,7 +137,7 @@ class PDFSidebarViewController: UIViewController { libraryId: viewModel.state.library.identifier, document: viewModel.state.document, selectedPageIndex: viewModel.state.visiblePage, - isDark: viewModel.state.interfaceStyle == .dark + appearance: .from(appearanceMode: viewModel.state.settings.appearanceMode, interfaceStyle: viewModel.state.interfaceStyle) ) let thumbnailsViewModel = ViewModel(initialState: thumbnailsState, handler: PDFThumbnailsActionHandler(thumbnailController: viewModel.handler.pdfThumbnailController)) let thumbnailsController = PDFThumbnailsViewController(viewModel: thumbnailsViewModel) @@ -154,6 +154,9 @@ class PDFSidebarViewController: UIViewController { if state.changes.contains(.annotations) { thumbnailsViewModel.process(action: .reloadThumbnails) } + if state.changes.contains(.appearance) { + thumbnailsViewModel.process(action: .setAppearance(.from(appearanceMode: state.settings.appearanceMode, interfaceStyle: state.interfaceStyle))) + } }) .disposed(by: disposeBag) diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFThumbnailsViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFThumbnailsViewController.swift index 1523a7382..a0a90dd03 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFThumbnailsViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFThumbnailsViewController.swift @@ -79,12 +79,6 @@ class PDFThumbnailsViewController: UICollectionViewController { viewModel.process(action: .loadPages) } - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - guard traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) else { return } - self.viewModel.process(action: .setUserInterface(isDark: traitCollection.userInterfaceStyle == .dark)) - } - func set(visiblePage: Int) { viewModel.process(action: .setSelectedPage(pageIndex: visiblePage, type: .fromDocument)) } @@ -121,10 +115,12 @@ class PDFThumbnailsViewController: UICollectionViewController { // The following updates should be ignored if the collection hasn't loaded yet for the first time. guard snapshot.numberOfSections > 0 else { return } - if state.changes.contains(.userInterface) || state.changes.contains(.reload) { + if state.changes.contains(.appearance) || state.changes.contains(.reload) { updateQueue.sync { [weak self] in - snapshot.reloadSections([0]) - self?.dataSource.apply(snapshot) + guard let self else { return } + var snapshot = dataSource.snapshot() + snapshot.reconfigureItems(snapshot.itemIdentifiers) + dataSource.apply(snapshot, animatingDifferences: false, completion: nil) } }