From 997932f0df57fa52f7915a3efa43eefb16303790 Mon Sep 17 00:00:00 2001 From: Trey t Date: Sat, 6 Dec 2025 02:11:20 -0600 Subject: [PATCH] Add Quick Look Preview and Thumbnail extensions for .casera files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add CaseraQLPreview extension to show custom preview with contractor details and import instructions when viewing .casera files - Add CaseraQLThumbnail extension to display teal icon in Messages and Files app instead of generic white box - Update UTExportedTypeDeclarations to conform to public.content for better system file type recognition 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Base.lproj/MainInterface.storyboard | 27 ++ iosApp/CaseraQLPreview/Info.plist | 38 +++ iosApp/CaseraQLPreview/PreviewProvider.swift | 57 ++++ .../PreviewViewController.swift | 264 +++++++++++++++ iosApp/CaseraQLThumbnail/Info.plist | 38 +++ .../CaseraQLThumbnail/ThumbnailProvider.swift | 38 +++ iosApp/iosApp.xcodeproj/project.pbxproj | 310 ++++++++++++++++++ iosApp/iosApp/Info.plist | 8 +- 8 files changed, 778 insertions(+), 2 deletions(-) create mode 100644 iosApp/CaseraQLPreview/Base.lproj/MainInterface.storyboard create mode 100644 iosApp/CaseraQLPreview/Info.plist create mode 100644 iosApp/CaseraQLPreview/PreviewProvider.swift create mode 100644 iosApp/CaseraQLPreview/PreviewViewController.swift create mode 100644 iosApp/CaseraQLThumbnail/Info.plist create mode 100644 iosApp/CaseraQLThumbnail/ThumbnailProvider.swift 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