diff --git a/CHANGELOG.md b/CHANGELOG.md index f6a24b04..409a64bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### 🎉 New features +- Show minimal popover with only progress bar when opened via deep link. ([#312](https://github.com/expo/orbit/pull/312) by [@krystofwoldrich](https://github.com/krystofwoldrich)) + ### 🐛 Bug fixes - Hide horizontal scroll indicator in menu popover. ([#305](https://github.com/expo/orbit/pull/305) by [@gabrieldonadel](https://github.com/gabrieldonadel)) diff --git a/apps/menu-bar/macos/ExpoMenuBar-macOS/AppDelegate.swift b/apps/menu-bar/macos/ExpoMenuBar-macOS/AppDelegate.swift index 6f28b932..0e9f1304 100644 --- a/apps/menu-bar/macos/ExpoMenuBar-macOS/AppDelegate.swift +++ b/apps/menu-bar/macos/ExpoMenuBar-macOS/AppDelegate.swift @@ -82,8 +82,10 @@ class AppDelegate: RCTAppDelegate, NSUserNotificationCenterDelegate { } @objc func getUrlEventHandler(_ event: NSAppleEventDescriptor, withReplyEvent replyEvent: NSAppleEventDescriptor) { - popoverManager.openPopover() - RCTLinkingManager.getUrlEventHandler(event, withReplyEvent: replyEvent) + // Emit RCTLinkingManager event before opening popover so React sees the flag + // before the popoverFocused event arrives. +RCTLinkingManager.getUrlEventHandler(event, withReplyEvent: replyEvent) +popoverManager.openPopover() } // MARK: - RCTBridgeDelegate diff --git a/apps/menu-bar/src/popover/Core.tsx b/apps/menu-bar/src/popover/Core.tsx index a76c1960..23600808 100644 --- a/apps/menu-bar/src/popover/Core.tsx +++ b/apps/menu-bar/src/popover/Core.tsx @@ -1,7 +1,7 @@ import { InternalError } from 'common-types'; import { MultipleAppsInTarballErrorDetails } from 'common-types/build/InternalError'; import { Device } from 'common-types/build/devices'; -import React, { memo, useCallback, useState } from 'react'; +import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; import { SectionList } from 'react-native'; import BuildsSection, { BUILDS_SECTION_HEIGHT } from './BuildsSection'; @@ -27,6 +27,8 @@ import { useGetPinnedApps } from '../hooks/useGetPinnedApps'; import { usePopoverFocusEffect } from '../hooks/usePopoverFocus'; import { useSafeDisplayDimensions } from '../hooks/useSafeDisplayDimensions'; import Alert from '../modules/Alert'; +import { DeviceEventEmitter } from '../modules/DeviceEventEmitter'; +import { Linking } from '../modules/Linking'; import MenuBarModule from '../modules/MenuBarModule'; import { SelectedDevicesIds, @@ -48,6 +50,7 @@ import { WindowsNavigator } from '../windows'; type Props = { isDevWindow: boolean; + onDeepLinkModeChange?: (isDeepLinkMode: boolean) => void; }; function Core(props: Props) { @@ -96,6 +99,48 @@ function Core(props: Props) { [setTasks] ); + // Track whether the popover was opened via a deep link (browser) or user click. + // In deep link mode, only the progress UI is shown. + const [isDeepLinkMode, setIsDeepLinkMode] = useState(false); + const deepLinkPending = useRef(false); + + // Handle cold start: if app was launched via URL scheme, React wasn't mounted + // when deepLinkOpened fired, so check the initial URL instead. + useEffect(() => { + Linking.getInitialURL().then((url) => { + if (url) { + setIsDeepLinkMode(true); + } + }); + }, []); + + useEffect(() => { + const listener = DeviceEventEmitter.addListener('deepLinkOpened', () => { + deepLinkPending.current = true; + }); + return () => listener.remove(); + }, []); + + // When popover is focused by user click, exit deep link mode. + // Deep link opens also trigger popoverFocused, but deepLinkPending + // distinguishes them: the native side emits deepLinkOpened before + // popoverFocused, so we check and consume the flag here. + usePopoverFocusEffect( + useCallback(() => { + if (deepLinkPending.current) { + deepLinkPending.current = false; + setIsDeepLinkMode(true); + } else { + setIsDeepLinkMode(false); + } + }, []) + ); + + const { onDeepLinkModeChange } = props; + useEffect(() => { + onDeepLinkModeChange?.(isDeepLinkMode); + }, [isDeepLinkMode, onDeepLinkModeChange]); + const { devicesPerPlatform, numberOfDevices, @@ -543,40 +588,44 @@ function Core(props: Props) { return ( - - - {devicesError ? ( - - ) : ( - ( - + {!isDeepLinkMode && ( + <> + + + {devicesError ? ( + + ) : ( + ( + + )} + renderItem={({ item: device }: { item: Device }) => { + const platform = getDeviceOS(device); + const id = getDeviceId(device); + return ( + onSelectDevice(device)} + onPressLaunch={async () => { + Analytics.track(Event.LAUNCH_SIMULATOR); + await bootDeviceAsync({ platform, id }); + refetch(); + }} + selected={selectedDevicesIds[platform] === id} + /> + ); + }} + /> )} - renderItem={({ item: device }: { item: Device }) => { - const platform = getDeviceOS(device); - const id = getDeviceId(device); - return ( - onSelectDevice(device)} - onPressLaunch={async () => { - Analytics.track(Event.LAUNCH_SIMULATOR); - await bootDeviceAsync({ platform, id }); - refetch(); - }} - selected={selectedDevicesIds[platform] === id} - /> - ); - }} - /> - )} - + + + )} ); } diff --git a/apps/menu-bar/src/popover/index.tsx b/apps/menu-bar/src/popover/index.tsx index 7e61d916..d618ec43 100644 --- a/apps/menu-bar/src/popover/index.tsx +++ b/apps/menu-bar/src/popover/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import Core from './Core'; import { ErrorBoundary, FallbackProps } from './ErrorBoundary'; @@ -18,6 +18,11 @@ type Props = { function Popover(props: Props) { const { height } = useSafeDisplayDimensions(); const { hasInitialized } = useListDevices(); + const [isDeepLinkMode, setIsDeepLinkMode] = useState(false); + + const onDeepLinkModeChange = useCallback((mode: boolean) => { + setIsDeepLinkMode(mode); + }, []); useEffect(() => { const hasSeenOnboarding = storage.getBoolean(hasSeenOnboardingStorageKey); @@ -46,9 +51,9 @@ function Popover(props: Props) { maxHeight: height, }}> - + -