@planningcenter/chat-react-native 3.1.0-rc.13 → 3.1.0-rc.14
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/attachment_card.d.ts +9 -0
- package/build/components/conversation/attachments/attachment_card.d.ts.map +1 -0
- package/build/components/conversation/attachments/attachment_card.js +35 -0
- package/build/components/conversation/attachments/attachment_card.js.map +1 -0
- package/build/components/conversation/attachments/audio_attachment.d.ts +6 -0
- package/build/components/conversation/attachments/audio_attachment.d.ts.map +1 -0
- package/build/components/conversation/attachments/audio_attachment.js +84 -0
- package/build/components/conversation/attachments/audio_attachment.js.map +1 -0
- package/build/components/conversation/attachments/constants.d.ts +3 -0
- package/build/components/conversation/attachments/constants.d.ts.map +1 -0
- package/build/components/conversation/attachments/constants.js +4 -0
- package/build/components/conversation/attachments/constants.js.map +1 -0
- package/build/components/conversation/attachments/download_attachment_button.d.ts +9 -0
- package/build/components/conversation/attachments/download_attachment_button.d.ts.map +1 -0
- package/build/components/conversation/attachments/download_attachment_button.js +29 -0
- package/build/components/conversation/attachments/download_attachment_button.js.map +1 -0
- package/build/components/conversation/attachments/expanded_link.d.ts +5 -0
- package/build/components/conversation/attachments/expanded_link.d.ts.map +1 -0
- package/build/components/conversation/attachments/expanded_link.js +44 -0
- package/build/components/conversation/attachments/expanded_link.js.map +1 -0
- package/build/components/conversation/attachments/generic_file_attachment.d.ts +7 -0
- package/build/components/conversation/attachments/generic_file_attachment.d.ts.map +1 -0
- package/build/components/conversation/attachments/generic_file_attachment.js +63 -0
- package/build/components/conversation/attachments/generic_file_attachment.js.map +1 -0
- package/build/components/conversation/attachments/giphy_attachment.d.ts +6 -0
- package/build/components/conversation/attachments/giphy_attachment.d.ts.map +1 -0
- package/build/components/conversation/attachments/giphy_attachment.js +42 -0
- package/build/components/conversation/attachments/giphy_attachment.js.map +1 -0
- package/build/components/conversation/attachments/image_attachment.d.ts +5 -0
- package/build/components/conversation/attachments/image_attachment.d.ts.map +1 -0
- package/build/components/conversation/attachments/image_attachment.js +24 -0
- package/build/components/conversation/attachments/image_attachment.js.map +1 -0
- package/build/components/conversation/attachments/video_attachment.d.ts +6 -0
- package/build/components/conversation/attachments/video_attachment.d.ts.map +1 -0
- package/build/components/conversation/attachments/video_attachment.js +64 -0
- package/build/components/conversation/attachments/video_attachment.js.map +1 -0
- package/build/components/conversation/message.d.ts +1 -1
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +10 -6
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/message_attachments.d.ts +6 -0
- package/build/components/conversation/message_attachments.d.ts.map +1 -0
- package/build/components/conversation/message_attachments.js +52 -0
- package/build/components/conversation/message_attachments.js.map +1 -0
- package/build/components/conversations.d.ts +6 -1
- package/build/components/conversations.d.ts.map +1 -1
- package/build/components/conversations.js +19 -24
- package/build/components/conversations.js.map +1 -1
- package/build/components/display/badge.d.ts +9 -1
- package/build/components/display/badge.d.ts.map +1 -1
- package/build/components/display/badge.js +22 -10
- package/build/components/display/badge.js.map +1 -1
- package/build/components/display/button.d.ts +1 -1
- package/build/components/display/button.d.ts.map +1 -1
- package/build/components/display/button.js.map +1 -1
- package/build/components/display/child_notice.js +2 -2
- package/build/components/display/child_notice.js.map +1 -1
- package/build/components/display/icon.d.ts.map +1 -1
- package/build/components/display/icon.js +10 -8
- package/build/components/display/icon.js.map +1 -1
- package/build/components/page/error_boundary.d.ts +1 -1
- package/build/hooks/use_api.d.ts +6 -4
- package/build/hooks/use_api.d.ts.map +1 -1
- package/build/hooks/use_api.js +5 -2
- package/build/hooks/use_api.js.map +1 -1
- package/build/hooks/use_api_client.d.ts +1 -2
- package/build/hooks/use_api_client.d.ts.map +1 -1
- package/build/hooks/use_api_client.js.map +1 -1
- package/build/hooks/use_conversation.js +1 -1
- package/build/hooks/use_conversation.js.map +1 -1
- package/build/hooks/use_conversation_jolt_events.js +1 -1
- package/build/hooks/use_conversation_jolt_events.js.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
- package/build/hooks/use_conversations.d.ts +2 -3
- package/build/hooks/use_conversations.d.ts.map +1 -1
- package/build/hooks/use_conversations.js +3 -29
- package/build/hooks/use_conversations.js.map +1 -1
- package/build/hooks/use_groups.d.ts +214 -0
- package/build/hooks/use_groups.d.ts.map +1 -0
- package/build/hooks/use_groups.js +22 -0
- package/build/hooks/use_groups.js.map +1 -0
- package/build/hooks/use_groups_groups.d.ts +208 -0
- package/build/hooks/use_groups_groups.d.ts.map +1 -0
- package/build/hooks/use_groups_groups.js +18 -0
- package/build/hooks/use_groups_groups.js.map +1 -0
- package/build/hooks/use_services_team.d.ts +4 -0
- package/build/hooks/use_services_team.d.ts.map +1 -0
- package/build/hooks/use_services_team.js +22 -0
- package/build/hooks/use_services_team.js.map +1 -0
- package/build/hooks/use_suspense_api.d.ts +2 -1
- package/build/hooks/use_suspense_api.d.ts.map +1 -1
- package/build/hooks/use_suspense_api.js +2 -1
- package/build/hooks/use_suspense_api.js.map +1 -1
- package/build/hooks/use_teams.d.ts +208 -0
- package/build/hooks/use_teams.d.ts.map +1 -0
- package/build/hooks/use_teams.js +22 -0
- package/build/hooks/use_teams.js.map +1 -0
- package/build/navigation/index.d.ts +11 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +5 -0
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +86 -62
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_filters_screen.d.ts +13 -0
- package/build/screens/conversation_filters_screen.d.ts.map +1 -0
- package/build/screens/conversation_filters_screen.js +346 -0
- package/build/screens/conversation_filters_screen.js.map +1 -0
- package/build/screens/conversation_screen.js +2 -1
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/conversations_screen.d.ts +4 -2
- package/build/screens/conversations_screen.d.ts.map +1 -1
- package/build/screens/conversations_screen.js +139 -11
- package/build/screens/conversations_screen.js.map +1 -1
- package/build/screens/create/conversation_select_recipients_screen.d.ts.map +1 -1
- package/build/screens/create/conversation_select_recipients_screen.js +3 -11
- package/build/screens/create/conversation_select_recipients_screen.js.map +1 -1
- package/build/types/resources/group_resource.d.ts +4 -2
- package/build/types/resources/group_resource.d.ts.map +1 -1
- package/build/types/resources/group_resource.js.map +1 -1
- package/build/types/resources/index.d.ts +1 -0
- package/build/types/resources/index.d.ts.map +1 -1
- package/build/types/resources/index.js +1 -0
- package/build/types/resources/index.js.map +1 -1
- package/build/types/resources/services/index.d.ts +2 -0
- package/build/types/resources/services/index.d.ts.map +1 -0
- package/build/types/resources/services/index.js +2 -0
- package/build/types/resources/services/index.js.map +1 -0
- package/build/types/resources/services/team_resource.d.ts +48 -0
- package/build/types/resources/services/team_resource.d.ts.map +1 -0
- package/build/types/resources/services/team_resource.js +7 -0
- package/build/types/resources/services/team_resource.js.map +1 -0
- 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/native_adapters/audio.d.ts +13 -0
- package/build/utils/native_adapters/audio.d.ts.map +1 -0
- package/build/utils/native_adapters/audio.js +7 -0
- package/build/utils/native_adapters/audio.js.map +1 -0
- package/build/utils/native_adapters/configuration.d.ts +7 -1
- package/build/utils/native_adapters/configuration.d.ts.map +1 -1
- package/build/utils/native_adapters/configuration.js +17 -1
- package/build/utils/native_adapters/configuration.js.map +1 -1
- package/build/utils/native_adapters/index.d.ts +2 -0
- package/build/utils/native_adapters/index.d.ts.map +1 -1
- package/build/utils/native_adapters/index.js +2 -0
- package/build/utils/native_adapters/index.js.map +1 -1
- package/build/utils/native_adapters/video.d.ts +25 -0
- package/build/utils/native_adapters/video.d.ts.map +1 -0
- package/build/utils/native_adapters/video.js +7 -0
- package/build/utils/native_adapters/video.js.map +1 -0
- package/build/utils/pluralize.d.ts +2 -0
- package/build/utils/pluralize.d.ts.map +1 -0
- package/build/utils/pluralize.js +10 -0
- package/build/utils/pluralize.js.map +1 -0
- package/build/utils/request/conversation.d.ts +10 -0
- package/build/utils/request/conversation.d.ts.map +1 -0
- package/build/utils/request/conversation.js +32 -0
- package/build/utils/request/conversation.js.map +1 -0
- package/build/utils/request/messages.d.ts +15 -0
- package/build/utils/request/messages.d.ts.map +1 -0
- package/build/utils/request/messages.js +22 -0
- package/build/utils/request/messages.js.map +1 -0
- package/build/utils/theme.d.ts +1 -1
- package/build/utils/theme.d.ts.map +1 -1
- package/build/utils/theme.js +1 -1
- package/build/utils/theme.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/utils/pluralize.tsx +17 -0
- package/src/components/conversation/attachments/attachment_card.tsx +46 -0
- package/src/components/conversation/attachments/audio_attachment.tsx +102 -0
- package/src/components/conversation/attachments/constants.ts +5 -0
- package/src/components/conversation/attachments/download_attachment_button.tsx +46 -0
- package/src/components/conversation/attachments/expanded_link.tsx +54 -0
- package/src/components/conversation/attachments/generic_file_attachment.tsx +75 -0
- package/src/components/conversation/attachments/giphy_attachment.tsx +56 -0
- package/src/components/conversation/attachments/image_attachment.tsx +31 -0
- package/src/components/conversation/attachments/video_attachment.tsx +92 -0
- package/src/components/conversation/message.tsx +10 -5
- package/src/components/conversation/message_attachments.tsx +61 -0
- package/src/components/conversations.tsx +37 -35
- package/src/components/display/badge.tsx +41 -10
- package/src/components/display/button.tsx +1 -1
- package/src/components/display/child_notice.tsx +2 -2
- package/src/components/display/icon.tsx +10 -8
- package/src/hooks/use_api.ts +11 -10
- package/src/hooks/use_api_client.ts +1 -1
- package/src/hooks/use_conversation.ts +1 -1
- package/src/hooks/use_conversation_jolt_events.ts +1 -1
- package/src/hooks/use_conversation_messages_jolt_events.ts +1 -1
- package/src/hooks/use_conversations.ts +3 -31
- package/src/hooks/use_groups.ts +31 -0
- package/src/hooks/use_groups_groups.ts +20 -0
- package/src/hooks/use_services_team.ts +30 -0
- package/src/hooks/use_suspense_api.ts +4 -4
- package/src/hooks/use_teams.ts +25 -0
- package/src/navigation/index.tsx +8 -0
- package/src/screens/conversation_details_screen.tsx +149 -117
- package/src/screens/conversation_filters_screen.tsx +488 -0
- package/src/screens/conversation_screen.tsx +8 -1
- package/src/screens/conversations_screen.tsx +210 -13
- package/src/screens/create/conversation_select_recipients_screen.tsx +3 -11
- package/src/types/resources/group_resource.ts +5 -2
- package/src/types/resources/index.ts +1 -0
- package/src/types/resources/services/index.ts +1 -0
- package/src/types/resources/services/team_resource.ts +60 -0
- package/src/utils/client/types.d.ts +2 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/native_adapters/audio.ts +15 -0
- package/src/utils/native_adapters/configuration.ts +24 -1
- package/src/utils/native_adapters/index.ts +2 -0
- package/src/utils/native_adapters/video.ts +30 -0
- package/src/utils/pluralize.ts +11 -0
- package/src/utils/request/conversation.ts +46 -0
- package/src/utils/request/messages.ts +21 -0
- package/src/utils/theme.ts +2 -2
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Text, Image, StyleSheet, Linking, Pressable } from 'react-native'
|
|
3
|
+
import { tokens } from '../../../vendor/tapestry/tokens'
|
|
4
|
+
import { useTheme } from '../../../hooks'
|
|
5
|
+
import { DenormalizedGiphyAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'
|
|
6
|
+
|
|
7
|
+
export function GiphyAttachment({
|
|
8
|
+
attachment,
|
|
9
|
+
}: {
|
|
10
|
+
attachment: DenormalizedGiphyAttachmentResource
|
|
11
|
+
}) {
|
|
12
|
+
const styles = useStyles()
|
|
13
|
+
const { title, titleLink, giphy } = attachment
|
|
14
|
+
const { url, width, height } = giphy.fixedWidth
|
|
15
|
+
|
|
16
|
+
function handlePress() {
|
|
17
|
+
if (titleLink) {
|
|
18
|
+
// Open Giphy link in the default web browser
|
|
19
|
+
Linking.openURL(titleLink)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Pressable onPress={handlePress} style={styles.container}>
|
|
25
|
+
<Image
|
|
26
|
+
source={{ uri: url }}
|
|
27
|
+
style={[styles.image, { width, height }]}
|
|
28
|
+
accessibilityLabel={title}
|
|
29
|
+
/>
|
|
30
|
+
<Text style={styles.link}>
|
|
31
|
+
<Text style={styles.tag}>/giphy</Text> {title}
|
|
32
|
+
</Text>
|
|
33
|
+
</Pressable>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const useStyles = () => {
|
|
38
|
+
const { colors } = useTheme()
|
|
39
|
+
return StyleSheet.create({
|
|
40
|
+
container: {
|
|
41
|
+
gap: 4,
|
|
42
|
+
},
|
|
43
|
+
image: {
|
|
44
|
+
borderRadius: 8,
|
|
45
|
+
},
|
|
46
|
+
link: {
|
|
47
|
+
fontSize: tokens.fontSizeSm,
|
|
48
|
+
color: colors.fillColorInteractionDefault,
|
|
49
|
+
paddingHorizontal: 8,
|
|
50
|
+
paddingVertical: 6,
|
|
51
|
+
},
|
|
52
|
+
tag: {
|
|
53
|
+
fontWeight: 'bold',
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Image, StyleSheet } from 'react-native'
|
|
3
|
+
|
|
4
|
+
export function ImageAttachment({ attachment }: { attachment: any }) {
|
|
5
|
+
const styles = useStyles()
|
|
6
|
+
const { attributes } = attachment
|
|
7
|
+
const { url, urlMedium, filename, metadata = {} } = attributes
|
|
8
|
+
const width = metadata.width || 100
|
|
9
|
+
const height = metadata.height || 100
|
|
10
|
+
return (
|
|
11
|
+
<View style={styles.container}>
|
|
12
|
+
<Image
|
|
13
|
+
source={{ uri: urlMedium || url }}
|
|
14
|
+
style={[styles.image, { aspectRatio: width / height }]}
|
|
15
|
+
accessibilityLabel={filename}
|
|
16
|
+
/>
|
|
17
|
+
</View>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const useStyles = () => {
|
|
22
|
+
return StyleSheet.create({
|
|
23
|
+
container: {
|
|
24
|
+
maxWidth: '100%',
|
|
25
|
+
},
|
|
26
|
+
image: {
|
|
27
|
+
borderRadius: 8,
|
|
28
|
+
minWidth: 200,
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react'
|
|
2
|
+
import { View, StyleSheet, Pressable } from 'react-native'
|
|
3
|
+
import { DenormalizedMessageAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'
|
|
4
|
+
import { MESSAGE_ATTACHMENT_WIDTH_SINGLE } from './constants'
|
|
5
|
+
import { IconButton } from '../../display'
|
|
6
|
+
import { Video, VideoPlayerHandle } from '../../../utils/native_adapters'
|
|
7
|
+
|
|
8
|
+
export function VideoAttachment({
|
|
9
|
+
attachment,
|
|
10
|
+
}: {
|
|
11
|
+
attachment: DenormalizedMessageAttachmentResource
|
|
12
|
+
}) {
|
|
13
|
+
const { attributes } = attachment
|
|
14
|
+
const { width = MESSAGE_ATTACHMENT_WIDTH_SINGLE, height = MESSAGE_ATTACHMENT_WIDTH_SINGLE } =
|
|
15
|
+
attributes.metadata || {}
|
|
16
|
+
const { url } = attributes
|
|
17
|
+
|
|
18
|
+
const videoRef = useRef<VideoPlayerHandle>(null)
|
|
19
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
20
|
+
|
|
21
|
+
function openVideo() {
|
|
22
|
+
if (!isOpen && videoRef.current) {
|
|
23
|
+
videoRef.current.presentFullscreenPlayer()
|
|
24
|
+
videoRef.current.play()
|
|
25
|
+
setIsOpen(true)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function onFullscreenPlayerWillDismiss() {
|
|
30
|
+
videoRef.current?.pause()
|
|
31
|
+
setIsOpen(false)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const viewRef = useRef<View>(null)
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<View style={styles.container} ref={viewRef}>
|
|
38
|
+
<Pressable onPress={openVideo}>
|
|
39
|
+
<Video.Player
|
|
40
|
+
ref={videoRef}
|
|
41
|
+
source={{ uri: url }}
|
|
42
|
+
aspectRatio={width / height}
|
|
43
|
+
style={styles.video}
|
|
44
|
+
onFullscreenPlayerWillDismiss={onFullscreenPlayerWillDismiss}
|
|
45
|
+
/>
|
|
46
|
+
{!isOpen && (
|
|
47
|
+
<View style={styles.playButtonWrapper}>
|
|
48
|
+
<IconButton
|
|
49
|
+
name="services.play"
|
|
50
|
+
size="md"
|
|
51
|
+
accessibilityLabel="Play Video"
|
|
52
|
+
onPress={openVideo}
|
|
53
|
+
style={styles.button}
|
|
54
|
+
/>
|
|
55
|
+
</View>
|
|
56
|
+
)}
|
|
57
|
+
</Pressable>
|
|
58
|
+
</View>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const styles = StyleSheet.create({
|
|
63
|
+
container: {
|
|
64
|
+
maxWidth: 320,
|
|
65
|
+
maxHeight: 320,
|
|
66
|
+
},
|
|
67
|
+
video: {
|
|
68
|
+
width: '100%',
|
|
69
|
+
height: 'auto',
|
|
70
|
+
backgroundColor: 'black',
|
|
71
|
+
borderRadius: 8,
|
|
72
|
+
},
|
|
73
|
+
playButtonWrapper: {
|
|
74
|
+
position: 'absolute',
|
|
75
|
+
top: 0,
|
|
76
|
+
left: 0,
|
|
77
|
+
right: 0,
|
|
78
|
+
bottom: 0,
|
|
79
|
+
justifyContent: 'center',
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
},
|
|
82
|
+
button: {
|
|
83
|
+
width: 42,
|
|
84
|
+
height: 42,
|
|
85
|
+
justifyContent: 'center',
|
|
86
|
+
alignItems: 'center',
|
|
87
|
+
borderRadius: 25,
|
|
88
|
+
backgroundColor: '#eee',
|
|
89
|
+
borderColor: '#aaa',
|
|
90
|
+
borderWidth: 1,
|
|
91
|
+
},
|
|
92
|
+
})
|
|
@@ -8,6 +8,8 @@ import { Avatar, Text } from '../../components/display'
|
|
|
8
8
|
import { useTheme } from '../../hooks'
|
|
9
9
|
import { MessageResource } from '../../types'
|
|
10
10
|
import { ReactionCountResource } from '../../types/resources/reaction'
|
|
11
|
+
import { MessageAttachments } from './message_attachments'
|
|
12
|
+
import ErrorBoundary from '../page/error_boundary'
|
|
11
13
|
|
|
12
14
|
/** Message
|
|
13
15
|
* Component for display of a message within a conversation list
|
|
@@ -29,7 +31,6 @@ export function Message(props: MessageResource & { conversation_id: number }) {
|
|
|
29
31
|
reaction_value: reaction.value,
|
|
30
32
|
})
|
|
31
33
|
}
|
|
32
|
-
if (!text) return null
|
|
33
34
|
|
|
34
35
|
return (
|
|
35
36
|
<View style={styles.message}>
|
|
@@ -41,7 +42,10 @@ export function Message(props: MessageResource & { conversation_id: number }) {
|
|
|
41
42
|
<View style={styles.messageContent}>
|
|
42
43
|
{!props.mine && <Text variant="tertiary">{props.author.name}</Text>}
|
|
43
44
|
<PlatformPressable style={styles.messageBubble} onLongPress={handleMessagePress}>
|
|
44
|
-
<
|
|
45
|
+
<ErrorBoundary>
|
|
46
|
+
<MessageAttachments attachments={props.attachments} />
|
|
47
|
+
</ErrorBoundary>
|
|
48
|
+
{text && <Text style={styles.messageText}>{text}</Text>}
|
|
45
49
|
</PlatformPressable>
|
|
46
50
|
<View style={styles.messageReactions}>
|
|
47
51
|
{reactionCounts.map(reaction => (
|
|
@@ -76,12 +80,13 @@ const useMessageStyles = ({ mine }: MessageResource) => {
|
|
|
76
80
|
},
|
|
77
81
|
messageBubble: {
|
|
78
82
|
backgroundColor: mine ? activeColor : colors.fillColorNeutral070,
|
|
79
|
-
borderRadius:
|
|
80
|
-
|
|
81
|
-
paddingHorizontal: 8,
|
|
83
|
+
borderRadius: 8,
|
|
84
|
+
maxWidth: 232,
|
|
82
85
|
},
|
|
83
86
|
messageText: {
|
|
84
87
|
color: colors.textColorDefaultPrimary,
|
|
88
|
+
paddingVertical: 6,
|
|
89
|
+
paddingHorizontal: 8,
|
|
85
90
|
},
|
|
86
91
|
messageReactions: {
|
|
87
92
|
flexDirection: 'row',
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, StyleSheet } from 'react-native'
|
|
3
|
+
import { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'
|
|
4
|
+
import { AudioAttachment } from './attachments/audio_attachment'
|
|
5
|
+
import { VideoAttachment } from './attachments/video_attachment'
|
|
6
|
+
import { GiphyAttachment } from './attachments/giphy_attachment'
|
|
7
|
+
import { GenericFileAttachment } from './attachments/generic_file_attachment'
|
|
8
|
+
import { ExpandedLink } from './attachments/expanded_link'
|
|
9
|
+
import { ImageAttachment } from './attachments/image_attachment'
|
|
10
|
+
|
|
11
|
+
export function MessageAttachments(props: { attachments: DenormalizedAttachmentResource[] }) {
|
|
12
|
+
const styles = useStyles()
|
|
13
|
+
const { attachments } = props
|
|
14
|
+
if (!attachments || attachments.length === 0) return null
|
|
15
|
+
return (
|
|
16
|
+
<View style={styles.attachmentsContainer}>
|
|
17
|
+
{attachments.map(attachment => {
|
|
18
|
+
switch (attachment.type) {
|
|
19
|
+
case 'MessageAttachment':
|
|
20
|
+
return <MessageAttachment key={attachment.id} attachment={attachment} />
|
|
21
|
+
case 'giphy':
|
|
22
|
+
return (
|
|
23
|
+
<GiphyAttachment
|
|
24
|
+
key={attachment.id || attachment.titleLink}
|
|
25
|
+
attachment={attachment}
|
|
26
|
+
/>
|
|
27
|
+
)
|
|
28
|
+
case 'ExpandedLink':
|
|
29
|
+
return <ExpandedLink key={attachment.id} attachment={attachment} />
|
|
30
|
+
default:
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
})}
|
|
34
|
+
</View>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function MessageAttachment({ attachment }: { attachment: any }) {
|
|
39
|
+
const { attributes } = attachment
|
|
40
|
+
const contentType = attributes?.contentType
|
|
41
|
+
const basicType = contentType ? contentType.split('/')[0] : ''
|
|
42
|
+
switch (basicType) {
|
|
43
|
+
case 'image':
|
|
44
|
+
return <ImageAttachment attachment={attachment} />
|
|
45
|
+
case 'video':
|
|
46
|
+
return <VideoAttachment attachment={attachment} />
|
|
47
|
+
case 'audio':
|
|
48
|
+
return <AudioAttachment attachment={attachment} />
|
|
49
|
+
default:
|
|
50
|
+
return <GenericFileAttachment attachment={attachment} />
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const useStyles = () => {
|
|
55
|
+
return StyleSheet.create({
|
|
56
|
+
attachmentsContainer: {
|
|
57
|
+
gap: 4,
|
|
58
|
+
padding: 2,
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
}
|
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
import { useNavigation } from '@react-navigation/native'
|
|
2
2
|
import { FlashList } from '@shopify/flash-list'
|
|
3
|
-
import React
|
|
3
|
+
import React from 'react'
|
|
4
4
|
import { Pressable, StyleSheet, View } from 'react-native'
|
|
5
5
|
import { useTheme } from '../hooks'
|
|
6
6
|
import { useConversationsJoltEvents } from '../hooks/use_conversation_jolt_events'
|
|
7
7
|
import { useConversations } from '../hooks/use_conversations'
|
|
8
8
|
import { formatDatePreview } from '../utils/date'
|
|
9
|
-
import { AvatarGroup, Badge, Heading, Text
|
|
10
|
-
import {
|
|
11
|
-
import { useCanCreateConversations } from '../hooks/use_chat_permissions'
|
|
9
|
+
import { AvatarGroup, Badge, Heading, Text } from './display'
|
|
10
|
+
import { ConversationRequestArgs } from '../utils/request/conversation'
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
interface ConversationsProps extends Partial<ConversationRequestArgs> {
|
|
13
|
+
ListHeaderComponent?:
|
|
14
|
+
| React.ComponentType<any>
|
|
15
|
+
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
|
16
|
+
| null
|
|
17
|
+
| undefined
|
|
18
|
+
}
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
export const Conversations = ({
|
|
21
|
+
ListHeaderComponent,
|
|
22
|
+
filter,
|
|
23
|
+
group,
|
|
24
|
+
gids,
|
|
25
|
+
group_source_app_name,
|
|
26
|
+
}: ConversationsProps) => {
|
|
27
|
+
const styles = useStyles()
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
const { conversations, fetchNextPage, refetch, isRefetching, isFetched } = useConversations({
|
|
30
|
+
filter,
|
|
31
|
+
group,
|
|
32
|
+
gids,
|
|
33
|
+
group_source_app_name,
|
|
34
|
+
})
|
|
24
35
|
const navigation = useNavigation()
|
|
25
36
|
|
|
26
37
|
useConversationsJoltEvents()
|
|
@@ -28,20 +39,17 @@ export const Conversations = () => {
|
|
|
28
39
|
return (
|
|
29
40
|
<View style={styles.container}>
|
|
30
41
|
<FlashList
|
|
31
|
-
data={
|
|
42
|
+
data={conversations}
|
|
32
43
|
estimatedItemSize={97}
|
|
33
44
|
contentContainerStyle={styles.contentContainer}
|
|
34
45
|
onRefresh={refetch}
|
|
35
|
-
refreshing={isRefetching}
|
|
36
|
-
ListHeaderComponent={
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</Heading>
|
|
41
|
-
<TextButton>Mark all read</TextButton>
|
|
46
|
+
refreshing={!isFetched && isRefetching}
|
|
47
|
+
ListHeaderComponent={ListHeaderComponent}
|
|
48
|
+
ListEmptyComponent={
|
|
49
|
+
<View style={styles.listEmpty}>
|
|
50
|
+
<Text variant="secondary">No conversations found</Text>
|
|
42
51
|
</View>
|
|
43
52
|
}
|
|
44
|
-
ListEmptyComponent={<Text>No conversations found</Text>}
|
|
45
53
|
renderItem={({ item }) => (
|
|
46
54
|
<Pressable
|
|
47
55
|
style={styles.conversation}
|
|
@@ -75,11 +83,6 @@ export const Conversations = () => {
|
|
|
75
83
|
)}
|
|
76
84
|
onEndReached={() => fetchNextPage()}
|
|
77
85
|
/>
|
|
78
|
-
<ActionButton
|
|
79
|
-
visible={canCreateConversations}
|
|
80
|
-
title="New conversation"
|
|
81
|
-
onPress={() => navigation.navigate('Create')}
|
|
82
|
-
/>
|
|
83
86
|
</View>
|
|
84
87
|
)
|
|
85
88
|
}
|
|
@@ -101,13 +104,6 @@ const useStyles = () => {
|
|
|
101
104
|
const { colors } = useTheme()
|
|
102
105
|
|
|
103
106
|
return StyleSheet.create({
|
|
104
|
-
header: {
|
|
105
|
-
flexDirection: 'row',
|
|
106
|
-
justifyContent: 'space-between',
|
|
107
|
-
paddingTop: 8,
|
|
108
|
-
paddingBottom: 8,
|
|
109
|
-
paddingHorizontal: 16,
|
|
110
|
-
},
|
|
111
107
|
container: { flex: 1 },
|
|
112
108
|
contentContainer: { paddingVertical: 16 },
|
|
113
109
|
listItem: { color: colors.fillColorNeutral020 },
|
|
@@ -116,7 +112,7 @@ const useStyles = () => {
|
|
|
116
112
|
gap: 8,
|
|
117
113
|
borderBottomWidth: 1,
|
|
118
114
|
borderBottomColor: colors.fillColorNeutral060,
|
|
119
|
-
paddingTop:
|
|
115
|
+
paddingTop: 12,
|
|
120
116
|
paddingBottom: 12,
|
|
121
117
|
paddingHorizontal: 16,
|
|
122
118
|
},
|
|
@@ -140,5 +136,11 @@ const useStyles = () => {
|
|
|
140
136
|
borderRadius: 24,
|
|
141
137
|
color: 'white',
|
|
142
138
|
},
|
|
139
|
+
listEmpty: {
|
|
140
|
+
flex: 1,
|
|
141
|
+
justifyContent: 'center',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
paddingVertical: 16,
|
|
144
|
+
},
|
|
143
145
|
})
|
|
144
146
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import Svg, {
|
|
2
|
+
import type { TextStyle, ViewProps, ViewStyle } from 'react-native'
|
|
3
|
+
import { Pressable, StyleSheet, View } from 'react-native'
|
|
4
|
+
import Svg, { Defs, LinearGradient, Path, Stop } from 'react-native-svg'
|
|
5
5
|
import { useFontScale, useTheme } from '../../hooks'
|
|
6
|
-
import { Icon } from './icon'
|
|
7
|
-
import { Text } from './text'
|
|
8
6
|
import { platformFontWeightMedium, space } from '../../utils'
|
|
9
7
|
import { tokens } from '../../vendor/tapestry/tokens'
|
|
8
|
+
import { Icon } from './icon'
|
|
9
|
+
import { Text } from './text'
|
|
10
10
|
import { useStatusColorAppearanceMap, type StatusAppearanceUnion } from './utils/status_colors'
|
|
11
11
|
|
|
12
12
|
// =================================
|
|
@@ -72,6 +72,10 @@ interface BadgeProps {
|
|
|
72
72
|
* Shows an icon of the user choice to the left of the text.
|
|
73
73
|
*/
|
|
74
74
|
iconName?: string
|
|
75
|
+
/**
|
|
76
|
+
* Shows an icon of the user choice to the right of the text.
|
|
77
|
+
*/
|
|
78
|
+
iconNameRight?: string
|
|
75
79
|
/**
|
|
76
80
|
* Styles badge wrapper.
|
|
77
81
|
*/
|
|
@@ -80,6 +84,10 @@ interface BadgeProps {
|
|
|
80
84
|
* Specifies the maximum size a font can reach when allowFontScaling is enabled.
|
|
81
85
|
*/
|
|
82
86
|
maxFontSizeMultiplier?: number
|
|
87
|
+
/**
|
|
88
|
+
* Callback function when the badge is pressed.
|
|
89
|
+
*/
|
|
90
|
+
onPress?: () => void
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
export function Badge({
|
|
@@ -87,40 +95,59 @@ export function Badge({
|
|
|
87
95
|
metaLabel,
|
|
88
96
|
style,
|
|
89
97
|
iconName,
|
|
98
|
+
iconNameRight,
|
|
90
99
|
label,
|
|
91
100
|
productLogoName,
|
|
92
101
|
variant = 'default',
|
|
93
102
|
maxFontSizeMultiplier,
|
|
103
|
+
onPress,
|
|
94
104
|
}: BadgeProps) {
|
|
95
105
|
const styles = useStyles({ appearance, maxFontSizeMultiplier, variant })
|
|
96
|
-
const {
|
|
106
|
+
const { showBadgeProductLogo } = useTheme()
|
|
97
107
|
|
|
98
108
|
const isMetaSubtle = variant === 'metaSubtle'
|
|
99
|
-
const isMeta = variant === 'meta'
|
|
100
109
|
const hasMetaLabel = Boolean(metaLabel)
|
|
101
110
|
|
|
102
|
-
const showLogo =
|
|
111
|
+
const showLogo = showBadgeProductLogo && productLogoName
|
|
103
112
|
const ProductLogoSvg = showLogo && PRODUCT_LOGO_COMPONENT_MAP[productLogoName?.toLowerCase()]
|
|
104
113
|
const badgeLabel = isMetaSubtle && hasMetaLabel ? `${label}:` : label
|
|
105
114
|
|
|
106
115
|
return (
|
|
107
|
-
<
|
|
116
|
+
<BadgeWrapper style={[styles.badgeWrapper, style]} onPress={onPress} accessibilityRole="button">
|
|
108
117
|
<View style={styles.badge}>
|
|
109
118
|
{showLogo && ProductLogoSvg && <ProductLogoSvg logoSize={styles.logo.fontSize} />}
|
|
110
119
|
{iconName && <Icon name={iconName} style={styles.icon} />}
|
|
111
120
|
<Text variant="footnote" style={styles.label}>
|
|
112
121
|
{badgeLabel}
|
|
113
122
|
</Text>
|
|
123
|
+
{iconNameRight && <Icon name={iconNameRight} style={styles.iconRight} />}
|
|
114
124
|
</View>
|
|
115
125
|
{hasMetaLabel && (
|
|
116
126
|
<Text variant="footnote" style={styles.metaLabel} numberOfLines={1}>
|
|
117
127
|
{metaLabel}
|
|
118
128
|
</Text>
|
|
119
129
|
)}
|
|
120
|
-
</
|
|
130
|
+
</BadgeWrapper>
|
|
121
131
|
)
|
|
122
132
|
}
|
|
123
133
|
|
|
134
|
+
const BadgeWrapper = ({
|
|
135
|
+
children,
|
|
136
|
+
onPress,
|
|
137
|
+
accessibilityRole,
|
|
138
|
+
...restProps
|
|
139
|
+
}: ViewProps & Pick<BadgeProps, 'onPress'>) => {
|
|
140
|
+
if (onPress) {
|
|
141
|
+
return (
|
|
142
|
+
<Pressable accessibilityRole={accessibilityRole} onPress={onPress} {...restProps}>
|
|
143
|
+
{children}
|
|
144
|
+
</Pressable>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return <View {...restProps}>{children}</View>
|
|
149
|
+
}
|
|
150
|
+
|
|
124
151
|
// =================================
|
|
125
152
|
// ====== Styles ===================
|
|
126
153
|
// =================================
|
|
@@ -199,6 +226,10 @@ const useStyles = ({
|
|
|
199
226
|
color: statusColorMap[appearance].icon,
|
|
200
227
|
fontSize: badgeFontSize,
|
|
201
228
|
},
|
|
229
|
+
iconRight: {
|
|
230
|
+
color: statusColorMap[appearance].icon,
|
|
231
|
+
fontSize: badgeFontSize - 2,
|
|
232
|
+
},
|
|
202
233
|
logo: {
|
|
203
234
|
fontSize: badgeFontSize * fontScale,
|
|
204
235
|
},
|
|
@@ -50,7 +50,7 @@ type VariantColors = Record<
|
|
|
50
50
|
// ====== Component ================
|
|
51
51
|
// =================================
|
|
52
52
|
|
|
53
|
-
interface ButtonProps extends PressableProps {
|
|
53
|
+
export interface ButtonProps extends PressableProps {
|
|
54
54
|
/**
|
|
55
55
|
* Specifies whether fonts should be scaled down automatically to fit given style constraints.
|
|
56
56
|
*/
|
|
@@ -4,7 +4,7 @@ import { BannerCollapsible } from './banner_collapsible'
|
|
|
4
4
|
import BannerPrimitive from '../primitive/banner_primitive'
|
|
5
5
|
import { View, StyleSheet } from 'react-native'
|
|
6
6
|
import { Avatar } from './avatar'
|
|
7
|
-
import { platformFontWeightMedium } from '../../utils'
|
|
7
|
+
import { platformFontWeightMedium, pluralize } from '../../utils'
|
|
8
8
|
import { Text } from './text'
|
|
9
9
|
|
|
10
10
|
// =================================
|
|
@@ -17,7 +17,7 @@ interface ChildNoticeProps {
|
|
|
17
17
|
|
|
18
18
|
export function ChildNotice({ childMembers }: ChildNoticeProps) {
|
|
19
19
|
const styles = useStyles()
|
|
20
|
-
const heading = `${childMembers.length}
|
|
20
|
+
const heading = `${pluralize(childMembers.length, 'member')} under age 13`
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
23
|
<BannerCollapsible appearance="warning" iconName="general.shieldExclamation" heading={heading}>
|
|
@@ -4,14 +4,15 @@ import type { ColorValue, StyleProp, ViewStyle } from 'react-native'
|
|
|
4
4
|
import { SvgXml } from 'react-native-svg'
|
|
5
5
|
import type { XmlProps } from 'react-native-svg'
|
|
6
6
|
import { useFontScale, useTheme } from '../../hooks'
|
|
7
|
-
import * as
|
|
8
|
-
import * as
|
|
7
|
+
import * as api from '@planningcenter/icons/paths/api'
|
|
8
|
+
import * as brand from '@planningcenter/icons/paths/brand'
|
|
9
9
|
import * as calendar from '@planningcenter/icons/paths/calendar'
|
|
10
|
-
import * as people from '@planningcenter/icons/paths/people'
|
|
11
10
|
import * as churchCenter from '@planningcenter/icons/paths/church-center'
|
|
11
|
+
import * as general from '@planningcenter/icons/paths/general'
|
|
12
|
+
import * as groups from '@planningcenter/icons/paths/groups'
|
|
12
13
|
import * as logomark from '@planningcenter/icons/paths/logomark'
|
|
13
|
-
import * as
|
|
14
|
-
import * as
|
|
14
|
+
import * as people from '@planningcenter/icons/paths/people'
|
|
15
|
+
import * as services from '@planningcenter/icons/paths/services'
|
|
15
16
|
|
|
16
17
|
// =================================
|
|
17
18
|
// ====== Constants ================
|
|
@@ -21,13 +22,14 @@ const FALLBACK_SIZE = 12
|
|
|
21
22
|
|
|
22
23
|
const ICONS = {
|
|
23
24
|
api,
|
|
25
|
+
brand,
|
|
24
26
|
calendar,
|
|
25
|
-
people,
|
|
26
27
|
churchCenter,
|
|
27
|
-
groups,
|
|
28
28
|
general,
|
|
29
|
+
groups,
|
|
29
30
|
logomark,
|
|
30
|
-
|
|
31
|
+
people,
|
|
32
|
+
services,
|
|
31
33
|
} as const
|
|
32
34
|
|
|
33
35
|
type IconStyle = ViewStyle & {
|
package/src/hooks/use_api.ts
CHANGED
|
@@ -6,20 +6,20 @@ import {
|
|
|
6
6
|
} from '@tanstack/react-query'
|
|
7
7
|
import { ApiCollection, ApiResource, ResourceObject } from '../types'
|
|
8
8
|
import { GetRequest, RequestData } from '../utils/client/types'
|
|
9
|
-
import { useApiClient } from './use_api_client'
|
|
9
|
+
import { App, useApiClient } from './use_api_client'
|
|
10
10
|
import { getRequestQueryKey } from './use_suspense_api'
|
|
11
11
|
|
|
12
|
-
interface
|
|
13
|
-
app?:
|
|
12
|
+
interface ApiGetOptions extends GetRequest {
|
|
13
|
+
app?: App
|
|
14
|
+
enabled?: boolean
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const useApiGet = <T extends ResourceObject | ResourceObject[]>(
|
|
17
|
-
args: SuspenseGetOptions
|
|
18
|
-
) => {
|
|
17
|
+
export const useApiGet = <T extends ResourceObject | ResourceObject[]>(args: ApiGetOptions) => {
|
|
19
18
|
type Resource = ApiResource<T>
|
|
20
19
|
|
|
21
20
|
const { data, ...query } = useQuery<Resource, Response>({
|
|
22
21
|
queryKey: getRequestQueryKey(args),
|
|
22
|
+
enabled: args.enabled,
|
|
23
23
|
})
|
|
24
24
|
|
|
25
25
|
return { ...data, ...query }
|
|
@@ -36,7 +36,7 @@ export type SuspensePaginatorOptions = Omit<
|
|
|
36
36
|
>
|
|
37
37
|
|
|
38
38
|
export const useApiPaginator = <T extends ResourceObject>(
|
|
39
|
-
args:
|
|
39
|
+
args: ApiGetOptions,
|
|
40
40
|
opts?: SuspensePaginatorOptions
|
|
41
41
|
) => {
|
|
42
42
|
const apiClient = useApiClient()
|
|
@@ -52,11 +52,11 @@ export const useApiPaginator = <T extends ResourceObject>(
|
|
|
52
52
|
const pageParmWhere = pageParam?.where || {}
|
|
53
53
|
const argsWhere = args.data.where || {}
|
|
54
54
|
const where = { ...argsWhere, ...pageParmWhere }
|
|
55
|
-
|
|
55
|
+
const app = args.app || 'chat'
|
|
56
56
|
const offset = pageParam?.offset || args.data.offset
|
|
57
57
|
const data = { ...args.data, where, offset }
|
|
58
58
|
|
|
59
|
-
return apiClient.
|
|
59
|
+
return apiClient[app].get({
|
|
60
60
|
url: args.url,
|
|
61
61
|
data,
|
|
62
62
|
})
|
|
@@ -71,10 +71,11 @@ export const useApiPaginator = <T extends ResourceObject>(
|
|
|
71
71
|
|
|
72
72
|
return undefined
|
|
73
73
|
},
|
|
74
|
+
enabled: args.enabled,
|
|
74
75
|
...(opts || {}),
|
|
75
76
|
})
|
|
76
77
|
|
|
77
|
-
const data: T[] = query.data?.pages
|
|
78
|
+
const data: T[] = query.data?.pages?.flatMap(page => page.data) || []
|
|
78
79
|
|
|
79
80
|
return { ...query, data }
|
|
80
81
|
}
|
|
@@ -2,7 +2,7 @@ import { useContext, useMemo } from 'react'
|
|
|
2
2
|
import { ChatContext } from '../contexts/chat_context'
|
|
3
3
|
import { Client } from '../utils/client'
|
|
4
4
|
|
|
5
|
-
type App = 'chat' | 'groups' | 'services'
|
|
5
|
+
export type App = 'chat' | 'groups' | 'services'
|
|
6
6
|
const apps: App[] = ['chat', 'groups', 'services']
|
|
7
7
|
|
|
8
8
|
export type ApiClient = { [_K in App]: Client }
|
|
@@ -4,7 +4,7 @@ import { ApiResource, ConversationResource } from '../types'
|
|
|
4
4
|
import { transformGetToPost } from '../utils/client/request_helpers'
|
|
5
5
|
import { useApiClient } from './use_api_client'
|
|
6
6
|
import { getRequestQueryKey, useSuspenseGet } from './use_suspense_api'
|
|
7
|
-
import { getConversationsRequestArgs } from '
|
|
7
|
+
import { getConversationsRequestArgs } from '../utils/request/conversation'
|
|
8
8
|
|
|
9
9
|
export const getConversationRequestArgs = ({ conversation_id }: { conversation_id: number }) => ({
|
|
10
10
|
url: `/me/conversations/${conversation_id}`,
|