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;
|
||||
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 */;
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
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