Files
orca/mobile/src/session/mobile-session-command-input-styles.ts
T
Kelvin Amoaba 26aa174b3b feat(mobile): show dictated words as you speak and insert them at the cursor (#35502)
* feat(mobile): show dictated words as you speak and insert them at the cursor

Newer desktops already send the text so far with each audio chunk reply;
the phone now shows it: grey in the chat composer or command field where
it will land, and on the live-input status row. The final transcript
replaces the selection the field had when recording started, and is
appended when the field was never focused or changed since.

* refactor(mobile): pass the dictation preview by context and split its hooks out

The preview reaches composers through a context instead of four layers of
props, and the composer's and session's dictation logic move into their
own hooks, which keeps each file under its line cap.

* fix(mobile): keep dictation selections and live words to their own terminal

* test(mobile): keep the composer dictation test typechecked and under the line cap

* fix(mobile): keep the chat field controlled while dictation previews over it

The native composer field swapped its value for rich-text children while
showing heard words, so Android could duplicate or drop typed characters and
selection offsets could drift from the draft. Draw the preview as a hidden-
from-accessibility overlay shared with the web field, and only hide the
field's own text.

* refactor(mobile): build the dictation route context beside its destination

Keeps the session dictation hook under the line cap after rebasing onto main.

* fix(mobile): land dictation where its preview showed after switching chat or terminal

The final words now go to the target captured at start: the chat it began
in (its own draft when another chat is showing) rather than whichever
surface is visible. The command field preview only uses the saved selection
in the terminal it was read from, so it shows the words appended in another
terminal, as they will land.

* fix(mobile): send terminal dictation to the chat that replaced its terminal

Routing only by the captured target dropped the words when a chat tab
replaced the terminal mid-dictation, since no terminal draft was left to
take them.
2026-10-29 03:26:21 +00:00

246 lines
5.6 KiB
TypeScript

import { StyleSheet } from 'react-native'
import { colors, spacing, radii, typography } from '../theme/mobile-theme'
import { TEXT_INPUT_FONT_SIZE } from '../platform/text-input-font-size'
import { themedStyleSheet } from '../theme/themed-styles'
export const mobileSessionCommandInputStyles = themedStyleSheet(() => ({
createWarningBanner: {
flexDirection: 'row',
alignItems: 'flex-start',
gap: spacing.sm,
backgroundColor: colors.bgPanel,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.borderSubtle,
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm
},
createWarningText: {
flex: 1,
color: colors.textPrimary,
fontSize: 12,
lineHeight: 16
},
createWarningDismiss: {
width: 24,
height: 24,
alignItems: 'center',
justifyContent: 'center',
marginTop: -4
},
emptyState: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: spacing.xl
},
emptyText: {
color: colors.textSecondary,
fontSize: typography.bodySize,
marginBottom: spacing.lg
},
createError: {
color: colors.statusRed,
fontSize: 13,
marginBottom: spacing.sm
},
emptyActions: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
gap: spacing.sm
},
createButton: {
backgroundColor: colors.bgRaised,
borderWidth: 1,
borderColor: colors.borderSubtle,
paddingHorizontal: spacing.xl,
paddingVertical: spacing.sm + 2,
borderRadius: radii.button
},
createButtonDisabled: {
opacity: 0.5
},
createButtonText: {
color: colors.textPrimary,
fontSize: typography.bodySize,
fontWeight: '600'
},
commandDock: {
zIndex: 20,
backgroundColor: colors.bgPanel
},
accessoryBar: {
flexDirection: 'row',
alignItems: 'center',
borderTopWidth: 1,
borderTopColor: colors.borderSubtle,
backgroundColor: colors.bgPanel
},
accessoryScroll: {
flex: 1,
minWidth: 0
},
accessoryContent: {
paddingHorizontal: spacing.sm,
paddingVertical: spacing.xs,
gap: spacing.xs
},
accessoryKey: {
backgroundColor: colors.bgRaised,
paddingHorizontal: spacing.sm + 2,
paddingVertical: spacing.xs,
borderRadius: radii.button,
minWidth: 36,
alignItems: 'center'
},
accessoryKeyPressed: {
backgroundColor: colors.borderSubtle
},
accessoryKeyActive: {
backgroundColor: colors.textPrimary
},
customAccessoryKey: {
borderWidth: 1,
borderColor: colors.borderSubtle
},
accessoryKeyDisabled: {
opacity: 0.35
},
accessoryKeyText: {
color: colors.textSecondary,
fontSize: 12,
fontFamily: typography.monoFamily
},
accessoryKeyTextActive: {
color: colors.bgBase,
fontWeight: '700'
},
accessoryKeyTextDisabled: {
color: colors.textMuted
},
keyboardDismissKey: {
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'stretch',
marginLeft: spacing.sm,
marginVertical: spacing.xs,
backgroundColor: colors.bgRaised,
paddingHorizontal: spacing.sm + 2,
paddingVertical: 0,
borderRadius: radii.button,
minWidth: 36
},
// The Duo side rail column: full-width keys with room for a thumb.
accessoryRail: {
flex: 1,
minHeight: 0
},
accessoryScrollVertical: {
flex: 1
},
// Keys span the 84pt rail less 4pt each side, so the widest label (Shift+Tab) fits.
accessoryContentVertical: {
paddingHorizontal: spacing.xs,
paddingVertical: spacing.xs,
gap: spacing.sm
},
accessoryKeyVertical: {
minHeight: 44,
justifyContent: 'center',
paddingHorizontal: 2,
borderRadius: radii.composerItem
},
keyboardDismissKeyVertical: {
alignItems: 'center',
justifyContent: 'center',
marginHorizontal: spacing.xs,
marginBottom: spacing.sm,
minHeight: 44,
backgroundColor: colors.bgRaised,
borderRadius: radii.composerItem
},
keyboardDismissGlyph: {
alignItems: 'center',
height: 18,
justifyContent: 'flex-start',
position: 'relative',
width: 18
},
keyboardDismissChevron: {
bottom: -2,
position: 'absolute'
},
inputBar: {
flexDirection: 'row',
alignItems: 'center',
minHeight: 46,
paddingVertical: spacing.xs + 2,
paddingHorizontal: spacing.md,
borderTopWidth: 1,
borderTopColor: colors.borderSubtle,
backgroundColor: colors.bgPanel
},
textInput: {
flex: 1,
height: 34,
backgroundColor: colors.bgRaised,
color: colors.textPrimary,
borderRadius: radii.input,
paddingHorizontal: spacing.md,
paddingVertical: 0,
fontSize: TEXT_INPUT_FONT_SIZE,
fontFamily: typography.monoFamily,
marginRight: spacing.sm
},
commandField: {
flex: 1,
flexDirection: 'row',
marginRight: spacing.sm
},
commandFieldInput: {
marginRight: 0
},
// Dictation's preview draws the draft instead while it is hidden.
commandFieldInputHidden: {
color: 'transparent'
},
liveInputField: {
flex: 1,
marginRight: spacing.sm
},
liveInputCapture: {
minWidth: 0,
marginRight: 0
},
liveInputCaptureDisabled: {
opacity: 0.45
},
sendButton: {
backgroundColor: colors.bgRaised,
width: 34,
height: 34,
borderRadius: 17,
alignItems: 'center',
justifyContent: 'center'
},
dictationButton: {
backgroundColor: colors.bgRaised,
width: 34,
height: 34,
borderRadius: 17,
borderWidth: 1,
borderColor: 'transparent',
alignItems: 'center',
justifyContent: 'center',
marginRight: spacing.sm
},
dictationButtonActive: {
backgroundColor: colors.bgPanel,
borderColor: colors.textSecondary
},
sendButtonDisabled: {
opacity: 0.35
}
}))