@planningcenter/chat-react-native 3.30.0 → 3.31.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/conversation/system_message.d.ts +9 -0
- package/build/components/conversation/system_message.d.ts.map +1 -0
- package/build/components/conversation/system_message.js +93 -0
- package/build/components/conversation/system_message.js.map +1 -0
- package/build/navigation/index.d.ts +5 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +5 -0
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +20 -1
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/index.d.ts +1 -0
- package/build/screens/index.d.ts.map +1 -1
- package/build/screens/index.js +1 -0
- package/build/screens/index.js.map +1 -1
- package/build/screens/message_actions_screen.d.ts +1 -0
- package/build/screens/message_actions_screen.d.ts.map +1 -1
- package/build/screens/message_actions_screen.js +12 -12
- package/build/screens/message_actions_screen.js.map +1 -1
- package/build/screens/system_message_people_screen.d.ts +9 -0
- package/build/screens/system_message_people_screen.d.ts.map +1 -0
- package/build/screens/system_message_people_screen.js +68 -0
- package/build/screens/system_message_people_screen.js.map +1 -0
- package/build/types/jolt_events/message_events.d.ts +7 -0
- package/build/types/jolt_events/message_events.d.ts.map +1 -1
- package/build/types/jolt_events/message_events.js.map +1 -1
- package/build/types/resources/message.d.ts +7 -0
- package/build/types/resources/message.d.ts.map +1 -1
- package/build/types/resources/message.js.map +1 -1
- package/build/utils/cache/optimistically_create_message.d.ts.map +1 -1
- package/build/utils/cache/optimistically_create_message.js +1 -0
- package/build/utils/cache/optimistically_create_message.js.map +1 -1
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +1 -0
- package/build/utils/index.js.map +1 -1
- package/build/utils/jolt/transform_message_event_data_to_message_resource.d.ts.map +1 -1
- package/build/utils/jolt/transform_message_event_data_to_message_resource.js +9 -0
- package/build/utils/jolt/transform_message_event_data_to_message_resource.js.map +1 -1
- package/build/utils/request/messages_data_options.d.ts.map +1 -1
- package/build/utils/request/messages_data_options.js +3 -0
- package/build/utils/request/messages_data_options.js.map +1 -1
- package/build/utils/system_messages.d.ts +15 -0
- package/build/utils/system_messages.d.ts.map +1 -0
- package/build/utils/system_messages.js +5 -0
- package/build/utils/system_messages.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/utils/system_messages.ts +79 -0
- package/src/components/conversation/system_message.tsx +130 -0
- package/src/navigation/index.tsx +8 -0
- package/src/screens/conversation_screen.tsx +25 -2
- package/src/screens/index.ts +1 -0
- package/src/screens/message_actions_screen.tsx +64 -57
- package/src/screens/system_message_people_screen.tsx +94 -0
- package/src/types/jolt_events/message_events.ts +3 -0
- package/src/types/resources/message.ts +3 -0
- package/src/utils/cache/optimistically_create_message.ts +1 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/jolt/transform_message_event_data_to_message_resource.ts +9 -0
- package/src/utils/request/messages_data_options.ts +3 -0
- package/src/utils/system_messages.ts +16 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { useNavigation } from '@react-navigation/native'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { Pressable, StyleSheet, View } from 'react-native'
|
|
4
|
+
import Animated from 'react-native-reanimated'
|
|
5
|
+
import { useAnimatedMessageBackgroundColor, useTheme } from '../../hooks'
|
|
6
|
+
import { ReactionCountResource } from '../../types/resources/reaction'
|
|
7
|
+
import { Haptic } from '../../utils/native_adapters'
|
|
8
|
+
import { SystemMessageResource } from '../../utils/system_messages'
|
|
9
|
+
import { Text } from '../display'
|
|
10
|
+
import { MessageReaction } from './message_reaction'
|
|
11
|
+
|
|
12
|
+
interface SystemMessageProps {
|
|
13
|
+
message: SystemMessageResource
|
|
14
|
+
conversationId: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function SystemMessage({ message, conversationId }: SystemMessageProps) {
|
|
18
|
+
const { reactionCounts, systemTextParts, personIdsForSystemEvent } = message
|
|
19
|
+
const { names, overflowCount, action } = systemTextParts
|
|
20
|
+
const styles = useStyles()
|
|
21
|
+
const navigation = useNavigation()
|
|
22
|
+
const { colors } = useTheme()
|
|
23
|
+
const text = systemMessageText(names, overflowCount, action)
|
|
24
|
+
const hasPeople = personIdsForSystemEvent.length > 0
|
|
25
|
+
const hasReactions = reactionCounts.length > 0
|
|
26
|
+
const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
|
|
27
|
+
useAnimatedMessageBackgroundColor()
|
|
28
|
+
|
|
29
|
+
const handlePress = () => {
|
|
30
|
+
navigation.navigate('SystemMessagePeople', {
|
|
31
|
+
conversation_id: conversationId,
|
|
32
|
+
person_ids: personIdsForSystemEvent,
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const handleLongPress = () => {
|
|
37
|
+
Haptic.impactLight()
|
|
38
|
+
navigation.navigate('MessageActions', {
|
|
39
|
+
message_id: message.id,
|
|
40
|
+
conversation_id: conversationId,
|
|
41
|
+
isSystemMessage: true,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const handleReactionLongPress = (reaction: ReactionCountResource) => {
|
|
46
|
+
Haptic.impactLight()
|
|
47
|
+
navigation.navigate('Reactions', {
|
|
48
|
+
message_id: message.id,
|
|
49
|
+
conversation_id: conversationId,
|
|
50
|
+
reaction_value: reaction.value,
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Pressable
|
|
56
|
+
onPress={hasPeople ? handlePress : undefined}
|
|
57
|
+
onLongPress={handleLongPress}
|
|
58
|
+
onPressIn={handleMessagePressIn}
|
|
59
|
+
onPressOut={handleMessagePressOut}
|
|
60
|
+
android_ripple={{ color: colors.androidRippleNeutral, borderless: false, foreground: true }}
|
|
61
|
+
accessibilityRole="button"
|
|
62
|
+
accessibilityHint={
|
|
63
|
+
hasPeople
|
|
64
|
+
? 'Tap to see people. Long press for message actions.'
|
|
65
|
+
: 'Long press for message actions.'
|
|
66
|
+
}
|
|
67
|
+
>
|
|
68
|
+
<Animated.View style={[styles.container, animatedBackgroundColor]}>
|
|
69
|
+
<Text variant="tertiary" style={styles.text}>
|
|
70
|
+
{text}
|
|
71
|
+
</Text>
|
|
72
|
+
{hasReactions && (
|
|
73
|
+
<View style={styles.reactions}>
|
|
74
|
+
{message.reactionCounts.map(reaction => (
|
|
75
|
+
<MessageReaction
|
|
76
|
+
key={reaction.value}
|
|
77
|
+
reaction={reaction}
|
|
78
|
+
onLongPress={handleReactionLongPress}
|
|
79
|
+
message={message}
|
|
80
|
+
conversationId={conversationId}
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
83
|
+
</View>
|
|
84
|
+
)}
|
|
85
|
+
</Animated.View>
|
|
86
|
+
</Pressable>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function systemMessageText(names: string[], overflowCount: number, action: string): string {
|
|
91
|
+
if (overflowCount > 0) {
|
|
92
|
+
const overflowLabel = `${overflowCount} ${overflowCount === 1 ? 'other' : 'others'}`
|
|
93
|
+
return `${names.join(', ')}, and ${overflowLabel} ${action}`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
switch (names.length) {
|
|
97
|
+
case 0:
|
|
98
|
+
return `Someone ${action}`
|
|
99
|
+
case 1:
|
|
100
|
+
return `${names[0]} ${action}`
|
|
101
|
+
case 2:
|
|
102
|
+
return `${names[0]} and ${names[1]} ${action}`
|
|
103
|
+
// Backend guarantees names.length <= 3 when overflowCount is 0;
|
|
104
|
+
// overflow branch handles 4+ people.
|
|
105
|
+
default:
|
|
106
|
+
return `${names[0]}, ${names[1]}, and ${names[2]} ${action}`
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const useStyles = () => {
|
|
111
|
+
const { colors } = useTheme()
|
|
112
|
+
return StyleSheet.create({
|
|
113
|
+
container: {
|
|
114
|
+
paddingVertical: 12,
|
|
115
|
+
paddingHorizontal: 16,
|
|
116
|
+
alignItems: 'center',
|
|
117
|
+
},
|
|
118
|
+
text: {
|
|
119
|
+
color: colors.textColorDefaultSecondary,
|
|
120
|
+
textAlign: 'center',
|
|
121
|
+
},
|
|
122
|
+
reactions: {
|
|
123
|
+
flexDirection: 'row',
|
|
124
|
+
flexWrap: 'wrap',
|
|
125
|
+
justifyContent: 'center',
|
|
126
|
+
gap: 4,
|
|
127
|
+
marginTop: 8,
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
}
|
package/src/navigation/index.tsx
CHANGED
|
@@ -71,6 +71,10 @@ import { NotificationSettingsScreen } from '../screens/notification_settings_scr
|
|
|
71
71
|
import { PreferredAppSelectionScreen } from '../screens/preferred_app_selection_screen'
|
|
72
72
|
import { ReactionsScreen, ReactionsScreenOptions } from '../screens/reactions_screen'
|
|
73
73
|
import { SendGiphyScreen, SendGiphyScreenOptions } from '../screens/send_giphy_screen'
|
|
74
|
+
import {
|
|
75
|
+
SystemMessagePeopleScreen,
|
|
76
|
+
SystemMessagePeopleScreenOptions,
|
|
77
|
+
} from '../screens/system_message_people_screen'
|
|
74
78
|
import { TeamConversationScreen } from '../screens/team_conversation_screen'
|
|
75
79
|
import { ScreenLayoutWithChatAccessGate } from './screenLayout'
|
|
76
80
|
|
|
@@ -333,6 +337,10 @@ export const ChatStack = createNativeStackNavigator({
|
|
|
333
337
|
screen: ReactionsScreen,
|
|
334
338
|
options: ReactionsScreenOptions,
|
|
335
339
|
},
|
|
340
|
+
SystemMessagePeople: {
|
|
341
|
+
screen: SystemMessagePeopleScreen,
|
|
342
|
+
options: SystemMessagePeopleScreenOptions,
|
|
343
|
+
},
|
|
336
344
|
MessageReadReceipts: {
|
|
337
345
|
screen: MessageReadReceiptsScreen,
|
|
338
346
|
options: MessageReadReceiptsScreenOptions,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
MemberMessagesDisabledBanner,
|
|
22
22
|
} from '../components/conversation/messages_disabled_banners'
|
|
23
23
|
import { ReplyShadowMessage } from '../components/conversation/reply_shadow_message'
|
|
24
|
+
import { SystemMessage } from '../components/conversation/system_message'
|
|
24
25
|
import { TypingIndicator } from '../components/conversation/typing_indicator'
|
|
25
26
|
import { KeyboardView } from '../components/display/keyboard_view'
|
|
26
27
|
import BlankState from '../components/primitive/blank_state_primitive'
|
|
@@ -40,6 +41,7 @@ import { ConversationBadgeResource } from '../types/resources/conversation_badge
|
|
|
40
41
|
import { getRelativeDateStatus } from '../utils/date'
|
|
41
42
|
import dayjs from '../utils/dayjs'
|
|
42
43
|
import { CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL } from '../utils/styles'
|
|
44
|
+
import { isSystemMessage } from '../utils/system_messages'
|
|
43
45
|
|
|
44
46
|
export type ConversationRouteProps = {
|
|
45
47
|
conversation_id: number
|
|
@@ -188,6 +190,10 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
188
190
|
)
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
if (isSystemMessage(item)) {
|
|
194
|
+
return <SystemMessage message={item} conversationId={conversation_id} />
|
|
195
|
+
}
|
|
196
|
+
|
|
191
197
|
return (
|
|
192
198
|
<Message
|
|
193
199
|
{...item}
|
|
@@ -293,6 +299,25 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
293
299
|
const prevMessage = ms[i + 1]
|
|
294
300
|
const nextMessage = ms[i - 1]
|
|
295
301
|
const date = dayjs(message.createdAt).format('YYYY-MM-DD')
|
|
302
|
+
|
|
303
|
+
const prevMessageIsDateSeparator =
|
|
304
|
+
prevMessage && date !== dayjs(prevMessage.createdAt).format('YYYY-MM-DD')
|
|
305
|
+
|
|
306
|
+
if (isSystemMessage(message)) {
|
|
307
|
+
message.myLatestInConversation = false
|
|
308
|
+
message.lastInGroup = true
|
|
309
|
+
message.renderAuthor = false
|
|
310
|
+
message.nextRendersAuthor = nextMessage?.renderAuthor
|
|
311
|
+
message.isReplyShadowMessage = false
|
|
312
|
+
message.nextIsReplyShadowMessage = false
|
|
313
|
+
message.threadPosition = null
|
|
314
|
+
enrichedMessages.push(message)
|
|
315
|
+
if (prevMessageIsDateSeparator) {
|
|
316
|
+
enrichedMessages.push({ type: 'DateSeparator', id: `day-divider-${message.id}`, date })
|
|
317
|
+
}
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
296
321
|
const inThread = message.replyRootId !== null
|
|
297
322
|
const nextMessageInThread = nextMessage?.replyRootId !== null
|
|
298
323
|
const threadRoot = message.replyRootId === message.id
|
|
@@ -307,8 +332,6 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
307
332
|
const nextMessageMoreThan5Minutes =
|
|
308
333
|
nextMessage &&
|
|
309
334
|
new Date(nextMessage.createdAt).getTime() - new Date(message.createdAt).getTime() > 60000 * 5
|
|
310
|
-
const prevMessageIsDateSeparator =
|
|
311
|
-
prevMessage && date !== dayjs(prevMessage.createdAt).format('YYYY-MM-DD')
|
|
312
335
|
const nextMessageIsDateSeparator =
|
|
313
336
|
nextMessage && date !== dayjs(nextMessage.createdAt).format('YYYY-MM-DD')
|
|
314
337
|
const insertReplyShadowMessage =
|
package/src/screens/index.ts
CHANGED
|
@@ -20,3 +20,4 @@ export * from './conversation_select_recipients/conversation_select_group_recipi
|
|
|
20
20
|
export * from './conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen'
|
|
21
21
|
export * from './attachment_actions/attachment_actions_screen'
|
|
22
22
|
export * from './conversations/conversations_screen'
|
|
23
|
+
export * from './system_message_people_screen'
|
|
@@ -28,6 +28,7 @@ export type MessageActionsScreenProps = StaticScreenProps<{
|
|
|
28
28
|
conversation_id: number
|
|
29
29
|
canDeleteNonAuthoredMessages?: boolean
|
|
30
30
|
inReplyScreen?: boolean
|
|
31
|
+
isSystemMessage?: boolean
|
|
31
32
|
}>
|
|
32
33
|
|
|
33
34
|
export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
|
|
@@ -37,6 +38,7 @@ export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
|
|
|
37
38
|
canDeleteNonAuthoredMessages,
|
|
38
39
|
inReplyScreen,
|
|
39
40
|
reply_root_author_name,
|
|
41
|
+
isSystemMessage,
|
|
40
42
|
} = route.params
|
|
41
43
|
|
|
42
44
|
const { messages, refetch } = useConversationMessages(
|
|
@@ -55,6 +57,7 @@ export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
|
|
|
55
57
|
refetchMessages={refetch}
|
|
56
58
|
inReplyScreen={inReplyScreen}
|
|
57
59
|
replyRootAuthorName={reply_root_author_name}
|
|
60
|
+
isSystemMessage={isSystemMessage}
|
|
58
61
|
/>
|
|
59
62
|
)
|
|
60
63
|
}
|
|
@@ -66,6 +69,7 @@ function MessageActionsScreenContent({
|
|
|
66
69
|
refetchMessages,
|
|
67
70
|
inReplyScreen,
|
|
68
71
|
replyRootAuthorName,
|
|
72
|
+
isSystemMessage,
|
|
69
73
|
}: {
|
|
70
74
|
message: MessageResource
|
|
71
75
|
conversation_id: number
|
|
@@ -73,6 +77,7 @@ function MessageActionsScreenContent({
|
|
|
73
77
|
refetchMessages: () => void
|
|
74
78
|
inReplyScreen?: boolean
|
|
75
79
|
replyRootAuthorName?: string
|
|
80
|
+
isSystemMessage?: boolean
|
|
76
81
|
}) {
|
|
77
82
|
const navigation = useNavigation()
|
|
78
83
|
const apiClient = useApiClient()
|
|
@@ -240,65 +245,67 @@ function MessageActionsScreenContent({
|
|
|
240
245
|
/>
|
|
241
246
|
))}
|
|
242
247
|
</View>
|
|
243
|
-
|
|
244
|
-
{
|
|
248
|
+
{!isSystemMessage && (
|
|
249
|
+
<View style={styles.actions}>
|
|
250
|
+
{!inReplyScreen && (
|
|
251
|
+
<FormSheet.Action
|
|
252
|
+
onPress={handleReplyPress}
|
|
253
|
+
title="Reply to message"
|
|
254
|
+
iconName="registrations.undo"
|
|
255
|
+
accessibilityHint="Navigates to the reply screen"
|
|
256
|
+
accessibilityRole="link"
|
|
257
|
+
/>
|
|
258
|
+
)}
|
|
245
259
|
<FormSheet.Action
|
|
246
|
-
onPress={
|
|
247
|
-
title="
|
|
248
|
-
iconName="
|
|
249
|
-
accessibilityHint="
|
|
250
|
-
accessibilityRole="link"
|
|
260
|
+
onPress={handleCopyPress}
|
|
261
|
+
title="Copy text"
|
|
262
|
+
iconName="services.fileCopy"
|
|
263
|
+
accessibilityHint="Copies text and links to clipboard"
|
|
251
264
|
/>
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
appearance="danger"
|
|
297
|
-
disabled={isPending}
|
|
298
|
-
accessibilityHint="Opens a confirmation alert to delete this message permanently."
|
|
299
|
-
/>
|
|
300
|
-
)}
|
|
301
|
-
</View>
|
|
265
|
+
{showReportMessageAction && (
|
|
266
|
+
<FormSheet.Action
|
|
267
|
+
onPress={handleReportPress}
|
|
268
|
+
title="Report message"
|
|
269
|
+
iconName="chat.reportMessageO"
|
|
270
|
+
accessibilityHint="Opens a form to report this message"
|
|
271
|
+
/>
|
|
272
|
+
)}
|
|
273
|
+
{message?.mine && (
|
|
274
|
+
<FormSheet.Action
|
|
275
|
+
onPress={() => handleEditPress()}
|
|
276
|
+
title="Edit message"
|
|
277
|
+
iconName="accounts.editor"
|
|
278
|
+
accessibilityHint="Opens existing text in the message form input."
|
|
279
|
+
/>
|
|
280
|
+
)}
|
|
281
|
+
{message?.mine && (
|
|
282
|
+
<FormSheet.Action
|
|
283
|
+
onPress={() => handleViewReadReceiptsPress()}
|
|
284
|
+
title="View read receipts"
|
|
285
|
+
iconName="general.checkPerson"
|
|
286
|
+
accessibilityHint="Opens a modal with a list of people who read your message."
|
|
287
|
+
/>
|
|
288
|
+
)}
|
|
289
|
+
{message?.mine && hasExpandedLink && (
|
|
290
|
+
<FormSheet.Action
|
|
291
|
+
onPress={() => handleRemoveLinkPreviewConfirm()}
|
|
292
|
+
title="Remove link preview"
|
|
293
|
+
iconName="general.brokenLink"
|
|
294
|
+
accessibilityHint="Removes an expanded link preview from the message."
|
|
295
|
+
/>
|
|
296
|
+
)}
|
|
297
|
+
{(message?.mine || canDeleteNonAuthoredMessages) && (
|
|
298
|
+
<FormSheet.Action
|
|
299
|
+
onPress={() => handleDeleteConfirm()}
|
|
300
|
+
title="Delete message"
|
|
301
|
+
iconName="publishing.trash"
|
|
302
|
+
appearance="danger"
|
|
303
|
+
disabled={isPending}
|
|
304
|
+
accessibilityHint="Opens a confirmation alert to delete this message permanently."
|
|
305
|
+
/>
|
|
306
|
+
)}
|
|
307
|
+
</View>
|
|
308
|
+
)}
|
|
302
309
|
</FormSheet.Root>
|
|
303
310
|
)
|
|
304
311
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { StaticScreenProps } from '@react-navigation/native'
|
|
2
|
+
import React, { memo } from 'react'
|
|
3
|
+
import { Platform, StyleSheet, View } from 'react-native'
|
|
4
|
+
import { FlatList } from 'react-native-gesture-handler'
|
|
5
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
6
|
+
import { Avatar, Text } from '../components'
|
|
7
|
+
import FormSheet, { getFormSheetScreenOptions } from '../components/primitive/form_sheet'
|
|
8
|
+
import { useSuspenseGet } from '../hooks'
|
|
9
|
+
import { MemberResource } from '../types'
|
|
10
|
+
|
|
11
|
+
export const SystemMessagePeopleScreenOptions = getFormSheetScreenOptions({
|
|
12
|
+
sheetAllowedDetents: Platform.select({
|
|
13
|
+
android: [0.5, 0.94],
|
|
14
|
+
default: [0.5, 1],
|
|
15
|
+
}),
|
|
16
|
+
headerTitle: 'People',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export type SystemMessagePeopleScreenProps = StaticScreenProps<{
|
|
20
|
+
conversation_id: number
|
|
21
|
+
person_ids: number[]
|
|
22
|
+
}>
|
|
23
|
+
|
|
24
|
+
export function SystemMessagePeopleScreen({ route }: SystemMessagePeopleScreenProps) {
|
|
25
|
+
const { conversation_id, person_ids } = route.params
|
|
26
|
+
const styles = useStyles()
|
|
27
|
+
|
|
28
|
+
const { data: members } = useSuspenseGet<MemberResource[]>({
|
|
29
|
+
url: `/me/conversations/${conversation_id}/members`,
|
|
30
|
+
data: {
|
|
31
|
+
fields: {
|
|
32
|
+
Member: ['id', 'name', 'avatar'],
|
|
33
|
+
},
|
|
34
|
+
where: {
|
|
35
|
+
id: person_ids,
|
|
36
|
+
},
|
|
37
|
+
limit: person_ids.length,
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const orderById = Object.fromEntries(person_ids.map((id, i) => [id, i]))
|
|
42
|
+
const sortedMembers = [...members].sort((a, b) => (orderById[a.id] ?? 0) - (orderById[b.id] ?? 0))
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<FormSheet.Root contentStyle={styles.formSheetContent}>
|
|
46
|
+
<FlatList
|
|
47
|
+
data={sortedMembers}
|
|
48
|
+
contentContainerStyle={styles.contentContainer}
|
|
49
|
+
keyExtractor={item => item.id.toString()}
|
|
50
|
+
renderItem={({ item }) => <Person person={item} />}
|
|
51
|
+
/>
|
|
52
|
+
</FormSheet.Root>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type PersonProps = Pick<MemberResource, 'id' | 'name' | 'avatar'>
|
|
57
|
+
|
|
58
|
+
const Person = memo(({ person }: { person: PersonProps }) => {
|
|
59
|
+
const styles = useStyles()
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<View style={styles.personRow}>
|
|
63
|
+
<Avatar sourceUri={person.avatar} size="sm" />
|
|
64
|
+
<Text variant="tertiary" numberOfLines={2} style={styles.personName}>
|
|
65
|
+
{person.name}
|
|
66
|
+
</Text>
|
|
67
|
+
</View>
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const useStyles = () => {
|
|
72
|
+
const { bottom } = useSafeAreaInsets()
|
|
73
|
+
|
|
74
|
+
return StyleSheet.create({
|
|
75
|
+
formSheetContent: {
|
|
76
|
+
paddingTop: 16,
|
|
77
|
+
},
|
|
78
|
+
contentContainer: {
|
|
79
|
+
paddingTop: 8,
|
|
80
|
+
paddingBottom: bottom + 24,
|
|
81
|
+
},
|
|
82
|
+
personRow: {
|
|
83
|
+
flexDirection: 'row',
|
|
84
|
+
alignItems: 'center',
|
|
85
|
+
gap: 8,
|
|
86
|
+
paddingVertical: 8,
|
|
87
|
+
paddingHorizontal: 16,
|
|
88
|
+
flex: 1,
|
|
89
|
+
},
|
|
90
|
+
personName: {
|
|
91
|
+
flex: 1,
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
}
|
|
@@ -18,6 +18,9 @@ interface BaseMessageEventData extends Record<string, unknown> {
|
|
|
18
18
|
idempotent_key?: string | null
|
|
19
19
|
reply_count: number
|
|
20
20
|
reply_root_id: string | null
|
|
21
|
+
message_type: string
|
|
22
|
+
system_event_person_ids: number[] | null
|
|
23
|
+
system_text_parts: { names: string[]; overflow_count: number; action: string } | null
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
26
|
|
|
@@ -17,6 +17,9 @@ export interface MessageResource {
|
|
|
17
17
|
reactionCounts: ReactionCountResource[]
|
|
18
18
|
replyCount: number
|
|
19
19
|
replyRootId?: string | null
|
|
20
|
+
messageType: string
|
|
21
|
+
personIdsForSystemEvent?: number[] | null
|
|
22
|
+
systemTextParts?: { names: string[]; overflowCount: number; action: string } | null
|
|
20
23
|
|
|
21
24
|
// Custom Local Properties we set for rendering
|
|
22
25
|
renderAuthor?: boolean
|
package/src/utils/index.ts
CHANGED
|
@@ -29,5 +29,14 @@ export function transformMessageEventDataToMessageResource({
|
|
|
29
29
|
reactionCounts: [],
|
|
30
30
|
replyCount: data.reply_count || 0,
|
|
31
31
|
replyRootId: data.reply_root_id || null,
|
|
32
|
+
messageType: data.message_type,
|
|
33
|
+
personIdsForSystemEvent: data.system_event_person_ids,
|
|
34
|
+
systemTextParts: data.system_text_parts
|
|
35
|
+
? {
|
|
36
|
+
names: data.system_text_parts.names,
|
|
37
|
+
overflowCount: data.system_text_parts.overflow_count,
|
|
38
|
+
action: data.system_text_parts.action,
|
|
39
|
+
}
|
|
40
|
+
: null,
|
|
32
41
|
}
|
|
33
42
|
}
|
|
@@ -10,6 +10,9 @@ export const getMessageFields = {
|
|
|
10
10
|
'reaction_counts',
|
|
11
11
|
'reply_count',
|
|
12
12
|
'reply_root',
|
|
13
|
+
'message_type',
|
|
14
|
+
'person_ids_for_system_event',
|
|
15
|
+
'system_text_parts',
|
|
13
16
|
],
|
|
14
17
|
Person: ['name', 'avatar'],
|
|
15
18
|
ReactionCount: ['value', 'count', 'mine', 'message_id', 'author_ids'],
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MessageResource } from '../types'
|
|
2
|
+
|
|
3
|
+
const SYSTEM_MESSAGE_TYPES = ['user_joined'] as const
|
|
4
|
+
|
|
5
|
+
type SystemMessageType = (typeof SYSTEM_MESSAGE_TYPES)[number]
|
|
6
|
+
|
|
7
|
+
export interface SystemMessageResource extends MessageResource {
|
|
8
|
+
messageType: SystemMessageType
|
|
9
|
+
systemTextParts: { names: string[]; overflowCount: number; action: string }
|
|
10
|
+
personIdsForSystemEvent: number[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const isSystemMessage = (message: MessageResource): message is SystemMessageResource =>
|
|
14
|
+
SYSTEM_MESSAGE_TYPES.includes(message.messageType as SystemMessageType) &&
|
|
15
|
+
message.systemTextParts != null &&
|
|
16
|
+
message.personIdsForSystemEvent != null
|