242 lines
8.2 KiB
Swift
242 lines
8.2 KiB
Swift
//
|
|
// PlatformSyncDemo.swift
|
|
// SecureStorageSample
|
|
//
|
|
// 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()
|
|
}
|
|
}
|