From c50c9cb60e4842336498b35171b39cdd1dfc86ad Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 16 Jan 2026 13:47:24 -0600 Subject: [PATCH] Add Design, Spacing, Color, Status (+1 more) --- README.md | 7 ++++-- .../Design/DesignConstants.swift | 23 +++++++++++++++++++ .../StorageKeys/Migration/MigrationKeys.swift | 12 ++++++---- SecureStorageSample/Views/MigrationDemo.swift | 22 ++++++++++-------- 4 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 SecureStorageSample/Design/DesignConstants.swift diff --git a/README.md b/README.md index a32cb92..b025a29 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,11 @@ The app demonstrates various storage configurations: - Global sync configuration (max file size) in app `init` ### Data Migration -- **Fallback**: Automatically moves data from `LegacyMigrationSourceKey` to `ModernMigrationDestinationKey` on first access. -- **Manual Sweep**: Explicitly triggers a "drain" of legacy keys to the Keychain using `StorageRouter.shared.migrate(for:)`. +- **Fallback**: Automatically moves data from `LegacyMigrationSourceKey` to `ModernMigrationDestinationKey` on first access using protocol-based migration. +- **Transforming**: Converts a legacy full-name string into a structured `ProfileName`. +- **Aggregating**: Combines legacy notification + theme settings into `UnifiedSettings`. +- **Conditional**: Migrates app mode only when the version rule is met. +- **Manual Sweep**: Explicitly triggers a "drain" of legacy keys to the Keychain using `StorageRouter.shared.forceMigration(for:)`. - **Startup Sweep**: Automatically cleanses all registered legacy keys at app launch via `registerCatalog(..., migrateImmediately: true)`. ## Global Configuration diff --git a/SecureStorageSample/Design/DesignConstants.swift b/SecureStorageSample/Design/DesignConstants.swift new file mode 100644 index 0000000..ac1d86a --- /dev/null +++ b/SecureStorageSample/Design/DesignConstants.swift @@ -0,0 +1,23 @@ +import SwiftUI + +enum Design { + enum Spacing { + static let xSmall: CGFloat = 4 + static let small: CGFloat = 8 + static let medium: CGFloat = 16 + static let large: CGFloat = 24 + } +} + +extension Color { + enum Status { + static let success = Color.green + static let info = Color.blue + static let warning = Color.orange + static let error = Color.red + } + + enum Text { + static let secondary = Color.secondary + } +} diff --git a/SecureStorageSample/StorageKeys/Migration/MigrationKeys.swift b/SecureStorageSample/StorageKeys/Migration/MigrationKeys.swift index 9e8e01c..83911c4 100644 --- a/SecureStorageSample/StorageKeys/Migration/MigrationKeys.swift +++ b/SecureStorageSample/StorageKeys/Migration/MigrationKeys.swift @@ -31,10 +31,14 @@ extension StorageKeys { let description = "Modern key in Keychain with biometric security." let availability: PlatformAvailability = .all let syncPolicy: SyncPolicy = .never - - // Define the migration path - var migrationSources: [AnyStorageKey] { - [AnyStorageKey(LegacyMigrationSourceKey())] + + var migration: AnyStorageMigration? { + AnyStorageMigration( + SimpleLegacyMigration( + destinationKey: self, + sourceKey: .key(LegacyMigrationSourceKey()) + ) + ) } } } diff --git a/SecureStorageSample/Views/MigrationDemo.swift b/SecureStorageSample/Views/MigrationDemo.swift index d13b514..d9274b3 100644 --- a/SecureStorageSample/Views/MigrationDemo.swift +++ b/SecureStorageSample/Views/MigrationDemo.swift @@ -13,12 +13,13 @@ struct MigrationDemo: View { Section("The Scenario") { Text("Imagine you have an old version of the app that stored a User ID in plain UserDefaults. Now, you want to move it to the secure Keychain automatically without the user noticing.") .font(.caption) - .foregroundStyle(.secondary) + .foregroundStyle(Color.Text.secondary) } Section("Step 1: Setup Legacy Data") { Text("First, save a value to the 'legacy' key in UserDefaults.") .font(.caption) + .foregroundStyle(Color.Text.secondary) TextField("Legacy Value", text: $legacyValue) .textFieldStyle(.roundedBorder) @@ -30,8 +31,9 @@ struct MigrationDemo: View { } Section("Step 2: Trigger Migration") { - Text("Now, attempt to load from the 'modern' Keychain key. It will automatically check the legacy key, move the data, and delete the old record.") + Text("Now, attempt to load from the 'modern' Keychain key. It will automatically check the legacy key, move the data, and delete the old record using the new migration protocol.") .font(.caption) + .foregroundStyle(Color.Text.secondary) Button(action: loadFromModern) { Label("Load from Modern (Keychain)", systemImage: "sparkles") @@ -40,17 +42,18 @@ struct MigrationDemo: View { if !modernValue.isEmpty { LabeledContent("Migrated Value", value: modernValue) - .foregroundStyle(.green) + .foregroundStyle(Color.Status.success) .bold() } } Section("Step 3: Proactive Sweep (Drain)") { - Text("Even if the modern key already has data, you can force a 'Sweep' of legacy sources. Try saving a NEW value to Legacy, then click Drain.") + Text("Even if the modern key already has data, you can force a sweep of legacy sources. Try saving a new value to Legacy, then click Force Migration.") .font(.caption) + .foregroundStyle(Color.Text.secondary) Button(action: runManualMigration) { - Label("Drain Migration Sources", systemImage: "arrow.up.circle.badge.clock") + Label("Force Migration", systemImage: "arrow.up.circle.badge.clock") } .disabled(isLoading) } @@ -58,6 +61,7 @@ struct MigrationDemo: View { Section("Step 4: Verify Cleanup") { Text("Check if the data was actually removed from UserDefaults after migration.") .font(.caption) + .foregroundStyle(Color.Text.secondary) Button(action: checkLegacyExists) { Label("Check Legacy Exists?", systemImage: "magnifyingglass") @@ -69,7 +73,7 @@ struct MigrationDemo: View { Section { Text(statusMessage) .font(.caption) - .foregroundStyle(statusMessage.contains("Error") ? .red : .blue) + .foregroundStyle(statusMessage.contains("Error") ? Color.Status.error : Color.Status.info) } } } @@ -113,14 +117,14 @@ struct MigrationDemo: View { private func runManualMigration() { isLoading = true - statusMessage = "Draining migration sources..." + statusMessage = "Running manual migration..." Task { do { let key = StorageKeys.ModernMigrationDestinationKey() - try await StorageRouter.shared.migrate(for: key) + _ = try await StorageRouter.shared.forceMigration(for: key) // Refresh modern value display modernValue = try await StorageRouter.shared.get(key) - statusMessage = "✓ Proactive migration complete. Legacy data drained into Keychain." + statusMessage = "✓ Manual migration complete. Legacy data drained into Keychain." } catch { statusMessage = "Error: \(error.localizedDescription)" }