SecureStorageSample/SecureStorgageSample/Views/PlatformSyncDemo.swift

232 lines
7.9 KiB
Swift

//
// 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
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)
}
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)
}
@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()
}
}