Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions apps/menu-bar/macos/ExpoMenuBar-macOS/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ class AppDelegate: RCTAppDelegate, NSUserNotificationCenterDelegate {
}

@objc func getUrlEventHandler(_ event: NSAppleEventDescriptor, withReplyEvent replyEvent: NSAppleEventDescriptor) {
// Emit deepLinkOpened before opening popover so React sees the flag
// before the popoverFocused event arrives.
bridge?.enqueueJSCall(
"RCTDeviceEventEmitter.emit", args: ["deepLinkOpened", [:]])
popoverManager.openPopover()
RCTLinkingManager.getUrlEventHandler(event, withReplyEvent: replyEvent)
Comment thread
gabrieldonadel marked this conversation as resolved.
Outdated
}
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);
}, []);
Comment thread
gabrieldonadel marked this conversation as resolved.

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