SecureStorageSample/SecureStorageSample/Views/MigrationDemo.swift

149 lines
5.8 KiB
Swift

import SwiftUI
import LocalData
@MainActor
struct MigrationDemo: View {
@State private var legacyValue = ""
@State private var modernValue = ""
@State private var statusMessage = ""
@State private var isLoading = false
var body: some View {
Form {
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(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)
Button(action: saveToLegacy) {
Label("Save to Legacy (UserDefaults)", systemImage: "arrow.down.doc")
}
.disabled(legacyValue.isEmpty || isLoading)
}
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 using the new migration protocol.")
.font(.caption)
.foregroundStyle(Color.Text.secondary)
Button(action: loadFromModern) {
Label("Load from Modern (Keychain)", systemImage: "sparkles")
}
.disabled(isLoading)
if !modernValue.isEmpty {
LabeledContent("Migrated Value", value: modernValue)
.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 Force Migration.")
.font(.caption)
.foregroundStyle(Color.Text.secondary)
Button(action: runManualMigration) {
Label("Force Migration", systemImage: "arrow.up.circle.badge.clock")
}
.disabled(isLoading)
}
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")
}
.disabled(isLoading)
}
if !statusMessage.isEmpty {
Section {
Text(statusMessage)
.font(.caption)
.foregroundStyle(statusMessage.contains("Error") ? Color.Status.error : Color.Status.info)
}
}
}
.navigationTitle("Data Migration")
.navigationBarTitleDisplayMode(.inline)
}
// MARK: - Actions
private func saveToLegacy() {
isLoading = true
Task {
do {
let key = StorageKeys.LegacyMigrationSourceKey()
try await StorageRouter.shared.set(legacyValue, for: key)
statusMessage = "✓ Saved '\(legacyValue)' to legacy UserDefaults"
} catch {
statusMessage = "Error: \(error.localizedDescription)"
}
isLoading = false
}
}
private func loadFromModern() {
isLoading = true
statusMessage = "Retrieving from Modern..."
Task {
do {
let key = StorageKeys.ModernMigrationDestinationKey()
let value = try await StorageRouter.shared.get(key)
modernValue = value
statusMessage = "✓ Success! Data migrated from UserDefaults to Keychain."
} catch StorageError.notFound {
statusMessage = "Modern key is empty (and no legacy data found)."
} catch {
statusMessage = "Error: \(error.localizedDescription)"
}
isLoading = false
}
}
private func runManualMigration() {
isLoading = true
statusMessage = "Running manual migration..."
Task {
do {
let key = StorageKeys.ModernMigrationDestinationKey()
_ = try await StorageRouter.shared.forceMigration(for: key)
// Refresh modern value display
modernValue = try await StorageRouter.shared.get(key)
statusMessage = "✓ Manual migration complete. Legacy data drained into Keychain."
} catch {
statusMessage = "Error: \(error.localizedDescription)"
}
isLoading = false
}
}
private func checkLegacyExists() {
isLoading = true
Task {
do {
let key = StorageKeys.LegacyMigrationSourceKey()
let exists = try await StorageRouter.shared.exists(key)
statusMessage = exists ? "⚠️ Legacy data STILL EXISTS in UserDefaults!" : "✅ Legacy data was successfully DELETED."
} catch {
statusMessage = "Error: \(error.localizedDescription)"
}
isLoading = false
}
}
}