import {useRef, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' import {Trans, useLingui} from '@lingui/react/macro' import {useApplyPullRequestOTAUpdate} from '#/lib/hooks/useOTAUpdates' import {atoms as a, useTheme, web} from '#/alf' import * as Admonition from '#/components/Admonition' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import * as TextField from '#/components/forms/TextField' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {IS_DEV, IS_INTERNAL, IS_NATIVE} from '#/env' /** * Reports which OTA channel the running bundle came from. On a standard channel * this is informational and only shown to internal builds, but a channel this * build doesn't normally receive updates from - e.g. a pull request deployment * applied from the dev settings or a deep link - is called out as a warning to * everyone, alongside a way back to a standard build. Renders nothing on web, or * in a dev build, where expo-updates is disabled and none of these actions can * do anything. */ export function OTAChannelNotice({style}: {style?: StyleProp}) { const t = useTheme() const {t: l} = useLingui() const switchChannelControl = Dialog.useDialogControl() /* * `pending` is shared by every action the hook exposes, so track which button * started the work to avoid spinning all of them at once. */ const [activeAction, setActiveAction] = useState<'restore' | 'check'>() const { currentChannel, defaultChannel, isCurrentlyRunningNonStandardChannel, isCurrentlyRunningPullRequestDeployment, restoreDefaultChannel, checkForUpdates, tryApplyUpdate, pending, } = useApplyPullRequestOTAUpdate() if (IS_DEV) return null if (!isCurrentlyRunningNonStandardChannel && !(IS_INTERNAL && IS_NATIVE)) { return null } /* * Internal builds get the dev tooling regardless of which channel is running. * Everyone else only ever sees the way back to a standard build. */ const showDevTools = IS_INTERNAL && IS_NATIVE return ( <> {isCurrentlyRunningNonStandardChannel ? ( <> Non-standard OTA channel This app is running a deployment of{' '} {currentChannel} . Restore the {defaultChannel} deployment to get back to a standard build. ) : ( <> OTA channel This app is receiving updates from{' '} {currentChannel ?? defaultChannel} . )} {isCurrentlyRunningNonStandardChannel && ( { setActiveAction('restore') void restoreDefaultChannel() }}> Restore default {pending && activeAction === 'restore' && ( )} )} {showDevTools && ( <> { setActiveAction('check') void checkForUpdates() }}> Check for updates {pending && activeAction === 'check' && ( )} switchChannelControl.open()}> Switch channel )} {showDevTools && ( void tryApplyUpdate(channel)} /> )} ) } /** * Prompts for an arbitrary OTA channel to apply an update from. Internal builds * only - the channel is free text because it can be any deployment our update * server knows about. */ function SwitchChannelDialog({ control, defaultChannel, onSubmit, }: { control: Dialog.DialogControlProps defaultChannel: string onSubmit: (channel: string) => void }) { const {t: l} = useLingui() const channel = useRef(defaultChannel) const onPressApply = () => { const value = channel.current.trim() if (!value) return /* * `onSubmit` shows native alerts of its own, which race with the sheet's * close animation if fired before it finishes. */ control.close(() => onSubmit(value)) } return ( Switch OTA channel Channel (channel.current = value)} accessibilityLabelledBy="ota-channel-label" autoCapitalize="none" autoCorrect={false} autoComplete="off" spellCheck={false} /> ) }