| author | |
| committer | |
| log | 930aec13da56f83c404bc00d92486b2409a5cac0 |
| tree | c11c30c894f4ba95fa047237a811e37781027567 |
| parent | fe93a5dbdd3c4496f402f71d232597c391c4973b |
| signature | Signed by PGP key B5690EEEBB952194 |
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 { |
| 5 | 5 | Linking, |
| 6 | 6 | Platform, |
| 7 | 7 | Pressable, |
| 8 | Text as SystemText, | |
| 8 | 9 | View, |
| 9 | 10 | type LayoutChangeEvent, |
| 10 | 11 | } from "react-native"; |
| ... | ... | @@ -245,6 +246,25 @@ const VoiceWaveform = memo(function VoiceWaveform(props: { |
| 245 | 246 | ); |
| 246 | 247 | }); |
| 247 | 248 | |
| 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 | */ | |
| 253 | export 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 | ||
| 248 | 268 | function VoiceActionButton(props: { |
| 249 | 269 | readonly accessibilityLabel: string; |
| 250 | 270 | readonly disabled?: boolean; |
| ... | ... | @@ -322,7 +342,6 @@ export function ComposerDictationStatus(props: { |
| 322 | 342 | |
| 323 | 343 | if (!props.presentation.statusLabel) return null; |
| 324 | 344 | const isError = props.presentation.statusKind === "error"; |
| 325 | const elapsedLabel = `${Math.floor(props.elapsedSeconds / 60)}:${String(props.elapsedSeconds % 60).padStart(2, "0")}`; | |
| 326 | 345 | return ( |
| 327 | 346 | <View className="relative h-11 min-w-0 flex-1 justify-center"> |
| 328 | 347 | {isError ? ( |
| ... | ... | @@ -357,13 +376,10 @@ export function ComposerDictationStatus(props: { |
| 357 | 376 | style={waveformStyle} |
| 358 | 377 | > |
| 359 | 378 | <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 | /> | |
| 367 | 383 | </Animated.View> |
| 368 | 384 | <Animated.View className="absolute inset-0 justify-center px-2" style={labelStyle}> |
| 369 | 385 | <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"; |
| 24 | 24 | import { SymbolView } from "../../components/AppSymbol"; |
| 25 | 25 | import { useAndroidControlSizing } from "../../components/useAndroidControlSizing"; |
| 26 | 26 | import { cn } from "../../lib/cn"; |
| 27 | import { CompactVoiceWaveform } from "./ComposerDictationControl"; | |
| 27 | import { CompactVoiceWaveform, DictationElapsedTime } from "./ComposerDictationControl"; | |
| 28 | 28 | import { useGlobalVoiceInput } from "./VoiceInputProvider"; |
| 29 | 29 | import { resolveVoiceComposerPresentation } from "./voiceInputPresentation"; |
| 30 | 30 | |
| ... | ... | @@ -114,7 +114,6 @@ function EdgeDictationPill(props: { readonly collapseRef: RefObject<(() => void) |
| 114 | 114 | const isError = phase === "error"; |
| 115 | 115 | const openSettings = isError && voice.state.errorAction === "settings"; |
| 116 | 116 | const label = voice.label ?? "Draft"; |
| 117 | const elapsedLabel = `${Math.floor(voice.elapsedSeconds / 60)}:${String(voice.elapsedSeconds % 60).padStart(2, "0")}`; | |
| 118 | 117 | |
| 119 | 118 | return ( |
| 120 | 119 | <View pointerEvents="box-none" className="absolute inset-0"> |
| ... | ... | @@ -149,12 +148,10 @@ function EdgeDictationPill(props: { readonly collapseRef: RefObject<(() => void) |
| 149 | 148 | ) : phase === "recording" ? ( |
| 150 | 149 | <> |
| 151 | 150 | <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 | /> | |
| 158 | 155 | </> |
| 159 | 156 | ) : ( |
| 160 | 157 | <ActivityIndicator size="small" colorClassName="accent-icon-muted" /> |
| ... | ... | @@ -202,12 +199,10 @@ function EdgeDictationPill(props: { readonly collapseRef: RefObject<(() => void) |
| 202 | 199 | {phase === "recording" ? ( |
| 203 | 200 | <> |
| 204 | 201 | <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 | /> | |
| 211 | 206 | <Pressable |
| 212 | 207 | accessibilityLabel="Finish dictation" |
| 213 | 208 | accessibilityRole="button" |