diff --git a/iosApp/CaseraQLPreview/Base.lproj/MainInterface.storyboard b/iosApp/CaseraQLPreview/Base.lproj/MainInterface.storyboard
new file mode 100644
index 0000000..4ddc584
--- /dev/null
+++ b/iosApp/CaseraQLPreview/Base.lproj/MainInterface.storyboard
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iosApp/CaseraQLPreview/Info.plist b/iosApp/CaseraQLPreview/Info.plist
new file mode 100644
index 0000000..033eb91
--- /dev/null
+++ b/iosApp/CaseraQLPreview/Info.plist
@@ -0,0 +1,38 @@
+
+
+
+
+ CFBundleDisplayName
+ Casera Preview
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ NSExtension
+
+ NSExtensionAttributes
+
+ QLSupportedContentTypes
+
+ com.casera.contractor
+
+ QLSupportsSearchableItems
+
+
+ NSExtensionMainStoryboard
+ MainInterface
+ NSExtensionPointIdentifier
+ com.apple.quicklook.preview
+
+
+
diff --git a/iosApp/CaseraQLPreview/PreviewProvider.swift b/iosApp/CaseraQLPreview/PreviewProvider.swift
new file mode 100644
index 0000000..9daa5ff
--- /dev/null
+++ b/iosApp/CaseraQLPreview/PreviewProvider.swift
@@ -0,0 +1,57 @@
+//
+// PreviewProvider.swift
+// CaseraQLPreview
+//
+// Created by Trey Tartt on 12/6/25.
+//
+
+import QuickLook
+
+class PreviewProvider: QLPreviewProvider, QLPreviewingController {
+
+
+ /*
+ Use a QLPreviewProvider to provide data-based previews.
+
+ To set up your extension as a data-based preview extension:
+
+ - Modify the extension's Info.plist by setting
+ QLIsDataBasedPreview
+
+
+ - Add the supported content types to QLSupportedContentTypes array in the extension's Info.plist.
+
+ - Remove
+ NSExtensionMainStoryboard
+ MainInterface
+
+ and replace it by setting the NSExtensionPrincipalClass to this class, e.g.
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).PreviewProvider
+
+ - Implement providePreview(for:)
+ */
+
+ func providePreview(for request: QLFilePreviewRequest) async throws -> QLPreviewReply {
+
+ //You can create a QLPreviewReply in several ways, depending on the format of the data you want to return.
+ //To return Data of a supported content type:
+
+ let contentType = UTType.plainText // replace with your data type
+
+ let reply = QLPreviewReply.init(dataOfContentType: contentType, contentSize: CGSize.init(width: 800, height: 800)) { (replyToUpdate : QLPreviewReply) in
+
+ let data = Data("Hello world".utf8)
+
+ //setting the stringEncoding for text and html data is optional and defaults to String.Encoding.utf8
+ replyToUpdate.stringEncoding = .utf8
+
+ //initialize your data here
+
+ return data
+ }
+
+ return reply
+ }
+
+}
diff --git a/iosApp/CaseraQLPreview/PreviewViewController.swift b/iosApp/CaseraQLPreview/PreviewViewController.swift
new file mode 100644
index 0000000..198e263
--- /dev/null
+++ b/iosApp/CaseraQLPreview/PreviewViewController.swift
@@ -0,0 +1,264 @@
+import UIKit
+import QuickLook
+
+class PreviewViewController: UIViewController, QLPreviewingController {
+
+ // MARK: - UI Elements
+
+ private let containerView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ return view
+ }()
+
+ private let iconImageView: UIImageView = {
+ let imageView = UIImageView()
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ imageView.contentMode = .scaleAspectFit
+ imageView.tintColor = UIColor(red: 7/255, green: 160/255, blue: 195/255, alpha: 1) // App primary color
+ let config = UIImage.SymbolConfiguration(pointSize: 60, weight: .light)
+ imageView.image = UIImage(systemName: "person.crop.rectangle.stack", withConfiguration: config)
+ return imageView
+ }()
+
+ private let titleLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.font = .systemFont(ofSize: 24, weight: .bold)
+ label.textColor = .label
+ label.textAlignment = .center
+ label.numberOfLines = 2
+ return label
+ }()
+
+ private let subtitleLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.font = .systemFont(ofSize: 15, weight: .medium)
+ label.textColor = .secondaryLabel
+ label.textAlignment = .center
+ label.text = "Casera Contractor File"
+ return label
+ }()
+
+ private let dividerView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.backgroundColor = .separator
+ return view
+ }()
+
+ private let detailsStackView: UIStackView = {
+ let stack = UIStackView()
+ stack.translatesAutoresizingMaskIntoConstraints = false
+ stack.axis = .vertical
+ stack.spacing = 12
+ stack.alignment = .leading
+ return stack
+ }()
+
+ private let instructionCard: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.backgroundColor = UIColor(red: 7/255, green: 160/255, blue: 195/255, alpha: 0.1)
+ view.layer.cornerRadius = 12
+ return view
+ }()
+
+ private let instructionLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.font = .systemFont(ofSize: 15, weight: .medium)
+ label.textColor = UIColor(red: 7/255, green: 160/255, blue: 195/255, alpha: 1)
+ label.textAlignment = .center
+ label.numberOfLines = 0
+ label.text = "Tap the share button below, then select \"Casera\" to import this contractor."
+ return label
+ }()
+
+ private let arrowImageView: UIImageView = {
+ let imageView = UIImageView()
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ imageView.contentMode = .scaleAspectFit
+ imageView.tintColor = .secondaryLabel
+ let config = UIImage.SymbolConfiguration(pointSize: 24, weight: .medium)
+ imageView.image = UIImage(systemName: "arrow.down", withConfiguration: config)
+ return imageView
+ }()
+
+ // MARK: - Data
+
+ private var contractorData: ContractorPreviewData?
+
+ // MARK: - Lifecycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ print("CaseraQLPreview: viewDidLoad called")
+ setupUI()
+ }
+
+ // MARK: - Setup
+
+ private func setupUI() {
+ view.backgroundColor = .systemBackground
+
+ view.addSubview(containerView)
+ containerView.addSubview(iconImageView)
+ containerView.addSubview(titleLabel)
+ containerView.addSubview(subtitleLabel)
+ containerView.addSubview(dividerView)
+ containerView.addSubview(detailsStackView)
+ containerView.addSubview(instructionCard)
+ instructionCard.addSubview(instructionLabel)
+ containerView.addSubview(arrowImageView)
+
+ NSLayoutConstraint.activate([
+ containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+ containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -40),
+ containerView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 32),
+ containerView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -32),
+ containerView.widthAnchor.constraint(lessThanOrEqualToConstant: 340),
+
+ iconImageView.topAnchor.constraint(equalTo: containerView.topAnchor),
+ iconImageView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
+ iconImageView.widthAnchor.constraint(equalToConstant: 80),
+ iconImageView.heightAnchor.constraint(equalToConstant: 80),
+
+ titleLabel.topAnchor.constraint(equalTo: iconImageView.bottomAnchor, constant: 16),
+ titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+ titleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+
+ subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
+ subtitleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+ subtitleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+
+ dividerView.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 20),
+ dividerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+ dividerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+ dividerView.heightAnchor.constraint(equalToConstant: 1),
+
+ detailsStackView.topAnchor.constraint(equalTo: dividerView.bottomAnchor, constant: 20),
+ detailsStackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+ detailsStackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+
+ instructionCard.topAnchor.constraint(equalTo: detailsStackView.bottomAnchor, constant: 24),
+ instructionCard.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+ instructionCard.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+
+ instructionLabel.topAnchor.constraint(equalTo: instructionCard.topAnchor, constant: 16),
+ instructionLabel.leadingAnchor.constraint(equalTo: instructionCard.leadingAnchor, constant: 16),
+ instructionLabel.trailingAnchor.constraint(equalTo: instructionCard.trailingAnchor, constant: -16),
+ instructionLabel.bottomAnchor.constraint(equalTo: instructionCard.bottomAnchor, constant: -16),
+
+ arrowImageView.topAnchor.constraint(equalTo: instructionCard.bottomAnchor, constant: 16),
+ arrowImageView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
+ arrowImageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+ ])
+ }
+
+ private func addDetailRow(icon: String, text: String) {
+ let rowStack = UIStackView()
+ rowStack.axis = .horizontal
+ rowStack.spacing = 12
+ rowStack.alignment = .center
+
+ let iconView = UIImageView()
+ iconView.translatesAutoresizingMaskIntoConstraints = false
+ let config = UIImage.SymbolConfiguration(pointSize: 16, weight: .medium)
+ iconView.image = UIImage(systemName: icon, withConfiguration: config)
+ iconView.tintColor = .secondaryLabel
+ iconView.widthAnchor.constraint(equalToConstant: 24).isActive = true
+ iconView.heightAnchor.constraint(equalToConstant: 24).isActive = true
+
+ let textLabel = UILabel()
+ textLabel.font = .systemFont(ofSize: 15)
+ textLabel.textColor = .label
+ textLabel.text = text
+ textLabel.numberOfLines = 1
+
+ rowStack.addArrangedSubview(iconView)
+ rowStack.addArrangedSubview(textLabel)
+
+ detailsStackView.addArrangedSubview(rowStack)
+ }
+
+ // MARK: - QLPreviewingController
+
+ func preparePreviewOfFile(at url: URL) async throws {
+ print("CaseraQLPreview: preparePreviewOfFile called with URL: \(url)")
+ // Parse the .casera file
+ let data = try Data(contentsOf: url)
+ let decoder = JSONDecoder()
+ let contractor = try decoder.decode(ContractorPreviewData.self, from: data)
+ self.contractorData = contractor
+ print("CaseraQLPreview: Parsed contractor: \(contractor.name)")
+
+ await MainActor.run {
+ self.updateUI(with: contractor)
+ }
+ }
+
+ private func updateUI(with contractor: ContractorPreviewData) {
+ titleLabel.text = contractor.name
+
+ // Clear existing details
+ detailsStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
+
+ // Add details
+ if let company = contractor.company, !company.isEmpty {
+ addDetailRow(icon: "building.2", text: company)
+ }
+
+ if let phone = contractor.phone, !phone.isEmpty {
+ addDetailRow(icon: "phone", text: phone)
+ }
+
+ if let email = contractor.email, !email.isEmpty {
+ addDetailRow(icon: "envelope", text: email)
+ }
+
+ if !contractor.specialtyNames.isEmpty {
+ let specialties = contractor.specialtyNames.joined(separator: ", ")
+ addDetailRow(icon: "wrench.and.screwdriver", text: specialties)
+ }
+
+ if let exportedBy = contractor.exportedBy, !exportedBy.isEmpty {
+ addDetailRow(icon: "person", text: "Shared by \(exportedBy)")
+ }
+ }
+}
+
+// MARK: - Data Model
+
+struct ContractorPreviewData: Codable {
+ let version: Int
+ let name: String
+ let company: String?
+ let phone: String?
+ let email: String?
+ let website: String?
+ let notes: String?
+ let streetAddress: String?
+ let city: String?
+ let stateProvince: String?
+ let postalCode: String?
+ let specialtyNames: [String]
+ let rating: Double?
+ let isFavorite: Bool
+ let exportedAt: String?
+ let exportedBy: String?
+
+ enum CodingKeys: String, CodingKey {
+ case version, name, company, phone, email, website, notes
+ case streetAddress = "street_address"
+ case city
+ case stateProvince = "state_province"
+ case postalCode = "postal_code"
+ case specialtyNames = "specialty_names"
+ case rating
+ case isFavorite = "is_favorite"
+ case exportedAt = "exported_at"
+ case exportedBy = "exported_by"
+ }
+}
diff --git a/iosApp/CaseraQLThumbnail/Info.plist b/iosApp/CaseraQLThumbnail/Info.plist
new file mode 100644
index 0000000..f77509c
--- /dev/null
+++ b/iosApp/CaseraQLThumbnail/Info.plist
@@ -0,0 +1,38 @@
+
+
+
+
+ CFBundleDisplayName
+ Casera Thumbnail
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ NSExtension
+
+ NSExtensionAttributes
+
+ QLSupportedContentTypes
+
+ com.casera.contractor
+
+ QLThumbnailMinimumDimension
+ 0
+
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).ThumbnailProvider
+ NSExtensionPointIdentifier
+ com.apple.quicklook.thumbnail
+
+
+
diff --git a/iosApp/CaseraQLThumbnail/ThumbnailProvider.swift b/iosApp/CaseraQLThumbnail/ThumbnailProvider.swift
new file mode 100644
index 0000000..d89ed78
--- /dev/null
+++ b/iosApp/CaseraQLThumbnail/ThumbnailProvider.swift
@@ -0,0 +1,38 @@
+//
+// ThumbnailProvider.swift
+// CaseraQLThumbnail
+//
+// Created by Trey Tartt on 12/6/25.
+//
+
+import UIKit
+import QuickLookThumbnailing
+
+class ThumbnailProvider: QLThumbnailProvider {
+
+ override func provideThumbnail(for request: QLFileThumbnailRequest, _ handler: @escaping (QLThumbnailReply?, Error?) -> Void) {
+
+ let thumbnailSize = request.maximumSize
+
+ handler(QLThumbnailReply(contextSize: thumbnailSize, currentContextDrawing: { () -> Bool in
+ // Draw background
+ let backgroundColor = UIColor(red: 7/255, green: 160/255, blue: 195/255, alpha: 1)
+ backgroundColor.setFill()
+ UIRectFill(CGRect(origin: .zero, size: thumbnailSize))
+
+ // Draw icon
+ let config = UIImage.SymbolConfiguration(pointSize: min(thumbnailSize.width, thumbnailSize.height) * 0.5, weight: .regular)
+ if let icon = UIImage(systemName: "person.crop.rectangle.stack", withConfiguration: config) {
+ let tintedIcon = icon.withTintColor(.white, renderingMode: .alwaysOriginal)
+ let iconSize = tintedIcon.size
+ let iconOrigin = CGPoint(
+ x: (thumbnailSize.width - iconSize.width) / 2,
+ y: (thumbnailSize.height - iconSize.height) / 2
+ )
+ tintedIcon.draw(at: iconOrigin)
+ }
+
+ return true
+ }), nil)
+ }
+}
diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj
index 98a35b9..9d87b07 100644
--- a/iosApp/iosApp.xcodeproj/project.pbxproj
+++ b/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -10,6 +10,10 @@
1C0789402EBC218B00392B46 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C07893F2EBC218B00392B46 /* WidgetKit.framework */; };
1C0789422EBC218B00392B46 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C0789412EBC218B00392B46 /* SwiftUI.framework */; };
1C0789532EBC218D00392B46 /* CaseraExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1C07893D2EBC218B00392B46 /* CaseraExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 1C81F26B2EE416EE000739EA /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C81F26A2EE416EE000739EA /* QuickLook.framework */; };
+ 1C81F2772EE416EF000739EA /* CaseraQLPreview.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1C81F2692EE416EE000739EA /* CaseraQLPreview.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 1C81F2822EE41BB6000739EA /* QuickLookThumbnailing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C81F2812EE41BB6000739EA /* QuickLookThumbnailing.framework */; };
+ 1C81F2892EE41BB6000739EA /* CaseraQLThumbnail.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1C81F2802EE41BB6000739EA /* CaseraQLThumbnail.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -20,6 +24,20 @@
remoteGlobalIDString = 1C07893C2EBC218B00392B46;
remoteInfo = CaseraExtension;
};
+ 1C81F2752EE416EF000739EA /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6A3E1D84F9F1A2FD92A75A6C /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 1C81F2682EE416EE000739EA;
+ remoteInfo = CaseraQLPreview;
+ };
+ 1C81F2872EE41BB6000739EA /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6A3E1D84F9F1A2FD92A75A6C /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 1C81F27F2EE41BB6000739EA;
+ remoteInfo = CaseraQLThumbnail;
+ };
1CBF16002ECD8AE4001BF56C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6A3E1D84F9F1A2FD92A75A6C /* Project object */;
@@ -44,6 +62,8 @@
dstSubfolderSpec = 13;
files = (
1C0789532EBC218D00392B46 /* CaseraExtension.appex in Embed Foundation Extensions */,
+ 1C81F2892EE41BB6000739EA /* CaseraQLThumbnail.appex in Embed Foundation Extensions */,
+ 1C81F2772EE416EF000739EA /* CaseraQLPreview.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
@@ -56,6 +76,10 @@
1C0789412EBC218B00392B46 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
1C0789612EBC2F5400392B46 /* CaseraExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CaseraExtension.entitlements; sourceTree = ""; };
1C685CD22EC5539000A9669B /* CaseraTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CaseraTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 1C81F2692EE416EE000739EA /* CaseraQLPreview.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = CaseraQLPreview.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ 1C81F26A2EE416EE000739EA /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; };
+ 1C81F2802EE41BB6000739EA /* CaseraQLThumbnail.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = CaseraQLThumbnail.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ 1C81F2812EE41BB6000739EA /* QuickLookThumbnailing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookThumbnailing.framework; path = System/Library/Frameworks/QuickLookThumbnailing.framework; sourceTree = SDKROOT; };
1C87A0C42EDB8ED40081E450 /* CaseraUITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CaseraUITests.xctestplan; sourceTree = ""; };
1CBF1BED2ECD9768001BF56C /* CaseraUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CaseraUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4B07E04F794A4C1CAA8CCD5D /* PhotoViewerSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoViewerSheet.swift; sourceTree = ""; };
@@ -85,6 +109,20 @@
);
target = 1CBF1BEC2ECD9768001BF56C /* CaseraUITests */;
};
+ 1C81F27B2EE416EF000739EA /* Exceptions for "CaseraQLPreview" folder in "CaseraQLPreview" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Info.plist,
+ );
+ target = 1C81F2682EE416EE000739EA /* CaseraQLPreview */;
+ };
+ 1C81F28D2EE41BB6000739EA /* Exceptions for "CaseraQLThumbnail" folder in "CaseraQLThumbnail" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Info.plist,
+ );
+ target = 1C81F27F2EE41BB6000739EA /* CaseraQLThumbnail */;
+ };
1C87A67A2EDCC3100081E450 /* Exceptions for "iosApp" folder in "CaseraExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
@@ -125,6 +163,22 @@
path = CaseraTests;
sourceTree = "";
};
+ 1C81F26C2EE416EE000739EA /* CaseraQLPreview */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ 1C81F27B2EE416EF000739EA /* Exceptions for "CaseraQLPreview" folder in "CaseraQLPreview" target */,
+ );
+ path = CaseraQLPreview;
+ sourceTree = "";
+ };
+ 1C81F2832EE41BB6000739EA /* CaseraQLThumbnail */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ 1C81F28D2EE41BB6000739EA /* Exceptions for "CaseraQLThumbnail" folder in "CaseraQLThumbnail" target */,
+ );
+ path = CaseraQLThumbnail;
+ sourceTree = "";
+ };
1CBF1BEE2ECD9768001BF56C /* CaseraUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
@@ -167,6 +221,22 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 1C81F2662EE416EE000739EA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 1C81F26B2EE416EE000739EA /* QuickLook.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 1C81F27D2EE41BB6000739EA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 1C81F2822EE41BB6000739EA /* QuickLookThumbnailing.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
1CBF1BEA2ECD9768001BF56C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -189,6 +259,8 @@
children = (
1C07893F2EBC218B00392B46 /* WidgetKit.framework */,
1C0789412EBC218B00392B46 /* SwiftUI.framework */,
+ 1C81F26A2EE416EE000739EA /* QuickLook.framework */,
+ 1C81F2812EE41BB6000739EA /* QuickLookThumbnailing.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -212,6 +284,8 @@
1C0789432EBC218B00392B46 /* Casera */,
1C685CD32EC5539000A9669B /* CaseraTests */,
1CBF1BEE2ECD9768001BF56C /* CaseraUITests */,
+ 1C81F26C2EE416EE000739EA /* CaseraQLPreview */,
+ 1C81F2832EE41BB6000739EA /* CaseraQLThumbnail */,
1C07893E2EBC218B00392B46 /* Frameworks */,
FA6022B7B844191C54E57EB4 /* Products */,
1C078A1B2EC1820B00392B46 /* Recovered References */,
@@ -225,6 +299,8 @@
1C07893D2EBC218B00392B46 /* CaseraExtension.appex */,
1C685CD22EC5539000A9669B /* CaseraTests.xctest */,
1CBF1BED2ECD9768001BF56C /* CaseraUITests.xctest */,
+ 1C81F2692EE416EE000739EA /* CaseraQLPreview.appex */,
+ 1C81F2802EE41BB6000739EA /* CaseraQLThumbnail.appex */,
);
name = Products;
sourceTree = "";
@@ -277,6 +353,50 @@
productReference = 1C685CD22EC5539000A9669B /* CaseraTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
+ 1C81F2682EE416EE000739EA /* CaseraQLPreview */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 1C81F2782EE416EF000739EA /* Build configuration list for PBXNativeTarget "CaseraQLPreview" */;
+ buildPhases = (
+ 1C81F2652EE416EE000739EA /* Sources */,
+ 1C81F2662EE416EE000739EA /* Frameworks */,
+ 1C81F2672EE416EE000739EA /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 1C81F26C2EE416EE000739EA /* CaseraQLPreview */,
+ );
+ name = CaseraQLPreview;
+ packageProductDependencies = (
+ );
+ productName = CaseraQLPreview;
+ productReference = 1C81F2692EE416EE000739EA /* CaseraQLPreview.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
+ 1C81F27F2EE41BB6000739EA /* CaseraQLThumbnail */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 1C81F28A2EE41BB6000739EA /* Build configuration list for PBXNativeTarget "CaseraQLThumbnail" */;
+ buildPhases = (
+ 1C81F27C2EE41BB6000739EA /* Sources */,
+ 1C81F27D2EE41BB6000739EA /* Frameworks */,
+ 1C81F27E2EE41BB6000739EA /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 1C81F2832EE41BB6000739EA /* CaseraQLThumbnail */,
+ );
+ name = CaseraQLThumbnail;
+ packageProductDependencies = (
+ );
+ productName = CaseraQLThumbnail;
+ productReference = 1C81F2802EE41BB6000739EA /* CaseraQLThumbnail.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
1CBF1BEC2ECD9768001BF56C /* CaseraUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1CBF1BF52ECD9768001BF56C /* Build configuration list for PBXNativeTarget "CaseraUITests" */;
@@ -314,6 +434,8 @@
);
dependencies = (
1C0789522EBC218D00392B46 /* PBXTargetDependency */,
+ 1C81F2762EE416EF000739EA /* PBXTargetDependency */,
+ 1C81F2882EE41BB6000739EA /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
E822E6B231E7783DE992578C /* iosApp */,
@@ -342,6 +464,12 @@
CreatedOnToolsVersion = 26.1.1;
TestTargetID = D4ADB376A7A4CFB73469E173;
};
+ 1C81F2682EE416EE000739EA = {
+ CreatedOnToolsVersion = 26.1.1;
+ };
+ 1C81F27F2EE41BB6000739EA = {
+ CreatedOnToolsVersion = 26.1.1;
+ };
1CBF1BEC2ECD9768001BF56C = {
CreatedOnToolsVersion = 26.1.1;
TestTargetID = D4ADB376A7A4CFB73469E173;
@@ -369,6 +497,8 @@
1C07893C2EBC218B00392B46 /* CaseraExtension */,
1C685CD12EC5539000A9669B /* CaseraTests */,
1CBF1BEC2ECD9768001BF56C /* CaseraUITests */,
+ 1C81F2682EE416EE000739EA /* CaseraQLPreview */,
+ 1C81F27F2EE41BB6000739EA /* CaseraQLThumbnail */,
);
};
/* End PBXProject section */
@@ -388,6 +518,20 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 1C81F2672EE416EE000739EA /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 1C81F27E2EE41BB6000739EA /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
1CBF1BEB2ECD9768001BF56C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -441,6 +585,20 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 1C81F2652EE416EE000739EA /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 1C81F27C2EE41BB6000739EA /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
1CBF1BE92ECD9768001BF56C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -463,6 +621,16 @@
target = 1C07893C2EBC218B00392B46 /* CaseraExtension */;
targetProxy = 1C0789512EBC218D00392B46 /* PBXContainerItemProxy */;
};
+ 1C81F2762EE416EF000739EA /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 1C81F2682EE416EE000739EA /* CaseraQLPreview */;
+ targetProxy = 1C81F2752EE416EF000739EA /* PBXContainerItemProxy */;
+ };
+ 1C81F2882EE41BB6000739EA /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 1C81F27F2EE41BB6000739EA /* CaseraQLThumbnail */;
+ targetProxy = 1C81F2872EE41BB6000739EA /* PBXContainerItemProxy */;
+ };
1CBF16012ECD8AE4001BF56C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D4ADB376A7A4CFB73469E173 /* Casera */;
@@ -622,6 +790,130 @@
};
name = Release;
};
+ 1C81F2792EE416EF000739EA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = V3PF3M6B6U;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = CaseraQLPreview/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = CaseraQLPreview;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 26.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tt.casera.CaseraDev.CaseraQLPreview;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 1C81F27A2EE416EF000739EA /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = V3PF3M6B6U;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = CaseraQLPreview/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = CaseraQLPreview;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 26.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tt.casera.CaseraDev.CaseraQLPreview;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ 1C81F28B2EE41BB6000739EA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = V3PF3M6B6U;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = CaseraQLThumbnail/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = CaseraQLThumbnail;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 26.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tt.casera.CaseraDev.CaseraQLThumbnail;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 1C81F28C2EE41BB6000739EA /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = V3PF3M6B6U;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = CaseraQLThumbnail/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = CaseraQLThumbnail;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 26.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tt.casera.CaseraDev.CaseraQLThumbnail;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
1CBF1BF62ECD9768001BF56C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -852,6 +1144,24 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 1C81F2782EE416EF000739EA /* Build configuration list for PBXNativeTarget "CaseraQLPreview" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1C81F2792EE416EF000739EA /* Debug */,
+ 1C81F27A2EE416EF000739EA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 1C81F28A2EE41BB6000739EA /* Build configuration list for PBXNativeTarget "CaseraQLThumbnail" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1C81F28B2EE41BB6000739EA /* Debug */,
+ 1C81F28C2EE41BB6000739EA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
1CBF1BF52ECD9768001BF56C /* Build configuration list for PBXNativeTarget "CaseraUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist
index 35516e8..d279db1 100644
--- a/iosApp/iosApp/Info.plist
+++ b/iosApp/iosApp/Info.plist
@@ -39,6 +39,8 @@
remote-notification
+ LSSupportsOpeningDocumentsInPlace
+
CFBundleDocumentTypes
@@ -63,9 +65,11 @@
Casera Contractor
UTTypeConformsTo
- public.json
public.data
+ public.content
+ UTTypeIconFiles
+
UTTypeTagSpecification
public.filename-extension
@@ -73,7 +77,7 @@
casera
public.mime-type
- application/json
+ application/x-casera