Files
OSGKeyboard/OSGKeyboardExt/Services/AppGroupPersistor.swift
T
Rocky 7f059dbd45 feat: TypeWhisper Flow sessions, Phase 4 UX, and GitHub Pages privacy site
Migrate keyboard dictation to continuous Flow sessions with auto-start,
tap-to-toggle recording, 60s countdown, five-step onboarding, and App Group
IPC. Add docs/ GitHub Pages site with en/zh privacy policy for App Store compliance.
2026-06-19 18:05:50 +08:00

80 lines
2.7 KiB
Swift

// AppGroupPersistor.swift
// OSGKeyboard · Keyboard Extension
//
// Extracted from KeyboardViewController so the view controller doesn't
// have to know about App Group availability checks, AppGroupStore
// reads/writes, or how to render the locale / mode into the State
// view model.
import Foundation
import OSGKeyboardShared
/// Outcome of `load()` — distinguishes "everything fine" from "the
/// App Group isn't configured so we can't read anything". The view
/// controller flips its `phase` accordingly.
public enum AppGroupLoadResult: Equatable {
case loaded
case unavailable
}
@MainActor
public struct AppGroupPersistor {
public init() {}
/// Hydrate `state` from the App Group. Returns `loaded` on success
/// or `unavailable` if the App Group suite can't be opened (which
/// in DEBUG `fatalError`s inside `AppGroup.isAvailable`).
public func load(into state: KeyboardViewController.State) -> AppGroupLoadResult {
guard AppGroup.isAvailable else {
return .unavailable
}
let store = AppGroupStore()
state.localeId = store.localeId
state.mode = KeyboardViewController.State.InputMode(rawValue: store.modeId) ?? .polish
state.engineMode = store.engineMode
#if DEBUG
// Print a masked view of the live App Group config so we can see
// from the device console exactly what the keyboard extension
// actually sees (and whether it agrees with the main App).
let key = store.apiKey
let masked: String
if key.count > 8 {
masked = "\(key.prefix(4))\(key.suffix(4)) (\(key.count) chars)"
} else if key.isEmpty {
masked = "<empty>"
} else {
masked = "<\(key.count) chars>"
}
print("""
🔍 [AppGroupPersistor.load]
providerId = \(store.providerId)
baseURL = \(store.baseURL)
apiKey = \(masked)
model = \(store.model)
modeId = \(store.modeId)
localeId = \(store.localeId)
""")
#endif
return .loaded
}
/// Persist `mode` to the App Group store.
public func persist(mode: KeyboardViewController.State.InputMode) {
guard AppGroup.isAvailable else { return }
AppGroupStore().setModeId(mode.rawValue)
}
/// Persist `localeId` to the App Group store.
public func persist(localeId: String) {
guard AppGroup.isAvailable else { return }
AppGroupStore().setLocaleId(localeId)
}
/// Persist `engineMode` to the App Group store.
public func persist(engineMode: String) {
guard AppGroup.isAvailable else { return }
AppGroupStore().setEngineMode(engineMode)
}
}