add nicer loading screen while OTA is loading

This commit is contained in:
Samuel Newman
2025-12-06 22:20:04 +02:00
parent 03f8020ee2
commit ad76954522
2 changed files with 43 additions and 8 deletions
+37 -7
View File
@@ -7,6 +7,7 @@ import {
fetchUpdateAsync, fetchUpdateAsync,
isEnabled, isEnabled,
reloadAsync, reloadAsync,
type ReloadScreenOptions,
setExtraParamAsync, setExtraParamAsync,
UpdateCheckResultNotAvailableReason, UpdateCheckResultNotAvailableReason,
useUpdates, useUpdates,
@@ -16,6 +17,7 @@ import {isNetworkError} from '#/lib/strings/errors'
import {logger} from '#/logger' import {logger} from '#/logger'
import {APP_VERSION, IS_IOS, IS_TESTFLIGHT} from '#/env' import {APP_VERSION, IS_IOS, IS_TESTFLIGHT} from '#/env'
import {device} from '#/storage' import {device} from '#/storage'
import {useTheme} from '#/alf'
const MINIMUM_MINIMIZE_TIME = 15 * 60e3 const MINIMUM_MINIMIZE_TIME = 15 * 60e3
const OTA_RECOVERY_WINDOW = 5 * 60e3 const OTA_RECOVERY_WINDOW = 5 * 60e3
@@ -83,7 +85,7 @@ async function setExtraParamsPullRequest(channel: string) {
await setExtraParamAsync('channel', channel) await setExtraParamAsync('channel', channel)
} }
async function updateTestflight() { async function updateTestflight(t: 'light' | 'dark') {
await setExtraParams() await setExtraParams()
const res = await checkForUpdateAsync() const res = await checkForUpdateAsync()
@@ -101,7 +103,9 @@ async function updateTestflight() {
text: 'Relaunch', text: 'Relaunch',
style: 'default', style: 'default',
onPress: async () => { onPress: async () => {
await reloadAsync() await reloadAsync({
reloadScreenOptions: splash(t),
})
}, },
}, },
], ],
@@ -110,6 +114,7 @@ async function updateTestflight() {
} }
export function useApplyPullRequestOTAUpdate() { export function useApplyPullRequestOTAUpdate() {
const t = useTheme()
const {currentlyRunning} = useUpdates() const {currentlyRunning} = useUpdates()
const [pending, setPending] = useState(false) const [pending, setPending] = useState(false)
const currentChannel = getRunningChannel(currentlyRunning) const currentChannel = getRunningChannel(currentlyRunning)
@@ -174,7 +179,9 @@ export function useApplyPullRequestOTAUpdate() {
* after the reload. * after the reload.
*/ */
// Linking.clearInitialURL() // Linking.clearInitialURL()
await reloadAsync() await reloadAsync({
reloadScreenOptions: splash(t.scheme),
})
} catch (e) { } catch (e) {
device.remove(['pendingOTAUpdate']) device.remove(['pendingOTAUpdate'])
throw e throw e
@@ -335,6 +342,7 @@ export function useOTAUpdateRecovery() {
export function useOTAUpdates() { export function useOTAUpdates() {
const shouldReceiveUpdates = isEnabled && !__DEV__ const shouldReceiveUpdates = isEnabled && !__DEV__
const t = useTheme()
const appState = useRef<AppStateStatus>('active') const appState = useRef<AppStateStatus>('active')
const lastMinimize = useRef(0) const lastMinimize = useRef(0)
const ranInitialCheck = useRef(false) const ranInitialCheck = useRef(false)
@@ -366,13 +374,13 @@ export function useOTAUpdates() {
const onIsTestFlight = useCallback(async () => { const onIsTestFlight = useCallback(async () => {
try { try {
await updateTestflight() await updateTestflight(t.scheme)
} catch (err: any) { } catch (err: any) {
if (!isNetworkError(err)) { if (!isNetworkError(err)) {
logger.error('Internal OTA Update Error', {safeMessage: err}) logger.error('Internal OTA Update Error', {safeMessage: err})
} }
} }
}, []) }, [t.scheme])
useEffect(() => { useEffect(() => {
// We don't need to check anything if the current update is a PR update // 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. // chances are that there isn't anything important going on in the current session.
if (lastMinimize.current <= Date.now() - MINIMUM_MINIMIZE_TIME) { if (lastMinimize.current <= Date.now() - MINIMUM_MINIMIZE_TIME) {
if (isUpdatePending) { if (isUpdatePending) {
await reloadAsync() await reloadAsync({
reloadScreenOptions: splash(t.scheme),
})
} else { } else {
setCheckTimeout() setCheckTimeout()
} }
@@ -431,5 +441,25 @@ export function useOTAUpdates() {
clearTimeout(timeout.current) clearTimeout(timeout.current)
subscription.remove() 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
} }
+6 -1
View File
@@ -4,6 +4,8 @@ import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
import {useMutation, useQuery} from '@tanstack/react-query' 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 {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as RetryIcon} from '#/components/icons/ArrowRotate' import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as RetryIcon} from '#/components/icons/ArrowRotate'
import {Shapes_Stroke2_Corner0_Rounded as ShapesIcon} from '#/components/icons/Shapes' import {Shapes_Stroke2_Corner0_Rounded as ShapesIcon} from '#/components/icons/Shapes'
@@ -13,6 +15,7 @@ import * as SettingsList from '../components/SettingsList'
export function OTAInfo() { export function OTAInfo() {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme()
const { const {
data: isAvailable, data: isAvailable,
isPending: isPendingInfo, isPending: isPendingInfo,
@@ -31,7 +34,9 @@ export function OTAInfo() {
useMutation({ useMutation({
mutationFn: async () => { mutationFn: async () => {
await Updates.fetchUpdateAsync() await Updates.fetchUpdateAsync()
await Updates.reloadAsync() await Updates.reloadAsync({
reloadScreenOptions: splash(t.scheme),
})
}, },
onError: error => onError: error =>
Toast.show(`Failed to update: ${error.message}`, { Toast.show(`Failed to update: ${error.message}`, {