@planningcenter/chat-react-native 3.5.0-rc.1 → 3.5.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.
Files changed (217) hide show
  1. package/build/components/conversation/attachments/audio_attachment.d.ts +2 -2
  2. package/build/components/conversation/attachments/audio_attachment.d.ts.map +1 -1
  3. package/build/components/conversation/attachments/audio_attachment.js +2 -2
  4. package/build/components/conversation/attachments/audio_attachment.js.map +1 -1
  5. package/build/components/conversation/attachments/expanded_link.d.ts +2 -2
  6. package/build/components/conversation/attachments/expanded_link.d.ts.map +1 -1
  7. package/build/components/conversation/attachments/expanded_link.js +2 -2
  8. package/build/components/conversation/attachments/expanded_link.js.map +1 -1
  9. package/build/components/conversation/attachments/generic_file_attachment.d.ts +2 -2
  10. package/build/components/conversation/attachments/generic_file_attachment.d.ts.map +1 -1
  11. package/build/components/conversation/attachments/generic_file_attachment.js +2 -2
  12. package/build/components/conversation/attachments/generic_file_attachment.js.map +1 -1
  13. package/build/components/conversation/attachments/giphy_attachment.d.ts +2 -2
  14. package/build/components/conversation/attachments/giphy_attachment.d.ts.map +1 -1
  15. package/build/components/conversation/attachments/giphy_attachment.js +18 -5
  16. package/build/components/conversation/attachments/giphy_attachment.js.map +1 -1
  17. package/build/components/conversation/attachments/image_attachment.d.ts +2 -2
  18. package/build/components/conversation/attachments/image_attachment.d.ts.map +1 -1
  19. package/build/components/conversation/attachments/image_attachment.js +3 -3
  20. package/build/components/conversation/attachments/image_attachment.js.map +1 -1
  21. package/build/components/conversation/attachments/video_attachment.d.ts +2 -2
  22. package/build/components/conversation/attachments/video_attachment.d.ts.map +1 -1
  23. package/build/components/conversation/attachments/video_attachment.js +2 -2
  24. package/build/components/conversation/attachments/video_attachment.js.map +1 -1
  25. package/build/components/conversation/empty_conversation_blank_state.js +1 -1
  26. package/build/components/conversation/empty_conversation_blank_state.js.map +1 -1
  27. package/build/components/conversation/message.d.ts +5 -2
  28. package/build/components/conversation/message.d.ts.map +1 -1
  29. package/build/components/conversation/message.js +11 -9
  30. package/build/components/conversation/message.js.map +1 -1
  31. package/build/components/conversation/message_attachments.d.ts +3 -2
  32. package/build/components/conversation/message_attachments.d.ts.map +1 -1
  33. package/build/components/conversation/message_attachments.js +9 -9
  34. package/build/components/conversation/message_attachments.js.map +1 -1
  35. package/build/components/conversations/conversation_actions.d.ts.map +1 -1
  36. package/build/components/conversations/conversation_actions.js +1 -1
  37. package/build/components/conversations/conversation_actions.js.map +1 -1
  38. package/build/components/conversations/conversation_preview.d.ts.map +1 -1
  39. package/build/components/conversations/conversation_preview.js +13 -3
  40. package/build/components/conversations/conversation_preview.js.map +1 -1
  41. package/build/components/conversations/conversations.js +3 -3
  42. package/build/components/conversations/conversations.js.map +1 -1
  43. package/build/components/display/action_button.d.ts +4 -1
  44. package/build/components/display/action_button.d.ts.map +1 -1
  45. package/build/components/display/action_button.js +20 -5
  46. package/build/components/display/action_button.js.map +1 -1
  47. package/build/components/display/avatar.d.ts +4 -1
  48. package/build/components/display/avatar.d.ts.map +1 -1
  49. package/build/components/display/avatar.js +3 -2
  50. package/build/components/display/avatar.js.map +1 -1
  51. package/build/components/display/avatar_group.d.ts +4 -1
  52. package/build/components/display/avatar_group.d.ts.map +1 -1
  53. package/build/components/display/avatar_group.js +6 -3
  54. package/build/components/display/avatar_group.js.map +1 -1
  55. package/build/components/display/blank_state.d.ts +7 -2
  56. package/build/components/display/blank_state.d.ts.map +1 -1
  57. package/build/components/display/blank_state.js +6 -5
  58. package/build/components/display/blank_state.js.map +1 -1
  59. package/build/components/display/child_notice.d.ts +2 -1
  60. package/build/components/display/child_notice.d.ts.map +1 -1
  61. package/build/components/display/child_notice.js +4 -4
  62. package/build/components/display/child_notice.js.map +1 -1
  63. package/build/components/display/heading.d.ts +2 -3
  64. package/build/components/display/heading.d.ts.map +1 -1
  65. package/build/components/display/heading.js.map +1 -1
  66. package/build/components/display/icon.d.ts +2 -1
  67. package/build/components/display/icon.d.ts.map +1 -1
  68. package/build/components/display/icon.js +3 -0
  69. package/build/components/display/icon.js.map +1 -1
  70. package/build/components/display/icon_button.d.ts +4 -0
  71. package/build/components/display/icon_button.d.ts.map +1 -1
  72. package/build/components/display/icon_button.js +32 -0
  73. package/build/components/display/icon_button.js.map +1 -1
  74. package/build/components/display/image_attachment_preview.d.ts +14 -0
  75. package/build/components/display/image_attachment_preview.d.ts.map +1 -0
  76. package/build/components/display/image_attachment_preview.js +42 -0
  77. package/build/components/display/image_attachment_preview.js.map +1 -0
  78. package/build/components/display/index.d.ts +2 -0
  79. package/build/components/display/index.d.ts.map +1 -1
  80. package/build/components/display/index.js +2 -0
  81. package/build/components/display/index.js.map +1 -1
  82. package/build/components/display/keyboard_view.d.ts +2 -1
  83. package/build/components/display/keyboard_view.d.ts.map +1 -1
  84. package/build/components/display/keyboard_view.js +13 -11
  85. package/build/components/display/keyboard_view.js.map +1 -1
  86. package/build/components/display/person.d.ts.map +1 -1
  87. package/build/components/display/person.js +4 -1
  88. package/build/components/display/person.js.map +1 -1
  89. package/build/components/display/platform_modal_header_buttons.d.ts +15 -0
  90. package/build/components/display/platform_modal_header_buttons.d.ts.map +1 -0
  91. package/build/components/display/platform_modal_header_buttons.js +43 -0
  92. package/build/components/display/platform_modal_header_buttons.js.map +1 -0
  93. package/build/components/display/video_attachment_preview.d.ts +8 -0
  94. package/build/components/display/video_attachment_preview.d.ts.map +1 -0
  95. package/build/components/display/video_attachment_preview.js +71 -0
  96. package/build/components/display/video_attachment_preview.js.map +1 -0
  97. package/build/components/group_conversation_list.d.ts +1 -1
  98. package/build/components/group_conversation_list.d.ts.map +1 -1
  99. package/build/components/group_conversation_list.js +8 -6
  100. package/build/components/group_conversation_list.js.map +1 -1
  101. package/build/components/page/error_boundary.d.ts.map +1 -1
  102. package/build/components/page/error_boundary.js +4 -1
  103. package/build/components/page/error_boundary.js.map +1 -1
  104. package/build/components/primitive/avatar_primitive.d.ts +6 -1
  105. package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
  106. package/build/components/primitive/avatar_primitive.js +24 -3
  107. package/build/components/primitive/avatar_primitive.js.map +1 -1
  108. package/build/contexts/chat_context.d.ts +1 -0
  109. package/build/contexts/chat_context.d.ts.map +1 -1
  110. package/build/contexts/chat_context.js +5 -3
  111. package/build/contexts/chat_context.js.map +1 -1
  112. package/build/hooks/groups/use_group_members_for_new_conversation.d.ts +14 -196
  113. package/build/hooks/groups/use_group_members_for_new_conversation.d.ts.map +1 -1
  114. package/build/hooks/groups/use_group_members_for_new_conversation.js +2 -2
  115. package/build/hooks/groups/use_group_members_for_new_conversation.js.map +1 -1
  116. package/build/hooks/use_conversation.d.ts.map +1 -1
  117. package/build/hooks/use_conversation.js +7 -1
  118. package/build/hooks/use_conversation.js.map +1 -1
  119. package/build/hooks/use_giphy.js.map +1 -1
  120. package/build/hooks/use_report_bug_action.d.ts +5 -0
  121. package/build/hooks/use_report_bug_action.d.ts.map +1 -0
  122. package/build/hooks/use_report_bug_action.js +30 -0
  123. package/build/hooks/use_report_bug_action.js.map +1 -0
  124. package/build/navigation/index.d.ts +5 -0
  125. package/build/navigation/index.d.ts.map +1 -1
  126. package/build/navigation/index.js +5 -0
  127. package/build/navigation/index.js.map +1 -1
  128. package/build/screens/attachment_actions/attachment_actions_screen.d.ts +6 -2
  129. package/build/screens/attachment_actions/attachment_actions_screen.d.ts.map +1 -1
  130. package/build/screens/attachment_actions/attachment_actions_screen.js +16 -16
  131. package/build/screens/attachment_actions/attachment_actions_screen.js.map +1 -1
  132. package/build/screens/attachment_actions/hooks/useDeleteAttachment.d.ts +10 -0
  133. package/build/screens/attachment_actions/hooks/useDeleteAttachment.d.ts.map +1 -0
  134. package/build/screens/attachment_actions/hooks/useDeleteAttachment.js +29 -0
  135. package/build/screens/attachment_actions/hooks/useDeleteAttachment.js.map +1 -0
  136. package/build/screens/bug_report_screen.d.ts +5 -0
  137. package/build/screens/bug_report_screen.d.ts.map +1 -0
  138. package/build/screens/bug_report_screen.js +237 -0
  139. package/build/screens/bug_report_screen.js.map +1 -0
  140. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  141. package/build/screens/conversation_details_screen.js +29 -10
  142. package/build/screens/conversation_details_screen.js.map +1 -1
  143. package/build/screens/conversation_new/components/form_list.d.ts +3 -1
  144. package/build/screens/conversation_new/components/form_list.d.ts.map +1 -1
  145. package/build/screens/conversation_new/components/form_list.js +6 -2
  146. package/build/screens/conversation_new/components/form_list.js.map +1 -1
  147. package/build/screens/conversation_new/components/groups_form.d.ts.map +1 -1
  148. package/build/screens/conversation_new/components/groups_form.js +3 -6
  149. package/build/screens/conversation_new/components/groups_form.js.map +1 -1
  150. package/build/screens/conversation_new/components/services_form.js +1 -1
  151. package/build/screens/conversation_new/components/services_form.js.map +1 -1
  152. package/build/screens/conversation_screen.d.ts.map +1 -1
  153. package/build/screens/conversation_screen.js +3 -5
  154. package/build/screens/conversation_screen.js.map +1 -1
  155. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  156. package/build/screens/conversations/conversations_screen.js +7 -2
  157. package/build/screens/conversations/conversations_screen.js.map +1 -1
  158. package/build/screens/design_system_screen.d.ts.map +1 -1
  159. package/build/screens/design_system_screen.js +29 -4
  160. package/build/screens/design_system_screen.js.map +1 -1
  161. package/build/types/resources/denormalized_attachment_resource.d.ts +2 -2
  162. package/build/types/resources/denormalized_attachment_resource.js.map +1 -1
  163. package/build/types/resources/member_ability.d.ts +1 -0
  164. package/build/types/resources/member_ability.d.ts.map +1 -1
  165. package/build/types/resources/member_ability.js.map +1 -1
  166. package/build/utils/request/conversation.d.ts +2 -1
  167. package/build/utils/request/conversation.d.ts.map +1 -1
  168. package/build/utils/request/conversation.js.map +1 -1
  169. package/package.json +2 -2
  170. package/src/components/conversation/attachments/audio_attachment.tsx +3 -3
  171. package/src/components/conversation/attachments/expanded_link.tsx +3 -8
  172. package/src/components/conversation/attachments/generic_file_attachment.tsx +3 -3
  173. package/src/components/conversation/attachments/giphy_attachment.tsx +23 -8
  174. package/src/components/conversation/attachments/image_attachment.tsx +4 -4
  175. package/src/components/conversation/attachments/video_attachment.tsx +3 -3
  176. package/src/components/conversation/empty_conversation_blank_state.tsx +1 -1
  177. package/src/components/conversation/message.tsx +20 -8
  178. package/src/components/conversation/message_attachments.tsx +18 -11
  179. package/src/components/conversations/conversation_actions.tsx +1 -0
  180. package/src/components/conversations/conversation_preview.tsx +24 -3
  181. package/src/components/conversations/conversations.tsx +3 -3
  182. package/src/components/display/action_button.tsx +32 -4
  183. package/src/components/display/avatar.tsx +17 -2
  184. package/src/components/display/avatar_group.tsx +19 -3
  185. package/src/components/display/blank_state.tsx +21 -8
  186. package/src/components/display/child_notice.tsx +7 -4
  187. package/src/components/display/heading.tsx +2 -2
  188. package/src/components/display/icon.tsx +4 -1
  189. package/src/components/display/icon_button.tsx +32 -0
  190. package/src/components/display/image_attachment_preview.tsx +74 -0
  191. package/src/components/display/index.ts +2 -0
  192. package/src/components/display/keyboard_view.tsx +16 -17
  193. package/src/components/display/person.tsx +4 -1
  194. package/src/components/display/platform_modal_header_buttons.tsx +83 -0
  195. package/src/components/display/video_attachment_preview.tsx +105 -0
  196. package/src/components/group_conversation_list.tsx +14 -7
  197. package/src/components/page/error_boundary.tsx +4 -1
  198. package/src/components/primitive/avatar_primitive.tsx +42 -4
  199. package/src/contexts/chat_context.tsx +6 -3
  200. package/src/hooks/groups/use_group_members_for_new_conversation.ts +6 -4
  201. package/src/hooks/use_conversation.ts +7 -1
  202. package/src/hooks/use_giphy.ts +2 -2
  203. package/src/hooks/use_report_bug_action.ts +37 -0
  204. package/src/navigation/index.tsx +5 -0
  205. package/src/screens/attachment_actions/attachment_actions_screen.tsx +35 -16
  206. package/src/screens/attachment_actions/hooks/useDeleteAttachment.tsx +46 -0
  207. package/src/screens/bug_report_screen.tsx +334 -0
  208. package/src/screens/conversation_details_screen.tsx +41 -7
  209. package/src/screens/conversation_new/components/form_list.tsx +17 -1
  210. package/src/screens/conversation_new/components/groups_form.tsx +6 -5
  211. package/src/screens/conversation_new/components/services_form.tsx +3 -1
  212. package/src/screens/conversation_screen.tsx +9 -5
  213. package/src/screens/conversations/conversations_screen.tsx +11 -1
  214. package/src/screens/design_system_screen.tsx +68 -3
  215. package/src/types/resources/denormalized_attachment_resource.ts +2 -2
  216. package/src/types/resources/member_ability.ts +1 -0
  217. package/src/utils/request/conversation.ts +2 -1
@@ -7,10 +7,10 @@ import { PlatformPressable } from '@react-navigation/elements'
7
7
 
8
8
  export function ExpandedLink({
9
9
  attachment,
10
- onAttachmentLongPress,
10
+ onMessageLongPress,
11
11
  }: {
12
12
  attachment: DenormalizedExpandedLinkAttachmentResource
13
- onAttachmentLongPress: (attachment: DenormalizedExpandedLinkAttachmentResource) => void
13
+ onMessageLongPress: () => void
14
14
  }) {
15
15
  const styles = useStyles()
16
16
  const { attributes } = attachment
@@ -23,12 +23,7 @@ export function ExpandedLink({
23
23
  }
24
24
 
25
25
  return (
26
- <PlatformPressable
27
- style={styles.container}
28
- onPress={openUrl}
29
- onLongPress={() => onAttachmentLongPress(attachment)}
30
- accessibilityHint="Long press for more options"
31
- >
26
+ <PlatformPressable style={styles.container} onPress={openUrl} onLongPress={onMessageLongPress}>
32
27
  {imageUrl && (
33
28
  <Image source={{ uri: imageUrl }} style={[styles.image, { aspectRatio }]} alt={title} />
34
29
  )}
@@ -9,10 +9,10 @@ import { PlatformPressable } from '@react-navigation/elements'
9
9
 
10
10
  export function GenericFileAttachment({
11
11
  attachment,
12
- onAttachmentLongPress,
12
+ onMessageAttachmentLongPress,
13
13
  }: {
14
14
  attachment: DenormalizedMessageAttachmentResource
15
- onAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void
15
+ onMessageAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void
16
16
  }) {
17
17
  const styles = useStyles()
18
18
  const { url, filename, contentType } = attachment.attributes
@@ -21,7 +21,7 @@ export function GenericFileAttachment({
21
21
 
22
22
  return (
23
23
  <PlatformPressable
24
- onLongPress={() => onAttachmentLongPress(attachment)}
24
+ onLongPress={() => onMessageAttachmentLongPress(attachment)}
25
25
  accessibilityHint="Long press for more options"
26
26
  >
27
27
  <AttachmentCard>
@@ -1,19 +1,21 @@
1
1
  import React from 'react'
2
- import { Text, Image, StyleSheet, Linking, Pressable } from 'react-native'
2
+ import { Text, Image, StyleSheet, Linking } from 'react-native'
3
3
  import { tokens } from '../../../vendor/tapestry/tokens'
4
4
  import { useTheme } from '../../../hooks'
5
5
  import { DenormalizedGiphyAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'
6
+ import { PlatformPressable } from '@react-navigation/elements'
6
7
 
7
8
  export function GiphyAttachment({
8
9
  attachment,
9
- onAttachmentLongPress,
10
+ onMessageLongPress,
10
11
  }: {
11
12
  attachment: DenormalizedGiphyAttachmentResource
12
- onAttachmentLongPress: (attachment: DenormalizedGiphyAttachmentResource) => void
13
+ onMessageLongPress: () => void
13
14
  }) {
14
15
  const styles = useStyles()
15
16
  const { title, titleLink, giphy } = attachment
16
- const { url, width, height } = giphy.fixedWidth
17
+ const { url } = giphy.fixedWidth
18
+ const { width, height } = assertKeysAreNumbers(giphy.fixedWidth)
17
19
 
18
20
  function handlePress() {
19
21
  if (titleLink) {
@@ -23,11 +25,10 @@ export function GiphyAttachment({
23
25
  }
24
26
 
25
27
  return (
26
- <Pressable
28
+ <PlatformPressable
27
29
  onPress={handlePress}
30
+ onLongPress={onMessageLongPress}
28
31
  style={styles.container}
29
- onLongPress={() => onAttachmentLongPress(attachment)}
30
- accessibilityHint="Long press for more options"
31
32
  >
32
33
  <Image
33
34
  source={{ uri: url }}
@@ -37,10 +38,24 @@ export function GiphyAttachment({
37
38
  <Text style={styles.link}>
38
39
  <Text style={styles.tag}>/giphy</Text> {title}
39
40
  </Text>
40
- </Pressable>
41
+ </PlatformPressable>
41
42
  )
42
43
  }
43
44
 
45
+ const assertKeysAreNumbers = (obj: Object): Record<any, number> => {
46
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, assertNumber(value)]))
47
+ }
48
+
49
+ const assertNumber = (value: string): number => {
50
+ if (typeof value === 'number') return value
51
+
52
+ if (typeof value === 'string' && !isNaN(Number(value))) {
53
+ return Number(value)
54
+ }
55
+
56
+ return 0
57
+ }
58
+
44
59
  const useStyles = () => {
45
60
  const { colors } = useTheme()
46
61
  return StyleSheet.create({
@@ -38,11 +38,11 @@ export type MetaProps = {
38
38
  export function ImageAttachment({
39
39
  attachment,
40
40
  metaProps,
41
- onAttachmentLongPress,
41
+ onMessageAttachmentLongPress,
42
42
  }: {
43
43
  attachment: DenormalizedMessageAttachmentResource
44
44
  metaProps: MetaProps
45
- onAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void
45
+ onMessageAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void
46
46
  }) {
47
47
  const { attributes } = attachment
48
48
  const { url, urlMedium, filename, metadata = {} } = attributes
@@ -83,12 +83,12 @@ export function ImageAttachment({
83
83
  <PlatformPressable
84
84
  style={styles.container}
85
85
  onPress={() => setVisible(true)}
86
- onLongPress={() => onAttachmentLongPress(attachment)}
86
+ onLongPress={() => onMessageAttachmentLongPress(attachment)}
87
87
  accessibilityHint="Long press for more options"
88
88
  >
89
89
  <Image
90
90
  source={{ uri: urlMedium || url }}
91
- style={{ borderRadius: 8 }}
91
+ style={styles.image}
92
92
  wrapperStyle={styles.imageWrapper}
93
93
  alt={filename}
94
94
  />
@@ -8,10 +8,10 @@ import { PlatformPressable } from '@react-navigation/elements'
8
8
 
9
9
  export function VideoAttachment({
10
10
  attachment,
11
- onAttachmentLongPress,
11
+ onMessageAttachmentLongPress,
12
12
  }: {
13
13
  attachment: DenormalizedMessageAttachmentResource
14
- onAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void
14
+ onMessageAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void
15
15
  }) {
16
16
  const { attributes } = attachment
17
17
  const { width = MESSAGE_ATTACHMENT_WIDTH_SINGLE, height = MESSAGE_ATTACHMENT_WIDTH_SINGLE } =
@@ -39,7 +39,7 @@ export function VideoAttachment({
39
39
  return (
40
40
  <View style={styles.container} ref={viewRef}>
41
41
  <PlatformPressable
42
- onLongPress={() => onAttachmentLongPress(attachment)}
42
+ onLongPress={() => onMessageAttachmentLongPress(attachment)}
43
43
  accessibilityHint="Long press for more options"
44
44
  >
45
45
  <Video.Player
@@ -1,4 +1,4 @@
1
- import { BlankState } from '../display/blank_state'
1
+ import { BlankState } from '../display'
2
2
 
3
3
  export const EmptyConversationBlankState = () => {
4
4
  return (
@@ -11,16 +11,22 @@ import { ReactionCountResource } from '../../types/resources/reaction'
11
11
  import { MessageAttachments } from './message_attachments'
12
12
  import ErrorBoundary from '../page/error_boundary'
13
13
  import { MessageMarkdown } from './message_markdown'
14
- import { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'
14
+ import { DenormalizedMessageAttachmentResource } from '../../types/resources/denormalized_attachment_resource'
15
15
 
16
16
  /** Message
17
17
  * Component for display of a message within a conversation list
18
18
  */
19
- export function Message(props: MessageResource & { conversation_id: number }) {
20
- const { text, conversation_id, reactionCounts } = props
19
+ interface MessageProps extends MessageResource {
20
+ canDeleteNonAuthoredMessages: boolean
21
+ conversation_id: number
22
+ }
23
+
24
+ export function Message(props: MessageProps) {
25
+ const { conversation_id, canDeleteNonAuthoredMessages, text, reactionCounts } = props
21
26
  const styles = useMessageStyles(props)
22
27
  const navigation = useNavigation()
23
- const handleMessagePress = () => {
28
+
29
+ const handleMessageLongPress = () => {
24
30
  navigation.navigate('MessageActions', {
25
31
  message_id: props.id,
26
32
  conversation_id,
@@ -34,9 +40,14 @@ export function Message(props: MessageResource & { conversation_id: number }) {
34
40
  })
35
41
  }
36
42
 
37
- const handleAttachmentLongPress = (attachment: DenormalizedAttachmentResource) => {
43
+ const handleMessageAttachmentLongPress = (attachment: DenormalizedMessageAttachmentResource) => {
38
44
  navigation.navigate('AttachmentActions', {
39
- attachment,
45
+ attachmentId: attachment?.id,
46
+ attachmentContentType: attachment?.attributes.contentType,
47
+ attachmentUrl: attachment?.attributes.url,
48
+ conversation_id,
49
+ canDeleteNonAuthoredMessages,
50
+ myMessage: props.mine,
40
51
  })
41
52
  }
42
53
 
@@ -54,12 +65,13 @@ export function Message(props: MessageResource & { conversation_id: number }) {
54
65
  )}
55
66
  <View style={styles.messageContent}>
56
67
  {!props.mine && <Text variant="tertiary">{props.author.name}</Text>}
57
- <PlatformPressable style={styles.messageBubble} onLongPress={handleMessagePress}>
68
+ <PlatformPressable style={styles.messageBubble} onLongPress={handleMessageLongPress}>
58
69
  <ErrorBoundary>
59
70
  <MessageAttachments
60
71
  attachments={props.attachments}
61
72
  metaProps={metaProps}
62
- onAttachmentLongPress={handleAttachmentLongPress}
73
+ onMessageAttachmentLongPress={handleMessageAttachmentLongPress}
74
+ onMessageLongPress={handleMessageLongPress}
63
75
  />
64
76
  </ErrorBoundary>
65
77
  {text && (
@@ -14,10 +14,11 @@ import { ImageAttachment, type MetaProps } from './attachments/image_attachment'
14
14
  export function MessageAttachments(props: {
15
15
  attachments: DenormalizedAttachmentResource[]
16
16
  metaProps: MetaProps
17
- onAttachmentLongPress: (attachment: DenormalizedAttachmentResource) => void
17
+ onMessageAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void
18
+ onMessageLongPress: () => void
18
19
  }) {
19
20
  const styles = useStyles()
20
- const { attachments, metaProps, onAttachmentLongPress } = props
21
+ const { attachments, metaProps, onMessageAttachmentLongPress, onMessageLongPress } = props
21
22
  if (!attachments || attachments.length === 0) return null
22
23
  return (
23
24
  <View style={styles.attachmentsContainer}>
@@ -29,7 +30,7 @@ export function MessageAttachments(props: {
29
30
  key={attachment.id}
30
31
  attachment={attachment}
31
32
  metaProps={metaProps}
32
- onAttachmentLongPress={onAttachmentLongPress}
33
+ onMessageAttachmentLongPress={onMessageAttachmentLongPress}
33
34
  />
34
35
  )
35
36
  case 'giphy':
@@ -37,7 +38,7 @@ export function MessageAttachments(props: {
37
38
  <GiphyAttachment
38
39
  key={attachment.id || attachment.titleLink}
39
40
  attachment={attachment}
40
- onAttachmentLongPress={onAttachmentLongPress}
41
+ onMessageLongPress={onMessageLongPress}
41
42
  />
42
43
  )
43
44
  case 'ExpandedLink':
@@ -45,7 +46,7 @@ export function MessageAttachments(props: {
45
46
  <ExpandedLink
46
47
  key={attachment.id}
47
48
  attachment={attachment}
48
- onAttachmentLongPress={onAttachmentLongPress}
49
+ onMessageLongPress={onMessageLongPress}
49
50
  />
50
51
  )
51
52
  default:
@@ -59,11 +60,11 @@ export function MessageAttachments(props: {
59
60
  function MessageAttachment({
60
61
  attachment,
61
62
  metaProps,
62
- onAttachmentLongPress,
63
+ onMessageAttachmentLongPress,
63
64
  }: {
64
65
  attachment: DenormalizedMessageAttachmentResource
65
66
  metaProps: MetaProps
66
- onAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void
67
+ onMessageAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void
67
68
  }) {
68
69
  const { attributes } = attachment
69
70
  const contentType = attributes?.contentType
@@ -74,22 +75,28 @@ function MessageAttachment({
74
75
  <ImageAttachment
75
76
  attachment={attachment}
76
77
  metaProps={metaProps}
77
- onAttachmentLongPress={onAttachmentLongPress}
78
+ onMessageAttachmentLongPress={onMessageAttachmentLongPress}
78
79
  />
79
80
  )
80
81
  case 'video':
81
82
  return (
82
- <VideoAttachment attachment={attachment} onAttachmentLongPress={onAttachmentLongPress} />
83
+ <VideoAttachment
84
+ attachment={attachment}
85
+ onMessageAttachmentLongPress={onMessageAttachmentLongPress}
86
+ />
83
87
  )
84
88
  case 'audio':
85
89
  return (
86
- <AudioAttachment attachment={attachment} onAttachmentLongPress={onAttachmentLongPress} />
90
+ <AudioAttachment
91
+ attachment={attachment}
92
+ onMessageAttachmentLongPress={onMessageAttachmentLongPress}
93
+ />
87
94
  )
88
95
  default:
89
96
  return (
90
97
  <GenericFileAttachment
91
98
  attachment={attachment}
92
- onAttachmentLongPress={onAttachmentLongPress}
99
+ onMessageAttachmentLongPress={onMessageAttachmentLongPress}
93
100
  />
94
101
  )
95
102
  }
@@ -61,6 +61,7 @@ export function ConversationActions({
61
61
  overshootRight={false}
62
62
  onSwipeableOpenStartDrag={handleSwipeableOpen}
63
63
  onSwipeableClose={() => setDisabled(false)}
64
+ renderRightActions={() => <></>}
64
65
  renderLeftActions={() => (
65
66
  <LeftActions conversation={conversation} onClose={handleSwipeableClose} />
66
67
  )}
@@ -33,19 +33,37 @@ export const ConversationPreview = ({
33
33
  muted,
34
34
  } = conversation
35
35
 
36
+ const emptyConversation = !lastMessageCreatedAt
37
+ const hasAvatarUrls = previewAvatarUrls && previewAvatarUrls.length > 0
38
+ const shouldShowFallback = emptyConversation || !hasAvatarUrls
39
+ const fallbackIconName = emptyConversation ? 'people.noTextMessage' : 'general.person'
40
+
41
+ const conversationPreviewText = lastMessageTextPreview
42
+ ? `${lastMessageAuthorName}: ${lastMessageTextPreview}`
43
+ : 'Send your first message'
44
+
36
45
  return (
37
46
  <ConversationActions
38
47
  conversation={conversation}
39
48
  style={[styles.previewRow, style]}
40
49
  onPress={onPress}
41
50
  >
42
- <AvatarGroup size="lg" sourceUris={previewAvatarUrls || []} />
51
+ <AvatarGroup
52
+ size="lg"
53
+ sourceUris={previewAvatarUrls || []}
54
+ showFallback={shouldShowFallback}
55
+ fallbackIconName={fallbackIconName}
56
+ />
43
57
  <View style={styles.conversationBody}>
44
58
  <Heading numberOfLines={1} variant="h3" style={styles.title}>
45
59
  {title}
46
60
  </Heading>
47
- <Text variant="tertiary" numberOfLines={2}>
48
- {lastMessageAuthorName}: {lastMessageTextPreview}
61
+ <Text
62
+ variant="tertiary"
63
+ numberOfLines={2}
64
+ style={emptyConversation && styles.emptyConversationPreviewText}
65
+ >
66
+ {conversationPreviewText}
49
67
  </Text>
50
68
  <ConversationBadges visible={showBadges} badges={badges} />
51
69
  </View>
@@ -103,6 +121,9 @@ const useStyles = () => {
103
121
  flex: 1,
104
122
  rowGap: 2,
105
123
  },
124
+ emptyConversationPreviewText: {
125
+ color: colors.interaction,
126
+ },
106
127
  metaContainer: {
107
128
  rowGap: 4,
108
129
  alignItems: 'flex-end',
@@ -4,7 +4,7 @@ import React from 'react'
4
4
  import { StyleSheet, View } from 'react-native'
5
5
  import { useConversationsContext } from '../../contexts/conversations_context'
6
6
  import { useTheme } from '../../hooks'
7
- import { Text } from '../display'
7
+ import { BlankState } from '../display'
8
8
  import { ConversationPreview } from './conversation_preview'
9
9
 
10
10
  interface ConversationsProps {
@@ -41,7 +41,7 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
41
41
  ListHeaderComponent={ListHeaderComponent}
42
42
  ListEmptyComponent={
43
43
  <View style={styles.listEmpty}>
44
- <Text variant="secondary">No conversations found</Text>
44
+ <BlankState iconName="general.outlinedTextMessage" title="No conversations" />
45
45
  </View>
46
46
  }
47
47
  renderItem={({ item }) => (
@@ -68,7 +68,7 @@ const useStyles = () => {
68
68
  flex: 1,
69
69
  justifyContent: 'center',
70
70
  alignItems: 'center',
71
- paddingVertical: 16,
71
+ paddingVertical: 32,
72
72
  },
73
73
  })
74
74
  }
@@ -1,10 +1,11 @@
1
1
  import React, { useState } from 'react'
2
- import { Animated, LayoutAnimation, StyleSheet } from 'react-native'
2
+ import { Animated, LayoutAnimation, StyleSheet, View } from 'react-native'
3
3
  import { Button } from './button'
4
4
  import { useEffect } from 'react'
5
5
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
6
6
  import { useTheme } from '../../hooks'
7
7
  import { Text } from './text'
8
+ import { IconString } from './icon'
8
9
 
9
10
  export const ActionButton = ({
10
11
  visible = true,
@@ -12,12 +13,16 @@ export const ActionButton = ({
12
13
  onPress,
13
14
  title,
14
15
  infoText,
16
+ buttonIconNameLeft,
17
+ secondaryButton,
15
18
  }: {
16
19
  visible?: boolean
17
20
  disabled?: boolean
18
21
  onPress: () => void
19
22
  title: string
20
23
  infoText?: string
24
+ buttonIconNameLeft?: IconString
25
+ secondaryButton?: React.ReactNode
21
26
  }) => {
22
27
  const styles = useStyles()
23
28
  const [show, setShow] = useState(visible)
@@ -38,7 +43,18 @@ export const ActionButton = ({
38
43
  {infoText}
39
44
  </Text>
40
45
  )}
41
- <Button variant="fill" size="lg" onPress={onPress} title={title} disabled={disabled} />
46
+ <View style={styles.buttonRow}>
47
+ {secondaryButton}
48
+ <Button
49
+ variant="fill"
50
+ size="lg"
51
+ onPress={onPress}
52
+ title={title}
53
+ disabled={disabled}
54
+ style={secondaryButton ? null : styles.fullWidthButton}
55
+ iconNameLeft={buttonIconNameLeft}
56
+ />
57
+ </View>
42
58
  </Animated.View>
43
59
  )
44
60
  }
@@ -46,16 +62,28 @@ export const ActionButton = ({
46
62
  const useStyles = () => {
47
63
  const { bottom } = useSafeAreaInsets()
48
64
  const { colors } = useTheme()
65
+ const containerVerticalPadding = 16
49
66
 
50
67
  return StyleSheet.create({
51
68
  container: {
52
- paddingVertical: 16,
69
+ backgroundColor: colors.surfaceColor090,
53
70
  paddingHorizontal: 24,
54
- paddingBottom: bottom,
71
+ paddingTop: containerVerticalPadding,
72
+ paddingBottom: bottom + containerVerticalPadding,
55
73
  borderTopWidth: 1,
56
74
  borderTopColor: colors.fillColorNeutral060,
57
75
  gap: 16,
58
76
  },
77
+ buttonRow: {
78
+ flexDirection: 'row',
79
+ justifyContent: 'space-between',
80
+ alignItems: 'center',
81
+ gap: 16,
82
+ flexWrap: 'wrap-reverse',
83
+ },
84
+ fullWidthButton: {
85
+ flexGrow: 1,
86
+ },
59
87
  infoText: {
60
88
  textAlign: 'center',
61
89
  },
@@ -4,18 +4,33 @@ import AvatarPrimitive, {
4
4
  type AvatarPresenceProps,
5
5
  type AvatarRootProps,
6
6
  } from '../primitive/avatar_primitive'
7
+ import { type IconString } from './icon'
7
8
 
8
9
  interface AvatarProps {
9
10
  sourceUri: AvatarImageProps['sourceUri']
10
11
  size?: AvatarRootProps['size']
11
12
  presence?: AvatarPresenceProps['presence']
13
+ showFallback?: boolean
14
+ fallbackIconName?: IconString
12
15
  }
13
16
 
14
- export function Avatar({ presence, size = 'lg', sourceUri }: AvatarProps) {
17
+ export function Avatar({
18
+ presence,
19
+ size = 'lg',
20
+ sourceUri,
21
+ showFallback = false,
22
+ fallbackIconName = 'general.person',
23
+ }: AvatarProps) {
24
+ const shouldShowFallback = showFallback || !sourceUri
25
+
15
26
  return (
16
27
  <AvatarPrimitive.Root size={size}>
17
28
  <AvatarPrimitive.Mask>
18
- <AvatarPrimitive.Image sourceUri={sourceUri} />
29
+ {shouldShowFallback ? (
30
+ <AvatarPrimitive.ImageFallback name={fallbackIconName} />
31
+ ) : (
32
+ <AvatarPrimitive.Image sourceUri={sourceUri} />
33
+ )}
19
34
  </AvatarPrimitive.Mask>
20
35
  {presence && <AvatarPrimitive.Presence presence={presence} />}
21
36
  </AvatarPrimitive.Root>
@@ -3,18 +3,34 @@ import AvatarPrimitive, {
3
3
  type AvatarGroupProps,
4
4
  type AvatarRootProps,
5
5
  } from '../primitive/avatar_primitive'
6
+ import { type IconString } from './icon'
6
7
 
7
8
  interface AvatarGroupDisplayProps {
8
9
  sourceUris: AvatarGroupProps['sourceUris']
10
+ showFallback?: boolean
11
+ fallbackIconName?: IconString
9
12
  size?: AvatarRootProps['size']
10
13
  }
11
14
 
12
- export function AvatarGroup({ sourceUris, size = 'lg' }: AvatarGroupDisplayProps) {
15
+ export function AvatarGroup({
16
+ sourceUris,
17
+ showFallback = false,
18
+ fallbackIconName = 'general.person',
19
+ size = 'lg',
20
+ }: AvatarGroupDisplayProps) {
21
+ const shouldShowFallback = showFallback || !sourceUris || sourceUris.length === 0
22
+
13
23
  return (
14
24
  <AvatarPrimitive.Root size={size}>
15
25
  <AvatarPrimitive.Mask>
16
- <AvatarPrimitive.Group sourceUris={sourceUris} />
17
- <AvatarPrimitive.GroupLoader />
26
+ {shouldShowFallback ? (
27
+ <AvatarPrimitive.ImageFallback name={fallbackIconName} />
28
+ ) : (
29
+ <>
30
+ <AvatarPrimitive.Group sourceUris={sourceUris} />
31
+ <AvatarPrimitive.GroupLoader />
32
+ </>
33
+ )}
18
34
  </AvatarPrimitive.Mask>
19
35
  </AvatarPrimitive.Root>
20
36
  )
@@ -1,30 +1,43 @@
1
1
  import { View, type ViewStyle, StyleSheet } from 'react-native'
2
- import { Heading } from './heading'
3
- import { Icon, type IconString } from './icon'
4
- import { Text } from './text'
2
+ import { Heading, type HeadingProps } from './heading'
3
+ import { Icon, type IconString, type IconStyle } from './icon'
4
+ import { Text, type TextProps } from './text'
5
5
  import { useTheme } from '../../hooks'
6
6
  import { Button, type ButtonProps } from './button'
7
7
 
8
8
  interface BlankStateProps {
9
9
  iconName?: IconString
10
+ iconStyle?: IconStyle
10
11
  title: string
12
+ titleStyle?: HeadingProps['style']
11
13
  subtitle?: string
14
+ subtitleStyle?: TextProps['style']
12
15
  style?: ViewStyle
13
16
  buttonProps?: ButtonProps
14
17
  }
15
18
 
16
- export const BlankState = ({ iconName, title, subtitle, style, buttonProps }: BlankStateProps) => {
19
+ export const BlankState = ({
20
+ iconName,
21
+ title,
22
+ titleStyle,
23
+ subtitle,
24
+ subtitleStyle,
25
+ style,
26
+ iconStyle,
27
+ buttonProps,
28
+ }: BlankStateProps) => {
17
29
  const styles = useStyles()
18
30
 
19
31
  return (
20
32
  <View style={[styles.container, style]}>
21
- {iconName && <Icon name={iconName} size={32} style={styles.icon} />}
33
+ {iconName && <Icon name={iconName} size={32} style={[styles.icon, iconStyle]} />}
34
+
22
35
  <View style={styles.content}>
23
- <Heading variant="h3" style={styles.baseText}>
36
+ <Heading variant="h3" style={[styles.baseText, titleStyle]}>
24
37
  {title}
25
38
  </Heading>
26
39
  {subtitle && (
27
- <Text variant="tertiary" style={styles.baseText}>
40
+ <Text variant="tertiary" style={[styles.baseText, subtitleStyle]}>
28
41
  {subtitle}
29
42
  </Text>
30
43
  )}
@@ -46,7 +59,7 @@ const useStyles = () => {
46
59
  padding: 16,
47
60
  },
48
61
  icon: {
49
- color: colors.iconColorDefaultSecondary,
62
+ color: colors.iconColorDefaultDisabled,
50
63
  },
51
64
  content: {
52
65
  alignItems: 'center',
@@ -13,10 +13,11 @@ import { Text } from './text'
13
13
 
14
14
  interface ChildNoticeProps {
15
15
  childMembers: MemberResource[]
16
+ showMembers?: boolean
16
17
  style?: ViewStyle
17
18
  }
18
19
 
19
- export function ChildNotice({ childMembers, style }: ChildNoticeProps) {
20
+ export function ChildNotice({ childMembers, showMembers = false, style }: ChildNoticeProps) {
20
21
  const styles = useStyles()
21
22
  const heading = `${pluralize(childMembers.length, 'member')} under age 13`
22
23
 
@@ -30,9 +31,11 @@ export function ChildNotice({ childMembers, style }: ChildNoticeProps) {
30
31
  <BannerPrimitive.Text>
31
32
  Under age members cannot chat. They won't see conversations or notifications.
32
33
  </BannerPrimitive.Text>
33
- <View style={styles.childrenWrapper}>
34
- {childMembers?.map(child => <Child key={child.id} child={child} />)}
35
- </View>
34
+ {showMembers && (
35
+ <View style={styles.childrenWrapper}>
36
+ {childMembers?.map(child => <Child key={child.id} child={child} />)}
37
+ </View>
38
+ )}
36
39
  </BannerCollapsible>
37
40
  )
38
41
  }
@@ -13,7 +13,7 @@ import { platformFontWeightBold } from '../../utils/styles'
13
13
  // ====== Component ================
14
14
  // =================================
15
15
 
16
- interface TextProps extends ReactNativeTextProps {
16
+ export interface HeadingProps extends ReactNativeTextProps {
17
17
  /**
18
18
  * Changes the styles and size of the text.
19
19
  * Semantically all React Native headings have the same 'hierarchical' level.
@@ -21,7 +21,7 @@ interface TextProps extends ReactNativeTextProps {
21
21
  variant?: 'h1' | 'h2' | 'h3' | 'h4'
22
22
  }
23
23
 
24
- export function Heading({ style, variant = 'h1', children, ...props }: TextProps) {
24
+ export function Heading({ style, variant = 'h1', children, ...props }: HeadingProps) {
25
25
  const styles = useStyles()
26
26
  const variantStyleMap = {
27
27
  h1: styles.heading1,