From dd9528a974ba534faa8d1588f3f137fa2a281860 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 17 Sep 2026 02:37:55 -0700 Subject: [PATCH] fix(mobile): keep screen awake during dictation (#12227) --- apps/mobile/package.json | 1 + .../voice-input/useVoiceInputController.ts | 17 ++++++++++++++++- docs/user/composer.md | 3 ++- pnpm-lock.yaml | 15 +++++++++------ 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/apps/mobile/package.json b/apps/mobile/package.json index b1d736621407ed75ab32f5e2a5a1b70bfe296b73..031a23b666d1a10d900993cfee9536b843bdf2ab 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -93,6 +93,7 @@ "expo-image": "~57.0.3", "expo-image-manipulator": "~57.0.17", "expo-image-picker": "~57.0.14", + "expo-keep-awake": "~57.0.2", "expo-linking": "~57.0.8", "expo-network": "~57.0.1", "expo-notifications": "~57.0.15", diff --git a/apps/mobile/src/features/voice-input/useVoiceInputController.ts b/apps/mobile/src/features/voice-input/useVoiceInputController.ts index 8d1c9a9780a6f43934995b18239f1ebce3e6f183..5fd15482d03738092b0c58132a6bbf7aff52a79c 100644 --- a/apps/mobile/src/features/voice-input/useVoiceInputController.ts +++ b/apps/mobile/src/features/voice-input/useVoiceInputController.ts @@ -7,8 +7,9 @@ import { type RecordingStatus, } from "expo-audio"; import { File } from "expo-file-system"; +import { activateKeepAwakeAsync, deactivateKeepAwake } from "expo-keep-awake"; import { useFocusEffect } from "@react-navigation/native"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useId, useRef, useState } from "react"; import { AppState } from "react-native"; import { useSharedValue } from "react-native-reanimated"; @@ -71,6 +72,8 @@ export function useVoiceInputController(input: { }) { const [state, setState] = useState(INITIAL_STATE); const [elapsedSeconds, setElapsedSeconds] = useState(0); + const keepAwakeId = useId(); + const keepAwakeSessionRef = useRef(0); const elapsedSecondsRef = useRef(0); const audioLevelsRef = useRef(Array(VOICE_WAVEFORM_SAMPLE_COUNT).fill(0)); const audioLevels = useSharedValue(audioLevelsRef.current); @@ -156,6 +159,18 @@ export function useVoiceInputController(input: { useEffect(() => () => controller.dispose(), [controller]); + useEffect(() => { + if (state.phase !== "recording") return; + + const tag = `voice-input:${keepAwakeId}:${++keepAwakeSessionRef.current}`; + const activation = activateKeepAwakeAsync(tag); + void activation.catch(() => {}); + return () => { + // Release after activation settles, even if the recording ends immediately. + void activation.then(() => deactivateKeepAwake(tag)).catch(() => {}); + }; + }, [keepAwakeId, state.phase]); + useEffect(() => { if (state.phase !== "preparing" && state.phase !== "recording") return; diff --git a/docs/user/composer.md b/docs/user/composer.md index f3be7a5f342d2ca75da5dacb5e7c9b08ff38644f..b0b42850c4c763de2d9d3098848740ac7b91d2b6 100644 --- a/docs/user/composer.md +++ b/docs/user/composer.md @@ -131,7 +131,8 @@ recording started, ready for you to review and edit before sending. The first use may download Apple's speech model and needs a network connection. Later transcription works offline for that language. Recordings can be up to five minutes long. Canceling, leaving the screen, or an audio interruption discards the -recording and preserves your existing draft. +recording and preserves your existing draft. While recording, the screen stays +awake; it can sleep normally once recording stops. Transcription runs on your device. T3 Code deletes the temporary audio after transcription or cancellation; only the message text is sent when you submit. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 905d4ac1de2d0b94c821e4c98894ab2c14306145..a2a0d9a96339a8510d1dc54af582b375a754419b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -373,6 +373,9 @@ importers: expo-image-picker: specifier: ~57.0.14 version: 57.0.14(expo@57.0.18) + expo-keep-awake: + specifier: ~57.0.2 + version: 57.0.2(expo@57.0.18)(react@19.2.3) expo-linking: specifier: ~57.0.8 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: expo-json-utils@57.0.1: resolution: {integrity: sha512-cgTe1NqzQdYs/WN+3nIY5IZg8s0pb0xaTUbhYvxQDn137GbwRfHoGM2se3m3Vsl4Qu+B9G4RPEK5WJDEU2Do7g==} - expo-keep-awake@57.0.1: - resolution: {integrity: sha512-28lkFImeXTS+bhAjuCFV7w7tW5bXg27BJVrxv+nC/nyYa86qEa0oFeHwqol6ha5k4pdVDQgBF09GM4A1k76Ssg==} + expo-keep-awake@57.0.2: + resolution: {integrity: sha512-GqgH746wtJImmsbyHmCQoi7cOslKuWQBbJHXr53S35Bpf5UvxCAztvnitUEd7D0QvFN2rvkOhylbZpgZZfBLlQ==} peerDependencies: expo: '*' react: '*' @@ -18157,12 +18160,12 @@ snapshots: expo-json-utils@57.0.1: {} - expo-keep-awake@57.0.1(expo@57.0.18)(react@19.2.3): + expo-keep-awake@57.0.2(expo@57.0.18)(react@19.2.3): dependencies: expo: 57.0.18(41fd11498a34454c91128cdad32f22f8) react: 19.2.3 - expo-keep-awake@57.0.1(expo@57.0.18)(react@19.2.6): + expo-keep-awake@57.0.2(expo@57.0.18)(react@19.2.6): dependencies: expo: 57.0.18(0bf40d5ad6281d8f1e92517b24812edb) react: 19.2.6 @@ -18384,7 +18387,7 @@ snapshots: 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)) 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)) 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) - expo-keep-awake: 57.0.1(expo@57.0.18)(react@19.2.6) + expo-keep-awake: 57.0.2(expo@57.0.18)(react@19.2.6) expo-modules-autolinking: 57.0.12(typescript@7.0.2) 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) pretty-format: 29.7.0 @@ -18427,7 +18430,7 @@ snapshots: 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)) 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)) 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) - expo-keep-awake: 57.0.1(expo@57.0.18)(react@19.2.3) + expo-keep-awake: 57.0.2(expo@57.0.18)(react@19.2.3) expo-modules-autolinking: 57.0.12(typescript@7.0.2) 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) pretty-format: 29.7.0 -- 2.54.0