@planningcenter/chat-react-native 3.18.0-rc.7 → 3.18.0-rc.9
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 +21 -11
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +2 -4
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/messages_disabled_banners.d.ts +3 -0
- package/build/components/conversation/messages_disabled_banners.d.ts.map +1 -0
- package/build/components/conversation/messages_disabled_banners.js +53 -0
- package/build/components/conversation/messages_disabled_banners.js.map +1 -0
- package/build/components/conversation/typing_indicator.d.ts +1 -5
- package/build/components/conversation/typing_indicator.d.ts.map +1 -1
- package/build/components/conversation/typing_indicator.js +2 -2
- package/build/components/conversation/typing_indicator.js.map +1 -1
- package/build/contexts/conversation_context.d.ts +13 -0
- package/build/contexts/conversation_context.d.ts.map +1 -0
- package/build/contexts/conversation_context.js +14 -0
- package/build/contexts/conversation_context.js.map +1 -0
- package/build/hooks/use_broadcast_typing_status.d.ts +1 -1
- package/build/hooks/use_broadcast_typing_status.d.ts.map +1 -1
- package/build/hooks/use_broadcast_typing_status.js +7 -3
- package/build/hooks/use_broadcast_typing_status.js.map +1 -1
- package/build/hooks/use_typing_indicators.d.ts +1 -1
- package/build/hooks/use_typing_indicators.d.ts.map +1 -1
- package/build/hooks/use_typing_indicators.js +16 -3
- package/build/hooks/use_typing_indicators.js.map +1 -1
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +6 -2
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +11 -4
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/types/jolt_events/typing_events.d.ts +1 -0
- package/build/types/jolt_events/typing_events.d.ts.map +1 -1
- package/build/types/jolt_events/typing_events.js.map +1 -1
- package/package.json +2 -2
- package/src/components/conversation/message.tsx +32 -15
- package/src/components/conversation/message_form.tsx +2 -9
- package/src/components/conversation/messages_disabled_banners.tsx +69 -0
- package/src/components/conversation/typing_indicator.tsx +2 -6
- package/src/contexts/conversation_context.tsx +34 -0
- package/src/hooks/use_broadcast_typing_status.ts +7 -3
- package/src/hooks/use_typing_indicators.ts +15 -3
- package/src/screens/conversation_details_screen.tsx +6 -2
- package/src/screens/conversation_screen.tsx +20 -6
- package/src/types/jolt_events/typing_events.ts +1 -0
- package/build/components/conversation/disabled_replies_banners.d.ts +0 -3
- package/build/components/conversation/disabled_replies_banners.d.ts.map +0 -1
- package/build/components/conversation/disabled_replies_banners.js +0 -41
- package/build/components/conversation/disabled_replies_banners.js.map +0 -1
- package/src/components/conversation/disabled_replies_banners.tsx +0 -58
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.18.0-rc.
|
|
3
|
+
"version": "3.18.0-rc.9",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"react-native-url-polyfill": "^2.0.0",
|
|
59
59
|
"typescript": "<5.6.0"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "1c8ef808381f80f84eba2890e1bbfc706574c69d"
|
|
62
62
|
}
|
|
@@ -80,7 +80,6 @@ export function Message({
|
|
|
80
80
|
const isReplyRootMessage = message.replyRootId === message.id
|
|
81
81
|
const isDeletedReplyRootMessage = isReplyRootMessage && !!message.deletedAt
|
|
82
82
|
|
|
83
|
-
const messageText = isDeletedReplyRootMessage ? 'Message deleted' : text
|
|
84
83
|
const replyCountText = pluralize(message.replyCount, 'reply')
|
|
85
84
|
const messagePendingLabel = isPersisted ? 'Saving' : 'Sending'
|
|
86
85
|
const replyRootAuthorName = message.replyRootId
|
|
@@ -118,6 +117,10 @@ export function Message({
|
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
119
|
|
|
120
|
+
const handleMessagePress = () => {
|
|
121
|
+
setShowMessageMetaToggle(!showMessageMetaToggle)
|
|
122
|
+
}
|
|
123
|
+
|
|
121
124
|
const handleMessageLongPress = () => {
|
|
122
125
|
if (!isPersisted) return
|
|
123
126
|
|
|
@@ -130,6 +133,7 @@ export function Message({
|
|
|
130
133
|
reply_root_author_name: replyRootAuthorName,
|
|
131
134
|
})
|
|
132
135
|
}
|
|
136
|
+
|
|
133
137
|
const handleReactionLongPress = (reaction: ReactionCountResource) => {
|
|
134
138
|
Haptic.impactLight()
|
|
135
139
|
navigation.navigate('Reactions', {
|
|
@@ -162,11 +166,12 @@ export function Message({
|
|
|
162
166
|
return (
|
|
163
167
|
<Pressable
|
|
164
168
|
onLongPress={handleMessageLongPress}
|
|
165
|
-
onPress={
|
|
169
|
+
onPress={handleMessagePress}
|
|
166
170
|
onPressIn={handleMessagePressIn}
|
|
167
171
|
onPressOut={handleMessagePressOut}
|
|
168
172
|
android_ripple={{ color: colors.androidRippleNeutral, borderless: false, foreground: true }}
|
|
169
173
|
accessibilityHint="Long press to view message actions like reacting and copying."
|
|
174
|
+
disabled={isDeletedReplyRootMessage}
|
|
170
175
|
>
|
|
171
176
|
<Animated.View style={[styles.message, animatedBackgroundColor]}>
|
|
172
177
|
{!message.mine && (
|
|
@@ -190,7 +195,7 @@ export function Message({
|
|
|
190
195
|
</View>
|
|
191
196
|
)}
|
|
192
197
|
<View style={[styles.messageContent, { marginBottom: messageBottomMargin }]}>
|
|
193
|
-
{renderAuthor && (
|
|
198
|
+
{renderAuthor && !isDeletedReplyRootMessage && (
|
|
194
199
|
<Text variant="footnote" style={styles.authorName}>
|
|
195
200
|
{message.author.name}
|
|
196
201
|
</Text>
|
|
@@ -199,18 +204,26 @@ export function Message({
|
|
|
199
204
|
style={styles.messageBubble}
|
|
200
205
|
onLayout={e => setMessageBubbleHeight(e.nativeEvent.layout.height)}
|
|
201
206
|
>
|
|
202
|
-
|
|
203
|
-
<MessageAttachments
|
|
204
|
-
attachments={message.attachments}
|
|
205
|
-
metaProps={metaProps}
|
|
206
|
-
onMessageAttachmentLongPress={handleMessageAttachmentLongPress}
|
|
207
|
-
onMessageLongPress={handleMessageLongPress}
|
|
208
|
-
/>
|
|
209
|
-
</ErrorBoundary>
|
|
210
|
-
{messageText && (
|
|
207
|
+
{isDeletedReplyRootMessage ? (
|
|
211
208
|
<View style={styles.messageText}>
|
|
212
|
-
<
|
|
209
|
+
<Text style={styles.replyRootDeletedText}>Message deleted</Text>
|
|
213
210
|
</View>
|
|
211
|
+
) : (
|
|
212
|
+
<>
|
|
213
|
+
<ErrorBoundary>
|
|
214
|
+
<MessageAttachments
|
|
215
|
+
attachments={message.attachments}
|
|
216
|
+
metaProps={metaProps}
|
|
217
|
+
onMessageAttachmentLongPress={handleMessageAttachmentLongPress}
|
|
218
|
+
onMessageLongPress={handleMessageLongPress}
|
|
219
|
+
/>
|
|
220
|
+
</ErrorBoundary>
|
|
221
|
+
{text && (
|
|
222
|
+
<View style={styles.messageText}>
|
|
223
|
+
<MessageMarkdown text={text} />
|
|
224
|
+
</View>
|
|
225
|
+
)}
|
|
226
|
+
</>
|
|
214
227
|
)}
|
|
215
228
|
</View>
|
|
216
229
|
{showReplyCountButton && (
|
|
@@ -224,7 +237,7 @@ export function Message({
|
|
|
224
237
|
{replyCountText}
|
|
225
238
|
</TextButton>
|
|
226
239
|
)}
|
|
227
|
-
{hasReactions && (
|
|
240
|
+
{hasReactions && !isDeletedReplyRootMessage && (
|
|
228
241
|
<View style={styles.messageReactions}>
|
|
229
242
|
{reactionCounts.map(reaction => (
|
|
230
243
|
<MessageReaction
|
|
@@ -237,7 +250,7 @@ export function Message({
|
|
|
237
250
|
))}
|
|
238
251
|
</View>
|
|
239
252
|
)}
|
|
240
|
-
{showMessageMeta && (
|
|
253
|
+
{showMessageMeta && !isDeletedReplyRootMessage && (
|
|
241
254
|
<View style={styles.messageMeta}>
|
|
242
255
|
{message.mine && !pending && !error && (
|
|
243
256
|
<MessageReadReceipts
|
|
@@ -365,5 +378,9 @@ const useMessageStyles = ({ mine }: MessageResource) => {
|
|
|
365
378
|
errorText: {
|
|
366
379
|
color: colors.statusErrorText,
|
|
367
380
|
},
|
|
381
|
+
replyRootDeletedText: {
|
|
382
|
+
color: colors.textColorDefaultSecondary,
|
|
383
|
+
fontStyle: 'italic',
|
|
384
|
+
},
|
|
368
385
|
})
|
|
369
386
|
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RouteProp,
|
|
3
|
-
useNavigation,
|
|
4
|
-
useTheme as useNavigationTheme,
|
|
5
|
-
useRoute,
|
|
6
|
-
} from '@react-navigation/native'
|
|
1
|
+
import { useNavigation, useTheme as useNavigationTheme, useRoute } from '@react-navigation/native'
|
|
7
2
|
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
|
8
3
|
import {
|
|
9
4
|
Platform,
|
|
@@ -319,9 +314,7 @@ function MessageFormInput() {
|
|
|
319
314
|
React.useContext(MessageFormContext)
|
|
320
315
|
const attachmentError = attachmentUploader?.errorMessage
|
|
321
316
|
|
|
322
|
-
const
|
|
323
|
-
const conversationId = route.params.conversation_id
|
|
324
|
-
const broadcastTypingStatus = useBroadcastTypingStatus(conversationId)
|
|
317
|
+
const broadcastTypingStatus = useBroadcastTypingStatus()
|
|
325
318
|
|
|
326
319
|
const handleTextChange = (newText: string) => {
|
|
327
320
|
setText(newText)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { StyleSheet, View, type ViewStyle } from 'react-native'
|
|
2
|
+
import { useTheme } from '../../hooks'
|
|
3
|
+
import { Text } from '../display'
|
|
4
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
5
|
+
import { useFeatures } from '../../hooks/use_features'
|
|
6
|
+
import { availableFeatures } from '../../hooks/use_features'
|
|
7
|
+
|
|
8
|
+
export const LeaderMessagesDisabledBanner = () => {
|
|
9
|
+
const { featureEnabled } = useFeatures()
|
|
10
|
+
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
11
|
+
|
|
12
|
+
const description = repliesEnabled
|
|
13
|
+
? 'Only leaders can send messages in this conversation.'
|
|
14
|
+
: 'Replies are frozen for everyone else.'
|
|
15
|
+
|
|
16
|
+
return <MessagesDisabledBanner description={description} />
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const MemberMessagesDisabledBanner = () => {
|
|
20
|
+
const styles = useStyles()
|
|
21
|
+
const { featureEnabled } = useFeatures()
|
|
22
|
+
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
23
|
+
|
|
24
|
+
const description = repliesEnabled
|
|
25
|
+
? 'Only leaders can send messages, but you can still add reactions.'
|
|
26
|
+
: 'Replies have been disabled by a leader, but you can still add reactions.'
|
|
27
|
+
|
|
28
|
+
return <MessagesDisabledBanner description={description} style={styles.memberBanner} />
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface MessagesDisabledBannerProps {
|
|
32
|
+
description: string
|
|
33
|
+
style?: ViewStyle
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const MessagesDisabledBanner = ({ description, style }: MessagesDisabledBannerProps) => {
|
|
37
|
+
const styles = useStyles()
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<View style={[styles.baseBanner, style]}>
|
|
41
|
+
<Text style={styles.text} variant="tertiary">
|
|
42
|
+
{description}
|
|
43
|
+
</Text>
|
|
44
|
+
</View>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const useStyles = () => {
|
|
49
|
+
const { colors } = useTheme()
|
|
50
|
+
const { bottom } = useSafeAreaInsets()
|
|
51
|
+
|
|
52
|
+
return StyleSheet.create({
|
|
53
|
+
baseBanner: {
|
|
54
|
+
paddingHorizontal: 16,
|
|
55
|
+
paddingVertical: 4,
|
|
56
|
+
backgroundColor: colors.statusNeutralComposedBackground,
|
|
57
|
+
borderTopWidth: 1,
|
|
58
|
+
borderTopColor: colors.borderColorDefaultBase,
|
|
59
|
+
},
|
|
60
|
+
text: {
|
|
61
|
+
textAlign: 'center',
|
|
62
|
+
},
|
|
63
|
+
memberBanner: {
|
|
64
|
+
paddingTop: 16,
|
|
65
|
+
paddingBottom: 16 + bottom,
|
|
66
|
+
marginBottom: -bottom,
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
}
|
|
@@ -41,16 +41,12 @@ const TypingDots = () => {
|
|
|
41
41
|
)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
interface TypingIndicatorProps {
|
|
45
|
-
conversationId: number
|
|
46
|
-
}
|
|
47
|
-
|
|
48
44
|
/**
|
|
49
45
|
* Component to display typing indicators in a conversation
|
|
50
46
|
* Shows "X is typing..." with animated dots
|
|
51
47
|
*/
|
|
52
|
-
export const TypingIndicator = (
|
|
53
|
-
const typingPeople = useTypingIndicators(
|
|
48
|
+
export const TypingIndicator = () => {
|
|
49
|
+
const typingPeople = useTypingIndicators()
|
|
54
50
|
const styles = useStyles()
|
|
55
51
|
const enabled = typingPeople.length > 0
|
|
56
52
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, { createContext, PropsWithChildren, useContext, useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
interface ConversationContextValue {
|
|
4
|
+
conversationId: number
|
|
5
|
+
currentPageReplyRootId: string | null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ConversationContextProviderProps extends PropsWithChildren {
|
|
9
|
+
conversationId: number
|
|
10
|
+
currentPageReplyRootId: string | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ConversationContext = createContext<ConversationContextValue>({
|
|
14
|
+
conversationId: 0,
|
|
15
|
+
currentPageReplyRootId: null,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const ConversationContextProvider = ({
|
|
19
|
+
children,
|
|
20
|
+
conversationId,
|
|
21
|
+
currentPageReplyRootId,
|
|
22
|
+
}: ConversationContextProviderProps) => {
|
|
23
|
+
const value = useMemo(
|
|
24
|
+
() => ({
|
|
25
|
+
conversationId,
|
|
26
|
+
currentPageReplyRootId,
|
|
27
|
+
}),
|
|
28
|
+
[conversationId, currentPageReplyRootId]
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return <ConversationContext.Provider value={value}>{children}</ConversationContext.Provider>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const useConversationContext = () => useContext(ConversationContext)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useRef } from 'react'
|
|
2
2
|
import { useApiClient } from './use_api_client'
|
|
3
|
+
import { useConversationContext } from '../contexts/conversation_context'
|
|
3
4
|
|
|
4
5
|
const THROTTLE_INTERVAL = 3000 // 3 seconds
|
|
5
6
|
|
|
@@ -10,7 +11,8 @@ const THROTTLE_INTERVAL = 3000 // 3 seconds
|
|
|
10
11
|
* after receiving a typing event. This is how we can show a steady typing indicator even
|
|
11
12
|
* if the user types once every 2.9 seconds.
|
|
12
13
|
*/
|
|
13
|
-
export const useBroadcastTypingStatus = (
|
|
14
|
+
export const useBroadcastTypingStatus = () => {
|
|
15
|
+
const { conversationId, currentPageReplyRootId } = useConversationContext()
|
|
14
16
|
const apiClient = useApiClient()
|
|
15
17
|
const lastBroadcastTime = useRef<number>(0)
|
|
16
18
|
|
|
@@ -26,12 +28,14 @@ export const useBroadcastTypingStatus = (conversationId: string | number) => {
|
|
|
26
28
|
apiClient.chat
|
|
27
29
|
.post({
|
|
28
30
|
url: `/me/conversations/${conversationId}/broadcast_typing_status`,
|
|
29
|
-
data: {
|
|
31
|
+
data: {
|
|
32
|
+
data: { type: 'TypingStatus', attributes: { reply_root_id: currentPageReplyRootId } },
|
|
33
|
+
},
|
|
30
34
|
})
|
|
31
35
|
.catch(error => {
|
|
32
36
|
console.error('Failed to broadcast typing status:', error)
|
|
33
37
|
})
|
|
34
|
-
}, [apiClient, conversationId])
|
|
38
|
+
}, [apiClient.chat, conversationId, currentPageReplyRootId])
|
|
35
39
|
|
|
36
40
|
return broadcastTypingStatus
|
|
37
41
|
}
|
|
@@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query'
|
|
|
2
2
|
import { useMemo } from 'react'
|
|
3
3
|
import { PersonTyping } from './use_typing_status_cache'
|
|
4
4
|
import { useCurrentPerson } from './use_current_person'
|
|
5
|
+
import { useConversationContext } from '../contexts/conversation_context'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Hook for getting people currently typing in a conversation
|
|
@@ -9,9 +10,12 @@ import { useCurrentPerson } from './use_current_person'
|
|
|
9
10
|
* The query function itself doesn't do anything, but we add to the cache
|
|
10
11
|
* from useTypingStatusCache when we receive a typing event.
|
|
11
12
|
*/
|
|
12
|
-
export const useTypingIndicators = (
|
|
13
|
+
export const useTypingIndicators = () => {
|
|
14
|
+
const { conversationId, currentPageReplyRootId } = useConversationContext()
|
|
13
15
|
const cacheKey = useMemo(() => ['conversationTyping', String(conversationId)], [conversationId])
|
|
14
16
|
const currentPerson = useCurrentPerson()
|
|
17
|
+
const isCurrentPerson = (authorId: number) => authorId === currentPerson?.id
|
|
18
|
+
const inMainConversationView = !currentPageReplyRootId
|
|
15
19
|
const now = Date.now()
|
|
16
20
|
const { data } = useQuery({
|
|
17
21
|
queryKey: cacheKey,
|
|
@@ -19,8 +23,16 @@ export const useTypingIndicators = (conversationId: number) => {
|
|
|
19
23
|
initialData: stableArray,
|
|
20
24
|
})
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
return data.filter(({ author_id, expires, reply_root_id }) => {
|
|
27
|
+
if (isCurrentPerson(author_id)) return false
|
|
28
|
+
if (now > expires) return false
|
|
29
|
+
|
|
30
|
+
// If you are in the main conversation view, you will see any message sent
|
|
31
|
+
if (inMainConversationView) return true
|
|
32
|
+
|
|
33
|
+
// If you are in a reply view, you will only see messages sent in this reply thread
|
|
34
|
+
return reply_root_id === currentPageReplyRootId
|
|
35
|
+
})
|
|
24
36
|
}
|
|
25
37
|
|
|
26
38
|
/**
|
|
@@ -44,6 +44,8 @@ import { HeaderTextButton } from '../components/display/platform_modal_header_bu
|
|
|
44
44
|
import { tokens } from '../vendor/tapestry/tokens'
|
|
45
45
|
import { ButtonAppearanceUnion } from '../components/display/utils/button_colors'
|
|
46
46
|
import { GroupResource } from '../types/resources/group_resource'
|
|
47
|
+
import { useFeatures } from '../hooks/use_features'
|
|
48
|
+
import { availableFeatures } from '../hooks/use_features'
|
|
47
49
|
|
|
48
50
|
// =========================================
|
|
49
51
|
// ====== Factory Constants & Types ========
|
|
@@ -93,6 +95,8 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
93
95
|
const { repliesDisabled, setRepliesDisabled } = useConversationDisableReplies(route.params)
|
|
94
96
|
const { mutate: saveTitle } = useConversationUpdate(route.params)
|
|
95
97
|
const { mutate: deleteConversation } = useConversationDelete(route.params)
|
|
98
|
+
const { featureEnabled } = useFeatures()
|
|
99
|
+
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
96
100
|
|
|
97
101
|
const trimmedTitle = title.trim()
|
|
98
102
|
const emptyTitle = trimmedTitle === '' || title === null
|
|
@@ -250,8 +254,8 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
250
254
|
{
|
|
251
255
|
type: canUpdate ? SectionTypes.setting : SectionTypes.hidden,
|
|
252
256
|
data: {
|
|
253
|
-
title: 'Freeze conversation',
|
|
254
|
-
subtitle: 'Disables replies for everyone except leaders.',
|
|
257
|
+
title: repliesEnabled ? 'Leader messages only' : 'Freeze conversation',
|
|
258
|
+
subtitle: repliesEnabled ? undefined : 'Disables replies for everyone except leaders.',
|
|
255
259
|
rightItem: (
|
|
256
260
|
<Switch value={repliesDisabled} onChange={() => setRepliesDisabled(!repliesDisabled)} />
|
|
257
261
|
),
|
|
@@ -14,9 +14,9 @@ import { FlatList, Platform, StyleSheet, View } from 'react-native'
|
|
|
14
14
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
15
15
|
import { Badge, Icon, Text } from '../components'
|
|
16
16
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from '../components/conversation/
|
|
17
|
+
LeaderMessagesDisabledBanner,
|
|
18
|
+
MemberMessagesDisabledBanner,
|
|
19
|
+
} from '../components/conversation/messages_disabled_banners'
|
|
20
20
|
import { EmptyConversationBlankState } from '../components/conversation/empty_conversation_blank_state'
|
|
21
21
|
import BlankState from '../components/primitive/blank_state_primitive'
|
|
22
22
|
import { Message } from '../components/conversation/message'
|
|
@@ -36,6 +36,7 @@ import { useConversationJoltEvents } from '../hooks/use_conversation_jolt_events
|
|
|
36
36
|
import { JumpToBottomButton } from '../components/conversation/jump_to_bottom_button'
|
|
37
37
|
import { ReplyShadowMessage } from '../components/conversation/reply_shadow_message'
|
|
38
38
|
import { availableFeatures, useFeatures } from '../hooks/use_features'
|
|
39
|
+
import { ConversationContextProvider } from '../contexts/conversation_context'
|
|
39
40
|
|
|
40
41
|
export type ConversationRouteProps = {
|
|
41
42
|
conversation_id: number
|
|
@@ -53,6 +54,19 @@ export type ConversationRouteProps = {
|
|
|
53
54
|
export type ConversationScreenProps = StaticScreenProps<ConversationRouteProps>
|
|
54
55
|
|
|
55
56
|
export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
57
|
+
const { conversation_id, reply_root_id } = route.params
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<ConversationContextProvider
|
|
61
|
+
conversationId={conversation_id}
|
|
62
|
+
currentPageReplyRootId={reply_root_id ?? null}
|
|
63
|
+
>
|
|
64
|
+
<ConversationScreenContent route={route} />
|
|
65
|
+
</ConversationContextProvider>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
56
70
|
const styles = useStyles()
|
|
57
71
|
const navigation = useNavigation()
|
|
58
72
|
const { conversation_id, editing_message_id, reply_root_id, reply_root_author_name } =
|
|
@@ -179,8 +193,8 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
179
193
|
/>
|
|
180
194
|
)}
|
|
181
195
|
<JumpToBottomButton onPress={handleReturnToBottom} visible={showJumpToBottomButton} />
|
|
182
|
-
{!noMessages && <TypingIndicator
|
|
183
|
-
{showLeaderDisabledReplyBanner && <
|
|
196
|
+
{!noMessages && <TypingIndicator />}
|
|
197
|
+
{showLeaderDisabledReplyBanner && <LeaderMessagesDisabledBanner />}
|
|
184
198
|
{canReply ? (
|
|
185
199
|
<MessageForm.Root
|
|
186
200
|
replyRootAuthorFirstName={replyRootAuthorFirstName}
|
|
@@ -201,7 +215,7 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
201
215
|
<MessageForm.SubmitButton />
|
|
202
216
|
</MessageForm.Root>
|
|
203
217
|
) : (
|
|
204
|
-
<
|
|
218
|
+
<MemberMessagesDisabledBanner />
|
|
205
219
|
)}
|
|
206
220
|
</KeyboardView>
|
|
207
221
|
</View>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"disabled_replies_banners.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/disabled_replies_banners.tsx"],"names":[],"mappings":"AAKA,eAAO,MAAM,2BAA2B,mCAEvC,CAAA;AAED,eAAO,MAAM,2BAA2B,mCAQvC,CAAA"}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { StyleSheet, View } from 'react-native';
|
|
2
|
-
import { useTheme } from '../../hooks';
|
|
3
|
-
import { Text } from '../display';
|
|
4
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
|
-
export const LeaderDisabledRepliesBanner = () => {
|
|
6
|
-
return <DisabledRepliesBanner description="Replies are frozen for everyone else."/>;
|
|
7
|
-
};
|
|
8
|
-
export const MemberDisabledRepliesBanner = () => {
|
|
9
|
-
const styles = useStyles();
|
|
10
|
-
return (<DisabledRepliesBanner description="Replies have been disabled by a leader, but you can still add reactions." style={styles.memberBanner}/>);
|
|
11
|
-
};
|
|
12
|
-
const DisabledRepliesBanner = ({ description, style }) => {
|
|
13
|
-
const styles = useStyles();
|
|
14
|
-
return (<View style={[styles.baseBanner, style]}>
|
|
15
|
-
<Text style={styles.text} variant="tertiary">
|
|
16
|
-
{description}
|
|
17
|
-
</Text>
|
|
18
|
-
</View>);
|
|
19
|
-
};
|
|
20
|
-
const useStyles = () => {
|
|
21
|
-
const { colors } = useTheme();
|
|
22
|
-
const { bottom } = useSafeAreaInsets();
|
|
23
|
-
return StyleSheet.create({
|
|
24
|
-
baseBanner: {
|
|
25
|
-
paddingHorizontal: 16,
|
|
26
|
-
paddingVertical: 4,
|
|
27
|
-
backgroundColor: colors.statusNeutralComposedBackground,
|
|
28
|
-
borderTopWidth: 1,
|
|
29
|
-
borderTopColor: colors.borderColorDefaultBase,
|
|
30
|
-
},
|
|
31
|
-
text: {
|
|
32
|
-
textAlign: 'center',
|
|
33
|
-
},
|
|
34
|
-
memberBanner: {
|
|
35
|
-
paddingTop: 16,
|
|
36
|
-
paddingBottom: 16 + bottom,
|
|
37
|
-
marginBottom: -bottom,
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
};
|
|
41
|
-
//# sourceMappingURL=disabled_replies_banners.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"disabled_replies_banners.js","sourceRoot":"","sources":["../../../src/components/conversation/disabled_replies_banners.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAkB,MAAM,cAAc,CAAA;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAElE,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,EAAE;IAC9C,OAAO,CAAC,qBAAqB,CAAC,WAAW,CAAC,uCAAuC,EAAG,CAAA;AACtF,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,EAAE;IAC9C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,OAAO,CACL,CAAC,qBAAqB,CACpB,WAAW,CAAC,0EAA0E,CACtF,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,EAC3B,CACH,CAAA;AACH,CAAC,CAAA;AAOD,MAAM,qBAAqB,GAAG,CAAC,EAAE,WAAW,EAAE,KAAK,EAA8B,EAAE,EAAE;IACnF,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAE1B,OAAO,CACL,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CACtC;MAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,CAC1C;QAAA,CAAC,WAAW,CACd;MAAA,EAAE,IAAI,CACR;IAAA,EAAE,IAAI,CAAC,CACR,CAAA;AACH,CAAC,CAAA;AAED,MAAM,SAAS,GAAG,GAAG,EAAE;IACrB,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,CAAA;IAC7B,MAAM,EAAE,MAAM,EAAE,GAAG,iBAAiB,EAAE,CAAA;IAEtC,OAAO,UAAU,CAAC,MAAM,CAAC;QACvB,UAAU,EAAE;YACV,iBAAiB,EAAE,EAAE;YACrB,eAAe,EAAE,CAAC;YAClB,eAAe,EAAE,MAAM,CAAC,+BAA+B;YACvD,cAAc,EAAE,CAAC;YACjB,cAAc,EAAE,MAAM,CAAC,sBAAsB;SAC9C;QACD,IAAI,EAAE;YACJ,SAAS,EAAE,QAAQ;SACpB;QACD,YAAY,EAAE;YACZ,UAAU,EAAE,EAAE;YACd,aAAa,EAAE,EAAE,GAAG,MAAM;YAC1B,YAAY,EAAE,CAAC,MAAM;SACtB;KACF,CAAC,CAAA;AACJ,CAAC,CAAA","sourcesContent":["import { StyleSheet, View, type ViewStyle } from 'react-native'\nimport { useTheme } from '../../hooks'\nimport { Text } from '../display'\nimport { useSafeAreaInsets } from 'react-native-safe-area-context'\n\nexport const LeaderDisabledRepliesBanner = () => {\n return <DisabledRepliesBanner description=\"Replies are frozen for everyone else.\" />\n}\n\nexport const MemberDisabledRepliesBanner = () => {\n const styles = useStyles()\n return (\n <DisabledRepliesBanner\n description=\"Replies have been disabled by a leader, but you can still add reactions.\"\n style={styles.memberBanner}\n />\n )\n}\n\ninterface DisabledRepliesBannerProps {\n description: string\n style?: ViewStyle\n}\n\nconst DisabledRepliesBanner = ({ description, style }: DisabledRepliesBannerProps) => {\n const styles = useStyles()\n\n return (\n <View style={[styles.baseBanner, style]}>\n <Text style={styles.text} variant=\"tertiary\">\n {description}\n </Text>\n </View>\n )\n}\n\nconst useStyles = () => {\n const { colors } = useTheme()\n const { bottom } = useSafeAreaInsets()\n\n return StyleSheet.create({\n baseBanner: {\n paddingHorizontal: 16,\n paddingVertical: 4,\n backgroundColor: colors.statusNeutralComposedBackground,\n borderTopWidth: 1,\n borderTopColor: colors.borderColorDefaultBase,\n },\n text: {\n textAlign: 'center',\n },\n memberBanner: {\n paddingTop: 16,\n paddingBottom: 16 + bottom,\n marginBottom: -bottom,\n },\n })\n}\n"]}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { StyleSheet, View, type ViewStyle } from 'react-native'
|
|
2
|
-
import { useTheme } from '../../hooks'
|
|
3
|
-
import { Text } from '../display'
|
|
4
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
5
|
-
|
|
6
|
-
export const LeaderDisabledRepliesBanner = () => {
|
|
7
|
-
return <DisabledRepliesBanner description="Replies are frozen for everyone else." />
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const MemberDisabledRepliesBanner = () => {
|
|
11
|
-
const styles = useStyles()
|
|
12
|
-
return (
|
|
13
|
-
<DisabledRepliesBanner
|
|
14
|
-
description="Replies have been disabled by a leader, but you can still add reactions."
|
|
15
|
-
style={styles.memberBanner}
|
|
16
|
-
/>
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface DisabledRepliesBannerProps {
|
|
21
|
-
description: string
|
|
22
|
-
style?: ViewStyle
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const DisabledRepliesBanner = ({ description, style }: DisabledRepliesBannerProps) => {
|
|
26
|
-
const styles = useStyles()
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<View style={[styles.baseBanner, style]}>
|
|
30
|
-
<Text style={styles.text} variant="tertiary">
|
|
31
|
-
{description}
|
|
32
|
-
</Text>
|
|
33
|
-
</View>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const useStyles = () => {
|
|
38
|
-
const { colors } = useTheme()
|
|
39
|
-
const { bottom } = useSafeAreaInsets()
|
|
40
|
-
|
|
41
|
-
return StyleSheet.create({
|
|
42
|
-
baseBanner: {
|
|
43
|
-
paddingHorizontal: 16,
|
|
44
|
-
paddingVertical: 4,
|
|
45
|
-
backgroundColor: colors.statusNeutralComposedBackground,
|
|
46
|
-
borderTopWidth: 1,
|
|
47
|
-
borderTopColor: colors.borderColorDefaultBase,
|
|
48
|
-
},
|
|
49
|
-
text: {
|
|
50
|
-
textAlign: 'center',
|
|
51
|
-
},
|
|
52
|
-
memberBanner: {
|
|
53
|
-
paddingTop: 16,
|
|
54
|
-
paddingBottom: 16 + bottom,
|
|
55
|
-
marginBottom: -bottom,
|
|
56
|
-
},
|
|
57
|
-
})
|
|
58
|
-
}
|