import { useEffect, useRef, useState } from 'react'
import { ActivityIndicator, Animated, Easing, StyleSheet, Text } from 'react-native'
import { colors, spacing, typography } from '../theme/mobile-theme'
/** What the shell calls the wait from the bytes being on disk to the page having a frame. */
export const SHELL_OPENING_LABEL = 'Opening workspace'
/**
* Eased in, so the cover holds near full opacity through the handover: the page reports its own
* renderer's paint, and the compositor needs a frame or two more to put that on the app's surface.
* A linear fade left two frames of bare surface between the two on an emulator.
*/
export const SHELL_PAGE_COVER_FADE_MS = 220
/** The shell's neutral frame: one spinner and what it is waiting on. */
export function ShellWaitingFrame({ label }: { label: string }) {
return (
<>
{label}
>
)
}
/**
* That same frame, held over a mounted view until the page reports one of its own: a WebView that
* has not painted draws nothing, so uncovering at `ready` shows the surface behind it and nothing
* else for the whole of the page's boot. Never interactive even while opaque, so a report that
* never arrives strands a spinner over a usable page rather than a dead one.
*/
export function ShellPageCover({ label, visible }: { label: string; visible: boolean }) {
const opacity = useRef(new Animated.Value(1)).current
const [mounted, setMounted] = useState(visible)
useEffect(() => {
if (visible) {
opacity.setValue(1)
setMounted(true)
return
}
const fade = Animated.timing(opacity, {
toValue: 0,
duration: SHELL_PAGE_COVER_FADE_MS,
easing: Easing.in(Easing.quad),
useNativeDriver: true
})
// Unmounted on the callback rather than on a timer, so a fade the platform cut short does not
// leave an opaque cover behind; one that never calls back leaves a transparent inert one.
fade.start(({ finished }) => {
if (finished) {
setMounted(false)
}
})
return () => {
fade.stop()
}
}, [opacity, visible])
if (!mounted) {
return null
}
return (
)
}
const styles = StyleSheet.create({
cover: {
...StyleSheet.absoluteFillObject,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.bgBase,
paddingHorizontal: spacing.lg
},
label: {
fontSize: typography.bodySize,
color: colors.textSecondary,
marginTop: spacing.md,
textAlign: 'center'
}
})