Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-01-13 21:20:49 -06:00
parent 9621e1287c
commit 3efeb3d032
5 changed files with 215 additions and 1 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.DS_Store
.build/
.swiftpm/
DerivedData/
*.xcodeproj/project.xcworkspace/xcuserdata
*.xcodeproj/xcuserdata

View File

@ -6,6 +6,13 @@
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
EA179D392F17290D00B1D54A /* StorageKeys in Frameworks */ = {isa = PBXBuildFile; productRef = EA179D382F17290D00B1D54A /* StorageKeys */; };
EA179D402F17326400B1D54A /* StorageKeys in Frameworks */ = {isa = PBXBuildFile; productRef = EA179D3F2F17326400B1D54A /* StorageKeys */; };
EA179D532F17367700B1D54A /* StorageKeys in Frameworks */ = {isa = PBXBuildFile; productRef = EA179D522F17367700B1D54A /* StorageKeys */; };
EA179D562F17379800B1D54A /* LocalData in Frameworks */ = {isa = PBXBuildFile; productRef = EA179D552F17379800B1D54A /* LocalData */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
EA179D0F2F1722BC00B1D54A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
@ -52,6 +59,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
EA179D532F17367700B1D54A /* StorageKeys in Frameworks */,
EA179D402F17326400B1D54A /* StorageKeys in Frameworks */,
EA179D562F17379800B1D54A /* LocalData in Frameworks */,
EA179D392F17290D00B1D54A /* StorageKeys in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -112,6 +123,10 @@
);
name = SecureStorgageSample;
packageProductDependencies = (
EA179D382F17290D00B1D54A /* StorageKeys */,
EA179D3F2F17326400B1D54A /* StorageKeys */,
EA179D522F17367700B1D54A /* StorageKeys */,
EA179D552F17379800B1D54A /* LocalData */,
);
productName = SecureStorgageSample;
productReference = EA179D012F1722BB00B1D54A /* SecureStorgageSample.app */;
@ -195,6 +210,9 @@
);
mainGroup = EA179CF82F1722BB00B1D54A;
minimizedProjectReferenceProxies = 1;
packageReferences = (
EA179D542F17379800B1D54A /* XCLocalSwiftPackageReference "../localPackages/LocalData" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = EA179D022F1722BB00B1D54A /* Products */;
projectDirPath = "";
@ -402,6 +420,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -433,6 +452,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -570,6 +590,32 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
EA179D542F17379800B1D54A /* XCLocalSwiftPackageReference "../localPackages/LocalData" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../localPackages/LocalData;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
EA179D382F17290D00B1D54A /* StorageKeys */ = {
isa = XCSwiftPackageProductDependency;
productName = StorageKeys;
};
EA179D3F2F17326400B1D54A /* StorageKeys */ = {
isa = XCSwiftPackageProductDependency;
productName = StorageKeys;
};
EA179D522F17367700B1D54A /* StorageKeys */ = {
isa = XCSwiftPackageProductDependency;
productName = StorageKeys;
};
EA179D552F17379800B1D54A /* LocalData */ = {
isa = XCSwiftPackageProductDependency;
productName = LocalData;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = EA179CF92F1722BB00B1D54A /* Project object */;
}

View File

@ -6,14 +6,46 @@
//
import SwiftUI
import LocalData
struct ContentView: View {
@State private var appVersion: String = ""
@State private var retrievedVersion: String = ""
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
Text("Secure Storage Sample")
TextField("Enter app version", text: $appVersion)
.textFieldStyle(.roundedBorder)
.padding()
Button("Save Version") {
Task {
do {
let key = StorageKeys.AppVersionKey()
try await StorageRouter.shared.set(appVersion, for: key)
} catch {
print("Error saving: \(error)")
}
}
}
Button("Retrieve Version") {
Task {
do {
let key = StorageKeys.AppVersionKey()
retrievedVersion = try await StorageRouter.shared.get(key)
} catch {
print("Error retrieving: \(error)")
}
}
}
Text("Retrieved: \(retrievedVersion)")
}
.padding()
}

View File

@ -0,0 +1,69 @@
import Foundation
import LocalData
struct SampleLocationData: Codable, Sendable {
let lat: Double
let lon: Double
}
extension StorageKeys {
struct AppVersionKey: StorageKey {
typealias Value = String
let name: String = "last_app_version"
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "SampleApp"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .automaticSmall
}
struct LastLocationKey: StorageKey {
typealias Value = SampleLocationData
let name: String = "last_known_location"
let domain: StorageDomain = .keychain(service: "com.example.app.security")
let security: SecurityPolicy = .keychain(accessibility: .afterFirstUnlock, accessControl: .userPresence)
let serializer: Serializer<SampleLocationData> = .json
let owner: String = "SampleApp"
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .manual
}
struct UserProfileFileKey: StorageKey {
typealias Value = [String: AnyCodable]
let name: String = "user_profile.json"
let domain: StorageDomain = .fileSystem(directory: .documents)
let security: SecurityPolicy = .none
let serializer: Serializer<[String: AnyCodable]> = .json
let owner: String = "SampleApp"
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
}
struct SessionLogsKey: StorageKey {
typealias Value = [String]
let name: String = "session_logs.json"
let domain: StorageDomain = .encryptedFileSystem(directory: .caches)
let security: SecurityPolicy = .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: 10_000)))
let serializer: Serializer<[String]> = .json
let owner: String = "SampleApp"
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
}
struct WatchVibrationKey: StorageKey {
typealias Value = Bool
let name: String = "watch_vibration_enabled"
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<Bool> = .json
let owner: String = "SampleApp"
let availability: PlatformAvailability = .watchOnly
let syncPolicy: SyncPolicy = .never
}
}

View File

@ -0,0 +1,61 @@
import Foundation
// Example shared models for Watch-optimized data
struct Workout: Codable {
let date: Date
// Add other properties as needed
}
struct Preferences: Codable {
let isPremium: Bool
let appearance: Appearance
}
enum Appearance: Codable {
case light, dark
}
// Shared model (iOS + watchOS)
struct FullUserProfile: Codable {
let id: UUID
let fullName: String
let email: String
let avatarURL: URL?
let allWorkouts: [Workout] // potentially huge
let preferences: Preferences
// Lightweight Watch version computed
var watchVersion: WatchUserProfile {
WatchUserProfile(
id: id,
displayName: String(fullName.split(separator: " ").first ?? Substring(fullName)),
hasAvatar: avatarURL != nil,
recentWorkoutCount: allWorkouts.count,
lastWorkoutDate: allWorkouts.last?.date,
showPremiumBadge: preferences.isPremium,
darkModeEnabled: preferences.appearance == .dark
)
}
}
struct WatchUserProfile: Codable, Sendable {
let id: UUID
let displayName: String
let hasAvatar: Bool
let recentWorkoutCount: Int
let lastWorkoutDate: Date?
let showPremiumBadge: Bool
let darkModeEnabled: Bool
}
// Example usage on phone (commented out)
// let profile = FullUserProfile(...) // full heavy object
// let watchData = try JSONEncoder().encode(profile.watchVersion)
// WCSession.default.updateApplicationContext(["profile": watchData])
// Alternative: WatchRepresentable protocol
public protocol WatchRepresentable {
associatedtype WatchVersion: Codable, Sendable
var watchVersion: WatchVersion { get }
}