Skip to content

Commit 141a74c

Browse files
committed
Replace ActionSheet with ContextMenu, switch to @expo/ui packages, fix thread share scale and strict equality
1 parent 89f6647 commit 141a74c

6 files changed

Lines changed: 595 additions & 329 deletions

File tree

components/Comment.jsx

Lines changed: 166 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
1-
import { useCallback, useRef } from 'react';
2-
import {
3-
ActionSheetIOS,
4-
Alert,
5-
Animated,
6-
Pressable,
7-
StyleSheet,
8-
View,
9-
findNodeHandle,
10-
} from 'react-native';
1+
import { useCallback } from 'react';
2+
import { Alert, StyleSheet, View, useWindowDimensions } from 'react-native';
113

4+
import {
5+
ContextMenu,
6+
Button,
7+
Divider,
8+
Host,
9+
RNHostView,
10+
} from '@expo/ui/swift-ui';
1211
import { useRouter } from 'expo-router';
1312
import { TouchableOpacity } from 'react-native-gesture-handler';
1413

1514
import * as Haptics from 'expo-haptics';
1615

17-
import { format } from 'date-fns/format';
18-
1916
import useStore from '../hooks/useStore';
2017
import useTheme from '../hooks/useTheme';
21-
import useViewport from '../hooks/useViewport';
2218

2319
import openBrowser from '../utils/openBrowser';
2420
import openShare from '../utils/openShare';
@@ -54,9 +50,8 @@ export default function Comment({
5450
}) {
5551
const router = useRouter();
5652
const { colors } = useTheme();
57-
const { exceedsReadableWidth } = useViewport();
53+
const { height: windowHeight } = useWindowDimensions();
5854
const { id, user, time, content, deleted, dead, comments } = item;
59-
const datetime = new Date(time * 1000);
6055
const currentOP = useStore(
6156
useCallback(
6257
(state) =>
@@ -71,190 +66,168 @@ export default function Comment({
7166
if (dead || (deleted && !comments.length)) return null;
7267
const hnURL = `https://news.ycombinator.com/item?id=${id}`;
7368

74-
const scaleAnim = useRef(new Animated.Value(1)).current;
75-
const bobble = useCallback(() => {
76-
Animated.sequence([
77-
Animated.timing(scaleAnim, {
78-
toValue: 0.97,
79-
duration: 150,
80-
useNativeDriver: true,
81-
}),
82-
Animated.timing(scaleAnim, {
83-
toValue: 1,
84-
duration: 150,
85-
useNativeDriver: true,
86-
}),
87-
]).start();
88-
}, [scaleAnim]);
89-
90-
const commentRef = useRef();
91-
92-
const options = [
93-
{
94-
text: 'View profile',
95-
action: () => {
96-
router.push(`/user/${user}`);
97-
},
98-
},
99-
!settingsInteractions && {
100-
text: 'View comment on HN web site',
101-
action: () => {
102-
openBrowser(hnURL);
103-
},
104-
},
105-
settingsInteractions && {
106-
text: 'Upvote comment on HN',
107-
action: () => {
108-
const jsKey = `web-view-${Date.now()}`;
109-
useStore.getState().setRouteInjectedJS(
110-
jsKey,
111-
`
112-
try {
113-
document.getElementById('up_${id}').click();
114-
} catch (e) {}
115-
true;
116-
`,
117-
);
118-
router.push({
119-
pathname: '/web-view',
120-
params: {
121-
url: hnURL,
122-
jsKey,
123-
},
124-
});
125-
},
126-
},
127-
settingsInteractions && {
128-
text: 'View or Reply comment on HN',
129-
action: () => {
130-
router.push({
131-
pathname: '/web-view',
132-
params: {
133-
url: `https://news.ycombinator.com/reply?id=${id}&goto=${encodeURIComponent(
134-
`item?id=${id}`,
135-
)}`,
136-
},
137-
});
138-
},
139-
},
140-
!disableViewThread && {
141-
text: "View comment's thread",
142-
action: () => {
143-
router.push(`/thread/${storyID}/${id}`);
144-
},
145-
},
146-
!disableViewThread && {
147-
text: 'Share as Image…',
148-
action: () => {
149-
router.push(`/thread/${storyID}/${id}?tab=share`);
150-
},
151-
},
152-
{
153-
text: 'Share comment…',
154-
action: () => {
155-
openShare({ url: hnURL });
156-
},
157-
},
158-
__DEV__ && {
159-
text: '🚧 View HTML',
160-
action: () => {
161-
Alert.alert('Comment HTML', content);
162-
},
163-
},
164-
{
165-
text: 'Cancel',
166-
cancel: true,
167-
},
168-
].filter(Boolean);
169-
170-
const onShowActionSheet = useCallback(() => {
171-
ActionSheetIOS.showActionSheetWithOptions(
172-
{
173-
title: `Comment by ${user}`,
174-
message: `${format(datetime, 'EEEE, d LLLL yyyy, h:mm a')}\n${hnURL}`,
175-
options: options.map((o) => o.text),
176-
cancelButtonIndex: options.findIndex((o) => o.cancel),
177-
anchor: exceedsReadableWidth
178-
? findNodeHandle(commentRef.current)
179-
: undefined,
180-
},
181-
(index) => {
182-
options[index].action?.();
183-
},
184-
);
185-
}, [options, user, datetime, hnURL]);
186-
187-
return (
188-
<Pressable
189-
ref={commentRef}
190-
onLongPress={() => {
191-
bobble();
192-
Haptics.selectionAsync();
193-
onShowActionSheet();
194-
}}
195-
>
196-
<Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
197-
{!deleted && (
198-
<View style={styles.metadata}>
199-
<Text
200-
size={significant ? 'body' : 'subhead'}
201-
bold={!significant}
202-
bolder={significant}
203-
style={{
204-
color: colors.red,
205-
flexShrink: 1,
206-
}}
207-
numberOfLines={1}
69+
const commentContent = (
70+
<View>
71+
{!deleted && (
72+
<View style={styles.metadata}>
73+
<Text
74+
size={significant ? 'body' : 'subhead'}
75+
bold={!significant}
76+
bolder={significant}
77+
style={{
78+
color: colors.red,
79+
flexShrink: 1,
80+
}}
81+
numberOfLines={1}
82+
onPress={() => {
83+
router.push(`/user/${user}`);
84+
}}
85+
>
86+
{user}
87+
</Text>
88+
{user === currentOP && (
89+
<TouchableOpacity
90+
style={[
91+
styles.opBox,
92+
{
93+
backgroundColor: colors.red,
94+
},
95+
]}
20896
onPress={() => {
209-
router.push(`/user/${user}`);
97+
Alert.alert(
98+
'What does OP mean?',
99+
'OP is short for Original Poster — the person who posted this story.',
100+
);
101+
}}
102+
hitSlop={{
103+
top: 10,
104+
right: 10,
105+
bottom: 20,
106+
left: 10,
210107
}}
211108
>
212-
{user}
213-
</Text>
214-
{user === currentOP && (
215-
<TouchableOpacity
216-
style={[
217-
styles.opBox,
218-
{
219-
backgroundColor: colors.red,
220-
},
221-
]}
222-
onPress={() => {
223-
Alert.alert(
224-
'What does OP mean?',
225-
'OP is short for Original Poster — the person who posted this story.',
226-
);
227-
}}
228-
hitSlop={{
229-
top: 10,
230-
right: 10,
231-
bottom: 20,
232-
left: 10,
233-
}}
234-
>
235-
<Text
236-
size={significant ? 'footnote' : 'caption2'}
237-
style={styles.op}
238-
>
239-
OP
240-
</Text>
241-
</TouchableOpacity>
242-
)}
243-
<Text size={significant ? 'body' : 'subhead'} type="insignificant">
244-
{' '}
245-
&bull;{' '}
246109
<Text
247-
size={significant ? 'body' : 'subhead'}
248-
type="insignificant"
249-
onPress={onShowActionSheet}
110+
size={significant ? 'footnote' : 'caption2'}
111+
style={styles.op}
250112
>
251-
<TimeAgo time={datetime} />
113+
OP
252114
</Text>
115+
</TouchableOpacity>
116+
)}
117+
<Text size={significant ? 'body' : 'subhead'} type="insignificant">
118+
{' '}
119+
&bull;{' '}
120+
<Text size={significant ? 'body' : 'subhead'} type="insignificant">
121+
<TimeAgo time={new Date(time * 1000)} />
253122
</Text>
254-
</View>
255-
)}
256-
<HTMLView2 html={content} fontSize={significant ? 17 : undefined} />
257-
</Animated.View>
258-
</Pressable>
123+
</Text>
124+
</View>
125+
)}
126+
<HTMLView2 html={content} fontSize={significant ? 17 : undefined} />
127+
</View>
128+
);
129+
130+
return (
131+
<Host matchContents>
132+
<ContextMenu>
133+
<ContextMenu.Trigger>
134+
<RNHostView matchContents>{commentContent}</RNHostView>
135+
</ContextMenu.Trigger>
136+
<ContextMenu.Preview>
137+
<RNHostView matchContents>
138+
<View
139+
style={{
140+
padding: 15,
141+
maxHeight: Math.max(100, windowHeight * 0.33),
142+
}}
143+
>
144+
{commentContent}
145+
</View>
146+
</RNHostView>
147+
</ContextMenu.Preview>
148+
<ContextMenu.Items>
149+
<Button
150+
label="View profile"
151+
systemImage="person"
152+
onPress={() => router.push(`/user/${user}`)}
153+
/>
154+
<Divider />
155+
{!settingsInteractions && (
156+
<Button
157+
label="View comment on HN web site"
158+
systemImage="safari"
159+
onPress={() => openBrowser(hnURL)}
160+
/>
161+
)}
162+
{settingsInteractions && (
163+
<Button
164+
label="Upvote comment on HN"
165+
systemImage="arrowtriangle.up.fill"
166+
onPress={() => {
167+
Haptics.selectionAsync();
168+
const jsKey = `web-view-${Date.now()}`;
169+
useStore.getState().setRouteInjectedJS(
170+
jsKey,
171+
`
172+
try {
173+
document.getElementById('up_${id}').click();
174+
} catch (e) {}
175+
true;
176+
`,
177+
);
178+
router.push({
179+
pathname: '/web-view',
180+
params: { url: hnURL, jsKey },
181+
});
182+
}}
183+
/>
184+
)}
185+
{settingsInteractions && (
186+
<Button
187+
label="View or Reply comment on HN"
188+
systemImage="arrowshape.turn.up.left"
189+
onPress={() =>
190+
router.push({
191+
pathname: '/web-view',
192+
params: {
193+
url: `https://news.ycombinator.com/reply?id=${id}&goto=${encodeURIComponent(
194+
`item?id=${id}`,
195+
)}`,
196+
},
197+
})
198+
}
199+
/>
200+
)}
201+
<Divider />
202+
{!disableViewThread && (
203+
<Button
204+
label="View comment's thread"
205+
systemImage="text.bubble"
206+
onPress={() => router.push(`/thread/${storyID}/${id}`)}
207+
/>
208+
)}
209+
{!disableViewThread && (
210+
<Button
211+
label="Share as Image…"
212+
systemImage="photo"
213+
onPress={() => router.push(`/thread/${storyID}/${id}?tab=share`)}
214+
/>
215+
)}
216+
<Button
217+
label="Share comment…"
218+
systemImage="square.and.arrow.up"
219+
onPress={() => openShare({ url: hnURL })}
220+
/>
221+
{__DEV__ && <Divider />}
222+
{__DEV__ && (
223+
<Button
224+
label="View HTML"
225+
systemImage="chevron.left.forwardslash.chevron.right"
226+
onPress={() => Alert.alert('Comment HTML', content)}
227+
/>
228+
)}
229+
</ContextMenu.Items>
230+
</ContextMenu>
231+
</Host>
259232
);
260233
}

0 commit comments

Comments
 (0)