chore(typing): snapshot local pinyin WIP before syncing cloud branch

Preserve the in-progress local typing/pinyin implementation so feat/pinyin
can safely reset to origin/feat/pinyin (cloud English + Chinese typing).
This commit is contained in:
Rocky
2026-08-03 13:14:01 +08:00
parent c037be3654
commit d1e5fed964
58 changed files with 370539 additions and 121 deletions
+41 -57
View File
@@ -8,7 +8,7 @@
// in `applyPresentationHeightOffset()`.
//
//
// [polish] [] header band (top)
// [OSG] EN header band (top)
// (transcript preview)
//
// mic (centred) action cluster:
@@ -22,17 +22,14 @@ import OSGKeyboardShared
private enum KeyboardLayoutMetrics {
static let micSize: CGFloat = 121
static let micToButtonGap: CGFloat = 8
static let bottomActionRowHeight: CGFloat = 48
static let bottomActionRowHeight: CGFloat = KeyboardChromeLayout.actionKeyHeight
static let bottomActionFixedWidth: CGFloat = 86
static let bottomActionSpacing: CGFloat = Spacing.xs
/// Gap between the top chip row and the transcript / hint line.
/// Tightened (8 4) so the "" line hugs the chip row. The
/// space reclaimed here and from `actionClusterTopGap` is added back
/// into `actionClusterBottomGap`, keeping `totalHeight` constant while
/// nudging the mic up toward the vertical centre.
/// Gap between the top control row and the transcript / hint line.
/// Four points keeps the "" line visually attached to the controls.
static let topBarToTranscriptSpacing: CGFloat = Spacing.xs / 2
/// Outer inset for the bottom action row from screen edges (8 pt 24 pt, +200%).
static let sideActionHorizontalInset: CGFloat = Spacing.xs * 3
/// Match the typing key grid's outer edge.
static let sideActionHorizontalInset: CGFloat = KeyboardChromeLayout.horizontalInset
/// iPad: cap the content column. A full-width (~1180 pt) keyboard would
/// park delete/return at the far screen edges and turn each cursor-drag
/// pad into a ~450 pt runway capping keeps the reach ergonomics of the
@@ -40,41 +37,40 @@ private enum KeyboardLayoutMetrics {
static let contentMaxWidth: CGFloat = 700
// MARK: - Content-driven keyboard height (single source of truth)
static let outerPaddingTop: CGFloat = 2
static let outerPaddingBottom: CGFloat = 1
static let topBarHeight: CGFloat = 38
static let outerPaddingTop: CGFloat = 4
static let outerPaddingBottom: CGFloat = 4
static let topBarHeight: CGFloat = KeyboardTopBarMetrics.height
static let transcriptLineHeight: CGFloat = 22
/// mic (121) + gap (8) + bottom row (48) = 177 pt
/// mic (121) + gap (8) + bottom row (50) = 179 pt
static let actionClusterHeight: CGFloat = micSize + micToButtonGap + bottomActionRowHeight
/// Gap between transcript line and mic. Tightened (11.2 4) to pull
/// the mic up; the reclaimed space moves to `actionClusterBottomGap`.
static let actionClusterTopGap: CGFloat = Spacing.xs / 2
/// Gap below the bottom action row.
static let actionClusterBottomGap: CGFloat = 6
/// Moves the action cluster down so its keys share the typing row's baseline.
static let actionClusterTopGap: CGFloat = Spacing.xl
/// The shared 4 pt outer padding is the complete bottom inset.
static let actionClusterBottomGap: CGFloat = 0
static var headerBandHeight: CGFloat {
topBarHeight + topBarToTranscriptSpacing + transcriptLineHeight
}
/// 2 + 64 + 4 + 177 + 15.2 + 1 = 263.2 pt (unchanged; the mic cluster
/// just sits higher now that the top gaps moved to the bottom gap).
static var totalHeight: CGFloat {
outerPaddingTop
+ headerBandHeight
+ actionClusterTopGap
+ actionClusterHeight
+ actionClusterBottomGap
+ outerPaddingBottom
}
/// 4 + 70 + 24 + 179 + 0 + 4 = 281 pt, matching Chinese / English.
static let totalHeight: CGFloat = KeyboardChromeLayout.totalHeight
}
public struct KeyboardRootView: View {
@Environment(\.colorScheme) private var colorScheme
@ObservedObject var state: State
@ObservedObject var typing: TypingSessionController
let onInsert: (String) -> Void
public init(state: KeyboardViewController.State) {
public init(
state: KeyboardViewController.State,
typing: TypingSessionController = TypingSessionController(),
onInsert: @escaping (String) -> Void = { _ in }
) {
self.state = state
self.typing = typing
self.onInsert = onInsert
}
/// Content-driven keyboard height; mirrored on `UIInputViewController.view`
@@ -136,7 +132,7 @@ public struct KeyboardRootView: View {
.animation(.easeInOut(duration: 0.12), value: state.cursorDragActive)
}
/// Top chip row + transcript / hint line.
/// Top brand / mode row + transcript / hint line.
private var headerBand: some View {
VStack(spacing: KeyboardLayoutMetrics.topBarToTranscriptSpacing) {
topBar
@@ -158,35 +154,18 @@ public struct KeyboardRootView: View {
private var topBar: some View {
HStack(spacing: Spacing.xs) {
if state.isLocalEngine {
LocalEngineChip()
} else {
CloudEngineChip()
}
KeyboardBrandLogo(action: state.openSettings)
// Engine controls remain available in the host app.
// App context is auto-detected on each mic press no UI.
if state.isTranslationChipVisible {
TranslationChip(
palette: palette,
targetLocaleId: state.translationTargetLocaleId,
onSelect: state.setTranslationTargetLocaleId
)
// Decouple the open picker from the keyboard's 1 Hz App
// Group poll so scrolling doesn't reset / dismiss it.
.equatable()
}
Spacer(minLength: 0)
Button(action: state.openSettings) {
Image(systemName: "gearshape.fill")
.font(.system(size: 14, weight: .medium))
.foregroundStyle(palette.textSecondary)
.frame(width: 34, height: 34)
.background(palette.surface, in: Circle())
.overlay(Circle().stroke(palette.divider, lineWidth: 0.5))
}
.buttonStyle(.plain)
.accessibilityLabel(ExtL10n.text("keyboard.openSettingsA11y"))
KeyboardTopControls(
state: state,
typing: typing,
palette: palette,
onInsert: onInsert
)
}
.padding(.horizontal, Spacing.md)
.padding(.horizontal, KeyboardTopBarMetrics.horizontalInset)
}
// MARK: - Action cluster
@@ -273,7 +252,12 @@ public struct KeyboardRootView: View {
private func bottomReturnButton(disabled: Bool) -> some View {
let title = ExtL10n.string(state.returnKeyRole.titleKey)
return RectangularToolbarButton(title: title, label: title, disabled: disabled) {
return RectangularToolbarButton(
title: title,
label: title,
disabled: disabled,
isSend: state.returnKeyRole == .send
) {
state.insertNewline()
}
.frame(height: KeyboardLayoutMetrics.bottomActionRowHeight)