Skip to content

Commit 84fa15d

Browse files
bra1nDumpclaudehappy-otter
committed
fix(app): restore native SwiftUI/Jetpack context menus on session avatar
Brings back .ios.tsx (SwiftUI ContextMenu) and .android.tsx (Jetpack Compose DropdownMenu) for long-press on session detail header avatar. Reverts barrel routing from .native to .ios/.android. Removes web long-press on avatar (right-click only). Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
1 parent ef2d50d commit 84fa15d

5 files changed

Lines changed: 133 additions & 4 deletions

File tree

packages/happy-app/sources/components/ChatHeaderView.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ export const ChatHeaderView: React.FC<ChatHeaderViewProps> = ({
167167
>
168168
<Pressable
169169
hitSlop={15}
170-
onLongPress={Platform.OS === 'web' && onAvatarMenuRequest ? requestAvatarMenuFromTrigger : undefined}
171170
onPress={handleAvatarPress}
172171
style={styles.avatarButton}
173172
{...webAvatarMenuProps}
@@ -183,7 +182,6 @@ export const ChatHeaderView: React.FC<ChatHeaderViewProps> = ({
183182
) : (
184183
<Pressable
185184
hitSlop={15}
186-
onLongPress={Platform.OS === 'web' && onAvatarMenuRequest ? requestAvatarMenuFromTrigger : undefined}
187185
onPress={handleAvatarPress}
188186
style={styles.avatarButton}
189187
{...webAvatarMenuProps}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as React from 'react';
2+
import { DropdownMenu, DropdownMenuItem } from '@expo/ui/jetpack-compose';
3+
import { useSessionQuickActions } from '@/hooks/useSessionQuickActions';
4+
import { Session } from '@/sync/storageTypes';
5+
import { t } from '@/text';
6+
7+
interface SessionActionsNativeMenuProps {
8+
children: React.ReactNode;
9+
onAfterArchive?: () => void;
10+
onAfterDelete?: () => void;
11+
session: Session;
12+
}
13+
14+
export function SessionActionsNativeMenu({
15+
children,
16+
onAfterArchive,
17+
onAfterDelete,
18+
session,
19+
}: SessionActionsNativeMenuProps) {
20+
const {
21+
archiveSession,
22+
canArchive,
23+
canCopySessionMetadata,
24+
canShowResume,
25+
copySessionMetadata,
26+
openDetails,
27+
resumeSession,
28+
} = useSessionQuickActions(session, {
29+
onAfterArchive,
30+
onAfterDelete,
31+
});
32+
33+
return (
34+
<DropdownMenu>
35+
<DropdownMenu.Items>
36+
<DropdownMenuItem onClick={openDetails}>
37+
<DropdownMenuItem.Text>Details</DropdownMenuItem.Text>
38+
</DropdownMenuItem>
39+
{canArchive && (
40+
<DropdownMenuItem onClick={archiveSession}>
41+
<DropdownMenuItem.Text>Archive</DropdownMenuItem.Text>
42+
</DropdownMenuItem>
43+
)}
44+
{canShowResume && (
45+
<DropdownMenuItem onClick={resumeSession}>
46+
<DropdownMenuItem.Text>Resume</DropdownMenuItem.Text>
47+
</DropdownMenuItem>
48+
)}
49+
{canCopySessionMetadata && (
50+
<DropdownMenuItem onClick={copySessionMetadata}>
51+
<DropdownMenuItem.Text>{t('sessionInfo.copyMetadata')}</DropdownMenuItem.Text>
52+
</DropdownMenuItem>
53+
)}
54+
</DropdownMenu.Items>
55+
<DropdownMenu.Trigger>{children}</DropdownMenu.Trigger>
56+
</DropdownMenu>
57+
);
58+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as React from 'react';
2+
import { Button, ContextMenu, Host } from '@expo/ui/swift-ui';
3+
import { useSessionQuickActions } from '@/hooks/useSessionQuickActions';
4+
import { Session } from '@/sync/storageTypes';
5+
import { t } from '@/text';
6+
7+
interface SessionActionsNativeMenuProps {
8+
children: React.ReactNode;
9+
onAfterArchive?: () => void;
10+
onAfterDelete?: () => void;
11+
session: Session;
12+
}
13+
14+
const iosSymbol = (name: string) =>
15+
name as unknown as React.ComponentProps<typeof Button>['systemImage'];
16+
17+
export function SessionActionsNativeMenu({
18+
children,
19+
onAfterArchive,
20+
onAfterDelete,
21+
session,
22+
}: SessionActionsNativeMenuProps) {
23+
const {
24+
archiveSession,
25+
canArchive,
26+
canCopySessionMetadata,
27+
canShowResume,
28+
copySessionMetadata,
29+
openDetails,
30+
resumeSession,
31+
} = useSessionQuickActions(session, {
32+
onAfterArchive,
33+
onAfterDelete,
34+
});
35+
36+
return (
37+
<Host matchContents>
38+
<ContextMenu>
39+
<ContextMenu.Items>
40+
<Button onPress={openDetails} systemImage={iosSymbol('info.circle')} label="Details" />
41+
{canArchive && (
42+
<Button onPress={archiveSession} systemImage={iosSymbol('archivebox')} label="Archive" />
43+
)}
44+
{canShowResume && (
45+
<Button onPress={resumeSession} systemImage={iosSymbol('play.circle')} label="Resume" />
46+
)}
47+
{canCopySessionMetadata && (
48+
<Button onPress={copySessionMetadata} systemImage={iosSymbol('ladybug')} label={t('sessionInfo.copyMetadata')} />
49+
)}
50+
</ContextMenu.Items>
51+
<ContextMenu.Trigger>{children}</ContextMenu.Trigger>
52+
</ContextMenu>
53+
</Host>
54+
);
55+
}

packages/happy-app/sources/components/SessionActionsNativeMenu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export interface SessionActionsNativeMenuProps {
1010
}
1111

1212
const SessionActionsNativeMenuImpl = Platform.select<React.ComponentType<SessionActionsNativeMenuProps>>({
13-
ios: require('./SessionActionsNativeMenu.native').SessionActionsNativeMenu,
14-
android: require('./SessionActionsNativeMenu.native').SessionActionsNativeMenu,
13+
ios: require('./SessionActionsNativeMenu.ios').SessionActionsNativeMenu,
14+
android: require('./SessionActionsNativeMenu.android').SessionActionsNativeMenu,
1515
default: require('./SessionActionsNativeMenu.web').SessionActionsNativeMenu,
1616
}) ?? require('./SessionActionsNativeMenu.web').SessionActionsNativeMenu;
1717

packages/happy-app/sources/hooks/useSessionQuickActions.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,24 @@ export function useSessionQuickActions(
208208

209209
const canCopySessionMetadata = __DEV__ || devModeEnabled;
210210

211+
const showActionAlert = React.useCallback(() => {
212+
const buttons: Array<{ text: string; onPress?: () => void; style?: 'cancel' | 'destructive' | 'default' }> = [];
213+
214+
buttons.push({ text: t('profile.details'), onPress: openDetails });
215+
216+
if (resumeAvailability.canShowResume) {
217+
buttons.push({ text: t('sessionInfo.resumeSession'), onPress: resumeSession });
218+
}
219+
220+
if (canCopySessionMetadata) {
221+
buttons.push({ text: t('sessionInfo.copyMetadata'), onPress: copySessionMetadata });
222+
}
223+
224+
buttons.push({ text: 'Archive', onPress: archiveSession, style: 'destructive' });
225+
buttons.push({ text: t('common.cancel'), style: 'cancel' });
226+
Modal.alert('Session', undefined, buttons);
227+
}, [archiveSession, canCopySessionMetadata, copySessionMetadata, openDetails, resumeAvailability.canShowResume, resumeSession]);
228+
211229
const actionItems = React.useMemo<SessionActionItem[]>(() => {
212230
const items: SessionActionItem[] = [
213231
{ id: 'details', icon: 'information-circle-outline', label: t('profile.details'), onPress: openDetails },

0 commit comments

Comments
 (0)