From ad7695452270eeb955c513f171eb6e973cc90b15 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Sat, 6 Dec 2025 22:20:04 +0200 Subject: [PATCH] add nicer loading screen while OTA is loading --- src/lib/hooks/useOTAUpdates.ts | 44 +++++++++++++++++---- src/screens/Settings/components/OTAInfo.tsx | 7 +++- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/lib/hooks/useOTAUpdates.ts b/src/lib/hooks/useOTAUpdates.ts index 7b7224bcb0..6bab6337f6 100644 --- a/src/lib/hooks/useOTAUpdates.ts +++ b/src/lib/hooks/useOTAUpdates.ts @@ -7,6 +7,7 @@ import { fetchUpdateAsync, isEnabled, reloadAsync, + type ReloadScreenOptions, setExtraParamAsync, UpdateCheckResultNotAvailableReason, useUpdates, @@ -16,6 +17,7 @@ import {isNetworkError} from '#/lib/strings/errors' import {logger} from '#/logger' import {APP_VERSION, IS_IOS, IS_TESTFLIGHT} from '#/env' import {device} from '#/storage' +import {useTheme} from '#/alf' const MINIMUM_MINIMIZE_TIME = 15 * 60e3 const OTA_RECOVERY_WINDOW = 5 * 60e3 @@ -83,7 +85,7 @@ async function setExtraParamsPullRequest(channel: string) { await setExtraParamAsync('channel', channel) } -async function updateTestflight() { +async function updateTestflight(t: 'light' | 'dark') { await setExtraParams() const res = await checkForUpdateAsync() @@ -101,7 +103,9 @@ async function updateTestflight() { text: 'Relaunch', style: 'default', onPress: async () => { - await reloadAsync() + await reloadAsync({ + reloadScreenOptions: splash(t), + }) }, }, ], @@ -110,6 +114,7 @@ async function updateTestflight() { } export function useApplyPullRequestOTAUpdate() { + const t = useTheme() const {currentlyRunning} = useUpdates() const [pending, setPending] = useState(false) const currentChannel = getRunningChannel(currentlyRunning) @@ -174,7 +179,9 @@ export function useApplyPullRequestOTAUpdate() { * after the reload. */ // Linking.clearInitialURL() - await reloadAsync() + await reloadAsync({ + reloadScreenOptions: splash(t.scheme), + }) } catch (e) { device.remove(['pendingOTAUpdate']) throw e @@ -335,6 +342,7 @@ export function useOTAUpdateRecovery() { export function useOTAUpdates() { const shouldReceiveUpdates = isEnabled && !__DEV__ + const t = useTheme() const appState = useRef('active') const lastMinimize = useRef(0) const ranInitialCheck = useRef(false) @@ -366,13 +374,13 @@ export function useOTAUpdates() { const onIsTestFlight = useCallback(async () => { try { - await updateTestflight() + await updateTestflight(t.scheme) } catch (err: any) { if (!isNetworkError(err)) { logger.error('Internal OTA Update Error', {safeMessage: err}) } } - }, []) + }, [t.scheme]) useEffect(() => { // We don't need to check anything if the current update is a PR update @@ -414,7 +422,9 @@ export function useOTAUpdates() { // chances are that there isn't anything important going on in the current session. if (lastMinimize.current <= Date.now() - MINIMUM_MINIMIZE_TIME) { if (isUpdatePending) { - await reloadAsync() + await reloadAsync({ + reloadScreenOptions: splash(t.scheme), + }) } else { setCheckTimeout() } @@ -431,5 +441,25 @@ export function useOTAUpdates() { clearTimeout(timeout.current) subscription.remove() } - }, [isUpdatePending, currentChannel, setCheckTimeout]) + }, [isUpdatePending, currentChannel, setCheckTimeout, t.scheme]) +} + +/** + * Splash screen for while the app is updating + */ +export const splash = (scheme: 'light' | 'dark') => { + return { + image: + scheme === 'light' + ? require('../../../assets/splash/splash.png') + : require('../../../assets/splash/splash-dark.png'), + imageFullScreen: true, + imageResizeMode: 'cover', + backgroundColor: scheme === 'light' ? '#0c7cff' : '#0c2a49', + spinner: { + enabled: true, + color: '#ffffff', + size: 'large', + }, + } satisfies ReloadScreenOptions } diff --git a/src/screens/Settings/components/OTAInfo.tsx b/src/screens/Settings/components/OTAInfo.tsx index 9ab1962b97..25b2c828d9 100644 --- a/src/screens/Settings/components/OTAInfo.tsx +++ b/src/screens/Settings/components/OTAInfo.tsx @@ -4,6 +4,8 @@ import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useMutation, useQuery} from '@tanstack/react-query' +import {splash} from '#/lib/hooks/useOTAUpdates' +import {useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as RetryIcon} from '#/components/icons/ArrowRotate' import {Shapes_Stroke2_Corner0_Rounded as ShapesIcon} from '#/components/icons/Shapes' @@ -13,6 +15,7 @@ import * as SettingsList from '../components/SettingsList' export function OTAInfo() { const {_} = useLingui() + const t = useTheme() const { data: isAvailable, isPending: isPendingInfo, @@ -31,7 +34,9 @@ export function OTAInfo() { useMutation({ mutationFn: async () => { await Updates.fetchUpdateAsync() - await Updates.reloadAsync() + await Updates.reloadAsync({ + reloadScreenOptions: splash(t.scheme), + }) }, onError: error => Toast.show(`Failed to update: ${error.message}`, {