Add residence sharing via .casera files
- Add SharedResidence model and package type detection for .casera files - Add generateSharePackage API endpoint integration - Create ResidenceSharingManager for iOS and Android - Add share button to residence detail screens (owner only) - Add residence import handling with confirmation dialogs - Update Quick Look extensions to show house icon for residence packages - Route .casera imports by type (contractor vs residence) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,14 @@ import QuickLook
|
||||
|
||||
class PreviewViewController: UIViewController, QLPreviewingController {
|
||||
|
||||
// MARK: - Types
|
||||
|
||||
/// Represents the type of .casera package
|
||||
private enum PackageType {
|
||||
case contractor
|
||||
case residence
|
||||
}
|
||||
|
||||
// MARK: - UI Elements
|
||||
|
||||
private let containerView: UIView = {
|
||||
@@ -89,6 +97,8 @@ class PreviewViewController: UIViewController, QLPreviewingController {
|
||||
// MARK: - Data
|
||||
|
||||
private var contractorData: ContractorPreviewData?
|
||||
private var residenceData: ResidencePreviewData?
|
||||
private var currentPackageType: PackageType = .contractor
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
@@ -187,20 +197,46 @@ class PreviewViewController: UIViewController, 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)
|
||||
// Detect package type first
|
||||
if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let typeString = json["type"] as? String,
|
||||
typeString == "residence" {
|
||||
currentPackageType = .residence
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
let residence = try decoder.decode(ResidencePreviewData.self, from: data)
|
||||
self.residenceData = residence
|
||||
print("CaseraQLPreview: Parsed residence: \(residence.residenceName)")
|
||||
|
||||
await MainActor.run {
|
||||
self.updateUIForResidence(with: residence)
|
||||
}
|
||||
} else {
|
||||
currentPackageType = .contractor
|
||||
|
||||
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.updateUIForContractor(with: contractor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateUI(with contractor: ContractorPreviewData) {
|
||||
private func updateUIForContractor(with contractor: ContractorPreviewData) {
|
||||
// Update icon
|
||||
let config = UIImage.SymbolConfiguration(pointSize: 60, weight: .light)
|
||||
iconImageView.image = UIImage(systemName: "person.crop.rectangle.stack", withConfiguration: config)
|
||||
|
||||
titleLabel.text = contractor.name
|
||||
subtitleLabel.text = "Casera Contractor File"
|
||||
instructionLabel.text = "Tap the share button below, then select \"Casera\" to import this contractor."
|
||||
|
||||
// Clear existing details
|
||||
detailsStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||
@@ -227,6 +263,28 @@ class PreviewViewController: UIViewController, QLPreviewingController {
|
||||
addDetailRow(icon: "person", text: "Shared by \(exportedBy)")
|
||||
}
|
||||
}
|
||||
|
||||
private func updateUIForResidence(with residence: ResidencePreviewData) {
|
||||
// Update icon
|
||||
let config = UIImage.SymbolConfiguration(pointSize: 60, weight: .light)
|
||||
iconImageView.image = UIImage(systemName: "house.fill", withConfiguration: config)
|
||||
|
||||
titleLabel.text = residence.residenceName
|
||||
subtitleLabel.text = "Casera Residence Invite"
|
||||
instructionLabel.text = "Tap the share button below, then select \"Casera\" to join this residence."
|
||||
|
||||
// Clear existing details
|
||||
detailsStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||
|
||||
// Add details
|
||||
if let sharedBy = residence.sharedBy, !sharedBy.isEmpty {
|
||||
addDetailRow(icon: "person", text: "Shared by \(sharedBy)")
|
||||
}
|
||||
|
||||
if let expiresAt = residence.expiresAt, !expiresAt.isEmpty {
|
||||
addDetailRow(icon: "clock", text: "Expires: \(expiresAt)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Data Model
|
||||
@@ -262,3 +320,24 @@ struct ContractorPreviewData: Codable {
|
||||
case exportedBy = "exported_by"
|
||||
}
|
||||
}
|
||||
|
||||
struct ResidencePreviewData: Codable {
|
||||
let version: Int
|
||||
let type: String
|
||||
let shareCode: String
|
||||
let residenceName: String
|
||||
let sharedBy: String?
|
||||
let expiresAt: String?
|
||||
let exportedAt: String?
|
||||
let exportedBy: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case version, type
|
||||
case shareCode = "share_code"
|
||||
case residenceName = "residence_name"
|
||||
case sharedBy = "shared_by"
|
||||
case expiresAt = "expires_at"
|
||||
case exportedAt = "exported_at"
|
||||
case exportedBy = "exported_by"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user