Add Design, Spacing, Color, Status (+1 more)

This commit is contained in:
Matt Bruce 2026-01-16 13:47:24 -06:00
parent 67dbfa9e09
commit c50c9cb60e
4 changed files with 49 additions and 15 deletions

View File

@ -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

View File

@ -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
}
}

View File

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

View File

@ -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)"
}