mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #688 from omnivore-app/fix/pdf-loading
Fix/pdf loading
This commit is contained in:
commit
a1bccce9b5
17 changed files with 357 additions and 290 deletions
|
|
@ -10,9 +10,6 @@
|
|||
0411792A26A22860004AE24F /* MacAppSmokeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0411792926A22860004AE24F /* MacAppSmokeTest.swift */; };
|
||||
0418837E2742E99F003E0001 /* Intercom in Frameworks */ = {isa = PBXBuildFile; productRef = 0418837D2742E99F003E0001 /* Intercom */; };
|
||||
041883802742FCF2003E0001 /* Utils in Frameworks */ = {isa = PBXBuildFile; productRef = 0418837F2742FCF2003E0001 /* Utils */; };
|
||||
042184ED273AD426002357B0 /* PSPDFKit in Frameworks */ = {isa = PBXBuildFile; productRef = 042184EC273AD426002357B0 /* PSPDFKit */; };
|
||||
042184EF273AD5F3002357B0 /* PDFViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 042184EE273AD5F3002357B0 /* PDFViewer.swift */; };
|
||||
042184F0273AD5F3002357B0 /* PDFViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 042184EE273AD5F3002357B0 /* PDFViewer.swift */; };
|
||||
042F48DC26DFD10E00BF98FC /* iOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 042F48DB26DFD10E00BF98FC /* iOSLaunchTests.swift */; };
|
||||
045B1681279147E7005047F7 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 045B1680279147E7005047F7 /* FirebaseMessaging */; };
|
||||
0465B9BE26CDD35F005558CD /* MainApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81BE98F0CB588F5FC577A13 /* MainApp.swift */; };
|
||||
|
|
@ -44,10 +41,6 @@
|
|||
42321E882714E6B00056429F /* styles in Resources */ = {isa = PBXBuildFile; fileRef = 42321E832714E6B00056429F /* styles */; };
|
||||
42321E892714E6B00056429F /* views in Resources */ = {isa = PBXBuildFile; fileRef = 42321E842714E6B00056429F /* views */; };
|
||||
42321E8A2714E6B00056429F /* views in Resources */ = {isa = PBXBuildFile; fileRef = 42321E842714E6B00056429F /* views */; };
|
||||
4255C6D82755A5350006422A /* NanoID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4255C6D72755A5350006422A /* NanoID.swift */; };
|
||||
4255C6D92755A5350006422A /* NanoID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4255C6D72755A5350006422A /* NanoID.swift */; };
|
||||
426408732744676C00A2AE46 /* PDFReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 426408722744676B00A2AE46 /* PDFReaderView.swift */; };
|
||||
426408742744676C00A2AE46 /* PDFReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 426408722744676B00A2AE46 /* PDFReaderView.swift */; };
|
||||
42FF1B33271154A700B38C38 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42FF1AEB271154A600B38C38 /* SafariWebExtensionHandler.swift */; };
|
||||
42FF1B34271154A700B38C38 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42FF1AEB271154A600B38C38 /* SafariWebExtensionHandler.swift */; };
|
||||
42FF1B35271154A700B38C38 /* _locales in Resources */ = {isa = PBXBuildFile; fileRef = 42FF1AED271154A600B38C38 /* _locales */; };
|
||||
|
|
@ -184,7 +177,6 @@
|
|||
0411792726A22860004AE24F /* UnitTests-MacApp.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UnitTests-MacApp.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0411792926A22860004AE24F /* MacAppSmokeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAppSmokeTest.swift; sourceTree = "<group>"; };
|
||||
0411792B26A22860004AE24F /* MacUnitTests.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MacUnitTests.plist; sourceTree = "<group>"; };
|
||||
042184EE273AD5F3002357B0 /* PDFViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFViewer.swift; sourceTree = "<group>"; };
|
||||
042F48D926DFD10E00BF98FC /* UITests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UITests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
042F48DB26DFD10E00BF98FC /* iOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSLaunchTests.swift; sourceTree = "<group>"; };
|
||||
042F48DD26DFD10E00BF98FC /* iOSUITests.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = iOSUITests.plist; sourceTree = "<group>"; };
|
||||
|
|
@ -215,8 +207,6 @@
|
|||
42321E822714E6B00056429F /* scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = scripts; sourceTree = "<group>"; };
|
||||
42321E832714E6B00056429F /* styles */ = {isa = PBXFileReference; lastKnownFileType = folder; path = styles; sourceTree = "<group>"; };
|
||||
42321E842714E6B00056429F /* views */ = {isa = PBXFileReference; lastKnownFileType = folder; path = views; sourceTree = "<group>"; };
|
||||
4255C6D72755A5350006422A /* NanoID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NanoID.swift; sourceTree = "<group>"; };
|
||||
426408722744676B00A2AE46 /* PDFReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFReaderView.swift; sourceTree = "<group>"; };
|
||||
42FF1AEB271154A600B38C38 /* SafariWebExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariWebExtensionHandler.swift; sourceTree = "<group>"; };
|
||||
42FF1AED271154A600B38C38 /* _locales */ = {isa = PBXFileReference; lastKnownFileType = folder; path = _locales; sourceTree = "<group>"; };
|
||||
42FF1AEE271154A600B38C38 /* images */ = {isa = PBXFileReference; lastKnownFileType = folder; path = images; sourceTree = "<group>"; };
|
||||
|
|
@ -315,7 +305,6 @@
|
|||
0418837E2742E99F003E0001 /* Intercom in Frameworks */,
|
||||
045B1681279147E7005047F7 /* FirebaseMessaging in Frameworks */,
|
||||
041883802742FCF2003E0001 /* Utils in Frameworks */,
|
||||
042184ED273AD426002357B0 /* PSPDFKit in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -478,9 +467,6 @@
|
|||
children = (
|
||||
0480E71B26D95096006CAE2F /* AppDelegate.swift */,
|
||||
D81BE98F0CB588F5FC577A13 /* MainApp.swift */,
|
||||
042184EE273AD5F3002357B0 /* PDFViewer.swift */,
|
||||
426408722744676B00A2AE46 /* PDFReaderView.swift */,
|
||||
4255C6D72755A5350006422A /* NanoID.swift */,
|
||||
42FF1AEA271154A600B38C38 /* SafariExtension */,
|
||||
B330B55BAF36E624637EE3BE /* ShareExtension */,
|
||||
04920CC5279671EF003EC1B6 /* PushNotificationConfig.swift */,
|
||||
|
|
@ -719,7 +705,6 @@
|
|||
name = "Omnivore-iOS";
|
||||
packageProductDependencies = (
|
||||
8F13016D33DF1598D7157563 /* App */,
|
||||
042184EC273AD426002357B0 /* PSPDFKit */,
|
||||
0418837D2742E99F003E0001 /* Intercom */,
|
||||
0418837F2742FCF2003E0001 /* Utils */,
|
||||
045B1680279147E7005047F7 /* FirebaseMessaging */,
|
||||
|
|
@ -796,7 +781,6 @@
|
|||
);
|
||||
mainGroup = 2EE5B57B588CF4ADBA39A106;
|
||||
packageReferences = (
|
||||
042184EB273AD426002357B0 /* XCRemoteSwiftPackageReference "PSPDFKit-SP" */,
|
||||
0418837C2742E99F003E0001 /* XCRemoteSwiftPackageReference "intercom-ios" */,
|
||||
048F592A2790EAF800E0B494 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
|
||||
);
|
||||
|
|
@ -1039,10 +1023,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0480E71D26D95096006CAE2F /* AppDelegate.swift in Sources */,
|
||||
4255C6D92755A5350006422A /* NanoID.swift in Sources */,
|
||||
0465B9BE26CDD35F005558CD /* MainApp.swift in Sources */,
|
||||
426408742744676C00A2AE46 /* PDFReaderView.swift in Sources */,
|
||||
042184F0273AD5F3002357B0 /* PDFViewer.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -1075,11 +1056,8 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0480E71C26D95096006CAE2F /* AppDelegate.swift in Sources */,
|
||||
4255C6D82755A5350006422A /* NanoID.swift in Sources */,
|
||||
04920CC6279671EF003EC1B6 /* PushNotificationConfig.swift in Sources */,
|
||||
CA7EE773095F267516D7AC98 /* MainApp.swift in Sources */,
|
||||
426408732744676C00A2AE46 /* PDFReaderView.swift in Sources */,
|
||||
042184EF273AD5F3002357B0 /* PDFViewer.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -1463,7 +1441,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MARKETING_VERSION = 1.7.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
|
||||
PRODUCT_NAME = Omnivore;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1495,7 +1473,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MARKETING_VERSION = 1.7.1;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
|
|
@ -1534,7 +1512,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MARKETING_VERSION = 1.7.1;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
|
|
@ -1696,7 +1674,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MARKETING_VERSION = 1.7.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension";
|
||||
PRODUCT_NAME = ShareExtension;
|
||||
SDKROOT = iphoneos;
|
||||
|
|
@ -1750,7 +1728,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MARKETING_VERSION = 1.7.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
|
||||
PRODUCT_NAME = Omnivore;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1778,7 +1756,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MARKETING_VERSION = 1.7.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension";
|
||||
PRODUCT_NAME = ShareExtension;
|
||||
SDKROOT = iphoneos;
|
||||
|
|
@ -1964,14 +1942,6 @@
|
|||
version = 11.1.2;
|
||||
};
|
||||
};
|
||||
042184EB273AD426002357B0 /* XCRemoteSwiftPackageReference "PSPDFKit-SP" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/PSPDFKit/PSPDFKit-SP";
|
||||
requirement = {
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
048F592A2790EAF800E0B494 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/firebase/firebase-ios-sdk";
|
||||
|
|
@ -1992,11 +1962,6 @@
|
|||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Utils;
|
||||
};
|
||||
042184EC273AD426002357B0 /* PSPDFKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 042184EB273AD426002357B0 /* XCRemoteSwiftPackageReference "PSPDFKit-SP" */;
|
||||
productName = PSPDFKit;
|
||||
};
|
||||
045B1680279147E7005047F7 /* FirebaseMessaging */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 048F592A2790EAF800E0B494 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@
|
|||
"location" : "https://github.com/PSPDFKit/PSPDFKit-SP",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "344c895fea62eb42c6b56e6a060d5dff73cc5bc2"
|
||||
"revision" : "0e18629c443e3f39ecfee0f600d9ef5551ecf488"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,15 +16,9 @@ let package = Package(
|
|||
.library(name: "Models", targets: ["Models"]),
|
||||
.library(name: "Utils", targets: ["Utils"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/openid/AppAuth-iOS.git", .upToNextMajor(from: "1.4.0")),
|
||||
.package(url: "https://github.com/Square/Valet", from: "4.1.2"),
|
||||
.package(url: "https://github.com/maticzav/swift-graphql", from: "2.3.1"),
|
||||
.package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.4"),
|
||||
.package(url: "git@github.com:segmentio/analytics-swift.git", .upToNextMajor(from: "1.0.0"))
|
||||
],
|
||||
dependencies: dependencies,
|
||||
targets: [
|
||||
.target(name: "App", dependencies: ["Views", "Services", "Models", "Utils"]),
|
||||
.target(name: "App", dependencies: appPackageDependencies),
|
||||
.testTarget(name: "AppTests", dependencies: ["App"]),
|
||||
.target(
|
||||
name: "Views",
|
||||
|
|
@ -58,3 +52,25 @@ let package = Package(
|
|||
.testTarget(name: "UtilsTests", dependencies: ["Utils"])
|
||||
]
|
||||
)
|
||||
|
||||
var appPackageDependencies: [Target.Dependency] {
|
||||
var deps: [Target.Dependency] = ["Views", "Services", "Models", "Utils"]
|
||||
// #if canImport(UIKit)
|
||||
deps.append(.product(name: "PSPDFKit", package: "PSPDFKit-SP"))
|
||||
// #endif
|
||||
return deps
|
||||
}
|
||||
|
||||
var dependencies: [Package.Dependency] {
|
||||
var deps: [Package.Dependency] = [
|
||||
.package(url: "https://github.com/openid/AppAuth-iOS.git", .upToNextMajor(from: "1.4.0")),
|
||||
.package(url: "https://github.com/Square/Valet", from: "4.1.2"),
|
||||
.package(url: "https://github.com/maticzav/swift-graphql", from: "2.3.1"),
|
||||
.package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.4"),
|
||||
.package(url: "git@github.com:segmentio/analytics-swift.git", .upToNextMajor(from: "1.0.0"))
|
||||
]
|
||||
// #if canImport(UIKit)
|
||||
deps.append(.package(url: "https://github.com/PSPDFKit/PSPDFKit-SP", branch: "master"))
|
||||
// #endif
|
||||
return deps
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
import Models
|
||||
import SwiftUI
|
||||
import Utils
|
||||
import WebKit
|
||||
|
||||
#if os(iOS)
|
||||
import Models
|
||||
import PSPDFKit
|
||||
import PSPDFKitUI
|
||||
import SwiftUI
|
||||
import Utils
|
||||
import WebKit
|
||||
|
||||
struct PDFReaderViewController: UIViewControllerRepresentable {
|
||||
static func registerKey() {
|
||||
if let pspdfKitKey = AppKeys.sharedInstance?.pspdfKitKey {
|
||||
SDK.setLicenseKey(pspdfKitKey)
|
||||
}
|
||||
}
|
||||
|
||||
let document: Document
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
|
@ -1,127 +1,148 @@
|
|||
import App
|
||||
import Combine
|
||||
import SwiftUI
|
||||
import Utils
|
||||
|
||||
#if os(iOS)
|
||||
import PDFKit
|
||||
import PSPDFKit
|
||||
import PSPDFKitUI
|
||||
import Services
|
||||
|
||||
struct PDFViewer: View {
|
||||
final class PDFStateObject: ObservableObject {
|
||||
@Published var document: Document?
|
||||
@Published var coordinator: PDFViewCoordinator?
|
||||
@Published var controllerNeedsConfig = true
|
||||
}
|
||||
|
||||
@EnvironmentObject var dataService: DataService
|
||||
|
||||
struct ShareLink: Identifiable {
|
||||
let id: UUID
|
||||
let url: URL
|
||||
}
|
||||
|
||||
let pdfURL: URL
|
||||
let document: Document
|
||||
let viewModel: PDFViewerViewModel
|
||||
let coordinator: PDFViewCoordinator
|
||||
|
||||
@StateObject var pdfStateObject = PDFStateObject()
|
||||
@State var readerView: Bool = false
|
||||
@State private var shareLink: ShareLink?
|
||||
|
||||
init(remoteURL: URL, viewModel: PDFViewerViewModel) {
|
||||
self.pdfURL = viewModel.dataURL(remoteURL: remoteURL)
|
||||
self.viewModel = viewModel
|
||||
self.document = HighlightedDocument(url: pdfURL, viewModel: viewModel)
|
||||
self.coordinator = PDFViewCoordinator(document: document, viewModel: viewModel)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
PDFView(document: document)
|
||||
.useParentNavigationBar(true)
|
||||
.updateConfiguration { builder in
|
||||
builder.textSelectionShouldSnapToWord = true
|
||||
}
|
||||
.updateControllerConfiguration { controller in
|
||||
print("document is valid", document.isValid)
|
||||
coordinator.setController(controller: controller)
|
||||
|
||||
// Disable the Document Editor
|
||||
controller.navigationItem.setRightBarButtonItems(
|
||||
[controller.thumbnailsButtonItem],
|
||||
for: .thumbnails,
|
||||
animated: false
|
||||
)
|
||||
|
||||
let barButtonItems = [
|
||||
UIBarButtonItem(
|
||||
image: UIImage(systemName: "textformat"),
|
||||
style: .plain,
|
||||
target: controller.settingsButtonItem.target,
|
||||
action: controller.settingsButtonItem.action
|
||||
),
|
||||
UIBarButtonItem(
|
||||
image: UIImage(systemName: "book"),
|
||||
style: .plain,
|
||||
target: coordinator,
|
||||
action: #selector(PDFViewCoordinator.toggleReaderView)
|
||||
),
|
||||
UIBarButtonItem(
|
||||
image: UIImage(systemName: "magnifyingglass"),
|
||||
style: .plain,
|
||||
target: controller.searchButtonItem.target,
|
||||
action: controller.searchButtonItem.action
|
||||
)
|
||||
]
|
||||
|
||||
document.areAnnotationsEnabled = true
|
||||
|
||||
coordinator.viewer = self
|
||||
|
||||
if viewModel.linkedItem.readingProgressAnchor > 0 {
|
||||
let pageIndex = UInt(viewModel.linkedItem.readingProgressAnchor)
|
||||
controller.setPageIndex(pageIndex, animated: false)
|
||||
if let document = pdfStateObject.document, let coordinator = pdfStateObject.coordinator {
|
||||
PDFView(document: document)
|
||||
.useParentNavigationBar(true)
|
||||
.updateConfiguration { builder in
|
||||
builder.textSelectionShouldSnapToWord = true
|
||||
}
|
||||
.updateControllerConfiguration { controller in
|
||||
// Store config state so we only run this update closure once
|
||||
guard pdfStateObject.controllerNeedsConfig else { return }
|
||||
print("document is valid", document.isValid)
|
||||
coordinator.setController(controller: controller, dataService: dataService)
|
||||
|
||||
controller.navigationItem.setRightBarButtonItems(barButtonItems, for: .document, animated: false)
|
||||
}
|
||||
.onShouldShowMenuItemsForSelectedText(perform: { pageView, menuItems, selectedText in
|
||||
let copy = menuItems.first(where: { $0.identifier == "Copy" })
|
||||
let highlight = MenuItem(title: "Highlight", block: {
|
||||
_ = self.coordinator.highlightSelection(pageView: pageView, selectedText: selectedText)
|
||||
})
|
||||
// Disable the Document Editor
|
||||
controller.navigationItem.setRightBarButtonItems(
|
||||
[controller.thumbnailsButtonItem],
|
||||
for: .thumbnails,
|
||||
animated: false
|
||||
)
|
||||
|
||||
let barButtonItems = [
|
||||
UIBarButtonItem(
|
||||
image: UIImage(systemName: "textformat"),
|
||||
style: .plain,
|
||||
target: controller.settingsButtonItem.target,
|
||||
action: controller.settingsButtonItem.action
|
||||
),
|
||||
UIBarButtonItem(
|
||||
image: UIImage(systemName: "book"),
|
||||
style: .plain,
|
||||
target: coordinator,
|
||||
action: #selector(PDFViewCoordinator.toggleReaderView)
|
||||
),
|
||||
UIBarButtonItem(
|
||||
image: UIImage(systemName: "magnifyingglass"),
|
||||
style: .plain,
|
||||
target: controller.searchButtonItem.target,
|
||||
action: controller.searchButtonItem.action
|
||||
)
|
||||
]
|
||||
|
||||
document.areAnnotationsEnabled = true
|
||||
|
||||
coordinator.viewer = self
|
||||
|
||||
if viewModel.pdfItem.readingProgressAnchor > 0 {
|
||||
let pageIndex = UInt(viewModel.pdfItem.readingProgressAnchor)
|
||||
controller.setPageIndex(pageIndex, animated: false)
|
||||
}
|
||||
|
||||
controller.navigationItem.setRightBarButtonItems(barButtonItems, for: .document, animated: false)
|
||||
pdfStateObject.controllerNeedsConfig = false
|
||||
}
|
||||
.onShouldShowMenuItemsForSelectedText(perform: { pageView, menuItems, selectedText in
|
||||
let copy = menuItems.first(where: { $0.identifier == "Copy" })
|
||||
let highlight = MenuItem(title: "Highlight", block: {
|
||||
_ = coordinator.highlightSelection(
|
||||
pageView: pageView,
|
||||
selectedText: selectedText,
|
||||
dataService: dataService
|
||||
)
|
||||
})
|
||||
// let share = MenuItem(title: "Share", block: {
|
||||
// let shortId = self.coordinator.highlightSelection(pageView: pageView, selectedText: selectedText)
|
||||
// if let shareURL = viewModel.highlightShareURL(shortId: shortId) {
|
||||
// shareLink = ShareLink(id: UUID(), url: shareURL)
|
||||
// }
|
||||
// })
|
||||
return [copy, highlight /* , share */ ].compactMap { $0 }
|
||||
})
|
||||
.onShouldShowMenuItemsForSelectedAnnotations(perform: { _, menuItems, annotations in
|
||||
var result = [MenuItem]()
|
||||
if let copy = menuItems.first(where: { $0.identifier == "Copy" }) {
|
||||
result.append(copy)
|
||||
}
|
||||
|
||||
let remove = MenuItem(title: "Remove", block: {
|
||||
self.coordinator.remove(annotations: annotations)
|
||||
return [copy, highlight /* , share */ ].compactMap { $0 }
|
||||
})
|
||||
result.append(remove)
|
||||
.onShouldShowMenuItemsForSelectedAnnotations(perform: { _, menuItems, annotations in
|
||||
var result = [MenuItem]()
|
||||
if let copy = menuItems.first(where: { $0.identifier == "Copy" }) {
|
||||
result.append(copy)
|
||||
}
|
||||
|
||||
let highlights = annotations?.compactMap { $0 as? HighlightAnnotation }
|
||||
let shortId = highlights.flatMap { coordinator.shortHighlightIds($0).first }
|
||||
|
||||
if let shortId = shortId, FeatureFlag.enableShareButton {
|
||||
let share = MenuItem(title: "Share", block: {
|
||||
if let shareURL = viewModel.highlightShareURL(shortId: shortId) {
|
||||
shareLink = ShareLink(id: UUID(), url: shareURL)
|
||||
}
|
||||
let remove = MenuItem(title: "Remove", block: {
|
||||
coordinator.remove(dataService: dataService, annotations: annotations)
|
||||
})
|
||||
result.append(share)
|
||||
}
|
||||
result.append(remove)
|
||||
|
||||
return result
|
||||
})
|
||||
.fullScreenCover(isPresented: $readerView, content: {
|
||||
PDFReaderViewController(document: document)
|
||||
})
|
||||
.accentColor(Color(red: 255 / 255.0, green: 234 / 255.0, blue: 159 / 255.0))
|
||||
.sheet(item: $shareLink) {
|
||||
ShareSheet(activityItems: [$0.url])
|
||||
}
|
||||
let highlights = annotations?.compactMap { $0 as? HighlightAnnotation }
|
||||
let shortId = highlights.flatMap { coordinator.shortHighlightIds($0).first }
|
||||
|
||||
if let shortId = shortId, FeatureFlag.enableShareButton {
|
||||
let share = MenuItem(title: "Share", block: {
|
||||
if let shareURL = viewModel.highlightShareURL(dataService: dataService, shortId: shortId) {
|
||||
shareLink = ShareLink(id: UUID(), url: shareURL)
|
||||
}
|
||||
})
|
||||
result.append(share)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
.fullScreenCover(isPresented: $readerView, content: {
|
||||
PDFReaderViewController(document: document)
|
||||
})
|
||||
.accentColor(Color(red: 255 / 255.0, green: 234 / 255.0, blue: 159 / 255.0))
|
||||
.sheet(item: $shareLink) {
|
||||
ShareSheet(activityItems: [$0.url])
|
||||
}
|
||||
} else {
|
||||
ProgressView()
|
||||
.task {
|
||||
let document = HighlightedDocument(url: pdfURL, viewModel: viewModel)
|
||||
pdfStateObject.document = document
|
||||
pdfStateObject.coordinator = PDFViewCoordinator(document: document, viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PDFViewCoordinator: NSObject, PDFDocumentViewControllerDelegate, PDFViewControllerDelegate {
|
||||
|
|
@ -137,7 +158,7 @@ import Utils
|
|||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
func setController(controller: PDFViewController) {
|
||||
func setController(controller: PDFViewController, dataService: DataService) {
|
||||
self.controller = controller
|
||||
|
||||
controller.pageIndexPublisher.sink { event in
|
||||
|
|
@ -145,8 +166,12 @@ import Utils
|
|||
let pageIndex = Int(event.pageIndex)
|
||||
if let totalPageCount = controller.document?.pageCount {
|
||||
let percent = min(100, max(0, ((Double(pageIndex) + 1.0) / Double(totalPageCount)) * 100.0))
|
||||
if percent > self.viewModel.linkedItem.readingProgress {
|
||||
self.viewModel.updateItemReadProgress(percent: percent, anchorIndex: pageIndex)
|
||||
if percent > self.viewModel.pdfItem.readingProgress {
|
||||
self.viewModel.updateItemReadProgress(
|
||||
dataService: dataService,
|
||||
percent: percent,
|
||||
anchorIndex: pageIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -187,7 +212,7 @@ import Utils
|
|||
return result
|
||||
}
|
||||
|
||||
func highlightSelection(pageView: PDFPageView, selectedText: String) -> String {
|
||||
func highlightSelection(pageView: PDFPageView, selectedText: String, dataService: DataService) -> String {
|
||||
let highlightID = UUID().uuidString.lowercased()
|
||||
let quote = quoteFromSelectedText(selectedText)
|
||||
let shortId = NanoID.generate(alphabet: NanoID.Alphabet.urlSafe.rawValue, size: 8)
|
||||
|
|
@ -199,7 +224,7 @@ import Utils
|
|||
"id": highlightID,
|
||||
"shortId": shortId,
|
||||
"quote": quote,
|
||||
"articleId": viewModel.linkedItem.unwrappedID
|
||||
"articleId": viewModel.pdfItem.itemID
|
||||
]
|
||||
]
|
||||
document.add(annotations: [highlight])
|
||||
|
|
@ -208,7 +233,13 @@ import Utils
|
|||
|
||||
if let patchData = try? highlight.generateInstantJSON(), let patch = String(data: patchData, encoding: .utf8) {
|
||||
if overlapping.isEmpty {
|
||||
viewModel.createHighlight(shortId: shortId, highlightID: highlightID, quote: quote, patch: patch)
|
||||
viewModel.createHighlight(
|
||||
dataService: dataService,
|
||||
shortId: shortId,
|
||||
highlightID: highlightID,
|
||||
quote: quote,
|
||||
patch: patch
|
||||
)
|
||||
} else {
|
||||
let overlappingRects = overlapping.map(\.rects).compactMap { $0 }.flatMap { $0 }
|
||||
let rects = overlappingRects + (highlight.rects ?? [])
|
||||
|
|
@ -223,6 +254,7 @@ import Utils
|
|||
document.remove(annotations: overlapping + [highlight])
|
||||
|
||||
viewModel.mergeHighlight(
|
||||
dataService: dataService,
|
||||
shortId: shortId,
|
||||
highlightID: highlightID,
|
||||
quote: quote,
|
||||
|
|
@ -238,10 +270,13 @@ import Utils
|
|||
return shortId
|
||||
}
|
||||
|
||||
public func remove(annotations: [Annotation]?) {
|
||||
public func remove(dataService: DataService, annotations: [Annotation]?) {
|
||||
if let annotations = annotations {
|
||||
document.remove(annotations: annotations)
|
||||
viewModel.removeHighlights(highlightIds: highlightIds(annotations.compactMap { $0 as? HighlightAnnotation }))
|
||||
viewModel.removeHighlights(
|
||||
dataService: dataService,
|
||||
highlightIds: highlightIds(annotations.compactMap { $0 as? HighlightAnnotation })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -8,15 +8,13 @@ public final class PDFViewerViewModel: ObservableObject {
|
|||
@Published public var errorMessage: String?
|
||||
@Published public var readerView: Bool = false
|
||||
|
||||
public var linkedItem: LinkedItem
|
||||
public let pdfItem: PDFItem
|
||||
private var storedURL: URL?
|
||||
|
||||
var subscriptions = Set<AnyCancellable>()
|
||||
let services: Services
|
||||
|
||||
public init(services: Services, linkedItem: LinkedItem) {
|
||||
self.services = services
|
||||
self.linkedItem = linkedItem
|
||||
public init(pdfItem: PDFItem) {
|
||||
self.pdfItem = pdfItem
|
||||
}
|
||||
|
||||
public func dataURL(remoteURL: URL) -> URL {
|
||||
|
|
@ -24,9 +22,9 @@ public final class PDFViewerViewModel: ObservableObject {
|
|||
return storedURL
|
||||
}
|
||||
|
||||
guard let data = linkedItem.pdfData else { return remoteURL }
|
||||
guard let data = pdfItem.documentData else { return remoteURL }
|
||||
|
||||
let subPath = linkedItem.unwrappedTitle.isEmpty ? UUID().uuidString : linkedItem.unwrappedTitle
|
||||
let subPath = pdfItem.title.isEmpty ? UUID().uuidString : pdfItem.title
|
||||
|
||||
let path = FileManager.default
|
||||
.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
|
|
@ -42,56 +40,63 @@ public final class PDFViewerViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
public func loadHighlightPatches(completion onComplete: @escaping ([String]) -> Void) {
|
||||
onComplete(linkedItem.highlights.asArray(of: Highlight.self).map { $0.patch ?? "" })
|
||||
onComplete(pdfItem.highlights.map { $0.patch ?? "" })
|
||||
}
|
||||
|
||||
public func createHighlight(shortId: String, highlightID: String, quote: String, patch: String) {
|
||||
_ = services.dataService.createHighlight(
|
||||
public func createHighlight(
|
||||
dataService: DataService,
|
||||
shortId: String,
|
||||
highlightID: String,
|
||||
quote: String,
|
||||
patch: String
|
||||
) {
|
||||
_ = dataService.createHighlight(
|
||||
shortId: shortId,
|
||||
highlightID: highlightID,
|
||||
quote: quote,
|
||||
patch: patch,
|
||||
articleId: linkedItem.unwrappedID
|
||||
articleId: pdfItem.itemID
|
||||
)
|
||||
}
|
||||
|
||||
public func mergeHighlight(
|
||||
dataService: DataService,
|
||||
shortId: String,
|
||||
highlightID: String,
|
||||
quote: String,
|
||||
patch: String,
|
||||
overlapHighlightIdList: [String]
|
||||
) {
|
||||
_ = services.dataService.mergeHighlights(
|
||||
_ = dataService.mergeHighlights(
|
||||
shortId: shortId,
|
||||
highlightID: highlightID,
|
||||
quote: quote,
|
||||
patch: patch,
|
||||
articleId: linkedItem.unwrappedID,
|
||||
articleId: pdfItem.itemID,
|
||||
overlapHighlightIdList: overlapHighlightIdList
|
||||
)
|
||||
}
|
||||
|
||||
public func removeHighlights(highlightIds: [String]) {
|
||||
public func removeHighlights(dataService: DataService, highlightIds: [String]) {
|
||||
highlightIds.forEach { highlightID in
|
||||
services.dataService.deleteHighlight(highlightID: highlightID)
|
||||
dataService.deleteHighlight(highlightID: highlightID)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateItemReadProgress(percent: Double, anchorIndex: Int) {
|
||||
services.dataService.updateLinkReadingProgress(
|
||||
itemID: linkedItem.unwrappedID,
|
||||
public func updateItemReadProgress(dataService: DataService, percent: Double, anchorIndex: Int) {
|
||||
dataService.updateLinkReadingProgress(
|
||||
itemID: pdfItem.itemID,
|
||||
readingProgress: percent,
|
||||
anchorIndex: anchorIndex
|
||||
)
|
||||
}
|
||||
|
||||
public func highlightShareURL(shortId: String) -> URL? {
|
||||
let baseURL = services.dataService.appEnvironment.serverBaseURL
|
||||
public func highlightShareURL(dataService: DataService, shortId: String) -> URL? {
|
||||
let baseURL = dataService.appEnvironment.serverBaseURL
|
||||
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)
|
||||
|
||||
if let username = services.dataService.currentViewer?.username {
|
||||
components?.path = "/\(username)/\(linkedItem.unwrappedSlug)/highlights/\(shortId)"
|
||||
if let username = dataService.currentViewer?.username {
|
||||
components?.path = "/\(username)/\(pdfItem.slug)/highlights/\(shortId)"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,61 +20,62 @@ public final class Services {
|
|||
}
|
||||
}
|
||||
|
||||
// Background fetching functions
|
||||
extension Services {
|
||||
public static func registerBackgroundFetch() {
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: fetchTaskID, using: nil) { task in
|
||||
if let task = task as? BGAppRefreshTask {
|
||||
EventTracker.trackForDebugging("executing app.omnivore.fetchLinkedItems bg task")
|
||||
logger.debug("in background task register closure")
|
||||
performBackgroundFetch(task: task)
|
||||
#if os(iOS)
|
||||
// Background fetching functions
|
||||
extension Services {
|
||||
public static func registerBackgroundFetch() {
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: fetchTaskID, using: nil) { task in
|
||||
if let task = task as? BGAppRefreshTask {
|
||||
EventTracker.trackForDebugging("executing app.omnivore.fetchLinkedItems bg task")
|
||||
logger.debug("in background task register closure")
|
||||
performBackgroundFetch(task: task)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func scheduleBackgroundFetch() {
|
||||
BGTaskScheduler.shared.cancelAllTaskRequests()
|
||||
let taskRequest = BGAppRefreshTaskRequest(identifier: fetchTaskID)
|
||||
taskRequest.earliestBeginDate = Date(timeIntervalSinceNow: secondsToWaitBeforeNextBackgroundRefresh)
|
||||
static func scheduleBackgroundFetch() {
|
||||
BGTaskScheduler.shared.cancelAllTaskRequests()
|
||||
let taskRequest = BGAppRefreshTaskRequest(identifier: fetchTaskID)
|
||||
taskRequest.earliestBeginDate = Date(timeIntervalSinceNow: secondsToWaitBeforeNextBackgroundRefresh)
|
||||
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(taskRequest)
|
||||
logger.debug("\(fetchTaskID) task scheduled")
|
||||
} catch {
|
||||
logger.debug("task scheduling failed: \(fetchTaskID)")
|
||||
}
|
||||
}
|
||||
|
||||
static func performBackgroundFetch(task: BGAppRefreshTask) {
|
||||
Services.logger.debug("starting background fetch")
|
||||
scheduleBackgroundFetch()
|
||||
let services = Services()
|
||||
|
||||
task.expirationHandler = {
|
||||
EventTracker.trackForDebugging("background fetch expiration handler called")
|
||||
logger.debug("handling background fetch expiration")
|
||||
}
|
||||
|
||||
guard services.authenticator.hasValidAuthToken else {
|
||||
EventTracker.trackForDebugging("background fetch failed: user does not have a valid auth token")
|
||||
Services.logger.debug("background fetch failed: user does not habe a valid auth token")
|
||||
task.setTaskCompleted(success: false)
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
do {
|
||||
try await services.dataService.fetchLinkedItemsBackgroundTask()
|
||||
logger.debug("fetch complete")
|
||||
EventTracker.trackForDebugging("background fetch task completed successfully")
|
||||
task.setTaskCompleted(success: true)
|
||||
try BGTaskScheduler.shared.submit(taskRequest)
|
||||
logger.debug("\(fetchTaskID) task scheduled")
|
||||
} catch {
|
||||
logger.debug("fetch failed")
|
||||
EventTracker.trackForDebugging("background fetch task failed")
|
||||
logger.debug("task scheduling failed: \(fetchTaskID)")
|
||||
}
|
||||
}
|
||||
|
||||
static func performBackgroundFetch(task: BGAppRefreshTask) {
|
||||
Services.logger.debug("starting background fetch")
|
||||
scheduleBackgroundFetch()
|
||||
let services = Services()
|
||||
|
||||
task.expirationHandler = {
|
||||
EventTracker.trackForDebugging("background fetch expiration handler called")
|
||||
logger.debug("handling background fetch expiration")
|
||||
}
|
||||
|
||||
guard services.authenticator.hasValidAuthToken else {
|
||||
EventTracker.trackForDebugging("background fetch failed: user does not have a valid auth token")
|
||||
Services.logger.debug("background fetch failed: user does not habe a valid auth token")
|
||||
task.setTaskCompleted(success: false)
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
do {
|
||||
try await services.dataService.fetchLinkedItemsBackgroundTask()
|
||||
logger.debug("fetch complete")
|
||||
EventTracker.trackForDebugging("background fetch task completed successfully")
|
||||
task.setTaskCompleted(success: true)
|
||||
} catch {
|
||||
logger.debug("fetch failed")
|
||||
EventTracker.trackForDebugging("background fetch task failed")
|
||||
task.setTaskCompleted(success: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
// e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"app.omnivore.fetchLinkedItems"]
|
||||
|
|
|
|||
|
|
@ -6,12 +6,17 @@ import Views
|
|||
struct FeedCardNavigationLink: View {
|
||||
@EnvironmentObject var dataService: DataService
|
||||
|
||||
@ObservedObject var item: LinkedItem
|
||||
let item: LinkedItem
|
||||
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
||||
var body: some View {
|
||||
let destination = LinkItemDetailView(viewModel: LinkItemDetailViewModel(item: item))
|
||||
let destination = LinkItemDetailView(
|
||||
viewModel: LinkItemDetailViewModel(
|
||||
linkedItemObjectID: item.objectID,
|
||||
dataService: dataService
|
||||
)
|
||||
)
|
||||
#if os(iOS)
|
||||
let modifiedDestination = destination
|
||||
.navigationTitle("")
|
||||
|
|
@ -22,7 +27,7 @@ struct FeedCardNavigationLink: View {
|
|||
return ZStack {
|
||||
NavigationLink(
|
||||
destination: modifiedDestination,
|
||||
tag: item,
|
||||
tag: item.objectID,
|
||||
selection: $viewModel.selectedLinkItem
|
||||
) {
|
||||
EmptyView()
|
||||
|
|
@ -50,7 +55,12 @@ struct GridCardNavigationLink: View {
|
|||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
||||
var body: some View {
|
||||
let destination = LinkItemDetailView(viewModel: LinkItemDetailViewModel(item: item))
|
||||
let destination = LinkItemDetailView(
|
||||
viewModel: LinkItemDetailViewModel(
|
||||
linkedItemObjectID: item.objectID,
|
||||
dataService: dataService
|
||||
)
|
||||
)
|
||||
#if os(iOS)
|
||||
let modifiedDestination = destination
|
||||
.navigationTitle("")
|
||||
|
|
@ -61,7 +71,7 @@ struct GridCardNavigationLink: View {
|
|||
return ZStack {
|
||||
NavigationLink(
|
||||
destination: modifiedDestination,
|
||||
tag: item,
|
||||
tag: item.objectID,
|
||||
selection: $viewModel.selectedLinkItem
|
||||
) {
|
||||
EmptyView()
|
||||
|
|
@ -71,7 +81,7 @@ struct GridCardNavigationLink: View {
|
|||
scale = 0.95
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(150)) {
|
||||
scale = 1.0
|
||||
viewModel.selectedLinkItem = item
|
||||
viewModel.selectedLinkItem = item.objectID
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import UserNotifications
|
|||
import Utils
|
||||
import Views
|
||||
|
||||
private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone
|
||||
|
||||
#if os(iOS)
|
||||
private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone
|
||||
|
||||
struct HomeFeedContainerView: View {
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@AppStorage(UserDefaultKey.homeFeedlayoutPreference.rawValue) var prefersListLayout = false
|
||||
|
|
@ -101,7 +101,7 @@ private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone
|
|||
guard let objectID = dataService.persist(jsonArticle: jsonArticle) else { return }
|
||||
guard let linkedItem = dataService.viewContext.object(with: objectID) as? LinkedItem else { return }
|
||||
viewModel.pushFeedItem(item: linkedItem)
|
||||
viewModel.selectedLinkItem = linkedItem
|
||||
viewModel.selectedLinkItem = linkedItem.objectID
|
||||
}
|
||||
.onOpenURL { url in
|
||||
withoutAnimation {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import Views
|
|||
@Published var negatedLabels = [LinkedItemLabel]()
|
||||
@Published var snoozePresented = false
|
||||
@Published var itemToSnoozeID: String?
|
||||
@Published var selectedLinkItem: LinkedItem?
|
||||
@Published var selectedLinkItem: NSManagedObjectID?
|
||||
@Published var linkRequest: LinkRequest?
|
||||
@Published var showLoadingBar = false
|
||||
|
||||
|
|
|
|||
|
|
@ -1,38 +1,46 @@
|
|||
import Combine
|
||||
import CoreData
|
||||
import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
import Utils
|
||||
import Views
|
||||
|
||||
enum PDFProvider {
|
||||
static var pdfViewerProvider: ((URL, LinkedItem) -> AnyView)?
|
||||
}
|
||||
|
||||
@MainActor final class LinkItemDetailViewModel: ObservableObject {
|
||||
@Published var item: LinkedItem
|
||||
let pdfItem: PDFItem?
|
||||
let item: LinkedItem?
|
||||
@Published var webAppWrapperViewModel: WebAppWrapperViewModel?
|
||||
|
||||
var subscriptions = Set<AnyCancellable>()
|
||||
|
||||
init(item: LinkedItem) {
|
||||
self.item = item
|
||||
init(linkedItemObjectID: NSManagedObjectID, dataService: DataService) {
|
||||
if let linkedItem = dataService.viewContext.object(with: linkedItemObjectID) as? LinkedItem {
|
||||
self.pdfItem = PDFItem.make(item: linkedItem)
|
||||
self.item = linkedItem
|
||||
} else {
|
||||
self.pdfItem = nil
|
||||
self.item = nil
|
||||
}
|
||||
}
|
||||
|
||||
func handleArchiveAction(dataService: DataService) {
|
||||
dataService.archiveLink(objectID: item.objectID, archived: !item.isArchived)
|
||||
Snackbar.show(message: !item.isArchived ? "Link archived" : "Link moved to Inbox")
|
||||
guard let objectID = item?.objectID ?? pdfItem?.objectID else { return }
|
||||
dataService.archiveLink(objectID: objectID, archived: !isItemArchived)
|
||||
Snackbar.show(message: !isItemArchived ? "Link archived" : "Link moved to Inbox")
|
||||
}
|
||||
|
||||
func handleDeleteAction(dataService: DataService) {
|
||||
guard let objectID = item?.objectID ?? pdfItem?.objectID else { return }
|
||||
Snackbar.show(message: "Link removed")
|
||||
dataService.removeLink(objectID: item.objectID)
|
||||
dataService.removeLink(objectID: objectID)
|
||||
}
|
||||
|
||||
func updateItemReadStatus(dataService: DataService) {
|
||||
guard let itemID = item?.unwrappedID ?? pdfItem?.itemID else { return }
|
||||
|
||||
dataService.updateLinkReadingProgress(
|
||||
itemID: item.unwrappedID,
|
||||
readingProgress: item.isRead ? 0 : 100,
|
||||
itemID: itemID,
|
||||
readingProgress: isItemRead ? 0 : 100,
|
||||
anchorIndex: 0
|
||||
)
|
||||
}
|
||||
|
|
@ -64,21 +72,34 @@ enum PDFProvider {
|
|||
}
|
||||
|
||||
func trackReadEvent() {
|
||||
guard let itemID = item?.unwrappedID ?? pdfItem?.itemID else { return }
|
||||
guard let slug = item?.unwrappedSlug ?? pdfItem?.slug else { return }
|
||||
guard let originalArticleURL = item?.unwrappedPageURLString ?? pdfItem?.originalArticleURL else { return }
|
||||
|
||||
EventTracker.track(
|
||||
.linkRead(
|
||||
linkID: item.unwrappedID,
|
||||
slug: item.unwrappedSlug,
|
||||
originalArticleURL: item.unwrappedPageURLString
|
||||
linkID: itemID,
|
||||
slug: slug,
|
||||
originalArticleURL: originalArticleURL
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
var isItemRead: Bool {
|
||||
item?.isRead ?? pdfItem?.isRead ?? false
|
||||
}
|
||||
|
||||
var isItemArchived: Bool {
|
||||
item?.isArchived ?? pdfItem?.isArchived ?? false
|
||||
}
|
||||
|
||||
private func createWebAppWrapperViewModel(username: String, dataService: DataService, rawAuthCookie: String?) {
|
||||
guard let slug = item?.unwrappedSlug ?? pdfItem?.slug else { return }
|
||||
let baseURL = dataService.appEnvironment.webAppBaseURL
|
||||
|
||||
let urlRequest = URLRequest.webRequest(
|
||||
baseURL: dataService.appEnvironment.webAppBaseURL,
|
||||
urlPath: "/app/\(username)/\(item.unwrappedSlug)",
|
||||
urlPath: "/app/\(username)/\(slug)",
|
||||
queryParams: ["isAppEmbedView": "true", "highlightBarDisabled": isMacApp ? "false" : "true"]
|
||||
)
|
||||
|
||||
|
|
@ -111,7 +132,7 @@ struct LinkItemDetailView: View {
|
|||
viewModel.updateItemReadStatus(dataService: dataService)
|
||||
},
|
||||
label: {
|
||||
Image(systemName: viewModel.item.isRead ? "line.horizontal.3.decrease.circle" : "checkmark.circle")
|
||||
Image(systemName: viewModel.isItemRead ? "line.horizontal.3.decrease.circle" : "checkmark.circle")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -139,15 +160,15 @@ struct LinkItemDetailView: View {
|
|||
|
||||
var body: some View {
|
||||
#if os(iOS)
|
||||
if viewModel.item.isPDF {
|
||||
if viewModel.pdfItem != nil {
|
||||
fixedNavBarReader
|
||||
.navigationBarHidden(hideNavBar)
|
||||
.task {
|
||||
hideNavBar = true
|
||||
viewModel.trackReadEvent()
|
||||
}
|
||||
} else {
|
||||
WebReaderContainerView(item: viewModel.item)
|
||||
} else if let item = viewModel.item {
|
||||
WebReaderContainerView(item: item)
|
||||
.navigationBarHidden(hideNavBar)
|
||||
.task {
|
||||
hideNavBar = true
|
||||
|
|
@ -189,8 +210,8 @@ struct LinkItemDetailView: View {
|
|||
action: { viewModel.handleArchiveAction(dataService: dataService) },
|
||||
label: {
|
||||
Label(
|
||||
viewModel.item.isArchived ? "Unarchive" : "Archive",
|
||||
systemImage: viewModel.item.isArchived ? "tray.and.arrow.down.fill" : "archivebox"
|
||||
viewModel.isItemArchived ? "Unarchive" : "Archive",
|
||||
systemImage: viewModel.isItemArchived ? "tray.and.arrow.down.fill" : "archivebox"
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -277,9 +298,9 @@ struct LinkItemDetailView: View {
|
|||
#endif
|
||||
|
||||
@ViewBuilder private var fixedNavBarReader: some View {
|
||||
if let pdfURL = viewModel.item.pdfURL {
|
||||
if let pdfItem = viewModel.pdfItem, let pdfURL = pdfItem.pdfURL {
|
||||
#if os(iOS)
|
||||
PDFProvider.pdfViewerProvider?(pdfURL, viewModel.item)
|
||||
PDFViewer(remoteURL: pdfURL, viewModel: PDFViewerViewModel(pdfItem: pdfItem))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#elseif os(macOS)
|
||||
PDFWrapperView(pdfURL: pdfURL)
|
||||
|
|
|
|||
|
|
@ -6,20 +6,18 @@ import Views
|
|||
|
||||
public struct RootView: View {
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
let pdfViewerProvider: ((URL, PDFViewerViewModel) -> AnyView)?
|
||||
@StateObject private var viewModel = RootViewModel()
|
||||
|
||||
public init(
|
||||
pdfViewerProvider: ((URL, PDFViewerViewModel) -> AnyView)?,
|
||||
intercomProvider: IntercomProvider?
|
||||
) {
|
||||
self.pdfViewerProvider = pdfViewerProvider
|
||||
|
||||
public init(intercomProvider: IntercomProvider?) {
|
||||
if let intercomProvider = intercomProvider {
|
||||
DataService.showIntercomMessenger = intercomProvider.showIntercomMessenger
|
||||
DataService.registerIntercomUser = intercomProvider.registerIntercomUser
|
||||
Authenticator.unregisterIntercomUser = intercomProvider.unregisterIntercomUser
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
PDFReaderViewController.registerKey()
|
||||
#endif
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
|
|
@ -27,14 +25,11 @@ public struct RootView: View {
|
|||
.environmentObject(viewModel.services.authenticator)
|
||||
.environmentObject(viewModel.services.dataService)
|
||||
.environment(\.managedObjectContext, viewModel.services.dataService.viewContext)
|
||||
.onAppear {
|
||||
if let pdfViewerProvider = pdfViewerProvider {
|
||||
viewModel.configurePDFProvider(pdfViewerProvider: pdfViewerProvider)
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { phase in
|
||||
if phase == .background {
|
||||
Services.scheduleBackgroundFetch()
|
||||
#if os(iOS)
|
||||
Services.scheduleBackgroundFetch()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,15 +32,6 @@ public final class RootViewModel: ObservableObject {
|
|||
#endif
|
||||
}
|
||||
|
||||
func configurePDFProvider(pdfViewerProvider: @escaping (URL, PDFViewerViewModel) -> AnyView) {
|
||||
guard PDFProvider.pdfViewerProvider == nil else { return }
|
||||
|
||||
PDFProvider.pdfViewerProvider = { [weak self] url, linkedItem in
|
||||
guard let self = self else { return AnyView(Text("")) }
|
||||
return pdfViewerProvider(url, PDFViewerViewModel(services: self.services, linkedItem: linkedItem))
|
||||
}
|
||||
}
|
||||
|
||||
func webAppWrapperViewModel(webLinkPath: String) -> WebAppWrapperViewModel {
|
||||
let baseURL = services.dataService.appEnvironment.webAppBaseURL
|
||||
|
||||
|
|
|
|||
36
apple/OmnivoreKit/Sources/Models/DataModels/PDFItem.swift
Normal file
36
apple/OmnivoreKit/Sources/Models/DataModels/PDFItem.swift
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import CoreData
|
||||
import Foundation
|
||||
|
||||
public struct PDFItem {
|
||||
public let objectID: NSManagedObjectID
|
||||
public let itemID: String
|
||||
public let pdfURL: URL?
|
||||
public let documentData: Data?
|
||||
public let title: String
|
||||
public let slug: String
|
||||
public let readingProgress: Double
|
||||
public let readingProgressAnchor: Int
|
||||
public let isArchived: Bool
|
||||
public let isRead: Bool
|
||||
public let originalArticleURL: String
|
||||
public let highlights: [Highlight]
|
||||
|
||||
public static func make(item: LinkedItem) -> PDFItem? {
|
||||
guard item.isPDF else { return nil }
|
||||
|
||||
return PDFItem(
|
||||
objectID: item.objectID,
|
||||
itemID: item.unwrappedID,
|
||||
pdfURL: URL(string: item.unwrappedPageURLString),
|
||||
documentData: item.pdfData,
|
||||
title: item.unwrappedID,
|
||||
slug: item.unwrappedSlug,
|
||||
readingProgress: item.readingProgress,
|
||||
readingProgressAnchor: Int(item.readingProgressAnchor),
|
||||
isArchived: item.isArchived,
|
||||
isRead: item.isRead,
|
||||
originalArticleURL: item.unwrappedPageURLString,
|
||||
highlights: item.highlights.asArray(of: Highlight.self)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ public struct SearchBar: View {
|
|||
TextField("Search", text: $searchTerm)
|
||||
.padding(7)
|
||||
.padding(.horizontal, 25)
|
||||
.background(Color(.systemGray6))
|
||||
.background(Color.systemGray6)
|
||||
.cornerRadius(8)
|
||||
.focused($isFocused)
|
||||
.overlay(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import SwiftUI
|
|||
import AppKit
|
||||
#elseif os(iOS)
|
||||
import Intercom
|
||||
import PSPDFKit
|
||||
import UIKit
|
||||
import Utils
|
||||
#endif
|
||||
|
|
@ -17,20 +16,12 @@ struct MainApp: App {
|
|||
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
#elseif os(iOS)
|
||||
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
|
||||
init() {
|
||||
// Activate PSPDFKit for app.omnivore.app
|
||||
if let pspdfKitKey = AppKeys.sharedInstance?.pspdfKitKey {
|
||||
SDK.setLicenseKey(pspdfKitKey)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
var body: some Scene {
|
||||
#if os(iOS)
|
||||
WindowGroup {
|
||||
RootView(
|
||||
pdfViewerProvider: pdfViewerProvider,
|
||||
intercomProvider: AppKeys.sharedInstance?.intercom != nil ? IntercomProvider(
|
||||
registerIntercomUser: { Intercom.registerUser(withUserId: $0) },
|
||||
unregisterIntercomUser: Intercom.logout,
|
||||
|
|
@ -40,12 +31,8 @@ struct MainApp: App {
|
|||
}
|
||||
#elseif os(macOS)
|
||||
WindowGroup {
|
||||
RootView(pdfViewerProvider: nil, intercomProvider: nil)
|
||||
RootView(intercomProvider: nil)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func pdfViewerProvider(url: URL, viewModel: PDFViewerViewModel) -> AnyView {
|
||||
AnyView(PDFViewer(remoteURL: url, viewModel: viewModel))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue