@planningcenter/chat-react-native 3.10.0 → 3.10.1-qa-291.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/attachments/image_attachment.d.ts.map +1 -1
- package/build/components/conversation/attachments/image_attachment.js +294 -34
- package/build/components/conversation/attachments/image_attachment.js.map +1 -1
- package/build/components/conversation/attachments/image_attachment_legacy.d.ts +12 -0
- package/build/components/conversation/attachments/image_attachment_legacy.d.ts.map +1 -0
- package/build/components/conversation/attachments/image_attachment_legacy.js +142 -0
- package/build/components/conversation/attachments/image_attachment_legacy.js.map +1 -0
- package/build/components/conversation/message_attachments.d.ts +1 -1
- package/build/components/conversation/message_attachments.d.ts.map +1 -1
- package/build/components/conversation/message_attachments.js +17 -5
- package/build/components/conversation/message_attachments.js.map +1 -1
- package/build/hooks/services/use_find_or_create_services_conversation.d.ts +2 -0
- package/build/hooks/services/use_find_or_create_services_conversation.d.ts.map +1 -1
- package/build/hooks/services/use_find_or_create_services_conversation.js +20 -19
- package/build/hooks/services/use_find_or_create_services_conversation.js.map +1 -1
- package/build/navigation/index.d.ts +9 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +10 -0
- package/build/navigation/index.js.map +1 -1
- package/build/screens/send_giphy_screen.d.ts +1 -1
- package/build/screens/send_giphy_screen.d.ts.map +1 -1
- package/build/screens/send_giphy_screen.js +14 -2
- package/build/screens/send_giphy_screen.js.map +1 -1
- package/build/screens/team_conversation_screen.d.ts +8 -0
- package/build/screens/team_conversation_screen.d.ts.map +1 -0
- package/build/screens/team_conversation_screen.js +28 -0
- package/build/screens/team_conversation_screen.js.map +1 -0
- package/package.json +2 -2
- package/src/components/conversation/attachments/image_attachment.tsx +375 -65
- package/src/components/conversation/attachments/image_attachment_legacy.tsx +258 -0
- package/src/components/conversation/message_attachments.tsx +35 -6
- package/src/hooks/services/use_find_or_create_services_conversation.ts +27 -21
- package/src/navigation/index.tsx +10 -0
- package/src/screens/send_giphy_screen.tsx +23 -2
- package/src/screens/team_conversation_screen.tsx +46 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
Modal,
|
|
5
|
+
useWindowDimensions,
|
|
6
|
+
SafeAreaView,
|
|
7
|
+
View,
|
|
8
|
+
Linking,
|
|
9
|
+
ImageStyle,
|
|
10
|
+
} from 'react-native'
|
|
11
|
+
import {
|
|
12
|
+
Gesture,
|
|
13
|
+
GestureDetector,
|
|
14
|
+
GestureHandlerRootView,
|
|
15
|
+
type PanGesture,
|
|
16
|
+
} from 'react-native-gesture-handler'
|
|
17
|
+
import {
|
|
18
|
+
runOnJS,
|
|
19
|
+
useAnimatedStyle,
|
|
20
|
+
useSharedValue,
|
|
21
|
+
withTiming,
|
|
22
|
+
type AnimatedStyle,
|
|
23
|
+
} from 'react-native-reanimated'
|
|
24
|
+
import { tokens } from '../../../vendor/tapestry/tokens'
|
|
25
|
+
import { IconButton, Image, Heading, Text } from '../../display'
|
|
26
|
+
import colorFunction from 'color'
|
|
27
|
+
import { formatDatePreview } from '../../../utils/date'
|
|
28
|
+
import { DenormalizedMessageAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'
|
|
29
|
+
import { PlatformPressable } from '@react-navigation/elements'
|
|
30
|
+
import { useTheme } from '../../../hooks'
|
|
31
|
+
|
|
32
|
+
const PAN_THRESHOLD_PX = 300
|
|
33
|
+
|
|
34
|
+
export type MetaProps = {
|
|
35
|
+
authorName: string
|
|
36
|
+
createdAt: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function ImageAttachmentLegacy({
|
|
40
|
+
attachment,
|
|
41
|
+
metaProps,
|
|
42
|
+
onMessageAttachmentLongPress,
|
|
43
|
+
}: {
|
|
44
|
+
attachment: DenormalizedMessageAttachmentResource
|
|
45
|
+
metaProps: MetaProps
|
|
46
|
+
onMessageAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void
|
|
47
|
+
}) {
|
|
48
|
+
const { attributes } = attachment
|
|
49
|
+
const { url, urlMedium, filename, metadata = {} } = attributes
|
|
50
|
+
const { colors } = useTheme()
|
|
51
|
+
|
|
52
|
+
const styles = useStyles({ imageWidth: metadata.width, imageHeight: metadata.height })
|
|
53
|
+
const [visible, setVisible] = useState(false)
|
|
54
|
+
|
|
55
|
+
// shared values run on the native UI thread and prevents clogging up the JS thread
|
|
56
|
+
const dismissY = useSharedValue(0)
|
|
57
|
+
const opacity = useSharedValue(1)
|
|
58
|
+
|
|
59
|
+
const resetAnimations = useCallback(() => {
|
|
60
|
+
dismissY.value = withTiming(0)
|
|
61
|
+
opacity.value = withTiming(1)
|
|
62
|
+
}, [dismissY, opacity])
|
|
63
|
+
|
|
64
|
+
const handleCloseModal = useCallback(() => {
|
|
65
|
+
setVisible(false)
|
|
66
|
+
resetAnimations()
|
|
67
|
+
}, [setVisible, resetAnimations])
|
|
68
|
+
|
|
69
|
+
const panGesture = Gesture.Pan()
|
|
70
|
+
.onUpdate(e => {
|
|
71
|
+
dismissY.value = e.translationY
|
|
72
|
+
opacity.value = 1 - Math.abs(e.translationY) / PAN_THRESHOLD_PX
|
|
73
|
+
})
|
|
74
|
+
.onEnd(() => {
|
|
75
|
+
runOnJS(handleCloseModal)() // Ensures we can call a JS function
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const animatedImageStyle = useAnimatedStyle(() => ({
|
|
79
|
+
transform: [{ translateY: dismissY.value }],
|
|
80
|
+
opacity: opacity.value,
|
|
81
|
+
}))
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<>
|
|
85
|
+
<PlatformPressable
|
|
86
|
+
style={styles.container}
|
|
87
|
+
onPress={() => setVisible(true)}
|
|
88
|
+
onLongPress={() => onMessageAttachmentLongPress(attachment)}
|
|
89
|
+
android_ripple={{ color: colors.androidRippleNeutral, foreground: true }}
|
|
90
|
+
accessibilityHint="Long press for more options"
|
|
91
|
+
>
|
|
92
|
+
<Image
|
|
93
|
+
source={{ uri: urlMedium || url }}
|
|
94
|
+
style={styles.image}
|
|
95
|
+
wrapperStyle={styles.imageWrapper}
|
|
96
|
+
alt={filename}
|
|
97
|
+
/>
|
|
98
|
+
</PlatformPressable>
|
|
99
|
+
<LightboxModal
|
|
100
|
+
visible={visible}
|
|
101
|
+
handleCloseModal={handleCloseModal}
|
|
102
|
+
uri={urlMedium || url}
|
|
103
|
+
metaProps={metaProps}
|
|
104
|
+
panGesture={panGesture}
|
|
105
|
+
animatedImageStyle={animatedImageStyle}
|
|
106
|
+
/>
|
|
107
|
+
</>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface LightboxModalProps {
|
|
112
|
+
visible: boolean
|
|
113
|
+
handleCloseModal: () => void
|
|
114
|
+
uri: string
|
|
115
|
+
metaProps: MetaProps
|
|
116
|
+
panGesture: PanGesture
|
|
117
|
+
animatedImageStyle: AnimatedStyle<ImageStyle>
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const LightboxModal = ({
|
|
121
|
+
uri,
|
|
122
|
+
visible,
|
|
123
|
+
handleCloseModal,
|
|
124
|
+
metaProps,
|
|
125
|
+
panGesture,
|
|
126
|
+
animatedImageStyle,
|
|
127
|
+
}: LightboxModalProps) => {
|
|
128
|
+
const styles = useStyles()
|
|
129
|
+
|
|
130
|
+
const { authorName, createdAt } = metaProps
|
|
131
|
+
|
|
132
|
+
const handleOpenInBrowser = () => {
|
|
133
|
+
Linking.openURL(uri)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<Modal visible={visible} transparent animationType="fade" onRequestClose={handleCloseModal}>
|
|
138
|
+
<SafeAreaView style={styles.modal}>
|
|
139
|
+
<GestureHandlerRootView>
|
|
140
|
+
<GestureDetector gesture={panGesture}>
|
|
141
|
+
<Image
|
|
142
|
+
source={{ uri }}
|
|
143
|
+
loadingBackgroundStyles={styles.lightboxImageLoading}
|
|
144
|
+
style={styles.lightboxImage}
|
|
145
|
+
animatedImageStyle={animatedImageStyle}
|
|
146
|
+
resizeMode="contain"
|
|
147
|
+
animated={true}
|
|
148
|
+
alt=""
|
|
149
|
+
/>
|
|
150
|
+
</GestureDetector>
|
|
151
|
+
<View style={styles.actionToolbar} accessibilityRole="toolbar">
|
|
152
|
+
<View style={styles.actionToolbarTextMeta}>
|
|
153
|
+
<Heading variant="h3" style={styles.actionToolbarTitle} numberOfLines={1}>
|
|
154
|
+
{authorName}
|
|
155
|
+
</Heading>
|
|
156
|
+
<Text variant="tertiary" style={styles.actionToolbarSubtitle}>
|
|
157
|
+
{formatDatePreview(createdAt)}
|
|
158
|
+
</Text>
|
|
159
|
+
</View>
|
|
160
|
+
<IconButton
|
|
161
|
+
onPress={handleOpenInBrowser}
|
|
162
|
+
name="general.newWindow"
|
|
163
|
+
accessibilityRole="link"
|
|
164
|
+
accessibilityLabel="Open image in browser"
|
|
165
|
+
accessibilityHint="Image can be downloaded and shared through the browser."
|
|
166
|
+
style={styles.actionButton}
|
|
167
|
+
iconStyle={styles.actionButtonIcon}
|
|
168
|
+
size="lg"
|
|
169
|
+
/>
|
|
170
|
+
<IconButton
|
|
171
|
+
onPress={handleCloseModal}
|
|
172
|
+
name="general.x"
|
|
173
|
+
accessibilityLabel="Close image"
|
|
174
|
+
style={styles.actionButton}
|
|
175
|
+
iconStyle={styles.actionButtonIcon}
|
|
176
|
+
/>
|
|
177
|
+
</View>
|
|
178
|
+
</GestureHandlerRootView>
|
|
179
|
+
</SafeAreaView>
|
|
180
|
+
</Modal>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
interface UseStylesProps {
|
|
185
|
+
imageWidth?: number
|
|
186
|
+
imageHeight?: number
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const useStyles = ({ imageWidth = 100, imageHeight = 100 }: UseStylesProps = {}) => {
|
|
190
|
+
const { width: windowWidth } = useWindowDimensions()
|
|
191
|
+
const backgroundColor = tokens.colorNeutral7
|
|
192
|
+
const transparentBackgroundColor = useMemo(
|
|
193
|
+
() => colorFunction(backgroundColor).alpha(0.8).toString(),
|
|
194
|
+
[backgroundColor]
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return StyleSheet.create({
|
|
198
|
+
container: {
|
|
199
|
+
maxWidth: '100%',
|
|
200
|
+
},
|
|
201
|
+
imageWrapper: {
|
|
202
|
+
width: '100%',
|
|
203
|
+
minWidth: 200,
|
|
204
|
+
aspectRatio: imageWidth / imageHeight,
|
|
205
|
+
},
|
|
206
|
+
image: {
|
|
207
|
+
borderRadius: 8,
|
|
208
|
+
},
|
|
209
|
+
modal: {
|
|
210
|
+
flex: 1,
|
|
211
|
+
backgroundColor,
|
|
212
|
+
justifyContent: 'center',
|
|
213
|
+
alignItems: 'center',
|
|
214
|
+
},
|
|
215
|
+
lightboxImage: {
|
|
216
|
+
height: '100%',
|
|
217
|
+
width: windowWidth,
|
|
218
|
+
backgroundColor,
|
|
219
|
+
},
|
|
220
|
+
lightboxImageLoading: {
|
|
221
|
+
backgroundColor,
|
|
222
|
+
},
|
|
223
|
+
actionToolbar: {
|
|
224
|
+
width: '100%',
|
|
225
|
+
position: 'absolute',
|
|
226
|
+
top: 0,
|
|
227
|
+
flexDirection: 'row',
|
|
228
|
+
alignItems: 'center',
|
|
229
|
+
gap: 20,
|
|
230
|
+
paddingHorizontal: 16,
|
|
231
|
+
paddingTop: 16,
|
|
232
|
+
paddingBottom: 8,
|
|
233
|
+
backgroundColor: transparentBackgroundColor,
|
|
234
|
+
},
|
|
235
|
+
actionToolbarTextMeta: {
|
|
236
|
+
flex: 1,
|
|
237
|
+
},
|
|
238
|
+
actionToolbarTitle: {
|
|
239
|
+
marginRight: 'auto',
|
|
240
|
+
flexShrink: 1,
|
|
241
|
+
color: tokens.colorNeutral88,
|
|
242
|
+
},
|
|
243
|
+
actionToolbarSubtitle: {
|
|
244
|
+
color: tokens.colorNeutral68,
|
|
245
|
+
},
|
|
246
|
+
actionButton: {
|
|
247
|
+
backgroundColor,
|
|
248
|
+
height: 40,
|
|
249
|
+
width: 40,
|
|
250
|
+
borderRadius: 50,
|
|
251
|
+
borderWidth: 1,
|
|
252
|
+
borderColor: tokens.colorNeutral24,
|
|
253
|
+
},
|
|
254
|
+
actionButtonIcon: {
|
|
255
|
+
color: tokens.colorNeutral88,
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
}
|
|
@@ -9,7 +9,11 @@ import { VideoAttachment } from './attachments/video_attachment'
|
|
|
9
9
|
import { GiphyAttachment } from './attachments/giphy_attachment'
|
|
10
10
|
import { GenericFileAttachment } from './attachments/generic_file_attachment'
|
|
11
11
|
import { ExpandedLink } from './attachments/expanded_link'
|
|
12
|
-
import {
|
|
12
|
+
import { ImageAttachmentLegacy, type MetaProps } from './attachments/image_attachment_legacy'
|
|
13
|
+
import { ImageAttachment } from './attachments/image_attachment'
|
|
14
|
+
|
|
15
|
+
// Temporarily controls whether image attachments can be opened in a gallery lightbox. (Will remove after QA approves project.)
|
|
16
|
+
const ENABLE_MESSAGE_ATTACHMENT_IMAGE_GALLERY = false
|
|
13
17
|
|
|
14
18
|
export function MessageAttachments(props: {
|
|
15
19
|
attachments: DenormalizedAttachmentResource[]
|
|
@@ -20,14 +24,34 @@ export function MessageAttachments(props: {
|
|
|
20
24
|
const styles = useStyles()
|
|
21
25
|
const { attachments, metaProps, onMessageAttachmentLongPress, onMessageLongPress } = props
|
|
22
26
|
if (!attachments || attachments.length === 0) return null
|
|
27
|
+
|
|
28
|
+
const imageAttachments = attachments.filter(
|
|
29
|
+
attachment =>
|
|
30
|
+
attachment.type === 'MessageAttachment' &&
|
|
31
|
+
attachment.attributes?.contentType?.startsWith('image/')
|
|
32
|
+
) as DenormalizedMessageAttachmentResource[]
|
|
33
|
+
|
|
34
|
+
const showImageAttachmentGroup =
|
|
35
|
+
ENABLE_MESSAGE_ATTACHMENT_IMAGE_GALLERY && imageAttachments.length > 0
|
|
36
|
+
|
|
23
37
|
return (
|
|
24
38
|
<View style={styles.attachmentsContainer}>
|
|
25
|
-
{
|
|
39
|
+
{showImageAttachmentGroup &&
|
|
40
|
+
imageAttachments.map((image, index) => (
|
|
41
|
+
<ImageAttachment
|
|
42
|
+
key={`${image.id}-${index}`}
|
|
43
|
+
attachment={image}
|
|
44
|
+
metaProps={metaProps}
|
|
45
|
+
onMessageAttachmentLongPress={onMessageAttachmentLongPress}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
|
|
49
|
+
{attachments.map((attachment, index) => {
|
|
26
50
|
switch (attachment.type) {
|
|
27
51
|
case 'MessageAttachment':
|
|
28
52
|
return (
|
|
29
53
|
<MessageAttachment
|
|
30
|
-
key={attachment.id}
|
|
54
|
+
key={`${attachment.id}-${index}`}
|
|
31
55
|
attachment={attachment}
|
|
32
56
|
metaProps={metaProps}
|
|
33
57
|
onMessageAttachmentLongPress={onMessageAttachmentLongPress}
|
|
@@ -36,7 +60,7 @@ export function MessageAttachments(props: {
|
|
|
36
60
|
case 'giphy':
|
|
37
61
|
return (
|
|
38
62
|
<GiphyAttachment
|
|
39
|
-
key={attachment.id || attachment.titleLink}
|
|
63
|
+
key={`${attachment.id || attachment.titleLink}-${index}`}
|
|
40
64
|
attachment={attachment}
|
|
41
65
|
onMessageLongPress={onMessageLongPress}
|
|
42
66
|
/>
|
|
@@ -44,7 +68,7 @@ export function MessageAttachments(props: {
|
|
|
44
68
|
case 'ExpandedLink':
|
|
45
69
|
return (
|
|
46
70
|
<ExpandedLink
|
|
47
|
-
key={attachment.id}
|
|
71
|
+
key={`${attachment.id}-${index}`}
|
|
48
72
|
attachment={attachment}
|
|
49
73
|
onMessageLongPress={onMessageLongPress}
|
|
50
74
|
/>
|
|
@@ -69,10 +93,15 @@ function MessageAttachment({
|
|
|
69
93
|
const { attributes } = attachment
|
|
70
94
|
const contentType = attributes?.contentType
|
|
71
95
|
const basicType = contentType ? contentType.split('/')[0] : ''
|
|
96
|
+
|
|
97
|
+
if (basicType === 'image' && ENABLE_MESSAGE_ATTACHMENT_IMAGE_GALLERY) {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
|
|
72
101
|
switch (basicType) {
|
|
73
102
|
case 'image':
|
|
74
103
|
return (
|
|
75
|
-
<
|
|
104
|
+
<ImageAttachmentLegacy
|
|
76
105
|
attachment={attachment}
|
|
77
106
|
metaProps={metaProps}
|
|
78
107
|
onMessageAttachmentLongPress={onMessageAttachmentLongPress}
|
|
@@ -15,34 +15,40 @@ interface Props {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function useFindOrCreateServicesConversation({ teamIds, planId, onSuccess }: Props) {
|
|
18
|
-
const teamAndPlanParams: TeamAndPlanParams = {
|
|
19
|
-
team_id: teamIds.join(','),
|
|
20
|
-
...(planId ? { plan_id: planId } : {}),
|
|
21
|
-
}
|
|
22
|
-
|
|
23
18
|
const apiClient = useApiClient()
|
|
24
19
|
return useMutation({
|
|
25
20
|
throwOnError: true,
|
|
26
21
|
onSuccess: result => {
|
|
27
22
|
onSuccess && onSuccess(result)
|
|
28
23
|
},
|
|
29
|
-
mutationFn: async () =>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
.then(groupIdentifiers => findConversationWithExactTeams(apiClient, groupIdentifiers))
|
|
33
|
-
.catch(() => null)
|
|
34
|
-
const foundConversation = foundConversations?.data[0]
|
|
24
|
+
mutationFn: async () => findOrCreateServicesConversation(apiClient, teamIds, planId),
|
|
25
|
+
})
|
|
26
|
+
}
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
export const findOrCreateServicesConversation = async (
|
|
29
|
+
apiClient: ApiClient,
|
|
30
|
+
teamIds: number[],
|
|
31
|
+
planId?: number
|
|
32
|
+
) => {
|
|
33
|
+
const teamAndPlanParams: TeamAndPlanParams = {
|
|
34
|
+
team_id: teamIds.join(','),
|
|
35
|
+
...(planId ? { plan_id: planId } : {}),
|
|
36
|
+
}
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
const foundConversations = await getGroupIdsFromServices(apiClient, teamAndPlanParams)
|
|
39
|
+
.then(res => res.data.groupIdentifiers)
|
|
40
|
+
.then(groupIdentifiers => findConversationWithExactTeams(apiClient, groupIdentifiers))
|
|
41
|
+
.catch(() => null)
|
|
42
|
+
const foundConversation = foundConversations?.data[0]
|
|
43
|
+
|
|
44
|
+
if (foundConversation?.id) {
|
|
45
|
+
return foundConversation
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return fetchServicesPayload(apiClient, teamAndPlanParams)
|
|
49
|
+
.then(res => res.data.payload)
|
|
50
|
+
.then(payload => createConversation(apiClient, payload))
|
|
51
|
+
.then(res => res.data)
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
interface TeamAndPlanParams {
|
|
@@ -79,7 +85,7 @@ function findConversationWithExactTeams(apiClient: ApiClient, groupIdentifiers:
|
|
|
79
85
|
url: '/me/conversations',
|
|
80
86
|
data: {
|
|
81
87
|
fields: {
|
|
82
|
-
Conversation: ['stream_channel'],
|
|
88
|
+
Conversation: ['stream_channel', 'title'],
|
|
83
89
|
},
|
|
84
90
|
filter: 'with_exact_groups',
|
|
85
91
|
gids: groupIdentifiers.join(','),
|
package/src/navigation/index.tsx
CHANGED
|
@@ -43,6 +43,8 @@ import {
|
|
|
43
43
|
} from '../screens/conversation/message_read_receipts_screen'
|
|
44
44
|
import { Platform } from 'react-native'
|
|
45
45
|
import { HeaderSubmitButton } from '../components/display/platform_modal_header_buttons'
|
|
46
|
+
import { TeamConversationScreen } from '../screens/team_conversation_screen'
|
|
47
|
+
import { CardStyleInterpolators } from '@react-navigation/stack'
|
|
46
48
|
|
|
47
49
|
const HEADER_BACK_BUTTON_LAYOUT_RESET_STYLES = {
|
|
48
50
|
marginLeft: Platform.select({ ios: -8, default: -3 }),
|
|
@@ -179,6 +181,14 @@ export const ChatStack = createNativeStackNavigator({
|
|
|
179
181
|
),
|
|
180
182
|
}),
|
|
181
183
|
},
|
|
184
|
+
TeamConversation: {
|
|
185
|
+
screen: TeamConversationScreen,
|
|
186
|
+
options: {
|
|
187
|
+
title: 'Finding conversation...',
|
|
188
|
+
animation: 'none',
|
|
189
|
+
cardStyleInterpolator: CardStyleInterpolators.forNoAnimation,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
182
192
|
ConversationDetails: {
|
|
183
193
|
screen: ConversationDetailsScreen,
|
|
184
194
|
options: ({ navigation }) => ({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StackActions, StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { Image as NativeImage, Platform, StyleSheet, useWindowDimensions, View } from 'react-native'
|
|
4
|
-
import { Button, IconButton, IconString, TextButton } from '../components'
|
|
4
|
+
import { BlankState, Button, IconButton, IconString, TextButton } from '../components'
|
|
5
5
|
import { DefaultLoading } from '../components/page/loading'
|
|
6
6
|
import FormSheet, { getFormSheetScreenOptions } from '../components/primitive/form_sheet'
|
|
7
7
|
import { useCreateAndroidRippleColor, useTheme } from '../hooks'
|
|
@@ -33,7 +33,21 @@ export function SendGiphyScreen({ route }: SendGiphyScreenProps) {
|
|
|
33
33
|
const { width } = useWindowDimensions()
|
|
34
34
|
const size = width - 24
|
|
35
35
|
|
|
36
|
-
if (!result)
|
|
36
|
+
if (!result)
|
|
37
|
+
return (
|
|
38
|
+
<FormSheet.Root contentStyle={styles.blankStateContainer}>
|
|
39
|
+
<BlankState
|
|
40
|
+
style={styles.blankState}
|
|
41
|
+
title="No Giphys found"
|
|
42
|
+
subtitle="Try a different search term."
|
|
43
|
+
iconName="general.bolt"
|
|
44
|
+
buttonProps={{
|
|
45
|
+
onPress: () => navigation.goBack(),
|
|
46
|
+
title: 'Close',
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
</FormSheet.Root>
|
|
50
|
+
)
|
|
37
51
|
|
|
38
52
|
const gif = result.giphy.fixed_width
|
|
39
53
|
const { url } = gif
|
|
@@ -158,6 +172,13 @@ const useStyles = () => {
|
|
|
158
172
|
paddingHorizontal: 16,
|
|
159
173
|
gap: 8,
|
|
160
174
|
},
|
|
175
|
+
blankStateContainer: {
|
|
176
|
+
paddingVertical: 64,
|
|
177
|
+
},
|
|
178
|
+
blankState: {
|
|
179
|
+
flex: 0,
|
|
180
|
+
flexGrow: 1,
|
|
181
|
+
},
|
|
161
182
|
poweredByGiphyContainer: {
|
|
162
183
|
alignSelf: 'flex-start',
|
|
163
184
|
padding: 4,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { StackActions, StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
2
|
+
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import { useApiClient } from '../hooks'
|
|
5
|
+
import { findOrCreateServicesConversation } from '../hooks/services/use_find_or_create_services_conversation'
|
|
6
|
+
import { DefaultLoading } from '../components/page/loading'
|
|
7
|
+
|
|
8
|
+
export type TeamConversationRouteProps = {
|
|
9
|
+
plan_id?: number
|
|
10
|
+
team_ids?: number[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type TeamConversationScreenProps = StaticScreenProps<TeamConversationRouteProps>
|
|
14
|
+
|
|
15
|
+
export const TeamConversationScreen = ({ route }: TeamConversationScreenProps) => {
|
|
16
|
+
const apiClient = useApiClient()
|
|
17
|
+
const queryClient = useQueryClient()
|
|
18
|
+
const navigation = useNavigation()
|
|
19
|
+
|
|
20
|
+
const { data: conversation } = useQuery({
|
|
21
|
+
queryKey: ['team-conversation', route.params.team_ids, route.params.plan_id],
|
|
22
|
+
queryFn: () =>
|
|
23
|
+
findOrCreateServicesConversation(
|
|
24
|
+
apiClient,
|
|
25
|
+
route.params.team_ids || [2317674, 5223552],
|
|
26
|
+
route.params.plan_id
|
|
27
|
+
),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!conversation?.id) return
|
|
32
|
+
|
|
33
|
+
navigation.dispatch(
|
|
34
|
+
StackActions.replace('Conversation', {
|
|
35
|
+
conversation_id: conversation.id,
|
|
36
|
+
title: conversation.title,
|
|
37
|
+
})
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
queryClient.removeQueries({ queryKey: ['team-conversation'] })
|
|
42
|
+
}
|
|
43
|
+
}, [conversation?.id, conversation?.title, navigation, queryClient])
|
|
44
|
+
|
|
45
|
+
return <DefaultLoading />
|
|
46
|
+
}
|