Add SampleLocationData, StorageKeys, AppVersionKey, Value (+10 more)
This commit is contained in:
parent
a8d6a90d5b
commit
0dd3ddcbae
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.DS_Store
|
||||||
|
.build/
|
||||||
|
.swiftpm/
|
||||||
|
DerivedData/
|
||||||
|
*.xcodeproj/project.xcworkspace/xcuserdata
|
||||||
|
*.xcodeproj/xcuserdata
|
||||||
@ -6,6 +6,13 @@
|
|||||||
objectVersion = 77;
|
objectVersion = 77;
|
||||||
objects = {
|
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 */
|
/* Begin PBXContainerItemProxy section */
|
||||||
EA179D0F2F1722BC00B1D54A /* PBXContainerItemProxy */ = {
|
EA179D0F2F1722BC00B1D54A /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
@ -52,6 +59,10 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
EA179D532F17367700B1D54A /* StorageKeys in Frameworks */,
|
||||||
|
EA179D402F17326400B1D54A /* StorageKeys in Frameworks */,
|
||||||
|
EA179D562F17379800B1D54A /* LocalData in Frameworks */,
|
||||||
|
EA179D392F17290D00B1D54A /* StorageKeys in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -112,6 +123,10 @@
|
|||||||
);
|
);
|
||||||
name = SecureStorgageSample;
|
name = SecureStorgageSample;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
|
EA179D382F17290D00B1D54A /* StorageKeys */,
|
||||||
|
EA179D3F2F17326400B1D54A /* StorageKeys */,
|
||||||
|
EA179D522F17367700B1D54A /* StorageKeys */,
|
||||||
|
EA179D552F17379800B1D54A /* LocalData */,
|
||||||
);
|
);
|
||||||
productName = SecureStorgageSample;
|
productName = SecureStorgageSample;
|
||||||
productReference = EA179D012F1722BB00B1D54A /* SecureStorgageSample.app */;
|
productReference = EA179D012F1722BB00B1D54A /* SecureStorgageSample.app */;
|
||||||
@ -195,6 +210,9 @@
|
|||||||
);
|
);
|
||||||
mainGroup = EA179CF82F1722BB00B1D54A;
|
mainGroup = EA179CF82F1722BB00B1D54A;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
|
packageReferences = (
|
||||||
|
EA179D542F17379800B1D54A /* XCLocalSwiftPackageReference "../localPackages/LocalData" */,
|
||||||
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = EA179D022F1722BB00B1D54A /* Products */;
|
productRefGroup = EA179D022F1722BB00B1D54A /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@ -402,6 +420,7 @@
|
|||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -433,6 +452,7 @@
|
|||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -570,6 +590,32 @@
|
|||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* 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 */;
|
rootObject = EA179CF92F1722BB00B1D54A /* Project object */;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,14 +6,46 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import LocalData
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
|
@State private var appVersion: String = ""
|
||||||
|
@State private var retrievedVersion: String = ""
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Image(systemName: "globe")
|
Image(systemName: "globe")
|
||||||
.imageScale(.large)
|
.imageScale(.large)
|
||||||
.foregroundStyle(.tint)
|
.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()
|
.padding()
|
||||||
}
|
}
|
||||||
|
|||||||
69
SecureStorgageSample/StorageKeys.swift
Normal file
69
SecureStorgageSample/StorageKeys.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
61
SecureStorgageSample/WatchOptimized.swift
Normal file
61
SecureStorgageSample/WatchOptimized.swift
Normal 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 }
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user