feat: keyboard bottom-row layout, handedness preference, and screen wake lock

Rework the action cluster to a mic-above-bottom-row layout, add left/right
handedness setting that swaps delete and return, and keep the screen awake
during Flow recording sessions.
This commit is contained in:
Rocky
2026-07-02 21:01:54 +08:00
parent 1bdb8824ac
commit bc77cabb62
18 changed files with 501 additions and 104 deletions
@@ -1,7 +1,7 @@
// FlowUtteranceChunkConfig.swift
// OSGKeyboard · Shared
//
// Chunking policy for pipelined Flow utterance ASR (up to 3 minutes).
// Chunking policy for pipelined Flow utterance ASR (up to 3.5 minutes).
import Foundation
@@ -0,0 +1,29 @@
// HandednessPreference.swift
// OSGKeyboard · Shared
//
// Which hand the user holds the phone with controls bottom-row key order
// on the keyboard (delete return swap for right-handed use).
import Foundation
public enum HandednessPreference: String, CaseIterable, Identifiable, Sendable, Codable {
case left
case right
public var id: String { rawValue }
public var labelKey: String {
switch self {
case .left: return "settings.handedness.left"
case .right: return "settings.handedness.right"
}
}
/// Right-handed preference places return on the left and delete on the right.
public var swapsActionKeys: Bool { self == .right }
public static func fromStored(_ raw: String?) -> HandednessPreference {
guard let raw, let value = HandednessPreference(rawValue: raw) else { return .left }
return value
}
}
@@ -52,6 +52,7 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
// "on" state during init, but new writes never touch the key.
static let translationTargetLocaleId = "config.translationTargetLocaleId"
static let polishScenarioId = "config.polishScenarioId"
static let handednessPreference = "config.handednessPreference"
}
@Published public var providerId: String {
@@ -163,6 +164,14 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
AppGroupConfigDarwin.postConfigChanged()
}
}
/// Which hand the user holds the phone with mirrors to the keyboard
/// extension so delete / return can swap on the bottom row.
@Published public var handednessPreference: HandednessPreference {
didSet {
defaults.set(handednessPreference.rawValue, forKey: Key.handednessPreference)
AppGroupConfigDarwin.postConfigChanged()
}
}
/// Whether the pipeline should run translate-and-polish (not just
/// polish). Cloud engine: any selected target locale. Local engine:
@@ -299,6 +308,9 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
self.polishScenarioId = PolishScenarioCatalog.defaultId
}
}
self.handednessPreference = HandednessPreference.fromStored(
resolvedDefaults.string(forKey: Key.handednessPreference)
)
// Cloud no longer exposes off/transcribe; migrate legacy values.
if self.engineMode == "cloud", self.modeId != "polish" {
@@ -350,6 +362,7 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
model = preset.defaultModel
systemPrompt = AppGroupStore.defaultSystemPrompt(for: "openai")
polishScenarioId = PolishScenarioCatalog.defaultId
handednessPreference = .left
hasAcknowledgedCloudSharing = false
}
}