mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 11:07:42 +00:00
On the OTA page a held press died ~500 ms in: the WebView's long-press selected nearby text and that selection's selectionchange/touchcancel ended the press. Page text is now unselectable unless it opts in (as native), hold surfaces declare onLongPress, the browser pane refuses contextmenu termination, and the chat mic's swapped icons no longer steal the touch target.
103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
import { ActivityIndicator, Pressable, type StyleProp, type ViewStyle } from 'react-native'
|
|
import { ImagePlus, Mic } from 'lucide-react-native'
|
|
import { colors } from '../theme/mobile-theme'
|
|
import { keepHeldPressThroughLongPress } from './held-press-long-press'
|
|
|
|
type DictationState = {
|
|
readonly isStarting: boolean
|
|
readonly isRecording: boolean
|
|
readonly isProcessing: boolean
|
|
}
|
|
|
|
type MobileTerminalInputActionsProps = {
|
|
readonly canSend: boolean
|
|
readonly isAttaching: boolean
|
|
readonly dictation: DictationState
|
|
readonly dictationMode: string | undefined
|
|
readonly buttonStyle: StyleProp<ViewStyle>
|
|
readonly activeButtonStyle: StyleProp<ViewStyle>
|
|
readonly disabledButtonStyle: StyleProp<ViewStyle>
|
|
readonly onAttachImage: () => void
|
|
readonly onAttachFile: () => void
|
|
readonly onDictationToggle: () => void
|
|
readonly onDictationPressIn: () => void
|
|
readonly onDictationPressOut: () => void
|
|
readonly onDictationCancel: () => void
|
|
}
|
|
|
|
// Image + mic peer actions shared by the live and buffered input bars so both
|
|
// surfaces offer identical multimodal entry points (and the JSX lives once).
|
|
export function MobileTerminalInputActions({
|
|
canSend,
|
|
isAttaching,
|
|
dictation,
|
|
dictationMode,
|
|
buttonStyle,
|
|
activeButtonStyle,
|
|
disabledButtonStyle,
|
|
onAttachImage,
|
|
onAttachFile,
|
|
onDictationToggle,
|
|
onDictationPressIn,
|
|
onDictationPressOut,
|
|
onDictationCancel
|
|
}: MobileTerminalInputActionsProps) {
|
|
const dictationActive = dictation.isStarting || dictation.isRecording
|
|
return (
|
|
<>
|
|
<Pressable
|
|
style={[buttonStyle, (!canSend || isAttaching) && disabledButtonStyle]}
|
|
disabled={!canSend || isAttaching}
|
|
// Tap opens the photo library; long-press picks a file. Uploads via host
|
|
// RPC so SSH/remote sessions attach the same as local ones.
|
|
onPress={onAttachImage}
|
|
onLongPress={onAttachFile}
|
|
delayLongPress={350}
|
|
accessibilityLabel={isAttaching ? 'Sending image' : 'Attach a photo'}
|
|
accessibilityHint="Long press to attach a file instead"
|
|
>
|
|
{isAttaching ? (
|
|
<ActivityIndicator size="small" color={colors.textSecondary} />
|
|
) : (
|
|
<ImagePlus size={17} color={colors.textSecondary} strokeWidth={2.4} />
|
|
)}
|
|
</Pressable>
|
|
<Pressable
|
|
style={[buttonStyle, dictationActive && activeButtonStyle, !canSend && disabledButtonStyle]}
|
|
disabled={!canSend}
|
|
onPress={dictationMode === 'toggle' ? onDictationToggle : undefined}
|
|
onPressIn={dictationMode === 'hold' ? onDictationPressIn : undefined}
|
|
onPressOut={dictationMode === 'hold' ? onDictationPressOut : undefined}
|
|
onLongPress={
|
|
dictationMode === 'toggle'
|
|
? () => {
|
|
if (dictation.isRecording || dictation.isProcessing) {
|
|
onDictationCancel()
|
|
}
|
|
}
|
|
: keepHeldPressThroughLongPress
|
|
}
|
|
accessibilityLabel={
|
|
dictation.isRecording
|
|
? 'Stop voice dictation'
|
|
: dictation.isProcessing
|
|
? 'Cancel voice dictation'
|
|
: dictation.isStarting
|
|
? 'Starting voice dictation'
|
|
: 'Start voice dictation'
|
|
}
|
|
>
|
|
{dictation.isProcessing ? (
|
|
<ActivityIndicator size="small" color={colors.textSecondary} />
|
|
) : (
|
|
<Mic
|
|
size={17}
|
|
color={dictationActive ? colors.textPrimary : colors.textSecondary}
|
|
strokeWidth={2.4}
|
|
/>
|
|
)}
|
|
</Pressable>
|
|
</>
|
|
)
|
|
}
|