Skip to content

Commit 1656b52

Browse files
committed
fix(app): emote sheet + badge sheet improvements
1 parent 475ddea commit 1656b52

11 files changed

Lines changed: 632 additions & 160 deletions

File tree

src/components/Chat/Chat.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
664664

665665
const handleActionSheetReply = useCallback(() => {
666666
if (!selectedMessage) return;
667-
handleReply(selectedMessage.messageData as ChatMessageType<'usernotice'>);
667+
handleReply(selectedMessage.messageData);
668668
actionSheetRef.current?.dismiss();
669669
}, [selectedMessage, handleReply]);
670670

@@ -804,8 +804,6 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
804804

805805
const getLightenedColorRef = useRef(getLightenedColor);
806806
getLightenedColorRef.current = getLightenedColor;
807-
const handleReplyRef = useRef(handleReply);
808-
handleReplyRef.current = handleReply;
809807
const handleBadgeLongPressRef = useRef(handleBadgeLongPress);
810808
handleBadgeLongPressRef.current = handleBadgeLongPress;
811809
const handleMessageLongPressRef = useRef(handleMessageLongPress);
@@ -836,7 +834,6 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
836834
style={styles.messageContainer}
837835
parentDisplayName={msg.parentDisplayName}
838836
parentColor={msg.parentColor}
839-
onReply={handleReplyRef.current}
840837
replyDisplayName={msg.replyDisplayName}
841838
replyBody={msg.replyBody}
842839
onBadgePress={handleBadgeLongPressRef.current}
@@ -868,7 +865,6 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
868865
style={styles.messageContainer}
869866
parentDisplayName={msg.parentDisplayName}
870867
parentColor={msg.parentColor}
871-
onReply={handleReplyRef.current}
872868
replyDisplayName={msg.replyDisplayName}
873869
replyBody={msg.replyBody}
874870
onBadgePress={handleBadgeLongPressRef.current}
Lines changed: 186 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import { Button } from '@app/components/Button/Button';
22
import { Icon } from '@app/components/Icon/Icon';
3+
import { Image } from '@app/components/Image/Image';
34
import { Text } from '@app/components/Text/Text';
4-
import { replaceEmotesWithText } from '@app/utils/chat/replaceEmotesWithText';
55
import { ParsedPart } from '@app/utils/chat/replaceTextWithEmotes';
6-
import { BottomSheetModal, BottomSheetView } from '@gorhom/bottom-sheet';
7-
import { forwardRef, useCallback, useMemo } from 'react';
8-
import { View } from 'react-native';
6+
import {
7+
BottomSheetBackdrop,
8+
BottomSheetModal,
9+
BottomSheetView,
10+
} from '@gorhom/bottom-sheet';
11+
import { SymbolView } from 'expo-symbols';
12+
import { forwardRef, useCallback, useMemo, type ComponentProps } from 'react';
13+
import { Platform, View } from 'react-native';
14+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
915
import { StyleSheet } from 'react-native-unistyles';
1016

1117
interface Props {
@@ -17,80 +23,218 @@ interface Props {
1723

1824
export const ActionSheet = forwardRef<BottomSheetModal, Props>((props, ref) => {
1925
const { message, username, handleReply, handleCopy } = props;
26+
const insets = useSafeAreaInsets();
2027

21-
const snapPoints = useMemo(() => ['25%', '50%', '60%'], []);
28+
const snapPoints = useMemo(() => ['40%'], []);
2229

23-
const messageText = useCallback(
24-
() => replaceEmotesWithText(message),
25-
[message],
30+
const renderBackdrop = useCallback(
31+
(backdropProps: ComponentProps<typeof BottomSheetBackdrop>) => (
32+
<BottomSheetBackdrop
33+
{...backdropProps}
34+
appearsOnIndex={0}
35+
disappearsOnIndex={-1}
36+
opacity={0.5}
37+
pressBehavior="close"
38+
/>
39+
),
40+
[],
2641
);
2742

43+
const actions = useMemo(
44+
() => [
45+
{
46+
id: 'copy' as const,
47+
icon: 'copy',
48+
label: 'Copy Message',
49+
onPress: handleCopy,
50+
},
51+
{
52+
id: 'reply' as const,
53+
icon: 'arrowshape.turn.up.left',
54+
label: 'Reply',
55+
onPress: handleReply,
56+
},
57+
{
58+
id: 'report' as const,
59+
icon: 'arrow.up.right.square',
60+
label: 'Report message',
61+
onPress: () => {},
62+
},
63+
],
64+
[handleCopy, handleReply],
65+
);
66+
67+
const getSFSymbolName = useCallback(
68+
(actionId: 'copy' | 'reply' | 'report') => {
69+
switch (actionId) {
70+
case 'copy':
71+
return 'doc.on.doc' as const;
72+
case 'reply':
73+
return 'arrowshape.turn.up.left' as const;
74+
case 'report':
75+
return 'arrow.up.right.square' as const;
76+
default:
77+
return 'questionmark.circle' as const;
78+
}
79+
},
80+
[],
81+
);
82+
83+
const renderMessagePart = useCallback((part: ParsedPart, index: number) => {
84+
switch (part.type) {
85+
case 'emote':
86+
if (!part.url) return null;
87+
return (
88+
<Image
89+
key={`${part.type}-${part.id ?? index}-${index}`}
90+
useNitro
91+
source={part.url}
92+
style={styles.messageEmote}
93+
contentFit="contain"
94+
transition={0}
95+
/>
96+
);
97+
case 'mention':
98+
case 'text':
99+
return (
100+
<Text key={`${part.type}-${index}`} style={styles.messageText}>
101+
{part.content}
102+
</Text>
103+
);
104+
default:
105+
if ('content' in part && typeof part.content === 'string') {
106+
return (
107+
<Text key={`${part.type}-${index}`} style={styles.messageText}>
108+
{part.content}
109+
</Text>
110+
);
111+
}
112+
return null;
113+
}
114+
}, []);
115+
28116
return (
29117
<BottomSheetModal
30118
ref={ref}
31-
style={styles.contentContainer}
119+
detached
120+
enableDynamicSizing
121+
maxDynamicContentSize={460}
122+
backdropComponent={renderBackdrop}
123+
bottomInset={Math.max(insets.bottom + 8, 16)}
124+
style={styles.modalContainer}
32125
backgroundStyle={styles.bottomSheet}
126+
handleIndicatorStyle={styles.handle}
33127
enablePanDownToClose
34128
snapPoints={snapPoints}
35129
>
36130
<BottomSheetView style={styles.wrapper}>
37-
<View style={styles.info}>
38-
<Text>
39-
{username} : {messageText()}
40-
</Text>
131+
<View style={styles.previewCard}>
132+
<View style={styles.messageLine}>
133+
{username ? (
134+
<Text style={styles.usernameText}>{username}: </Text>
135+
) : null}
136+
{message.map(renderMessagePart)}
137+
</View>
41138
</View>
42139

43-
<View style={styles.actions}>
44-
<Button onPress={handleCopy} style={styles.actionButton}>
45-
<View style={styles.actionContent}>
46-
<Icon icon="copy" color="#fff" size={16} />
47-
<Text style={styles.actionText}>Copy Message</Text>
48-
</View>
49-
</Button>
50-
<Button onPress={handleReply} style={styles.actionButton}>
51-
<View style={styles.actionContent}>
52-
<Icon icon="copy" color="#fff" size={16} />
53-
<Text style={styles.actionText}>Reply</Text>
54-
</View>
55-
</Button>
56-
<Button onPress={() => {}} style={styles.actionButton}>
57-
<View style={styles.actionContent}>
58-
<Icon icon="external-link" color="#fff" size={16} />
59-
<Text style={styles.actionText}>Report message</Text>
60-
</View>
61-
</Button>
140+
<View style={styles.actionGroup}>
141+
{actions.map(action => (
142+
<Button
143+
key={action.label}
144+
onPress={action.onPress}
145+
style={styles.actionButton}
146+
>
147+
<View style={styles.actionContent}>
148+
{Platform.OS === 'ios' ? (
149+
<SymbolView
150+
name={getSFSymbolName(action.id)}
151+
size={18}
152+
tintColor="#b7bdc9"
153+
weight="regular"
154+
style={styles.actionIcon}
155+
/>
156+
) : (
157+
<Icon icon={action.icon} color="#b7bdc9" size={18} />
158+
)}
159+
<Text style={styles.actionText}>{action.label}</Text>
160+
</View>
161+
</Button>
162+
))}
62163
</View>
63164
</BottomSheetView>
64165
</BottomSheetModal>
65166
);
66167
});
67168

68169
const styles = StyleSheet.create(theme => ({
69-
contentContainer: {
70-
paddingHorizontal: theme.spacing.xl,
71-
overflow: 'visible',
170+
modalContainer: {
171+
marginHorizontal: theme.spacing.md,
72172
},
73173
bottomSheet: {
74-
// backgroundColor: theme.colors.borderFaint,
174+
backgroundColor: '#171b23',
175+
borderRadius: 28,
176+
},
177+
handle: {
178+
backgroundColor: theme.colors.gray.accent,
179+
width: 42,
180+
height: 5.5,
181+
borderRadius: theme.radii.full,
182+
marginTop: 2,
75183
},
76184
wrapper: {
185+
paddingTop: theme.spacing.md,
186+
paddingBottom: theme.spacing.xl,
187+
backgroundColor: '#171b23',
188+
gap: theme.spacing.lg,
189+
},
190+
previewCard: {
191+
paddingHorizontal: theme.spacing.md,
77192
paddingVertical: theme.spacing.md,
193+
backgroundColor: 'transparent',
194+
},
195+
messageLine: {
196+
flexDirection: 'row',
197+
flexWrap: 'wrap',
198+
alignItems: 'center',
199+
},
200+
usernameText: {
201+
color: theme.colors.gray.text,
202+
fontWeight: '600',
203+
fontSize: theme.font.fontSize.lg,
204+
},
205+
messageText: {
206+
color: theme.colors.gray.text,
207+
fontSize: theme.font.fontSize.lg,
208+
lineHeight: theme.font.fontSize.lg * 1.25,
209+
},
210+
messageEmote: {
211+
width: 24,
212+
height: 24,
213+
marginHorizontal: 2,
214+
},
215+
actionGroup: {
216+
borderRadius: theme.radii.xl,
217+
backgroundColor: 'transparent',
218+
overflow: 'hidden',
78219
},
79-
actions: { marginTop: theme.spacing.xl },
80220
actionButton: {
81-
paddingVertical: theme.spacing.md,
82-
marginBottom: theme.spacing.sm,
221+
minHeight: Platform.select({ ios: 56, android: 56 }),
222+
paddingHorizontal: theme.spacing.md,
223+
backgroundColor: 'transparent',
83224
},
84225
actionContent: {
85226
flexDirection: 'row',
86227
alignItems: 'center',
87228
gap: theme.spacing.sm,
88229
},
230+
actionIcon: {
231+
opacity: 0.9,
232+
},
89233
actionText: {
90-
// fontWeight: theme.font.fontWeight.thin,
91-
// fontSize: theme.font.fontSize.sm,
234+
color: theme.colors.gray.text,
235+
fontSize: theme.font.fontSize.md,
236+
fontWeight: Platform.select({ ios: '400', android: '400' }),
92237
},
93-
info: {},
94238
}));
95239

96240
ActionSheet.displayName = 'ActionSheet';

0 commit comments

Comments
 (0)