authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-17 02:37:55-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-17 02:37:55-07:00
logdd9528a974ba534faa8d1588f3f137fa2a281860
tree92ff2f9d00265158ae0324397e86086aeff7c67b
parent0150c6a53b409ba3bcb45645709b649cf8708354
signature Signed by PGP key B5690EEEBB952194

fix(mobile): keep screen awake during dictation (#12227)


4 files changed, 28 insertions(+), 8 deletions(-)

apps/mobile/package.json+1
...@@ -93,6 +93,7 @@...@@ -93,6 +93,7 @@
93 "expo-image": "~57.0.3",93 "expo-image": "~57.0.3",
94 "expo-image-manipulator": "~57.0.17",94 "expo-image-manipulator": "~57.0.17",
95 "expo-image-picker": "~57.0.14",95 "expo-image-picker": "~57.0.14",
96 "expo-keep-awake": "~57.0.2",
96 "expo-linking": "~57.0.8",97 "expo-linking": "~57.0.8",
97 "expo-network": "~57.0.1",98 "expo-network": "~57.0.1",
98 "expo-notifications": "~57.0.15",99 "expo-notifications": "~57.0.15",
apps/mobile/src/features/voice-input/useVoiceInputController.ts+16-1
...@@ -7,8 +7,9 @@ import {...@@ -7,8 +7,9 @@ import {
7 type RecordingStatus,7 type RecordingStatus,
8} from "expo-audio";8} from "expo-audio";
9import { File } from "expo-file-system";9import { File } from "expo-file-system";
10import { activateKeepAwakeAsync, deactivateKeepAwake } from "expo-keep-awake";
10import { useFocusEffect } from "@react-navigation/native";11import { useFocusEffect } from "@react-navigation/native";
11import { useCallback, useEffect, useRef, useState } from "react";12import { useCallback, useEffect, useId, useRef, useState } from "react";
12import { AppState } from "react-native";13import { AppState } from "react-native";
13import { useSharedValue } from "react-native-reanimated";14import { useSharedValue } from "react-native-reanimated";
1415
...@@ -71,6 +72,8 @@ export function useVoiceInputController(input: {...@@ -71,6 +72,8 @@ export function useVoiceInputController(input: {
71}) {72}) {
72 const [state, setState] = useState<VoiceInputState>(INITIAL_STATE);73 const [state, setState] = useState<VoiceInputState>(INITIAL_STATE);
73 const [elapsedSeconds, setElapsedSeconds] = useState(0);74 const [elapsedSeconds, setElapsedSeconds] = useState(0);
75 const keepAwakeId = useId();
76 const keepAwakeSessionRef = useRef(0);
74 const elapsedSecondsRef = useRef(0);77 const elapsedSecondsRef = useRef(0);
75 const audioLevelsRef = useRef(Array<number>(VOICE_WAVEFORM_SAMPLE_COUNT).fill(0));78 const audioLevelsRef = useRef(Array<number>(VOICE_WAVEFORM_SAMPLE_COUNT).fill(0));
76 const audioLevels = useSharedValue(audioLevelsRef.current);79 const audioLevels = useSharedValue(audioLevelsRef.current);
...@@ -156,6 +159,18 @@ export function useVoiceInputController(input: {...@@ -156,6 +159,18 @@ export function useVoiceInputController(input: {
156159
157 useEffect(() => () => controller.dispose(), [controller]);160 useEffect(() => () => controller.dispose(), [controller]);
158161
162 useEffect(() => {
163 if (state.phase !== "recording") return;
164
165 const tag = `voice-input:${keepAwakeId}:${++keepAwakeSessionRef.current}`;
166 const activation = activateKeepAwakeAsync(tag);
167 void activation.catch(() => {});
168 return () => {
169 // Release after activation settles, even if the recording ends immediately.
170 void activation.then(() => deactivateKeepAwake(tag)).catch(() => {});
171 };
172 }, [keepAwakeId, state.phase]);
173
159 useEffect(() => {174 useEffect(() => {
160 if (state.phase !== "preparing" && state.phase !== "recording") return;175 if (state.phase !== "preparing" && state.phase !== "recording") return;
161176
docs/user/composer.md+2-1
...@@ -131,7 +131,8 @@ recording started, ready for you to review and edit before sending....@@ -131,7 +131,8 @@ recording started, ready for you to review and edit before sending.
131The first use may download Apple's speech model and needs a network connection.131The first use may download Apple's speech model and needs a network connection.
132Later transcription works offline for that language. Recordings can be up to five132Later transcription works offline for that language. Recordings can be up to five
133minutes long. Canceling, leaving the screen, or an audio interruption discards the133minutes long. Canceling, leaving the screen, or an audio interruption discards the
134recording and preserves your existing draft.134recording and preserves your existing draft. While recording, the screen stays
135awake; it can sleep normally once recording stops.
135136
136Transcription runs on your device. T3 Code deletes the temporary audio after137Transcription runs on your device. T3 Code deletes the temporary audio after
137transcription or cancellation; only the message text is sent when you submit.138transcription or cancellation; only the message text is sent when you submit.
pnpm-lock.yaml+9-6
...@@ -373,6 +373,9 @@ importers:...@@ -373,6 +373,9 @@ importers:
373 expo-image-picker:373 expo-image-picker:
374 specifier: ~57.0.14374 specifier: ~57.0.14
375 version: 57.0.14(expo@57.0.18)375 version: 57.0.14(expo@57.0.18)
376 expo-keep-awake:
377 specifier: ~57.0.2
378 version: 57.0.2(expo@57.0.18)(react@19.2.3)
376 expo-linking:379 expo-linking:
377 specifier: ~57.0.8380 specifier: ~57.0.8
378 version: 57.0.8(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)381 version: 57.0.8(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
...@@ -7568,8 +7571,8 @@ packages:...@@ -7568,8 +7571,8 @@ packages:
7568 expo-json-utils@57.0.1:7571 expo-json-utils@57.0.1:
7569 resolution: {integrity: sha512-cgTe1NqzQdYs/WN+3nIY5IZg8s0pb0xaTUbhYvxQDn137GbwRfHoGM2se3m3Vsl4Qu+B9G4RPEK5WJDEU2Do7g==}7572 resolution: {integrity: sha512-cgTe1NqzQdYs/WN+3nIY5IZg8s0pb0xaTUbhYvxQDn137GbwRfHoGM2se3m3Vsl4Qu+B9G4RPEK5WJDEU2Do7g==}
75707573
7571 expo-keep-awake@57.0.1:7574 expo-keep-awake@57.0.2:
7572 resolution: {integrity: sha512-28lkFImeXTS+bhAjuCFV7w7tW5bXg27BJVrxv+nC/nyYa86qEa0oFeHwqol6ha5k4pdVDQgBF09GM4A1k76Ssg==}7575 resolution: {integrity: sha512-GqgH746wtJImmsbyHmCQoi7cOslKuWQBbJHXr53S35Bpf5UvxCAztvnitUEd7D0QvFN2rvkOhylbZpgZZfBLlQ==}
7573 peerDependencies:7576 peerDependencies:
7574 expo: '*'7577 expo: '*'
7575 react: '*'7578 react: '*'
...@@ -18157,12 +18160,12 @@ snapshots:...@@ -18157,12 +18160,12 @@ snapshots:
1815718160
18158 expo-json-utils@57.0.1: {}18161 expo-json-utils@57.0.1: {}
1815918162
18160 expo-keep-awake@57.0.1(expo@57.0.18)(react@19.2.3):18163 expo-keep-awake@57.0.2(expo@57.0.18)(react@19.2.3):
18161 dependencies:18164 dependencies:
18162 expo: 57.0.18(41fd11498a34454c91128cdad32f22f8)18165 expo: 57.0.18(41fd11498a34454c91128cdad32f22f8)
18163 react: 19.2.318166 react: 19.2.3
1816418167
18165 expo-keep-awake@57.0.1(expo@57.0.18)(react@19.2.6):18168 expo-keep-awake@57.0.2(expo@57.0.18)(react@19.2.6):
18166 dependencies:18169 dependencies:
18167 expo: 57.0.18(0bf40d5ad6281d8f1e92517b24812edb)18170 expo: 57.0.18(0bf40d5ad6281d8f1e92517b24812edb)
18168 react: 19.2.618171 react: 19.2.6
...@@ -18384,7 +18387,7 @@ snapshots:...@@ -18384,7 +18387,7 @@ snapshots:
18384 expo-constants: 57.0.16(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))18387 expo-constants: 57.0.16(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))
18385 expo-file-system: 57.0.6(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))18388 expo-file-system: 57.0.6(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))
18386 expo-font: 57.0.2(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6)18389 expo-font: 57.0.2(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6)
18387 expo-keep-awake: 57.0.1(expo@57.0.18)(react@19.2.6)18390 expo-keep-awake: 57.0.2(expo@57.0.18)(react@19.2.6)
18388 expo-modules-autolinking: 57.0.12(typescript@7.0.2)18391 expo-modules-autolinking: 57.0.12(typescript@7.0.2)
18389 expo-modules-core: 57.0.14(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6)18392 expo-modules-core: 57.0.14(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6)
18390 pretty-format: 29.7.018393 pretty-format: 29.7.0
...@@ -18427,7 +18430,7 @@ snapshots:...@@ -18427,7 +18430,7 @@ snapshots:
18427 expo-constants: 57.0.16(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))18430 expo-constants: 57.0.16(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
18428 expo-file-system: 57.0.6(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))18431 expo-file-system: 57.0.6(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
18429 expo-font: 57.0.2(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)18432 expo-font: 57.0.2(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18430 expo-keep-awake: 57.0.1(expo@57.0.18)(react@19.2.3)18433 expo-keep-awake: 57.0.2(expo@57.0.18)(react@19.2.3)
18431 expo-modules-autolinking: 57.0.12(typescript@7.0.2)18434 expo-modules-autolinking: 57.0.12(typescript@7.0.2)
18432 expo-modules-core: 57.0.14(react-native-worklets@0.11.4(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)18435 expo-modules-core: 57.0.14(react-native-worklets@0.11.4(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18433 pretty-format: 29.7.018436 pretty-format: 29.7.0