@planningcenter/chat-react-native 3.17.0-rc.1 → 3.17.1-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/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +11 -1
- 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 +6 -4
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/reply_shadow_message.js +1 -1
- package/build/components/conversation/reply_shadow_message.js.map +1 -1
- package/build/contexts/api_provider.d.ts.map +1 -1
- package/build/contexts/api_provider.js +4 -1
- package/build/contexts/api_provider.js.map +1 -1
- package/build/hooks/use_conversation_message.d.ts +2 -1
- package/build/hooks/use_conversation_message.d.ts.map +1 -1
- package/build/hooks/use_conversation_message.js +5 -2
- package/build/hooks/use_conversation_message.js.map +1 -1
- package/build/hooks/use_message_draft.d.ts.map +1 -1
- package/build/hooks/use_message_draft.js +12 -5
- package/build/hooks/use_message_draft.js.map +1 -1
- package/build/navigation/index.d.ts +6 -2
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +3 -3
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_screen.d.ts +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +19 -10
- 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 +5 -5
- package/build/screens/message_actions_screen.js.map +1 -1
- package/package.json +7 -4
- package/src/components/conversation/message.tsx +12 -1
- package/src/components/conversation/message_form.tsx +8 -2
- package/src/components/conversation/reply_shadow_message.tsx +1 -1
- package/src/contexts/api_provider.tsx +5 -1
- package/src/hooks/use_conversation_message.ts +6 -1
- package/src/hooks/use_message_draft.ts +15 -5
- package/src/navigation/index.tsx +3 -3
- package/src/screens/conversation_screen.tsx +20 -10
- package/src/screens/message_actions_screen.tsx +13 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.17.
|
|
3
|
+
"version": "3.17.1-rc.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
"test": "expo-module test",
|
|
17
17
|
"prepublishOnly": "expo-module prepublishOnly"
|
|
18
18
|
},
|
|
19
|
+
"target": "18",
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"lodash-inflection": "^1.5.0"
|
|
21
|
+
"lodash-inflection": "^1.5.0",
|
|
22
|
+
"react-compiler-runtime": "^1.0.0"
|
|
21
23
|
},
|
|
22
24
|
"peerDependencies": {
|
|
23
25
|
"@planningcenter/datetime-fmt": ">=1.0.0",
|
|
@@ -33,7 +35,7 @@
|
|
|
33
35
|
"lodash": "*",
|
|
34
36
|
"moment": ">=2.0.0",
|
|
35
37
|
"moment-timezone": "*",
|
|
36
|
-
"react": "
|
|
38
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
37
39
|
"react-native": "*",
|
|
38
40
|
"react-native-device-info": "*",
|
|
39
41
|
"react-native-gesture-handler": "*",
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
"@testing-library/react-hooks": "^8.0.1",
|
|
48
50
|
"@types/jest": "^29.5.14",
|
|
49
51
|
"@typescript-eslint/parser": "^8.32.0",
|
|
52
|
+
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
|
50
53
|
"expo-module-scripts": "^5.0.7",
|
|
51
54
|
"fast-text-encoding": "^1.0.6",
|
|
52
55
|
"jest": "^29.7.0",
|
|
@@ -56,5 +59,5 @@
|
|
|
56
59
|
"react-native-url-polyfill": "^2.0.0",
|
|
57
60
|
"typescript": "<5.6.0"
|
|
58
61
|
},
|
|
59
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "06e96f7272163e97af39f8d18ef1ed26096c3741"
|
|
60
63
|
}
|
|
@@ -26,6 +26,7 @@ import { isNewMessage, useMessageCreateOrUpdate } from '../../hooks/use_message_
|
|
|
26
26
|
import { Haptic } from '../../utils/native_adapters'
|
|
27
27
|
import { TheirReplyConnector, MyReplyConnector, AVATAR_CONNECTOR_SPACING } from './reply_connectors'
|
|
28
28
|
import { pluralize, REPLIES_FEATURE_ENABLED } from '../../utils'
|
|
29
|
+
import { useConversationMessage } from '../../hooks/use_conversation_message'
|
|
29
30
|
|
|
30
31
|
/** Message
|
|
31
32
|
* Component for display of a message within a conversation list
|
|
@@ -59,6 +60,15 @@ export function Message({
|
|
|
59
60
|
const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)
|
|
60
61
|
const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
|
|
61
62
|
useAnimatedMessageBackgroundColor()
|
|
63
|
+
const { message: replyRootMessage } = useConversationMessage({
|
|
64
|
+
conversation_id,
|
|
65
|
+
messageId: message.replyRootId || '',
|
|
66
|
+
enabled: !!message.replyRootId,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const replyRootAuthorName = message.replyRootId
|
|
70
|
+
? replyRootMessage?.author.name
|
|
71
|
+
: message.author.name
|
|
62
72
|
|
|
63
73
|
useEffect(() => {
|
|
64
74
|
if (pending) {
|
|
@@ -93,6 +103,7 @@ export function Message({
|
|
|
93
103
|
conversation_id,
|
|
94
104
|
canDeleteNonAuthoredMessages,
|
|
95
105
|
inReplyScreen,
|
|
106
|
+
reply_root_author_name: replyRootAuthorName,
|
|
96
107
|
})
|
|
97
108
|
}
|
|
98
109
|
const handleReactionLongPress = (reaction: ReactionCountResource) => {
|
|
@@ -120,7 +131,7 @@ export function Message({
|
|
|
120
131
|
navigation.navigate('ConversationReply', {
|
|
121
132
|
conversation_id,
|
|
122
133
|
reply_root_id: message.id,
|
|
123
|
-
|
|
134
|
+
reply_root_author_name: message.author.name,
|
|
124
135
|
})
|
|
125
136
|
}
|
|
126
137
|
|
|
@@ -60,6 +60,7 @@ interface MessagesFormRootProps extends ViewProps {
|
|
|
60
60
|
conversation: ConversationResource
|
|
61
61
|
currentlyEditingMessage?: MessageResource | null
|
|
62
62
|
replyRootId?: string | null
|
|
63
|
+
replyRootAuthorFirstName?: string | null
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
const MessageFormContext = React.createContext<{
|
|
@@ -74,6 +75,7 @@ const MessageFormContext = React.createContext<{
|
|
|
74
75
|
currentlyEditingMessage?: MessageResource | null
|
|
75
76
|
reset: () => void
|
|
76
77
|
replyRootId?: string | null
|
|
78
|
+
replyRootAuthorFirstName?: string | null
|
|
77
79
|
}>({
|
|
78
80
|
text: '',
|
|
79
81
|
setText: (_text: string) => {},
|
|
@@ -85,6 +87,7 @@ const MessageFormContext = React.createContext<{
|
|
|
85
87
|
currentlyEditingMessage: null,
|
|
86
88
|
reset: () => {},
|
|
87
89
|
replyRootId: undefined,
|
|
90
|
+
replyRootAuthorFirstName: undefined,
|
|
88
91
|
})
|
|
89
92
|
|
|
90
93
|
const GIPHY_MAX_LENGTH = 50
|
|
@@ -95,6 +98,7 @@ function MessageFormRoot({
|
|
|
95
98
|
currentlyEditingMessage,
|
|
96
99
|
children,
|
|
97
100
|
replyRootId,
|
|
101
|
+
replyRootAuthorFirstName,
|
|
98
102
|
}: MessagesFormRootProps) {
|
|
99
103
|
const { giphyApiKey } = useContext(ChatContext)
|
|
100
104
|
const canGiphy = !!giphyApiKey && !currentlyEditingMessage
|
|
@@ -248,6 +252,7 @@ function MessageFormRoot({
|
|
|
248
252
|
currentlyEditingMessage,
|
|
249
253
|
reset,
|
|
250
254
|
replyRootId,
|
|
255
|
+
replyRootAuthorFirstName,
|
|
251
256
|
}}
|
|
252
257
|
>
|
|
253
258
|
<View style={styles.container}>
|
|
@@ -559,14 +564,15 @@ function EditingIndicator() {
|
|
|
559
564
|
}
|
|
560
565
|
|
|
561
566
|
function ReplyIndicator() {
|
|
562
|
-
const { replyRootId } = React.useContext(MessageFormContext)
|
|
567
|
+
const { replyRootId, replyRootAuthorFirstName } = React.useContext(MessageFormContext)
|
|
563
568
|
const navigation = useNavigation()
|
|
569
|
+
const title = replyRootAuthorFirstName ? `Reply to ${replyRootAuthorFirstName}` : 'Reply'
|
|
564
570
|
|
|
565
571
|
if (!REPLIES_FEATURE_ENABLED || !replyRootId) return null
|
|
566
572
|
|
|
567
573
|
return (
|
|
568
574
|
<FormIndicatorRow
|
|
569
|
-
title=
|
|
575
|
+
title={title}
|
|
570
576
|
buttonText="Exit replies"
|
|
571
577
|
accessibilityHint="Return to the main conversation"
|
|
572
578
|
iconName="registrations.undo"
|
|
@@ -76,7 +76,7 @@ function ShadowMessageContent({ conversation_id, ...message }: ShadowMessageCont
|
|
|
76
76
|
navigation.navigate('ConversationReply', {
|
|
77
77
|
conversation_id,
|
|
78
78
|
reply_root_id: message.replyRootId,
|
|
79
|
-
|
|
79
|
+
reply_root_author_name: message.author.name,
|
|
80
80
|
})
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -37,7 +37,7 @@ export function ApiProvider({ children }: ViewProps) {
|
|
|
37
37
|
const { token, env } = useContext(ChatContext)
|
|
38
38
|
const sessionChanged = useSessionChanged({ token, env })
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
const client = useApiClient()
|
|
41
41
|
|
|
42
42
|
useEffect(() => {
|
|
43
43
|
if (!sessionChanged) return
|
|
@@ -45,6 +45,10 @@ export function ApiProvider({ children }: ViewProps) {
|
|
|
45
45
|
chatQueryClient.clear()
|
|
46
46
|
}, [sessionChanged])
|
|
47
47
|
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
apiClient = client
|
|
50
|
+
}, [client])
|
|
51
|
+
|
|
48
52
|
return (
|
|
49
53
|
<QueryClientProvider client={chatQueryClient}>
|
|
50
54
|
<PrefetchQueries />
|
|
@@ -5,15 +5,20 @@ import { getMessageRequestArgs, getMessageQueryKey } from '../utils/request/get_
|
|
|
5
5
|
export const useConversationMessage = ({
|
|
6
6
|
conversation_id,
|
|
7
7
|
messageId,
|
|
8
|
+
enabled,
|
|
8
9
|
}: {
|
|
9
10
|
conversation_id: number
|
|
10
11
|
messageId: string
|
|
12
|
+
enabled?: boolean
|
|
11
13
|
}) => {
|
|
12
14
|
const {
|
|
13
15
|
data: message,
|
|
14
16
|
isError,
|
|
15
17
|
isLoading,
|
|
16
|
-
} = useApiGet<MessageResource>(
|
|
18
|
+
} = useApiGet<MessageResource>({
|
|
19
|
+
...getMessageRequestArgs({ conversation_id, messageId }),
|
|
20
|
+
enabled,
|
|
21
|
+
})
|
|
17
22
|
const queryKey = getMessageQueryKey({ conversation_id, messageId })
|
|
18
23
|
|
|
19
24
|
return { message, isError, isLoading, queryKey }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react'
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
2
|
import { useAsyncStorage } from './use_async_storage'
|
|
3
3
|
import type { FileAttachment } from '../types/resources/denormalized_attachment_resource_for_create'
|
|
4
4
|
|
|
@@ -27,17 +27,26 @@ export function useMessageDraft(conversationId: number) {
|
|
|
27
27
|
// Filter expired attachments
|
|
28
28
|
if (storedDraft?.attachments) {
|
|
29
29
|
const now = Date.now()
|
|
30
|
-
|
|
30
|
+
const validAttachments = storedDraft.attachments.filter(attachment => {
|
|
31
31
|
if (!attachment.uploadedAt) return false
|
|
32
32
|
return now - attachment.uploadedAt < ATTACHMENT_EXPIRY_MS
|
|
33
33
|
})
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
...storedDraft,
|
|
37
|
+
attachments: validAttachments,
|
|
38
|
+
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
return storedDraft
|
|
37
42
|
})
|
|
38
43
|
|
|
39
|
-
// Clean up expired drafts on mount
|
|
44
|
+
// Clean up expired drafts only once on mount - React 18 recommended pattern
|
|
45
|
+
const hasCleanedUp = useRef(false)
|
|
46
|
+
|
|
40
47
|
useEffect(() => {
|
|
48
|
+
if (hasCleanedUp.current) return
|
|
49
|
+
|
|
41
50
|
const now = Date.now()
|
|
42
51
|
const validDrafts: Record<string, MessageDraft> = {}
|
|
43
52
|
|
|
@@ -51,8 +60,9 @@ export function useMessageDraft(conversationId: number) {
|
|
|
51
60
|
if (Object.keys(validDrafts).length !== Object.keys(allDrafts).length) {
|
|
52
61
|
setAllDrafts(validDrafts)
|
|
53
62
|
}
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
|
|
64
|
+
hasCleanedUp.current = true
|
|
65
|
+
}, [allDrafts, setAllDrafts])
|
|
56
66
|
|
|
57
67
|
const saveDraft = useCallback(
|
|
58
68
|
(text: string, attachments: FileAttachment[]) => {
|
package/src/navigation/index.tsx
CHANGED
|
@@ -187,9 +187,9 @@ export const ChatStack = createNativeStackNavigator({
|
|
|
187
187
|
},
|
|
188
188
|
ConversationReply: {
|
|
189
189
|
screen: ConversationScreen,
|
|
190
|
-
options: {
|
|
191
|
-
title: 'Reply',
|
|
192
|
-
},
|
|
190
|
+
options: ({ route }) => ({
|
|
191
|
+
title: (route.params as ConversationRouteProps)?.title ?? 'Reply',
|
|
192
|
+
}),
|
|
193
193
|
},
|
|
194
194
|
TeamConversation: {
|
|
195
195
|
screen: TeamConversationScreen,
|
|
@@ -40,7 +40,7 @@ import { REPLIES_FEATURE_ENABLED } from '../utils'
|
|
|
40
40
|
export type ConversationRouteProps = {
|
|
41
41
|
conversation_id: number
|
|
42
42
|
reply_root_id?: string | null
|
|
43
|
-
|
|
43
|
+
reply_root_author_name?: string
|
|
44
44
|
chat_group_graph_id?: string
|
|
45
45
|
clear_input?: boolean
|
|
46
46
|
editing_message_id?: number | null
|
|
@@ -55,7 +55,8 @@ export type ConversationScreenProps = StaticScreenProps<ConversationRouteProps>
|
|
|
55
55
|
export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
56
56
|
const styles = useStyles()
|
|
57
57
|
const navigation = useNavigation()
|
|
58
|
-
const { conversation_id, editing_message_id, reply_root_id } =
|
|
58
|
+
const { conversation_id, editing_message_id, reply_root_id, reply_root_author_name } =
|
|
59
|
+
route.params
|
|
59
60
|
const { data: conversation } = useConversation(route.params)
|
|
60
61
|
const { messages, refetch, isRefetching, fetchNextPage } = useConversationMessages({
|
|
61
62
|
conversation_id,
|
|
@@ -73,6 +74,10 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
73
74
|
const showLeaderDisabledReplyBanner = canReply && repliesDisabled
|
|
74
75
|
const canDeleteNonAuthoredMessages = memberAbility?.canDeleteNonAuthoredMessages ?? false
|
|
75
76
|
const currentlyEditingMessage = messages.find(m => String(m.id) === String(editing_message_id))
|
|
77
|
+
const replyRootAuthorFirstName = reply_root_author_name?.split(' ')[0]
|
|
78
|
+
const replyHeaderTitle = replyRootAuthorFirstName
|
|
79
|
+
? `Reply to ${replyRootAuthorFirstName}`
|
|
80
|
+
: 'Reply'
|
|
76
81
|
|
|
77
82
|
const listRef = useRef<FlatList>(null)
|
|
78
83
|
const [showJumpToBottomButton, setShowJumpToBottomButton] = useState(false)
|
|
@@ -89,14 +94,18 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
89
94
|
}, [])
|
|
90
95
|
|
|
91
96
|
useEffect(() => {
|
|
92
|
-
if (reply_root_id)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
if (reply_root_id) {
|
|
98
|
+
navigation.setParams({
|
|
99
|
+
title: replyHeaderTitle,
|
|
100
|
+
})
|
|
101
|
+
} else {
|
|
102
|
+
navigation.setParams({
|
|
103
|
+
title: title,
|
|
104
|
+
badge: badges?.[0],
|
|
105
|
+
deleted: conversation?.deleted,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}, [navigation, title, badges, conversation?.deleted, reply_root_id, replyHeaderTitle])
|
|
100
109
|
|
|
101
110
|
if (!conversation || conversation.deleted) {
|
|
102
111
|
return (
|
|
@@ -167,6 +176,7 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
167
176
|
{showLeaderDisabledReplyBanner && <LeaderDisabledRepliesBanner />}
|
|
168
177
|
{canReply ? (
|
|
169
178
|
<MessageForm.Root
|
|
179
|
+
replyRootAuthorFirstName={replyRootAuthorFirstName}
|
|
170
180
|
conversation={conversation}
|
|
171
181
|
replyRootId={reply_root_id}
|
|
172
182
|
currentlyEditingMessage={currentlyEditingMessage}
|
|
@@ -23,13 +23,20 @@ export const MessageActionsScreenOptions = getFormSheetScreenOptions({
|
|
|
23
23
|
|
|
24
24
|
export type MessageActionsScreenProps = StaticScreenProps<{
|
|
25
25
|
message_id: string
|
|
26
|
+
reply_root_author_name?: string
|
|
26
27
|
conversation_id: number
|
|
27
28
|
canDeleteNonAuthoredMessages?: boolean
|
|
28
29
|
inReplyScreen?: boolean
|
|
29
30
|
}>
|
|
30
31
|
|
|
31
32
|
export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
|
|
32
|
-
const {
|
|
33
|
+
const {
|
|
34
|
+
conversation_id,
|
|
35
|
+
message_id,
|
|
36
|
+
canDeleteNonAuthoredMessages,
|
|
37
|
+
inReplyScreen,
|
|
38
|
+
reply_root_author_name,
|
|
39
|
+
} = route.params
|
|
33
40
|
|
|
34
41
|
const { messages, refetch } = useConversationMessages(
|
|
35
42
|
{ conversation_id },
|
|
@@ -46,6 +53,7 @@ export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
|
|
|
46
53
|
canDeleteNonAuthoredMessages={canDeleteNonAuthoredMessages || false}
|
|
47
54
|
refetchMessages={refetch}
|
|
48
55
|
inReplyScreen={inReplyScreen}
|
|
56
|
+
replyRootAuthorName={reply_root_author_name}
|
|
49
57
|
/>
|
|
50
58
|
)
|
|
51
59
|
}
|
|
@@ -56,12 +64,14 @@ function MessageActionsScreenContent({
|
|
|
56
64
|
canDeleteNonAuthoredMessages,
|
|
57
65
|
refetchMessages,
|
|
58
66
|
inReplyScreen,
|
|
67
|
+
replyRootAuthorName,
|
|
59
68
|
}: {
|
|
60
69
|
message: MessageResource
|
|
61
70
|
conversation_id: number
|
|
62
71
|
canDeleteNonAuthoredMessages: boolean
|
|
63
72
|
refetchMessages: () => void
|
|
64
73
|
inReplyScreen?: boolean
|
|
74
|
+
replyRootAuthorName?: string
|
|
65
75
|
}) {
|
|
66
76
|
const navigation = useNavigation()
|
|
67
77
|
const apiClient = useApiClient()
|
|
@@ -95,10 +105,10 @@ function MessageActionsScreenContent({
|
|
|
95
105
|
navigation.navigate('ConversationReply', {
|
|
96
106
|
conversation_id,
|
|
97
107
|
reply_root_id: message.replyRootId || message.id,
|
|
98
|
-
|
|
108
|
+
reply_root_author_name: replyRootAuthorName,
|
|
99
109
|
})
|
|
100
110
|
})
|
|
101
|
-
}, [navigation, conversation_id, message.id, message.replyRootId])
|
|
111
|
+
}, [navigation, conversation_id, message.id, message.replyRootId, replyRootAuthorName])
|
|
102
112
|
|
|
103
113
|
const handleCopyPress = () => {
|
|
104
114
|
Clipboard.setStringAsync(message?.text || attachmentForCopy() || '')
|