From 70c6af4ba9abdc5c43803e6b4f6d6ac7a782a033 Mon Sep 17 00:00:00 2001 From: Dominik Moreira Date: Sun, 20 Sep 2026 22:06:44 +0000 Subject: [PATCH] make pdf preview open pattern reading mode --- Localizable.xcstrings | 18 ++++++++++++- LoopLine/Services/ImportedPDFStorage.swift | 8 ++++++ LoopLine/ViewModels/PDFReadingViewModel.swift | 6 +---- LoopLine/Views/ProjectDetailView.swift | 26 +++++++++++++++---- LoopLineTests/ProjectPersistenceTests.swift | 17 ++++++++++++ LoopLineUITests/ReadingModeUITests.swift | 18 +++++++++++++ 6 files changed, 82 insertions(+), 11 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 87b1a9c..7949792 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -883,6 +883,22 @@ } } }, + "Open PDF instructions" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "PDF-Anleitung öffnen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open PDF instructions" + } + } + } + }, "Open Reading Mode" : { "comment" : "A button that opens a reading mode.", "isCommentAutoGenerated" : true, @@ -1493,4 +1509,4 @@ } }, "version" : "1.3" -} \ No newline at end of file +} diff --git a/LoopLine/Services/ImportedPDFStorage.swift b/LoopLine/Services/ImportedPDFStorage.swift index b5b6958..8598b03 100644 --- a/LoopLine/Services/ImportedPDFStorage.swift +++ b/LoopLine/Services/ImportedPDFStorage.swift @@ -1,6 +1,14 @@ import Foundation enum ImportedPDFStorage { + static func fileURL(for project: Project) -> URL? { + guard project.sourceType == .pdf, let storedReference = project.sourceFilePath else { + return nil + } + + return fileURL(for: storedReference) + } + static func directoryURL() throws -> URL { let applicationSupportDirectory = try FileManager.default.url( for: .applicationSupportDirectory, diff --git a/LoopLine/ViewModels/PDFReadingViewModel.swift b/LoopLine/ViewModels/PDFReadingViewModel.swift index e6d5479..7f5e388 100644 --- a/LoopLine/ViewModels/PDFReadingViewModel.swift +++ b/LoopLine/ViewModels/PDFReadingViewModel.swift @@ -10,11 +10,7 @@ final class PDFReadingViewModel { var markupError: PDFMarkupError? func pdfURL(for project: Project) -> URL? { - guard project.sourceType == .pdf, let sourceFilePath = project.sourceFilePath else { - return nil - } - - return ImportedPDFStorage.fileURL(for: sourceFilePath) + ImportedPDFStorage.fileURL(for: project) } func selectedMetricValue(for project: Project) -> Int { diff --git a/LoopLine/Views/ProjectDetailView.swift b/LoopLine/Views/ProjectDetailView.swift index 362324f..75f5b47 100644 --- a/LoopLine/Views/ProjectDetailView.swift +++ b/LoopLine/Views/ProjectDetailView.swift @@ -62,12 +62,19 @@ struct ProjectDetailView: View { private var mediaHeader: some View { Group { if project.sourceType == .pdf, let sourceFilePath = project.sourceFilePath { - StoredPDFPreview(storedReference: sourceFilePath, height: 190) - .clipShape(RoundedRectangle(cornerRadius: LoopLineTheme.cornerRadius, style: .continuous)) - .overlay { - RoundedRectangle(cornerRadius: LoopLineTheme.cornerRadius, style: .continuous) - .stroke(LoopLineTheme.subtleStroke, lineWidth: 1) + if ImportedPDFStorage.fileURL(for: project) != nil { + NavigationLink { + PDFReadingView(project: project) + } label: { + pdfPreview(storedReference: sourceFilePath) + .contentShape(Rectangle()) } + .buttonStyle(.plain) + .accessibilityLabel("Open PDF instructions") + .accessibilityIdentifier("openPDFPreview") + } else { + pdfPreview(storedReference: sourceFilePath) + } } else if project.sourceType == .image, let sourceFilePath = project.sourceFilePath { StoredImagePreview(storedReference: sourceFilePath, height: 190) .clipShape(RoundedRectangle(cornerRadius: LoopLineTheme.cornerRadius, style: .continuous)) @@ -82,6 +89,15 @@ struct ProjectDetailView: View { } } + private func pdfPreview(storedReference: String) -> some View { + StoredPDFPreview(storedReference: storedReference, height: 190) + .clipShape(RoundedRectangle(cornerRadius: LoopLineTheme.cornerRadius, style: .continuous)) + .overlay { + RoundedRectangle(cornerRadius: LoopLineTheme.cornerRadius, style: .continuous) + .stroke(LoopLineTheme.subtleStroke, lineWidth: 1) + } + } + private var titleBlock: some View { VStack(alignment: .leading, spacing: 10) { Text(project.name) diff --git a/LoopLineTests/ProjectPersistenceTests.swift b/LoopLineTests/ProjectPersistenceTests.swift index 8a4e644..eba0071 100644 --- a/LoopLineTests/ProjectPersistenceTests.swift +++ b/LoopLineTests/ProjectPersistenceTests.swift @@ -10,6 +10,23 @@ import XCTest @testable import LoopLine final class ProjectPersistenceTests: XCTestCase { + @MainActor + func testPDFURLRequiresPDFSourceAndExistingFile() throws { + let fileURL = FileManager.default.temporaryDirectory + .appendingPathComponent("LoopLine-PDF-Resolver-\(UUID().uuidString).pdf") + try Data().write(to: fileURL) + defer { try? FileManager.default.removeItem(at: fileURL) } + + let pdfProject = Project(name: "PDF", sourceType: .pdf, sourceFilePath: fileURL.path) + XCTAssertEqual(ImportedPDFStorage.fileURL(for: pdfProject), fileURL) + + let textProject = Project(name: "Text", sourceType: .text, sourceFilePath: fileURL.path) + XCTAssertNil(ImportedPDFStorage.fileURL(for: textProject)) + + let missingPDFProject = Project(name: "Missing PDF", sourceType: .pdf) + XCTAssertNil(ImportedPDFStorage.fileURL(for: missingPDFProject)) + } + @MainActor func testProjectPreservesTrackingValuesAfterSaveAndFetch() throws { let container = try ModelContainer( diff --git a/LoopLineUITests/ReadingModeUITests.swift b/LoopLineUITests/ReadingModeUITests.swift index aecd328..44e38bb 100644 --- a/LoopLineUITests/ReadingModeUITests.swift +++ b/LoopLineUITests/ReadingModeUITests.swift @@ -22,4 +22,22 @@ final class ReadingModeUITests: XCTestCase { let pdfReadingMode = app.descendants(matching: .any)["pdfReadingMode"] XCTAssertTrue(pdfReadingMode.waitForExistence(timeout: 5)) } + + @MainActor + func testUserCanOpenReadingModeFromPDFPreview() throws { + let app = XCUIApplication() + app.launchArguments.append("-uiTesting") + app.launch() + + let project = app.buttons["uiTestPDFProject"] + XCTAssertTrue(project.waitForExistence(timeout: 5)) + project.tap() + + let pdfPreview = app.buttons["openPDFPreview"] + XCTAssertTrue(pdfPreview.waitForExistence(timeout: 5)) + pdfPreview.tap() + + let pdfReadingMode = app.descendants(matching: .any)["pdfReadingMode"] + XCTAssertTrue(pdfReadingMode.waitForExistence(timeout: 5)) + } }