feat(keyboard): ship AI hint carousel, home library cards, and clipboard polish
Rotate AI idle suggestions with optional remote packs, move history/dictionary onto self-sizing Home preview cards, harden clipboard capture/prompting, and simplify keyboard chrome by dropping most liquid-glass shadows.
This commit is contained in:
@@ -12,6 +12,9 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
/// UI-owned ObservableObject; construct and mutate it on the main thread.
|
||||
/// `@unchecked Sendable` does not make `@Published` thread-safe. Credential
|
||||
/// observers write only Keychain, while non-secret configuration uses App Group defaults.
|
||||
public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
||||
public static let shared = ProviderConfig()
|
||||
|
||||
@@ -32,6 +35,8 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
||||
persistConfiguration()
|
||||
}
|
||||
}
|
||||
/// Its observer updates only the provider-scoped Keychain item; the value
|
||||
/// must never enter `configuration` or App Group UserDefaults.
|
||||
@Published public var apiKey: String {
|
||||
didSet {
|
||||
guard oldValue != apiKey, !isSyncingProviderAPIKey else { return }
|
||||
@@ -42,6 +47,9 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
||||
useICloudSync: configuration.settingsICloudSyncEnabled
|
||||
)
|
||||
} catch {
|
||||
isSyncingProviderAPIKey = true
|
||||
apiKey = oldValue
|
||||
isSyncingProviderAPIKey = false
|
||||
OSGLog.config.warning("Keychain write failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
@@ -80,6 +88,9 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
||||
useICloudSync: configuration.settingsICloudSyncEnabled
|
||||
)
|
||||
} catch {
|
||||
isSyncingASRProviderAPIKey = true
|
||||
asrApiKey = oldValue
|
||||
isSyncingASRProviderAPIKey = false
|
||||
OSGLog.config.warning("ASR Keychain write failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
@@ -167,7 +178,7 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
||||
persistConfiguration()
|
||||
}
|
||||
}
|
||||
/// v0.2.1: whether to translate the transcript into
|
||||
/// Whether to translate the transcript into
|
||||
/// `translationTargetLocaleId` before insertion. **Derived** —
|
||||
/// translation is on iff the user has selected a target locale
|
||||
/// (i.e. the persisted id is anything other than
|
||||
@@ -175,7 +186,7 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
||||
public var translationEnabled: Bool {
|
||||
configuration.translationEnabled
|
||||
}
|
||||
/// v0.2.1: BCP-47-ish target language id (e.g. `en`, `ja`, `ko`) the
|
||||
/// BCP-47-ish target language id (e.g. `en`, `ja`, `ko`) the
|
||||
/// translate-and-polish prompt should produce. Default `"off"` —
|
||||
/// translation is opt-in. Persisted in the App Group so the keyboard
|
||||
/// extension can honour it (and so the chip on the keyboard reflects
|
||||
@@ -349,6 +360,8 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
||||
|
||||
private let defaults: UserDefaults
|
||||
private var configuration: AppGroupConfiguration
|
||||
/// Suppresses `@Published` observer persistence while a complete snapshot
|
||||
/// or preset is applied, preventing reentrant writes of partial state.
|
||||
private var isApplyingConfiguration = false
|
||||
private var isSyncingProviderAPIKey = false
|
||||
private var isSyncingASRProviderAPIKey = false
|
||||
|
||||
Reference in New Issue
Block a user