294 lines
10 KiB
Swift
294 lines
10 KiB
Swift
//
|
|
// FileSystemDemo.swift
|
|
// SecureStorageSample
|
|
//
|
|
// Demonstrates file system storage with LocalData package.
|
|
//
|
|
|
|
import SwiftUI
|
|
import LocalData
|
|
import SharedKit
|
|
|
|
@MainActor
|
|
struct FileSystemDemo: View {
|
|
@State private var profileName = ""
|
|
@State private var profileEmail = ""
|
|
@State private var profileAge = ""
|
|
@State private var storedProfile: UserProfile?
|
|
@State private var appGroupProfile: UserProfile?
|
|
@State private var statusMessage = ""
|
|
@State private var appGroupStatusMessage = ""
|
|
@State private var isLoading = false
|
|
@State private var selectedDirectory: FileDirectory = .documents
|
|
@FocusState private var isFieldFocused: Bool
|
|
|
|
var body: some View {
|
|
Form {
|
|
Section {
|
|
Text("File system storage is great for larger data like user profiles, cached content, and structured documents.")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Section("Profile Data") {
|
|
TextField("Name", text: $profileName)
|
|
.focused($isFieldFocused)
|
|
TextField("Email", text: $profileEmail)
|
|
.keyboardType(.emailAddress)
|
|
.textContentType(.emailAddress)
|
|
.focused($isFieldFocused)
|
|
TextField("Age", text: $profileAge)
|
|
.keyboardType(.numberPad)
|
|
.focused($isFieldFocused)
|
|
}
|
|
|
|
Section("Storage Location") {
|
|
Picker("Directory", selection: $selectedDirectory) {
|
|
Text("Documents").tag(FileDirectory.documents)
|
|
Text("Caches").tag(FileDirectory.caches)
|
|
}
|
|
.pickerStyle(.segmented)
|
|
|
|
Text(selectedDirectory == .documents
|
|
? "Documents: Persisted, included in backups"
|
|
: "Caches: May be cleared by system, not backed up")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Section("Actions") {
|
|
Button(action: saveProfile) {
|
|
HStack {
|
|
Image(systemName: "doc.fill")
|
|
Text("Save to File System")
|
|
}
|
|
}
|
|
.disabled(profileName.isEmpty || isLoading)
|
|
|
|
Button(action: loadProfile) {
|
|
HStack {
|
|
Image(systemName: "doc.text.fill")
|
|
Text("Load from File System")
|
|
}
|
|
}
|
|
.disabled(isLoading)
|
|
|
|
Button(action: deleteProfile) {
|
|
HStack {
|
|
Image(systemName: "trash")
|
|
Text("Delete File")
|
|
}
|
|
}
|
|
.foregroundStyle(.red)
|
|
.disabled(isLoading)
|
|
}
|
|
|
|
Section("App Group Storage") {
|
|
Text("Requires App Group entitlement and matching identifier in AppGroupConfiguration.")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
|
|
Button(action: saveAppGroupProfile) {
|
|
HStack {
|
|
Image(systemName: "doc.fill")
|
|
Text("Save to App Group")
|
|
}
|
|
}
|
|
.disabled(profileName.isEmpty || isLoading)
|
|
|
|
Button(action: loadAppGroupProfile) {
|
|
HStack {
|
|
Image(systemName: "doc.text.fill")
|
|
Text("Load from App Group")
|
|
}
|
|
}
|
|
.disabled(isLoading)
|
|
|
|
Button(action: deleteAppGroupProfile) {
|
|
HStack {
|
|
Image(systemName: "trash")
|
|
Text("Delete App Group File")
|
|
}
|
|
}
|
|
.foregroundStyle(.red)
|
|
.disabled(isLoading)
|
|
}
|
|
|
|
if let profile = storedProfile {
|
|
Section("Retrieved Profile") {
|
|
LabeledContent("Name", value: profile.name)
|
|
LabeledContent("Email", value: profile.email)
|
|
LabeledContent("Age", value: profile.ageDescription)
|
|
LabeledContent("Created", value: profile.createdAt.formatted(date: .abbreviated, time: .shortened))
|
|
}
|
|
}
|
|
|
|
if let profile = appGroupProfile {
|
|
Section("App Group Profile") {
|
|
LabeledContent("Name", value: profile.name)
|
|
LabeledContent("Email", value: profile.email)
|
|
LabeledContent("Age", value: profile.ageDescription)
|
|
LabeledContent("Created", value: profile.createdAt.formatted(date: .abbreviated, time: .shortened))
|
|
}
|
|
}
|
|
|
|
if !statusMessage.isEmpty {
|
|
Section {
|
|
Text(statusMessage)
|
|
.font(.caption)
|
|
.foregroundStyle(statusMessage.contains("Error") ? .red : .green)
|
|
}
|
|
}
|
|
|
|
if !appGroupStatusMessage.isEmpty {
|
|
Section {
|
|
Text(appGroupStatusMessage)
|
|
.font(.caption)
|
|
.foregroundStyle(appGroupStatusMessage.contains("Error") ? .red : .green)
|
|
}
|
|
}
|
|
|
|
Section("Key Configuration") {
|
|
LabeledContent("Domain", value: "File System")
|
|
LabeledContent("Security", value: "None")
|
|
LabeledContent("Serializer", value: "JSON")
|
|
LabeledContent("Platform", value: "Phone + Watch Sync")
|
|
}
|
|
}
|
|
.navigationTitle("File System")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItemGroup(placement: .keyboard) {
|
|
Spacer()
|
|
Button("Done") {
|
|
isFieldFocused = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func saveProfile() {
|
|
isLoading = true
|
|
Task {
|
|
do {
|
|
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
|
|
let profile = UserProfile(
|
|
name: profileName,
|
|
email: profileEmail,
|
|
age: Int(profileAge),
|
|
createdAt: Date()
|
|
)
|
|
try await StorageRouter.shared.set(profile, for: key)
|
|
statusMessage = "✓ Saved to \(selectedDirectory == .documents ? "Documents" : "Caches")"
|
|
} catch {
|
|
statusMessage = "Error: \(error.localizedDescription)"
|
|
}
|
|
isLoading = false
|
|
}
|
|
}
|
|
|
|
private func loadProfile() {
|
|
isLoading = true
|
|
Task {
|
|
do {
|
|
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
|
|
let profile = try await StorageRouter.shared.get(key)
|
|
storedProfile = profile
|
|
|
|
// Sync to text fields
|
|
profileName = profile.name
|
|
profileEmail = profile.email
|
|
profileAge = profile.age.map(String.init) ?? ""
|
|
|
|
statusMessage = "✓ Loaded from file system"
|
|
} catch StorageError.notFound {
|
|
storedProfile = nil
|
|
statusMessage = "No profile file found"
|
|
} catch {
|
|
statusMessage = "Error: \(error.localizedDescription)"
|
|
}
|
|
isLoading = false
|
|
}
|
|
}
|
|
|
|
private func deleteProfile() {
|
|
isLoading = true
|
|
Task {
|
|
do {
|
|
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
|
|
try await StorageRouter.shared.remove(key)
|
|
storedProfile = nil
|
|
statusMessage = "✓ File deleted"
|
|
} catch {
|
|
statusMessage = "Error: \(error.localizedDescription)"
|
|
}
|
|
isLoading = false
|
|
}
|
|
}
|
|
|
|
private func saveAppGroupProfile() {
|
|
isLoading = true
|
|
Task {
|
|
do {
|
|
let key = StorageKeys.AppGroupUserProfileKey(directory: selectedDirectory)
|
|
let profile = UserProfile(
|
|
name: profileName,
|
|
email: profileEmail,
|
|
age: Int(profileAge),
|
|
createdAt: Date()
|
|
)
|
|
try await StorageRouter.shared.set(profile, for: key)
|
|
appGroupStatusMessage = "✓ App Group saved to \(selectedDirectory == .documents ? "Documents" : "Caches")"
|
|
} catch {
|
|
appGroupStatusMessage = "Error: \(error.localizedDescription)"
|
|
}
|
|
isLoading = false
|
|
}
|
|
}
|
|
|
|
private func loadAppGroupProfile() {
|
|
isLoading = true
|
|
Task {
|
|
do {
|
|
let key = StorageKeys.AppGroupUserProfileKey(directory: selectedDirectory)
|
|
let profile = try await StorageRouter.shared.get(key)
|
|
appGroupProfile = profile
|
|
|
|
// Sync to text fields
|
|
profileName = profile.name
|
|
profileEmail = profile.email
|
|
profileAge = profile.age.map(String.init) ?? ""
|
|
|
|
appGroupStatusMessage = "✓ App Group loaded from file system"
|
|
} catch StorageError.notFound {
|
|
appGroupProfile = nil
|
|
appGroupStatusMessage = "No App Group profile file found"
|
|
} catch {
|
|
appGroupStatusMessage = "Error: \(error.localizedDescription)"
|
|
}
|
|
isLoading = false
|
|
}
|
|
}
|
|
|
|
private func deleteAppGroupProfile() {
|
|
isLoading = true
|
|
Task {
|
|
do {
|
|
let key = StorageKeys.AppGroupUserProfileKey(directory: selectedDirectory)
|
|
try await StorageRouter.shared.remove(key)
|
|
appGroupProfile = nil
|
|
appGroupStatusMessage = "✓ App Group file deleted"
|
|
} catch {
|
|
appGroupStatusMessage = "Error: \(error.localizedDescription)"
|
|
}
|
|
isLoading = false
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
NavigationStack {
|
|
FileSystemDemo()
|
|
}
|
|
}
|