// // PlatformSyncDemo.swift // SecureStorgageSample // // Demonstrates platform availability and sync policies with LocalData package. // import SwiftUI import LocalData struct PlatformSyncDemo: View { @State private var settingValue = "" @State private var storedValue = "" @State private var statusMessage = "" @State private var isLoading = false @State private var selectedPlatform: PlatformAvailability = .all @State private var selectedSync: SyncPolicy = .never @FocusState private var isFieldFocused: Bool var body: some View { Form { Section { Text("Platform availability controls which devices can access data. Sync policy determines how data is shared between iPhone and Apple Watch via WatchConnectivity.") .font(.caption) .foregroundStyle(.secondary) } Section("Platform Availability") { Picker("Available On", selection: $selectedPlatform) { Text("All (iPhone + Watch)").tag(PlatformAvailability.all) Text("Phone Only").tag(PlatformAvailability.phoneOnly) Text("Watch Only").tag(PlatformAvailability.watchOnly) Text("Phone w/ Watch Sync").tag(PlatformAvailability.phoneWithWatchSync) } .pickerStyle(.menu) platformDescription } Section("Sync Policy") { Picker("Sync Behavior", selection: $selectedSync) { Text("Never").tag(SyncPolicy.never) Text("Manual").tag(SyncPolicy.manual) Text("Automatic (Small Data)").tag(SyncPolicy.automaticSmall) } .pickerStyle(.menu) syncDescription } Section("Test Data") { TextField("Enter a value to store", text: $settingValue) .focused($isFieldFocused) } Section("Actions") { Button(action: saveValue) { HStack { Image(systemName: "icloud.and.arrow.up") Text("Save with Current Settings") } } .disabled(settingValue.isEmpty || isLoading) Button(action: loadValue) { HStack { Image(systemName: "icloud.and.arrow.down") Text("Load Value") } } .disabled(isLoading) Button(action: testPlatformError) { HStack { Image(systemName: "exclamationmark.triangle") Text("Test Platform Restriction") } } .foregroundStyle(.orange) .disabled(isLoading) } if !storedValue.isEmpty { Section("Retrieved Value") { Text(storedValue) .font(.system(.body, design: .monospaced)) } } if !statusMessage.isEmpty { Section { Text(statusMessage) .font(.caption) .foregroundStyle(statusMessage.contains("Error") ? .red : statusMessage.contains("⚠") ? .orange : .green) } } Section("Current Configuration") { LabeledContent("Platform", value: selectedPlatform.displayName) LabeledContent("Sync", value: selectedSync.displayName) LabeledContent("Max Auto-Sync Size", value: "100 KB") } } .navigationTitle("Platform & Sync") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .keyboard) { Spacer() Button("Done") { isFieldFocused = false } } } } @ViewBuilder private var platformDescription: some View { switch selectedPlatform { case .all: Text("Data accessible on both iPhone and Apple Watch") .font(.caption) .foregroundStyle(.secondary) case .phoneOnly: Text("Data only accessible on iPhone. Watch access throws error.") .font(.caption) .foregroundStyle(.secondary) case .watchOnly: Text("Data only accessible on Watch. iPhone access throws error.") .font(.caption) .foregroundStyle(.secondary) case .phoneWithWatchSync: Text("Stored on iPhone, synced to Watch via WatchConnectivity") .font(.caption) .foregroundStyle(.secondary) } } @ViewBuilder private var syncDescription: some View { switch selectedSync { case .never: Text("Data stays local, never synced") .font(.caption) .foregroundStyle(.secondary) case .manual: Text("Sync triggered explicitly by app code") .font(.caption) .foregroundStyle(.secondary) case .automaticSmall: Text("Auto-sync if data ≤ 100KB, otherwise throws error") .font(.caption) .foregroundStyle(.secondary) } } private func saveValue() { isLoading = true Task { do { let key = StorageKeys.SyncableSettingKey( availability: selectedPlatform, syncPolicy: selectedSync ) try await StorageRouter.shared.set(settingValue, for: key) statusMessage = "✓ Saved with \(selectedPlatform.displayName) availability and \(selectedSync.displayName) sync" } catch StorageError.dataTooLargeForSync { statusMessage = "Error: Data too large for automatic sync (max 100KB)" } catch { statusMessage = "Error: \(error.localizedDescription)" } isLoading = false } } private func loadValue() { isLoading = true Task { do { let key = StorageKeys.SyncableSettingKey( availability: selectedPlatform, syncPolicy: selectedSync ) storedValue = try await StorageRouter.shared.get(key) statusMessage = "✓ Loaded value" } catch StorageError.notFound { storedValue = "" statusMessage = "No value stored" } catch { statusMessage = "Error: \(error.localizedDescription)" } isLoading = false } } private func testPlatformError() { isLoading = true Task { // Try to access a watchOnly key from iPhone let key = StorageKeys.WatchVibrationKey() do { _ = try await StorageRouter.shared.get(key) statusMessage = "⚠ Accessed successfully (running on Watch?)" } catch StorageError.watchOnlyKeyAccessedOnPhone(let name) { statusMessage = "⚠ Expected Error: Key '\(name)' is watchOnly, accessed from iPhone" } catch { statusMessage = "Error: \(error.localizedDescription)" } isLoading = false } } } // MARK: - Display Names extension PlatformAvailability { var displayName: String { switch self { case .all: return "All" case .phoneOnly: return "Phone Only" case .watchOnly: return "Watch Only" case .phoneWithWatchSync: return "Phone + Watch Sync" } } } extension SyncPolicy { var displayName: String { switch self { case .never: return "Never" case .manual: return "Manual" case .automaticSmall: return "Automatic" } } } #Preview { NavigationStack { PlatformSyncDemo() } }