authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-10-03 22:45:53-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-03 22:45:53-07:00
log930aec13da56f83c404bc00d92486b2409a5cac0
treec11c30c894f4ba95fa047237a811e37781027567
parentfe93a5dbdd3c4496f402f71d232597c391c4973b
signature Signed by PGP key B5690EEEBB952194

fix(mobile): keep the dictation timer from shifting width (#15504)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

2 files changed, 33 insertions(+), 22 deletions(-)

apps/mobile/src/features/voice-input/ComposerDictationControl.tsx+24-8
......@@ -5,6 +5,7 @@ import {
55 Linking,
66 Platform,
77 Pressable,
8 Text as SystemText,
89 View,
910 type LayoutChangeEvent,
1011} from "react-native";
......@@ -245,6 +246,25 @@ const VoiceWaveform = memo(function VoiceWaveform(props: {
245246 );
246247});
247248
249/**
250 * Renders the recording time in the system font: DM Sans has no tabular
251 * figures, so `tabular-nums` alone would still let the width jitter.
252 */
253export function DictationElapsedTime(props: {
254 readonly seconds: number;
255 readonly className?: string;
256}) {
257 return (
258 <SystemText
259 className={cn("text-xs", props.className)}
260 numberOfLines={1}
261 style={{ fontVariant: ["tabular-nums"] }}
262 >
263 {Math.floor(props.seconds / 60)}:{String(props.seconds % 60).padStart(2, "0")}
264 </SystemText>
265 );
266}
267
248268function VoiceActionButton(props: {
249269 readonly accessibilityLabel: string;
250270 readonly disabled?: boolean;
......@@ -322,7 +342,6 @@ export function ComposerDictationStatus(props: {
322342
323343 if (!props.presentation.statusLabel) return null;
324344 const isError = props.presentation.statusKind === "error";
325 const elapsedLabel = `${Math.floor(props.elapsedSeconds / 60)}:${String(props.elapsedSeconds % 60).padStart(2, "0")}`;
326345 return (
327346 <View className="relative h-11 min-w-0 flex-1 justify-center">
328347 {isError ? (
......@@ -357,13 +376,10 @@ export function ComposerDictationStatus(props: {
357376 style={waveformStyle}
358377 >
359378 <VoiceWaveform audioLevels={props.audioLevels} />
360 <Text
361 className="text-xs text-foreground-muted"
362 numberOfLines={1}
363 style={{ fontVariant: ["tabular-nums"] }}
364 >
365 {elapsedLabel}
366 </Text>
379 <DictationElapsedTime
380 className="text-foreground-muted"
381 seconds={props.elapsedSeconds}
382 />
367383 </Animated.View>
368384 <Animated.View className="absolute inset-0 justify-center px-2" style={labelStyle}>
369385 <Text className="text-center text-sm text-foreground-muted" numberOfLines={1}>
apps/mobile/src/features/voice-input/GlobalVoiceInputControl.tsx+9-14
......@@ -24,7 +24,7 @@ import { AppText as Text } from "../../components/AppText";
2424import { SymbolView } from "../../components/AppSymbol";
2525import { useAndroidControlSizing } from "../../components/useAndroidControlSizing";
2626import { cn } from "../../lib/cn";
27import { CompactVoiceWaveform } from "./ComposerDictationControl";
27import { CompactVoiceWaveform, DictationElapsedTime } from "./ComposerDictationControl";
2828import { useGlobalVoiceInput } from "./VoiceInputProvider";
2929import { resolveVoiceComposerPresentation } from "./voiceInputPresentation";
3030
......@@ -114,7 +114,6 @@ function EdgeDictationPill(props: { readonly collapseRef: RefObject<(() => void)
114114 const isError = phase === "error";
115115 const openSettings = isError && voice.state.errorAction === "settings";
116116 const label = voice.label ?? "Draft";
117 const elapsedLabel = `${Math.floor(voice.elapsedSeconds / 60)}:${String(voice.elapsedSeconds % 60).padStart(2, "0")}`;
118117
119118 return (
120119 <View pointerEvents="box-none" className="absolute inset-0">
......@@ -149,12 +148,10 @@ function EdgeDictationPill(props: { readonly collapseRef: RefObject<(() => void)
149148 ) : phase === "recording" ? (
150149 <>
151150 <View className="size-2 rounded-full bg-danger-foreground" />
152 <Text
153 className="text-xs text-foreground"
154 style={{ fontVariant: ["tabular-nums"] }}
155 >
156 {elapsedLabel}
157 </Text>
151 <DictationElapsedTime
152 className="text-foreground"
153 seconds={voice.elapsedSeconds}
154 />
158155 </>
159156 ) : (
160157 <ActivityIndicator size="small" colorClassName="accent-icon-muted" />
......@@ -202,12 +199,10 @@ function EdgeDictationPill(props: { readonly collapseRef: RefObject<(() => void)
202199 {phase === "recording" ? (
203200 <>
204201 <CompactVoiceWaveform audioLevels={voice.audioLevels} />
205 <Text
206 className="text-xs text-foreground-muted"
207 style={{ fontVariant: ["tabular-nums"] }}
208 >
209 {elapsedLabel}
210 </Text>
202 <DictationElapsedTime
203 className="text-foreground-muted"
204 seconds={voice.elapsedSeconds}
205 />
211206 <Pressable
212207 accessibilityLabel="Finish dictation"
213208 accessibilityRole="button"