fix(composer): keep message input visible after dictation (#2194)
The voice overlay transition could retain stale native opacity after the app was backgrounded, leaving the mounted composer transparent. Derive both surfaces directly from voice state so they cannot disagree after resume.
This commit is contained in:
@@ -25,7 +25,6 @@ import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ICON_SIZE, type Theme } from "@/styles/theme";
|
||||
import { ArrowUp, Mic, MicOff, CornerDownLeft, Plus, Square } from "lucide-react-native";
|
||||
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from "react-native-reanimated";
|
||||
import { useDictation } from "@/hooks/use-dictation";
|
||||
import { DictationOverlay } from "@/components/dictation-controls";
|
||||
import { RealtimeVoiceOverlay } from "@/components/realtime-voice-overlay";
|
||||
@@ -68,6 +67,7 @@ import {
|
||||
} from "./labels";
|
||||
import {
|
||||
computeCanStartDictation,
|
||||
resolveComposerSurfacePresentation,
|
||||
runAlternateSendAction,
|
||||
runDefaultSendAction,
|
||||
stopRealtimeVoice,
|
||||
@@ -1290,7 +1290,6 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
getNativeElement: () => (isWeb ? getTextInputNativeElement(textInputRef.current) : null),
|
||||
}));
|
||||
const inputHeightRef = useRef(MIN_INPUT_HEIGHT);
|
||||
const overlayTransition = useSharedValue(0);
|
||||
const sendAfterTranscriptRef = useRef(false);
|
||||
const valueRef = useRef(value);
|
||||
const serverInfo = useSessionStore(
|
||||
@@ -1407,6 +1406,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
);
|
||||
const showRealtimeOverlay = isRealtimeVoiceForCurrentAgent;
|
||||
const showOverlay = showDictationOverlay || showRealtimeOverlay;
|
||||
const surfacePresentation = resolveComposerSurfacePresentation(showOverlay);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDictating || isDictationProcessing) {
|
||||
@@ -1427,22 +1427,6 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
[canStartDictation, dictationUnavailableMessage, startDictation, toast],
|
||||
);
|
||||
|
||||
// Animate overlay
|
||||
useEffect(() => {
|
||||
overlayTransition.value = withTiming(showOverlay ? 1 : 0, {
|
||||
duration: 200,
|
||||
});
|
||||
}, [overlayTransition, showOverlay]);
|
||||
|
||||
const overlayAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: overlayTransition.value,
|
||||
pointerEvents: overlayTransition.value > 0.5 ? "auto" : "none",
|
||||
}));
|
||||
|
||||
const inputAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: 1 - overlayTransition.value,
|
||||
}));
|
||||
|
||||
const handleVoicePress = useCallback(
|
||||
() =>
|
||||
handleVoicePressImpl({
|
||||
@@ -1760,8 +1744,12 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
}, [handleStopRealtimeVoice]);
|
||||
|
||||
const inputWrapperCombinedStyle = useMemo(
|
||||
() => [styles.inputWrapper, inputWrapperStyle, inputAnimatedStyle],
|
||||
[inputWrapperStyle, inputAnimatedStyle],
|
||||
() => [
|
||||
styles.inputWrapper,
|
||||
inputWrapperStyle,
|
||||
{ opacity: surfacePresentation.input.opacity },
|
||||
],
|
||||
[inputWrapperStyle, surfacePresentation.input.opacity],
|
||||
);
|
||||
const textInputStyle = useMemo(
|
||||
() => [styles.textInput, computeTextInputHeightStyle(inputHeight, maxInputHeight)],
|
||||
@@ -1772,8 +1760,8 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
[isSendButtonDisabled],
|
||||
);
|
||||
const overlayContainerStyle = useMemo(
|
||||
() => [styles.overlayContainer, overlayAnimatedStyle],
|
||||
[overlayAnimatedStyle],
|
||||
() => [styles.overlayContainer, { opacity: surfacePresentation.overlay.opacity }],
|
||||
[surfacePresentation.overlay.opacity],
|
||||
);
|
||||
|
||||
const renderAttachButtonIcon = useCallback(
|
||||
@@ -1802,7 +1790,11 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
return (
|
||||
<View ref={rootRef} style={styles.container} testID="message-input-root">
|
||||
{/* Regular input */}
|
||||
<Animated.View ref={inputWrapperRef} style={inputWrapperCombinedStyle}>
|
||||
<View
|
||||
ref={inputWrapperRef}
|
||||
style={inputWrapperCombinedStyle}
|
||||
pointerEvents={surfacePresentation.input.pointerEvents}
|
||||
>
|
||||
{attachmentSlot}
|
||||
{/* Text input */}
|
||||
<View style={styles.textInputScrollWrapper}>
|
||||
@@ -1881,9 +1873,12 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</View>
|
||||
|
||||
<Animated.View style={overlayContainerStyle}>
|
||||
<View
|
||||
style={overlayContainerStyle}
|
||||
pointerEvents={surfacePresentation.overlay.pointerEvents}
|
||||
>
|
||||
<MessageInputOverlay
|
||||
showDictationOverlay={showDictationOverlay}
|
||||
showRealtimeOverlay={showRealtimeOverlay}
|
||||
@@ -1901,7 +1896,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
onDiscardFailedRecording={handleDiscardFailedRecording}
|
||||
onRealtimeVoiceStop={handleRealtimeVoiceStop}
|
||||
/>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user