Add Design, Spacing, Color, Status (+1 more)
This commit is contained in:
parent
67dbfa9e09
commit
c50c9cb60e
@ -122,8 +122,11 @@ The app demonstrates various storage configurations:
|
|||||||
- Global sync configuration (max file size) in app `init`
|
- Global sync configuration (max file size) in app `init`
|
||||||
|
|
||||||
### Data Migration
|
### Data Migration
|
||||||
- **Fallback**: Automatically moves data from `LegacyMigrationSourceKey` to `ModernMigrationDestinationKey` on first access.
|
- **Fallback**: Automatically moves data from `LegacyMigrationSourceKey` to `ModernMigrationDestinationKey` on first access using protocol-based migration.
|
||||||
- **Manual Sweep**: Explicitly triggers a "drain" of legacy keys to the Keychain using `StorageRouter.shared.migrate(for:)`.
|
- **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)`.
|
- **Startup Sweep**: Automatically cleanses all registered legacy keys at app launch via `registerCatalog(..., migrateImmediately: true)`.
|
||||||
|
|
||||||
## Global Configuration
|
## Global Configuration
|
||||||
|
|||||||
23
SecureStorageSample/Design/DesignConstants.swift
Normal file
23
SecureStorageSample/Design/DesignConstants.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,9 +32,13 @@ extension StorageKeys {
|
|||||||
let availability: PlatformAvailability = .all
|
let availability: PlatformAvailability = .all
|
||||||
let syncPolicy: SyncPolicy = .never
|
let syncPolicy: SyncPolicy = .never
|
||||||
|
|
||||||
// Define the migration path
|
var migration: AnyStorageMigration? {
|
||||||
var migrationSources: [AnyStorageKey] {
|
AnyStorageMigration(
|
||||||
[AnyStorageKey(LegacyMigrationSourceKey())]
|
SimpleLegacyMigration(
|
||||||
|
destinationKey: self,
|
||||||
|
sourceKey: .key(LegacyMigrationSourceKey())
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,12 +13,13 @@ struct MigrationDemo: View {
|
|||||||
Section("The Scenario") {
|
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.")
|
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)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(Color.Text.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Step 1: Setup Legacy Data") {
|
Section("Step 1: Setup Legacy Data") {
|
||||||
Text("First, save a value to the 'legacy' key in UserDefaults.")
|
Text("First, save a value to the 'legacy' key in UserDefaults.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
|
.foregroundStyle(Color.Text.secondary)
|
||||||
|
|
||||||
TextField("Legacy Value", text: $legacyValue)
|
TextField("Legacy Value", text: $legacyValue)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
@ -30,8 +31,9 @@ struct MigrationDemo: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section("Step 2: Trigger Migration") {
|
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)
|
.font(.caption)
|
||||||
|
.foregroundStyle(Color.Text.secondary)
|
||||||
|
|
||||||
Button(action: loadFromModern) {
|
Button(action: loadFromModern) {
|
||||||
Label("Load from Modern (Keychain)", systemImage: "sparkles")
|
Label("Load from Modern (Keychain)", systemImage: "sparkles")
|
||||||
@ -40,17 +42,18 @@ struct MigrationDemo: View {
|
|||||||
|
|
||||||
if !modernValue.isEmpty {
|
if !modernValue.isEmpty {
|
||||||
LabeledContent("Migrated Value", value: modernValue)
|
LabeledContent("Migrated Value", value: modernValue)
|
||||||
.foregroundStyle(.green)
|
.foregroundStyle(Color.Status.success)
|
||||||
.bold()
|
.bold()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Step 3: Proactive Sweep (Drain)") {
|
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)
|
.font(.caption)
|
||||||
|
.foregroundStyle(Color.Text.secondary)
|
||||||
|
|
||||||
Button(action: runManualMigration) {
|
Button(action: runManualMigration) {
|
||||||
Label("Drain Migration Sources", systemImage: "arrow.up.circle.badge.clock")
|
Label("Force Migration", systemImage: "arrow.up.circle.badge.clock")
|
||||||
}
|
}
|
||||||
.disabled(isLoading)
|
.disabled(isLoading)
|
||||||
}
|
}
|
||||||
@ -58,6 +61,7 @@ struct MigrationDemo: View {
|
|||||||
Section("Step 4: Verify Cleanup") {
|
Section("Step 4: Verify Cleanup") {
|
||||||
Text("Check if the data was actually removed from UserDefaults after migration.")
|
Text("Check if the data was actually removed from UserDefaults after migration.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
|
.foregroundStyle(Color.Text.secondary)
|
||||||
|
|
||||||
Button(action: checkLegacyExists) {
|
Button(action: checkLegacyExists) {
|
||||||
Label("Check Legacy Exists?", systemImage: "magnifyingglass")
|
Label("Check Legacy Exists?", systemImage: "magnifyingglass")
|
||||||
@ -69,7 +73,7 @@ struct MigrationDemo: View {
|
|||||||
Section {
|
Section {
|
||||||
Text(statusMessage)
|
Text(statusMessage)
|
||||||
.font(.caption)
|
.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() {
|
private func runManualMigration() {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
statusMessage = "Draining migration sources..."
|
statusMessage = "Running manual migration..."
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.ModernMigrationDestinationKey()
|
let key = StorageKeys.ModernMigrationDestinationKey()
|
||||||
try await StorageRouter.shared.migrate(for: key)
|
_ = try await StorageRouter.shared.forceMigration(for: key)
|
||||||
// Refresh modern value display
|
// Refresh modern value display
|
||||||
modernValue = try await StorageRouter.shared.get(key)
|
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 {
|
} catch {
|
||||||
statusMessage = "Error: \(error.localizedDescription)"
|
statusMessage = "Error: \(error.localizedDescription)"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user