Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
6 changes: 4 additions & 2 deletions apps/menu-bar/macos/ExpoMenuBar-macOS/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
117 changes: 83 additions & 34 deletions apps/menu-bar/src/popover/Core.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
Expand All @@ -48,6 +50,7 @@ import { WindowsNavigator } from '../windows';

type Props = {
isDevWindow: boolean;
onDeepLinkModeChange?: (isDeepLinkMode: boolean) => void;
};

function Core(props: Props) {
Expand Down Expand Up @@ -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);
Comment on lines +102 to +132
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can incorporate this logic into the useDeepLinking hook?

} else {
setIsDeepLinkMode(false);
}
}, [])
);

const { onDeepLinkModeChange } = props;
useEffect(() => {
onDeepLinkModeChange?.(isDeepLinkMode);
}, [isDeepLinkMode, onDeepLinkModeChange]);

const {
devicesPerPlatform,
numberOfDevices,
Expand Down Expand Up @@ -543,40 +588,44 @@ function Core(props: Props) {
return (
<View shrink="1">
<BuildsSection installAppFromURI={installAppFromURI} tasks={tasks} />
<ProjectsSection apps={apps} />
<View shrink="1" pt="tiny" overflow="hidden">
{devicesError ? (
<DevicesListError error={devicesError} />
) : (
<SectionList
sections={sections}
style={{ minHeight: estimatedListHeight }}
contentContainerStyle={{ width: '100%' }}
showsHorizontalScrollIndicator={false}
SectionSeparatorComponent={Separator}
renderSectionHeader={({ section: { label, error } }) => (
<DeviceListSectionHeader label={label} errorMessage={error?.message} />
{!isDeepLinkMode && (
<>
<ProjectsSection apps={apps} />
<View shrink="1" pt="tiny" overflow="hidden">
{devicesError ? (
<DevicesListError error={devicesError} />
) : (
<SectionList
sections={sections}
style={{ minHeight: estimatedListHeight }}
contentContainerStyle={{ width: '100%' }}
showsHorizontalScrollIndicator={false}
SectionSeparatorComponent={Separator}
renderSectionHeader={({ section: { label, error } }) => (
<DeviceListSectionHeader label={label} errorMessage={error?.message} />
)}
renderItem={({ item: device }: { item: Device }) => {
const platform = getDeviceOS(device);
const id = getDeviceId(device);
return (
<DeviceItem
device={device}
key={device.name}
onPress={() => 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 (
<DeviceItem
device={device}
key={device.name}
onPress={() => onSelectDevice(device)}
onPressLaunch={async () => {
Analytics.track(Event.LAUNCH_SIMULATOR);
await bootDeviceAsync({ platform, id });
refetch();
}}
selected={selectedDevicesIds[platform] === id}
/>
);
}}
/>
)}
</View>
</View>
</>
)}
</View>
);
}
Expand Down
11 changes: 8 additions & 3 deletions apps/menu-bar/src/popover/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -46,9 +51,9 @@ function Popover(props: Props) {
maxHeight: height,
}}>
<ErrorBoundary fallback={Fallback}>
<Core isDevWindow={props.isDevWindow} />
<Core isDevWindow={props.isDevWindow} onDeepLinkModeChange={onDeepLinkModeChange} />
</ErrorBoundary>
<Footer />
{!isDeepLinkMode && <Footer />}
</View>
);
}
Expand Down
Loading