@planningcenter/chat-react-native 3.16.0-rc.3 → 3.16.0-rc.5
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/message.d.ts +2 -1
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +19 -3
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/message_form.d.ts +2 -1
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +38 -16
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/reply_connectors.js +1 -1
- package/build/components/conversation/reply_connectors.js.map +1 -1
- package/build/components/conversation/shadow_message.d.ts +2 -1
- package/build/components/conversation/shadow_message.d.ts.map +1 -1
- package/build/components/conversation/shadow_message.js +10 -5
- package/build/components/conversation/shadow_message.js.map +1 -1
- package/build/components/display/icon.d.ts +3 -1
- package/build/components/display/icon.d.ts.map +1 -1
- package/build/components/display/icon.js +2 -0
- package/build/components/display/icon.js.map +1 -1
- package/build/hooks/use_conversation_messages.d.ts +6 -3
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +32 -25
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/hooks/use_message_create_or_update.d.ts +2 -1
- package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
- package/build/hooks/use_message_create_or_update.js +6 -2
- package/build/hooks/use_message_create_or_update.js.map +1 -1
- package/build/hooks/use_suspense_api.d.ts +3 -1
- package/build/hooks/use_suspense_api.d.ts.map +1 -1
- package/build/hooks/use_suspense_api.js +1 -0
- package/build/hooks/use_suspense_api.js.map +1 -1
- package/build/navigation/index.d.ts +6 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +6 -0
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_screen.d.ts +2 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +7 -5
- package/build/screens/conversation_screen.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 +16 -3
- package/build/screens/message_actions_screen.js.map +1 -1
- package/build/types/jolt_events/message_events.d.ts +2 -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 +2 -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 +2 -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 +2 -0
- package/build/utils/jolt/transform_message_event_data_to_message_resource.js.map +1 -1
- package/build/utils/replies_local_feature_flag.d.ts +2 -0
- package/build/utils/replies_local_feature_flag.d.ts.map +1 -0
- package/build/utils/replies_local_feature_flag.js +3 -0
- package/build/utils/replies_local_feature_flag.js.map +1 -0
- package/package.json +2 -2
- package/src/components/conversation/message.tsx +30 -2
- package/src/components/conversation/message_form.tsx +85 -15
- package/src/components/conversation/reply_connectors.tsx +1 -1
- package/src/components/conversation/shadow_message.tsx +15 -7
- package/src/components/display/icon.tsx +3 -0
- package/src/hooks/use_conversation_messages.ts +45 -25
- package/src/hooks/use_message_create_or_update.ts +7 -2
- package/src/hooks/use_suspense_api.ts +3 -0
- package/src/navigation/index.tsx +6 -0
- package/src/screens/conversation_screen.tsx +9 -4
- package/src/screens/message_actions_screen.tsx +27 -1
- package/src/types/jolt_events/message_events.ts +2 -0
- package/src/types/resources/message.ts +2 -0
- package/src/utils/cache/optimistically_create_message.ts +2 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/jolt/transform_message_event_data_to_message_resource.ts +2 -0
- package/src/utils/replies_local_feature_flag.ts +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replies_local_feature_flag.js","sourceRoot":"","sources":["../../src/utils/replies_local_feature_flag.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAA","sourcesContent":["// TODO: Replace this whole file with the flipper flag\nexport const REPLIES_FEATURE_ENABLED = false\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.16.0-rc.
|
|
3
|
+
"version": "3.16.0-rc.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"react-native-url-polyfill": "^2.0.0",
|
|
56
56
|
"typescript": "<5.6.0"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "fbd2ce7be47ac372eb6bcd876c7db571681c7219"
|
|
59
59
|
}
|
|
@@ -2,7 +2,7 @@ import { useNavigation } from '@react-navigation/native'
|
|
|
2
2
|
import React, { useEffect } from 'react'
|
|
3
3
|
import { Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'
|
|
4
4
|
import { MessageReaction } from '../../components/conversation/message_reaction'
|
|
5
|
-
import { Avatar, Icon, Spinner, Text, TextInlineButton } from '../../components/display'
|
|
5
|
+
import { Avatar, Icon, Spinner, Text, TextButton, TextInlineButton } from '../../components/display'
|
|
6
6
|
import {
|
|
7
7
|
useAnimatedMessageBackgroundColor,
|
|
8
8
|
useInteractionGhostBackgroundColor,
|
|
@@ -25,6 +25,7 @@ import { MessageReadReceipts } from './message_read_receipts'
|
|
|
25
25
|
import { isNewMessage, useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
|
|
26
26
|
import { Haptic } from '../../utils/native_adapters'
|
|
27
27
|
import { TheirReplyConnector, MyReplyConnector } from './reply_connectors'
|
|
28
|
+
import { REPLIES_FEATURE_ENABLED } from '../../utils'
|
|
28
29
|
|
|
29
30
|
/** Message
|
|
30
31
|
* Component for display of a message within a conversation list
|
|
@@ -33,12 +34,14 @@ interface MessageProps extends MessageResource {
|
|
|
33
34
|
canDeleteNonAuthoredMessages: boolean
|
|
34
35
|
conversation_id: number
|
|
35
36
|
latestReadMessageSortKey?: string
|
|
37
|
+
inReplyScreen?: boolean
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export function Message({
|
|
39
41
|
canDeleteNonAuthoredMessages,
|
|
40
42
|
conversation_id,
|
|
41
43
|
latestReadMessageSortKey,
|
|
44
|
+
inReplyScreen,
|
|
42
45
|
...message
|
|
43
46
|
}: MessageProps) {
|
|
44
47
|
const { text, reactionCounts, pending, error } = message
|
|
@@ -89,6 +92,7 @@ export function Message({
|
|
|
89
92
|
message_id: message.id,
|
|
90
93
|
conversation_id,
|
|
91
94
|
canDeleteNonAuthoredMessages,
|
|
95
|
+
inReplyScreen,
|
|
92
96
|
})
|
|
93
97
|
}
|
|
94
98
|
const handleReactionLongPress = (reaction: ReactionCountResource) => {
|
|
@@ -112,6 +116,14 @@ export function Message({
|
|
|
112
116
|
})
|
|
113
117
|
}
|
|
114
118
|
|
|
119
|
+
const handleNavigateToReplyPress = () => {
|
|
120
|
+
navigation.navigate('ConversationReply', {
|
|
121
|
+
conversation_id,
|
|
122
|
+
reply_root_id: message.id,
|
|
123
|
+
// TODO: Add a way to pass the reply root author's name
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
115
127
|
const metaProps = {
|
|
116
128
|
authorName: message.author.name,
|
|
117
129
|
createdAt: message.createdAt,
|
|
@@ -120,6 +132,8 @@ export function Message({
|
|
|
120
132
|
const renderAuthor = (!message.mine && message.renderAuthor) || false
|
|
121
133
|
const messageBottomMargin = message.lastInGroup ? 12 : hasReactions || showMessageMeta ? 8 : 4
|
|
122
134
|
const messagePendingLabel = isPersisted ? 'Saving' : 'Sending'
|
|
135
|
+
const showReplyCountButton =
|
|
136
|
+
!inReplyScreen && message.replyRootId === message.id && REPLIES_FEATURE_ENABLED
|
|
123
137
|
|
|
124
138
|
return (
|
|
125
139
|
<Pressable
|
|
@@ -127,7 +141,7 @@ export function Message({
|
|
|
127
141
|
onPress={() => setShowMessageMetaToggle(!showMessageMetaToggle)}
|
|
128
142
|
onPressIn={handleMessagePressIn}
|
|
129
143
|
onPressOut={handleMessagePressOut}
|
|
130
|
-
android_ripple={{ color: colors.androidRippleNeutral }}
|
|
144
|
+
android_ripple={{ color: colors.androidRippleNeutral, borderless: false, foreground: true }}
|
|
131
145
|
accessibilityHint="Long press to view message actions like reacting and copying."
|
|
132
146
|
>
|
|
133
147
|
<Animated.View style={[styles.message, animatedBackgroundColor]}>
|
|
@@ -167,6 +181,17 @@ export function Message({
|
|
|
167
181
|
</View>
|
|
168
182
|
)}
|
|
169
183
|
</View>
|
|
184
|
+
{showReplyCountButton && (
|
|
185
|
+
<TextButton
|
|
186
|
+
variant="footnote"
|
|
187
|
+
onPress={handleNavigateToReplyPress}
|
|
188
|
+
style={styles.messageReplyCount}
|
|
189
|
+
accessibilityHint="Navigates to reply screen for this message"
|
|
190
|
+
accessibilityRole="link"
|
|
191
|
+
>
|
|
192
|
+
{message.replyCount} replies
|
|
193
|
+
</TextButton>
|
|
194
|
+
)}
|
|
170
195
|
{hasReactions && (
|
|
171
196
|
<View style={styles.messageReactions}>
|
|
172
197
|
{reactionCounts.map(reaction => (
|
|
@@ -277,6 +302,9 @@ const useMessageStyles = ({ mine }: MessageResource) => {
|
|
|
277
302
|
gap: 4,
|
|
278
303
|
justifyContent: mine ? 'flex-end' : 'flex-start',
|
|
279
304
|
},
|
|
305
|
+
messageReplyCount: {
|
|
306
|
+
alignSelf: mine ? 'flex-end' : 'flex-start',
|
|
307
|
+
},
|
|
280
308
|
messageMeta: {
|
|
281
309
|
flexDirection: 'row',
|
|
282
310
|
alignItems: 'center',
|
|
@@ -15,11 +15,12 @@ import {
|
|
|
15
15
|
ViewProps,
|
|
16
16
|
Keyboard,
|
|
17
17
|
} from 'react-native'
|
|
18
|
-
import { Icon, IconButton, Text, TextButton } from '../../components'
|
|
18
|
+
import { Heading, Icon, IconButton, IconString, Text, TextButton } from '../../components'
|
|
19
19
|
import {
|
|
20
20
|
useCreateAndroidRippleColor,
|
|
21
21
|
useTheme,
|
|
22
22
|
useInteractionGhostBackgroundColor,
|
|
23
|
+
useScalableNumberOfLines,
|
|
23
24
|
} from '../../hooks'
|
|
24
25
|
import { ConversationResource, MessageResource } from '../../types'
|
|
25
26
|
|
|
@@ -27,7 +28,12 @@ import { ConversationScreenProps } from '../../screens/conversation_screen'
|
|
|
27
28
|
|
|
28
29
|
import { ChatContext } from '../../contexts/chat_context'
|
|
29
30
|
import { Haptic, ImagePicker, ImagePickerResult } from '../../utils/native_adapters'
|
|
30
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
MAX_FONT_SIZE_MULTIPLIER_LANDMARK,
|
|
33
|
+
REPLIES_FEATURE_ENABLED,
|
|
34
|
+
platformFontWeightMedium,
|
|
35
|
+
platformPressedOpacityStyle,
|
|
36
|
+
} from '../../utils'
|
|
31
37
|
import { useAttachmentUploader } from '../../hooks/use_attachment_uploader'
|
|
32
38
|
import { useMessageDraft } from '../../hooks/use_message_draft'
|
|
33
39
|
import {
|
|
@@ -53,6 +59,7 @@ export const MessageForm = {
|
|
|
53
59
|
interface MessagesFormRootProps extends ViewProps {
|
|
54
60
|
conversation: ConversationResource
|
|
55
61
|
currentlyEditingMessage?: MessageResource | null
|
|
62
|
+
replyRootId?: string
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
const MessageFormContext = React.createContext<{
|
|
@@ -66,6 +73,7 @@ const MessageFormContext = React.createContext<{
|
|
|
66
73
|
attachmentUploader?: ReturnType<typeof useAttachmentUploader>
|
|
67
74
|
currentlyEditingMessage?: MessageResource | null
|
|
68
75
|
reset: () => void
|
|
76
|
+
replyRootId?: string
|
|
69
77
|
}>({
|
|
70
78
|
text: '',
|
|
71
79
|
setText: (_text: string) => {},
|
|
@@ -76,6 +84,7 @@ const MessageFormContext = React.createContext<{
|
|
|
76
84
|
setUsingGiphy: (_usingGiphy: boolean) => {},
|
|
77
85
|
currentlyEditingMessage: null,
|
|
78
86
|
reset: () => {},
|
|
87
|
+
replyRootId: undefined,
|
|
79
88
|
})
|
|
80
89
|
|
|
81
90
|
const GIPHY_MAX_LENGTH = 50
|
|
@@ -85,6 +94,7 @@ function MessageFormRoot({
|
|
|
85
94
|
conversation,
|
|
86
95
|
currentlyEditingMessage,
|
|
87
96
|
children,
|
|
97
|
+
replyRootId,
|
|
88
98
|
}: MessagesFormRootProps) {
|
|
89
99
|
const { giphyApiKey } = useContext(ChatContext)
|
|
90
100
|
const canGiphy = !!giphyApiKey && !currentlyEditingMessage
|
|
@@ -105,6 +115,7 @@ function MessageFormRoot({
|
|
|
105
115
|
} = useMessageCreateOrUpdate({
|
|
106
116
|
conversationId: conversation.id,
|
|
107
117
|
message: currentlyEditingMessage || undefined,
|
|
118
|
+
replyRootId,
|
|
108
119
|
})
|
|
109
120
|
const attachmentUploader = useAttachmentUploader({
|
|
110
121
|
conversationId: conversation.id,
|
|
@@ -236,10 +247,12 @@ function MessageFormRoot({
|
|
|
236
247
|
attachmentUploader,
|
|
237
248
|
currentlyEditingMessage,
|
|
238
249
|
reset,
|
|
250
|
+
replyRootId,
|
|
239
251
|
}}
|
|
240
252
|
>
|
|
241
253
|
<View style={styles.container}>
|
|
242
254
|
<EditingIndicator />
|
|
255
|
+
<ReplyIndicator />
|
|
243
256
|
<View style={styles.textInputContainer}>{children}</View>
|
|
244
257
|
</View>
|
|
245
258
|
</MessageFormContext.Provider>
|
|
@@ -529,25 +542,76 @@ function MessageFormCommands() {
|
|
|
529
542
|
|
|
530
543
|
function EditingIndicator() {
|
|
531
544
|
const { currentlyEditingMessage, reset } = React.useContext(MessageFormContext)
|
|
532
|
-
const styles = useMessageFormStyles()
|
|
533
545
|
|
|
534
546
|
if (!currentlyEditingMessage) {
|
|
535
547
|
return null
|
|
536
548
|
}
|
|
537
549
|
|
|
538
550
|
return (
|
|
539
|
-
<
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
551
|
+
<FormIndicatorRow
|
|
552
|
+
title="Editing message"
|
|
553
|
+
buttonText="Cancel"
|
|
554
|
+
accessibilityHint="Exit message editing mode without saving"
|
|
555
|
+
iconName="accounts.editor"
|
|
556
|
+
onPress={() => reset()}
|
|
557
|
+
/>
|
|
558
|
+
)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function ReplyIndicator() {
|
|
562
|
+
const { replyRootId } = React.useContext(MessageFormContext)
|
|
563
|
+
const navigation = useNavigation()
|
|
564
|
+
|
|
565
|
+
if (!REPLIES_FEATURE_ENABLED || !replyRootId) return null
|
|
566
|
+
|
|
567
|
+
return (
|
|
568
|
+
<FormIndicatorRow
|
|
569
|
+
title="Reply" // TODO: Get root reply author
|
|
570
|
+
buttonText="Exit replies"
|
|
571
|
+
accessibilityHint="Return to the main conversation"
|
|
572
|
+
iconName="registrations.undo"
|
|
573
|
+
onPress={() => navigation.goBack()}
|
|
574
|
+
/>
|
|
575
|
+
)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
interface FormIndicatorRowProps {
|
|
579
|
+
title: string
|
|
580
|
+
buttonText: string
|
|
581
|
+
accessibilityHint: string
|
|
582
|
+
onPress: () => void
|
|
583
|
+
iconName: IconString
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function FormIndicatorRow({
|
|
587
|
+
title,
|
|
588
|
+
buttonText,
|
|
589
|
+
accessibilityHint,
|
|
590
|
+
iconName,
|
|
591
|
+
onPress,
|
|
592
|
+
}: FormIndicatorRowProps) {
|
|
593
|
+
const styles = useMessageFormStyles()
|
|
594
|
+
const scalableNumberOfLines = useScalableNumberOfLines(1)
|
|
595
|
+
|
|
596
|
+
return (
|
|
597
|
+
<View style={styles.formIndicatorRow}>
|
|
598
|
+
<View style={styles.formIndicatorRowTitleContainer}>
|
|
599
|
+
<Icon name={iconName} size={16} style={styles.formIndicatorRowIcon} />
|
|
600
|
+
<Heading
|
|
601
|
+
variant="h4"
|
|
602
|
+
style={styles.formIndicatorRowTitle}
|
|
603
|
+
numberOfLines={scalableNumberOfLines}
|
|
604
|
+
maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
|
|
605
|
+
>
|
|
606
|
+
{title}
|
|
607
|
+
</Heading>
|
|
545
608
|
</View>
|
|
546
609
|
<TextButton
|
|
547
|
-
onPress={
|
|
548
|
-
accessibilityHint=
|
|
610
|
+
onPress={onPress}
|
|
611
|
+
accessibilityHint={accessibilityHint}
|
|
612
|
+
maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
|
|
549
613
|
>
|
|
550
|
-
|
|
614
|
+
{buttonText}
|
|
551
615
|
</TextButton>
|
|
552
616
|
</View>
|
|
553
617
|
)
|
|
@@ -681,20 +745,26 @@ const useMessageFormStyles = () => {
|
|
|
681
745
|
paddingHorizontal: 16,
|
|
682
746
|
paddingVertical: 4,
|
|
683
747
|
},
|
|
684
|
-
|
|
748
|
+
formIndicatorRow: {
|
|
685
749
|
flexDirection: 'row',
|
|
686
750
|
alignItems: 'center',
|
|
687
751
|
justifyContent: 'space-between',
|
|
688
752
|
gap: 8,
|
|
689
753
|
paddingBottom: 12,
|
|
690
754
|
},
|
|
691
|
-
|
|
755
|
+
formIndicatorRowTitleContainer: {
|
|
692
756
|
flexDirection: 'row',
|
|
693
757
|
alignItems: 'center',
|
|
694
758
|
gap: 8,
|
|
759
|
+
flex: 1,
|
|
760
|
+
},
|
|
761
|
+
formIndicatorRowIcon: {
|
|
762
|
+
color: theme.colors.iconColorDefaultSecondary,
|
|
695
763
|
},
|
|
696
|
-
|
|
764
|
+
formIndicatorRowTitle: {
|
|
697
765
|
fontWeight: platformFontWeightMedium,
|
|
766
|
+
textTransform: 'none',
|
|
767
|
+
flexShrink: 1,
|
|
698
768
|
},
|
|
699
769
|
})
|
|
700
770
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
7
7
|
MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
8
8
|
} from '../../utils/styles'
|
|
9
|
-
import { REPLIES_FEATURE_ENABLED } from '../../
|
|
9
|
+
import { REPLIES_FEATURE_ENABLED } from '../../utils'
|
|
10
10
|
|
|
11
11
|
const MY_REPLY_CONNECTOR_WIDTH = 38
|
|
12
12
|
const CONNECTOR_BORDER_WIDTH = 4
|
|
@@ -23,22 +23,29 @@ import {
|
|
|
23
23
|
import Animated from 'react-native-reanimated'
|
|
24
24
|
import { TheirReplyConnector, MyReplyConnector } from './reply_connectors'
|
|
25
25
|
import { assertKeysAreNumbers } from '../../utils'
|
|
26
|
+
import { useNavigation } from '@react-navigation/native'
|
|
26
27
|
|
|
27
|
-
interface ShadowMessageProps extends MessageResource {
|
|
28
|
+
interface ShadowMessageProps extends MessageResource {
|
|
29
|
+
conversation_id: number
|
|
30
|
+
}
|
|
28
31
|
|
|
29
|
-
export function ShadowMessage({ ...message }: ShadowMessageProps) {
|
|
32
|
+
export function ShadowMessage({ conversation_id, ...message }: ShadowMessageProps) {
|
|
30
33
|
const { text } = message
|
|
31
34
|
const styles = useStyles(message)
|
|
32
35
|
const { colors } = useTheme()
|
|
36
|
+
const navigation = useNavigation()
|
|
37
|
+
|
|
33
38
|
const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)
|
|
34
39
|
const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
|
|
35
40
|
useAnimatedMessageBackgroundColor()
|
|
36
41
|
const scalableNumberOfLines = useScalableNumberOfLines(2)
|
|
37
42
|
|
|
38
|
-
const replyCount = 0 // TODO: Get reply count from message object
|
|
39
|
-
|
|
40
43
|
const handleNavigateToReplies = () => {
|
|
41
|
-
|
|
44
|
+
navigation.navigate('ConversationReply', {
|
|
45
|
+
conversation_id,
|
|
46
|
+
reply_root_id: message.id,
|
|
47
|
+
// TODO: Add a way to pass the reply root author's name
|
|
48
|
+
})
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
return (
|
|
@@ -47,7 +54,8 @@ export function ShadowMessage({ ...message }: ShadowMessageProps) {
|
|
|
47
54
|
onPress={handleNavigateToReplies}
|
|
48
55
|
onPressIn={handleMessagePressIn}
|
|
49
56
|
onPressOut={handleMessagePressOut}
|
|
50
|
-
accessibilityHint="Navigate to
|
|
57
|
+
accessibilityHint="Navigate to reply screen for this message"
|
|
58
|
+
accessibilityRole="link"
|
|
51
59
|
>
|
|
52
60
|
<Animated.View style={[styles.message, animatedBackgroundColor]}>
|
|
53
61
|
{!message.mine && (
|
|
@@ -81,7 +89,7 @@ export function ShadowMessage({ ...message }: ShadowMessageProps) {
|
|
|
81
89
|
</View>
|
|
82
90
|
<View style={styles.messageMeta}>
|
|
83
91
|
<Text variant="footnote" style={styles.replyCountText}>
|
|
84
|
-
{replyCount} replies
|
|
92
|
+
{message.replyCount} replies
|
|
85
93
|
</Text>
|
|
86
94
|
</View>
|
|
87
95
|
</View>
|
|
@@ -17,6 +17,7 @@ import * as logomark from '@planningcenter/icons/paths/logomark'
|
|
|
17
17
|
import * as people from '@planningcenter/icons/paths/people'
|
|
18
18
|
import * as services from '@planningcenter/icons/paths/services'
|
|
19
19
|
import * as publishing from '@planningcenter/icons/paths/publishing'
|
|
20
|
+
import * as registrations from '@planningcenter/icons/paths/registrations'
|
|
20
21
|
|
|
21
22
|
// =================================
|
|
22
23
|
// ====== Constants ================
|
|
@@ -37,6 +38,7 @@ const ICONS = {
|
|
|
37
38
|
people,
|
|
38
39
|
services,
|
|
39
40
|
publishing,
|
|
41
|
+
registrations,
|
|
40
42
|
} as const
|
|
41
43
|
|
|
42
44
|
export type IconStyle = ViewStyle & {
|
|
@@ -61,6 +63,7 @@ export type IconString =
|
|
|
61
63
|
| `people.${IconName<'people'>}`
|
|
62
64
|
| `services.${IconName<'services'>}`
|
|
63
65
|
| `publishing.${IconName<'publishing'>}`
|
|
66
|
+
| `registrations.${IconName<'registrations'>}`
|
|
64
67
|
|
|
65
68
|
// =================================
|
|
66
69
|
// ====== Component ================
|
|
@@ -7,14 +7,14 @@ import {
|
|
|
7
7
|
} from './use_suspense_api'
|
|
8
8
|
|
|
9
9
|
export const useConversationMessages = (
|
|
10
|
-
{ conversation_id }: { conversation_id: number },
|
|
10
|
+
{ conversation_id, reply_root_id }: { conversation_id: number; reply_root_id?: string },
|
|
11
11
|
opts?: SuspensePaginatorOptions
|
|
12
12
|
) => {
|
|
13
13
|
const { data, refetch, isRefetching, fetchNextPage } = useSuspensePaginator<MessageResource>(
|
|
14
|
-
getMessagesRequestArgs({ conversation_id }),
|
|
14
|
+
getMessagesRequestArgs({ conversation_id, reply_root_id }),
|
|
15
15
|
opts
|
|
16
16
|
)
|
|
17
|
-
const queryKey = getMessagesQueryKey({ conversation_id })
|
|
17
|
+
const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id })
|
|
18
18
|
const messages = useMemo(
|
|
19
19
|
() =>
|
|
20
20
|
data
|
|
@@ -28,29 +28,49 @@ export const useConversationMessages = (
|
|
|
28
28
|
return { messages, refetch, isRefetching, fetchNextPage, queryKey }
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export const getMessagesRequestArgs = ({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
31
|
+
export const getMessagesRequestArgs = ({
|
|
32
|
+
conversation_id,
|
|
33
|
+
reply_root_id,
|
|
34
|
+
}: {
|
|
35
|
+
conversation_id: number
|
|
36
|
+
reply_root_id?: string
|
|
37
|
+
}) => {
|
|
38
|
+
const url = reply_root_id
|
|
39
|
+
? `/me/conversations/${conversation_id}/messages/${reply_root_id}/replies`
|
|
40
|
+
: `/me/conversations/${conversation_id}/messages`
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
url,
|
|
44
|
+
data: {
|
|
45
|
+
perPage: 25,
|
|
46
|
+
fields: {
|
|
47
|
+
Message: [
|
|
48
|
+
'text',
|
|
49
|
+
'text_edited_at',
|
|
50
|
+
'mine',
|
|
51
|
+
'attachments',
|
|
52
|
+
'created_at',
|
|
53
|
+
'deleted_at',
|
|
54
|
+
'author',
|
|
55
|
+
'reaction_counts',
|
|
56
|
+
'reply_count',
|
|
57
|
+
'reply_root',
|
|
58
|
+
],
|
|
59
|
+
Person: ['name', 'avatar'],
|
|
60
|
+
ReactionCount: ['value', 'count', 'mine', 'message_id', 'author_ids'],
|
|
61
|
+
},
|
|
62
|
+
include: ['author', 'reaction_counts'],
|
|
48
63
|
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
52
66
|
|
|
53
|
-
export const getMessagesQueryKey = ({
|
|
54
|
-
|
|
67
|
+
export const getMessagesQueryKey = ({
|
|
68
|
+
conversation_id,
|
|
69
|
+
reply_root_id,
|
|
70
|
+
}: {
|
|
71
|
+
conversation_id: number
|
|
72
|
+
reply_root_id?: string
|
|
73
|
+
}) => {
|
|
74
|
+
const requestArgs = getMessagesRequestArgs({ conversation_id, reply_root_id })
|
|
55
75
|
return getRequestQueryKey(requestArgs)
|
|
56
76
|
}
|
|
@@ -18,9 +18,10 @@ import { startMessageCreationTracking } from '../utils/performance_tracking'
|
|
|
18
18
|
interface Props {
|
|
19
19
|
conversationId: number
|
|
20
20
|
message?: MessageResource
|
|
21
|
+
replyRootId?: string
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
export function useMessageCreateOrUpdate({ conversationId, message }: Props) {
|
|
24
|
+
export function useMessageCreateOrUpdate({ conversationId, message, replyRootId }: Props) {
|
|
24
25
|
const messageId = message?.id || null
|
|
25
26
|
const isEditing = !isNewMessage(message)
|
|
26
27
|
const apiClient = useApiClient()
|
|
@@ -38,7 +39,11 @@ export function useMessageCreateOrUpdate({ conversationId, message }: Props) {
|
|
|
38
39
|
const fieldsWithValueJoined = Object.fromEntries(
|
|
39
40
|
Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])
|
|
40
41
|
)
|
|
41
|
-
let attributes: any = {
|
|
42
|
+
let attributes: any = {
|
|
43
|
+
text,
|
|
44
|
+
...(attachments ? { attachments } : {}),
|
|
45
|
+
...(replyRootId ? { reply_root: { id: replyRootId } } : {}),
|
|
46
|
+
}
|
|
42
47
|
if (!isEditing) {
|
|
43
48
|
const idempotentKey = insecureUUID()
|
|
44
49
|
attributes.idempotent_key = idempotentKey
|
|
@@ -14,6 +14,7 @@ import { ResponseError } from '../utils/response_error'
|
|
|
14
14
|
|
|
15
15
|
interface SuspenseGetOptions extends GetRequest {
|
|
16
16
|
app?: App
|
|
17
|
+
reply_root_id?: string
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export type SuspenseGetQueryOptions<T extends ResourceObject | ResourceObject[]> = Omit<
|
|
@@ -114,10 +115,12 @@ export type RequestQueryKey = [
|
|
|
114
115
|
SuspenseGetOptions['data'],
|
|
115
116
|
SuspenseGetOptions['headers'],
|
|
116
117
|
SuspenseGetOptions['app'],
|
|
118
|
+
SuspenseGetOptions['reply_root_id'],
|
|
117
119
|
]
|
|
118
120
|
export const getRequestQueryKey = (args: SuspenseGetOptions): RequestQueryKey => [
|
|
119
121
|
args.url,
|
|
120
122
|
args.data,
|
|
121
123
|
args.headers,
|
|
122
124
|
args.app || 'chat',
|
|
125
|
+
args.reply_root_id,
|
|
123
126
|
]
|
package/src/navigation/index.tsx
CHANGED
|
@@ -184,6 +184,12 @@ export const ChatStack = createNativeStackNavigator({
|
|
|
184
184
|
),
|
|
185
185
|
}),
|
|
186
186
|
},
|
|
187
|
+
ConversationReply: {
|
|
188
|
+
screen: ConversationScreen,
|
|
189
|
+
options: {
|
|
190
|
+
title: 'Reply', // TODO: Get root reply author
|
|
191
|
+
},
|
|
192
|
+
},
|
|
187
193
|
TeamConversation: {
|
|
188
194
|
screen: TeamConversationScreen,
|
|
189
195
|
options: {
|
|
@@ -35,10 +35,10 @@ import { CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL } from '../utils/styles'
|
|
|
35
35
|
import { useConversationJoltEvents } from '../hooks/use_conversation_jolt_events'
|
|
36
36
|
import { JumpToBottomButton } from '../components/conversation/jump_to_bottom_button'
|
|
37
37
|
|
|
38
|
-
export const REPLIES_FEATURE_ENABLED = false
|
|
39
|
-
|
|
40
38
|
export type ConversationRouteProps = {
|
|
41
39
|
conversation_id: number
|
|
40
|
+
reply_root_id?: string
|
|
41
|
+
replyRootAuthor?: string
|
|
42
42
|
chat_group_graph_id?: string
|
|
43
43
|
clear_input?: boolean
|
|
44
44
|
editing_message_id?: number | null
|
|
@@ -53,10 +53,11 @@ export type ConversationScreenProps = StaticScreenProps<ConversationRouteProps>
|
|
|
53
53
|
export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
54
54
|
const styles = useStyles()
|
|
55
55
|
const navigation = useNavigation()
|
|
56
|
-
const { conversation_id, editing_message_id } = route.params
|
|
56
|
+
const { conversation_id, editing_message_id, reply_root_id } = route.params
|
|
57
57
|
const { data: conversation } = useConversation(route.params)
|
|
58
58
|
const { messages, refetch, isRefetching, fetchNextPage } = useConversationMessages({
|
|
59
59
|
conversation_id,
|
|
60
|
+
reply_root_id,
|
|
60
61
|
})
|
|
61
62
|
useConversationJoltEvents({ conversationId: conversation_id })
|
|
62
63
|
useConversationMessagesJoltEvents({ conversationId: conversation_id })
|
|
@@ -86,12 +87,14 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
86
87
|
}, [])
|
|
87
88
|
|
|
88
89
|
useEffect(() => {
|
|
90
|
+
if (reply_root_id) return
|
|
91
|
+
|
|
89
92
|
navigation.setParams({
|
|
90
93
|
title: title,
|
|
91
94
|
badge: badges?.[0],
|
|
92
95
|
deleted: conversation?.deleted,
|
|
93
96
|
})
|
|
94
|
-
}, [navigation, title, badges, conversation?.deleted])
|
|
97
|
+
}, [navigation, title, badges, conversation?.deleted, reply_root_id])
|
|
95
98
|
|
|
96
99
|
if (!conversation || conversation.deleted) {
|
|
97
100
|
return (
|
|
@@ -139,6 +142,7 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
139
142
|
canDeleteNonAuthoredMessages={canDeleteNonAuthoredMessages}
|
|
140
143
|
conversation_id={conversation_id}
|
|
141
144
|
latestReadMessageSortKey={conversation?.latestReadMessageSortKey}
|
|
145
|
+
inReplyScreen={!!reply_root_id}
|
|
142
146
|
/>
|
|
143
147
|
)
|
|
144
148
|
}}
|
|
@@ -152,6 +156,7 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
152
156
|
{canReply ? (
|
|
153
157
|
<MessageForm.Root
|
|
154
158
|
conversation={conversation}
|
|
159
|
+
replyRootId={reply_root_id}
|
|
155
160
|
currentlyEditingMessage={currentlyEditingMessage}
|
|
156
161
|
// We use a separate key so that it remounts component when switching between new
|
|
157
162
|
// and edit message. This simplifies internal state handling.
|