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
@@ -140,6 +140,7 @@ final class FlowSessionManager: ObservableObject {
FlowSessionBridge.writeHeartbeat()
FlowSessionDarwin.postSessionChanged()
isActive = true
ScreenWakeLock.acquire()
if let expires = FlowSessionBridge.sessionExpiresAt() {
sessionExpiresAt = Date(timeIntervalSince1970: expires)
}
@@ -187,6 +188,7 @@ final class FlowSessionManager: ObservableObject {
capture.stop()
endBackgroundKeepAlive()
ScreenWakeLock.release()
sessionASR = nil
FlowSessionBridge.markSessionInactive()
FlowSessionDarwin.postSessionChanged()
@@ -321,6 +323,7 @@ final class FlowSessionManager: ObservableObject {
FlowSessionBridge.markSessionActive(duration: duration)
FlowSessionDarwin.postSessionChanged()
isActive = true
ScreenWakeLock.acquire()
sessionExpiresAt = Date().addingTimeInterval(duration)
startHeartbeat()
@@ -0,0 +1,26 @@
// ScreenWakeLock.swift
// OSGKeyboard · Main App
//
// Reference-counted idle-timer disable for Flow session ownership.
import UIKit
@MainActor
enum ScreenWakeLock {
private static var holdCount = 0
static func acquire() {
holdCount += 1
if holdCount == 1 {
UIApplication.shared.isIdleTimerDisabled = true
}
}
static func release() {
guard holdCount > 0 else { return }
holdCount -= 1
if holdCount == 0 {
UIApplication.shared.isIdleTimerDisabled = false
}
}
}
+47
View File
@@ -78,6 +78,7 @@ struct SettingsView: View {
localEngineSettingsSection
}
if presentation == .tab {
preferencesSection
footerLinks
}
}
@@ -253,6 +254,27 @@ struct SettingsView: View {
dynamicLocales = entries
}
// MARK: - Preferences (tab settings only)
private var preferencesSection: some View {
VStack(alignment: .leading, spacing: SettingsListMetrics.sectionLabelSpacing) {
sectionHeader("settings.preferences.title")
VStack(spacing: 0) {
HandednessPickerRow(
selection: Binding(
get: { config.handednessPreference },
set: { config.handednessPreference = $0 }
)
)
}
.background(palette.surface, in: RoundedRectangle(cornerRadius: Radius.xl, style: .continuous))
.overlay(
RoundedRectangle(cornerRadius: Radius.xl, style: .continuous)
.stroke(palette.divider, lineWidth: 0.5)
)
}
}
// MARK: - Footer links (tab settings only)
private var footerLinks: some View {
@@ -366,6 +388,31 @@ struct SettingsView: View {
}
}
// MARK: - Handedness picker row
private struct HandednessPickerRow: View {
@Binding var selection: HandednessPreference
private var options: [(id: String, label: String)] {
HandednessPreference.allCases.map { preference in
(preference.rawValue, AppL10n.string(preference.labelKey))
}
}
var body: some View {
PickerRow(
title: AppL10n.string("settings.handedness.title"),
options: options,
selection: Binding(
get: { selection.rawValue },
set: { newValue in
selection = HandednessPreference(rawValue: newValue) ?? .left
}
)
)
}
}
// MARK: - Picker row (generic)
private struct PickerRow: View {
+4
View File
@@ -135,6 +135,10 @@
"settings.systemPrompt.edit" = "Edit system prompt";
"settings.systemPrompt.hint" = "Used by the cloud LLM when polishing your dictation. Local engine skips this step.";
"settings.about.title" = "About";
"settings.preferences.title" = "Preferences";
"settings.handedness.title" = "Handedness";
"settings.handedness.left" = "Left hand";
"settings.handedness.right" = "Right hand";
"settings.systemPrompt.reset" = "Reset";
"settings.asrLocale" = "ASR locale";
"settings.engineBadge.ios26" = "SpeechAnalyzer — always on-device";
@@ -135,6 +135,10 @@
"settings.systemPrompt.edit" = "编辑系统提示";
"settings.systemPrompt.hint" = "云端润色时使用。本地识别模式不会用到此提示词。";
"settings.about.title" = "关于";
"settings.preferences.title" = "偏好设置";
"settings.handedness.title" = "握持偏好";
"settings.handedness.left" = "左手";
"settings.handedness.right" = "右手";
"settings.systemPrompt.reset" = "重置";
"settings.asrLocale" = "识别语言";
"settings.engineBadge.ios26" = "SpeechAnalyzer — 始终端侧";