@planningcenter/chat-react-native 3.16.0-rc.2 → 3.16.0-rc.4

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 (104) hide show
  1. package/build/components/conversation/attachments/giphy_attachment.d.ts.map +1 -1
  2. package/build/components/conversation/attachments/giphy_attachment.js +1 -11
  3. package/build/components/conversation/attachments/giphy_attachment.js.map +1 -1
  4. package/build/components/conversation/message.d.ts +2 -1
  5. package/build/components/conversation/message.d.ts.map +1 -1
  6. package/build/components/conversation/message.js +23 -23
  7. package/build/components/conversation/message.js.map +1 -1
  8. package/build/components/conversation/message_form.d.ts +2 -1
  9. package/build/components/conversation/message_form.d.ts.map +1 -1
  10. package/build/components/conversation/message_form.js +38 -16
  11. package/build/components/conversation/message_form.js.map +1 -1
  12. package/build/components/conversation/reply_connectors.js +1 -1
  13. package/build/components/conversation/reply_connectors.js.map +1 -1
  14. package/build/components/conversation/shadow_message.d.ts +8 -0
  15. package/build/components/conversation/shadow_message.d.ts.map +1 -0
  16. package/build/components/conversation/shadow_message.js +161 -0
  17. package/build/components/conversation/shadow_message.js.map +1 -0
  18. package/build/components/display/icon.d.ts +3 -1
  19. package/build/components/display/icon.d.ts.map +1 -1
  20. package/build/components/display/icon.js +2 -0
  21. package/build/components/display/icon.js.map +1 -1
  22. package/build/components/primitive/avatar_primitive.d.ts +1 -0
  23. package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
  24. package/build/components/primitive/avatar_primitive.js +4 -0
  25. package/build/components/primitive/avatar_primitive.js.map +1 -1
  26. package/build/hooks/index.d.ts +1 -0
  27. package/build/hooks/index.d.ts.map +1 -1
  28. package/build/hooks/index.js +1 -0
  29. package/build/hooks/index.js.map +1 -1
  30. package/build/hooks/use_animated_message_background_color.d.ts +8 -0
  31. package/build/hooks/use_animated_message_background_color.d.ts.map +1 -0
  32. package/build/hooks/use_animated_message_background_color.js +29 -0
  33. package/build/hooks/use_animated_message_background_color.js.map +1 -0
  34. package/build/hooks/use_conversation_messages.d.ts +6 -3
  35. package/build/hooks/use_conversation_messages.d.ts.map +1 -1
  36. package/build/hooks/use_conversation_messages.js +32 -25
  37. package/build/hooks/use_conversation_messages.js.map +1 -1
  38. package/build/hooks/use_message_create_or_update.d.ts +2 -1
  39. package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
  40. package/build/hooks/use_message_create_or_update.js +6 -2
  41. package/build/hooks/use_message_create_or_update.js.map +1 -1
  42. package/build/hooks/use_suspense_api.d.ts +3 -1
  43. package/build/hooks/use_suspense_api.d.ts.map +1 -1
  44. package/build/hooks/use_suspense_api.js +1 -0
  45. package/build/hooks/use_suspense_api.js.map +1 -1
  46. package/build/navigation/index.d.ts +6 -0
  47. package/build/navigation/index.d.ts.map +1 -1
  48. package/build/navigation/index.js +6 -0
  49. package/build/navigation/index.js.map +1 -1
  50. package/build/screens/conversation_screen.d.ts +2 -1
  51. package/build/screens/conversation_screen.d.ts.map +1 -1
  52. package/build/screens/conversation_screen.js +7 -5
  53. package/build/screens/conversation_screen.js.map +1 -1
  54. package/build/screens/message_actions_screen.d.ts +1 -0
  55. package/build/screens/message_actions_screen.d.ts.map +1 -1
  56. package/build/screens/message_actions_screen.js +16 -3
  57. package/build/screens/message_actions_screen.js.map +1 -1
  58. package/build/types/jolt_events/message_events.d.ts +2 -0
  59. package/build/types/jolt_events/message_events.d.ts.map +1 -1
  60. package/build/types/jolt_events/message_events.js.map +1 -1
  61. package/build/types/resources/message.d.ts +2 -0
  62. package/build/types/resources/message.d.ts.map +1 -1
  63. package/build/types/resources/message.js.map +1 -1
  64. package/build/utils/assert_keys_are_numbers.d.ts +2 -0
  65. package/build/utils/assert_keys_are_numbers.d.ts.map +1 -0
  66. package/build/utils/assert_keys_are_numbers.js +12 -0
  67. package/build/utils/assert_keys_are_numbers.js.map +1 -0
  68. package/build/utils/cache/optimistically_create_message.d.ts.map +1 -1
  69. package/build/utils/cache/optimistically_create_message.js +2 -0
  70. package/build/utils/cache/optimistically_create_message.js.map +1 -1
  71. package/build/utils/index.d.ts +2 -0
  72. package/build/utils/index.d.ts.map +1 -1
  73. package/build/utils/index.js +2 -0
  74. package/build/utils/index.js.map +1 -1
  75. package/build/utils/jolt/transform_message_event_data_to_message_resource.d.ts.map +1 -1
  76. package/build/utils/jolt/transform_message_event_data_to_message_resource.js +2 -0
  77. package/build/utils/jolt/transform_message_event_data_to_message_resource.js.map +1 -1
  78. package/build/utils/replies_local_feature_flag.d.ts +2 -0
  79. package/build/utils/replies_local_feature_flag.d.ts.map +1 -0
  80. package/build/utils/replies_local_feature_flag.js +3 -0
  81. package/build/utils/replies_local_feature_flag.js.map +1 -0
  82. package/package.json +2 -2
  83. package/src/components/conversation/attachments/giphy_attachment.tsx +1 -14
  84. package/src/components/conversation/message.tsx +40 -36
  85. package/src/components/conversation/message_form.tsx +85 -15
  86. package/src/components/conversation/reply_connectors.tsx +1 -1
  87. package/src/components/conversation/shadow_message.tsx +274 -0
  88. package/src/components/display/icon.tsx +3 -0
  89. package/src/components/primitive/avatar_primitive.tsx +4 -0
  90. package/src/hooks/index.ts +1 -0
  91. package/src/hooks/use_animated_message_background_color.ts +44 -0
  92. package/src/hooks/use_conversation_messages.ts +45 -25
  93. package/src/hooks/use_message_create_or_update.ts +7 -2
  94. package/src/hooks/use_suspense_api.ts +3 -0
  95. package/src/navigation/index.tsx +6 -0
  96. package/src/screens/conversation_screen.tsx +9 -4
  97. package/src/screens/message_actions_screen.tsx +27 -1
  98. package/src/types/jolt_events/message_events.ts +2 -0
  99. package/src/types/resources/message.ts +2 -0
  100. package/src/utils/assert_keys_are_numbers.ts +13 -0
  101. package/src/utils/cache/optimistically_create_message.ts +2 -0
  102. package/src/utils/index.ts +2 -0
  103. package/src/utils/jolt/transform_message_event_data_to_message_resource.ts +2 -0
  104. package/src/utils/replies_local_feature_flag.ts +2 -0
@@ -17,6 +17,8 @@ export function transformMessageEventDataToMessageResource({ data, currentPerson
17
17
  avatar: data.author_avatar,
18
18
  },
19
19
  reactionCounts: [],
20
+ replyCount: data.reply_count || 0,
21
+ replyRootId: data.reply_root_id || null,
20
22
  };
21
23
  }
22
24
  //# sourceMappingURL=transform_message_event_data_to_message_resource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transform_message_event_data_to_message_resource.js","sourceRoot":"","sources":["../../../src/utils/jolt/transform_message_event_data_to_message_resource.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAExD,MAAM,UAAU,0CAA0C,CAAC,EACzD,IAAI,EACJ,eAAe,GAIhB;IACC,OAAO;QACL,IAAI,EAAE,SAAS;QACf,EAAE,EAAE,IAAI,CAAC,QAAQ;QACjB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,UAAU;QAC1B,SAAS,EAAE,IAAI,CAAC,UAAU;QAC1B,YAAY,EAAE,IAAI,CAAC,cAAc;QACjC,IAAI,EAAE,IAAI,CAAC,SAAS,KAAK,eAAe;QACxC,WAAW,EAAE,iBAAiB,CAAmC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE;QACxF,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,EAAE,EAAE,IAAI,CAAC,SAAS;YAClB,IAAI,EAAE,IAAI,CAAC,WAAW;YACtB,MAAM,EAAE,IAAI,CAAC,aAAa;SAC3B;QACD,cAAc,EAAE,EAAE;KACnB,CAAA;AACH,CAAC","sourcesContent":["import { MessageResource } from '../../types'\nimport { MessageCreatedEvent } from '../../types/jolt_events/message_events'\nimport { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport { deepCamelCaseKeys } from '../deepCamelCaseKeys'\n\nexport function transformMessageEventDataToMessageResource({\n data,\n currentPersonId,\n}: {\n data: MessageCreatedEvent['data']['data']\n currentPersonId: number\n}): MessageResource {\n return {\n type: 'Message',\n id: data.sort_key,\n text: data.text,\n html: data.html,\n createdAt: data.created_at,\n deletedAt: data.deleted_at,\n textEditedAt: data.text_edited_at,\n mine: data.author_id === currentPersonId,\n attachments: deepCamelCaseKeys<DenormalizedAttachmentResource[]>(data.attachments) || [],\n author: {\n type: 'Person',\n id: data.author_id,\n name: data.author_name,\n avatar: data.author_avatar,\n },\n reactionCounts: [],\n }\n}\n"]}
1
+ {"version":3,"file":"transform_message_event_data_to_message_resource.js","sourceRoot":"","sources":["../../../src/utils/jolt/transform_message_event_data_to_message_resource.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAExD,MAAM,UAAU,0CAA0C,CAAC,EACzD,IAAI,EACJ,eAAe,GAIhB;IACC,OAAO;QACL,IAAI,EAAE,SAAS;QACf,EAAE,EAAE,IAAI,CAAC,QAAQ;QACjB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,UAAU;QAC1B,SAAS,EAAE,IAAI,CAAC,UAAU;QAC1B,YAAY,EAAE,IAAI,CAAC,cAAc;QACjC,IAAI,EAAE,IAAI,CAAC,SAAS,KAAK,eAAe;QACxC,WAAW,EAAE,iBAAiB,CAAmC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE;QACxF,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,EAAE,EAAE,IAAI,CAAC,SAAS;YAClB,IAAI,EAAE,IAAI,CAAC,WAAW;YACtB,MAAM,EAAE,IAAI,CAAC,aAAa;SAC3B;QACD,cAAc,EAAE,EAAE;QAClB,UAAU,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC;QACjC,WAAW,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI;KACxC,CAAA;AACH,CAAC","sourcesContent":["import { MessageResource } from '../../types'\nimport { MessageCreatedEvent } from '../../types/jolt_events/message_events'\nimport { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport { deepCamelCaseKeys } from '../deepCamelCaseKeys'\n\nexport function transformMessageEventDataToMessageResource({\n data,\n currentPersonId,\n}: {\n data: MessageCreatedEvent['data']['data']\n currentPersonId: number\n}): MessageResource {\n return {\n type: 'Message',\n id: data.sort_key,\n text: data.text,\n html: data.html,\n createdAt: data.created_at,\n deletedAt: data.deleted_at,\n textEditedAt: data.text_edited_at,\n mine: data.author_id === currentPersonId,\n attachments: deepCamelCaseKeys<DenormalizedAttachmentResource[]>(data.attachments) || [],\n author: {\n type: 'Person',\n id: data.author_id,\n name: data.author_name,\n avatar: data.author_avatar,\n },\n reactionCounts: [],\n replyCount: data.reply_count || 0,\n replyRootId: data.reply_root_id || null,\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export declare const REPLIES_FEATURE_ENABLED = false;
2
+ //# sourceMappingURL=replies_local_feature_flag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replies_local_feature_flag.d.ts","sourceRoot":"","sources":["../../src/utils/replies_local_feature_flag.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,uBAAuB,QAAQ,CAAA"}
@@ -0,0 +1,3 @@
1
+ // TODO: Replace this whole file with the flipper flag
2
+ export const REPLIES_FEATURE_ENABLED = false;
3
+ //# sourceMappingURL=replies_local_feature_flag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replies_local_feature_flag.js","sourceRoot":"","sources":["../../src/utils/replies_local_feature_flag.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAA","sourcesContent":["// TODO: Replace this whole file with the flipper flag\nexport const REPLIES_FEATURE_ENABLED = false\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "3.16.0-rc.2",
3
+ "version": "3.16.0-rc.4",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -55,5 +55,5 @@
55
55
  "react-native-url-polyfill": "^2.0.0",
56
56
  "typescript": "<5.6.0"
57
57
  },
58
- "gitHead": "7049e04c5d4b3cc2b6d30b5869c66dba504244b1"
58
+ "gitHead": "9de20581feb4dbad2ab84082321718816506a330"
59
59
  }
@@ -5,6 +5,7 @@ import { useTheme } from '../../../hooks'
5
5
  import { DenormalizedGiphyAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'
6
6
  import { PlatformPressable } from '@react-navigation/elements'
7
7
  import { Image } from '../../display'
8
+ import { assertKeysAreNumbers } from '../../../utils'
8
9
 
9
10
  export function GiphyAttachment({
10
11
  attachment,
@@ -46,20 +47,6 @@ export function GiphyAttachment({
46
47
  )
47
48
  }
48
49
 
49
- const assertKeysAreNumbers = (obj: Object): Record<any, number> => {
50
- return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, assertNumber(value)]))
51
- }
52
-
53
- const assertNumber = (value: string): number => {
54
- if (typeof value === 'number') return value
55
-
56
- if (typeof value === 'string' && !isNaN(Number(value))) {
57
- return Number(value)
58
- }
59
-
60
- return 0
61
- }
62
-
63
50
  const useStyles = ({ imageWidth, imageHeight }: { imageWidth: number; imageHeight: number }) => {
64
51
  const { colors } = useTheme()
65
52
  return StyleSheet.create({
@@ -1,9 +1,13 @@
1
1
  import { useNavigation } from '@react-navigation/native'
2
2
  import React, { useEffect } from 'react'
3
- import { Platform, Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'
3
+ import { Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'
4
4
  import { MessageReaction } from '../../components/conversation/message_reaction'
5
- import { Avatar, Icon, Spinner, Text, TextInlineButton } from '../../components/display'
6
- import { useInteractionGhostBackgroundColor, useTheme } from '../../hooks'
5
+ import { Avatar, Icon, Spinner, Text, TextButton, TextInlineButton } from '../../components/display'
6
+ import {
7
+ useAnimatedMessageBackgroundColor,
8
+ useInteractionGhostBackgroundColor,
9
+ useTheme,
10
+ } from '../../hooks'
7
11
  import { MessageResource } from '../../types'
8
12
  import { ReactionCountResource } from '../../types/resources/reaction'
9
13
  import { MessageAttachments } from './message_attachments'
@@ -15,18 +19,13 @@ import {
15
19
  MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
16
20
  platformFontWeightMedium,
17
21
  } from '../../utils/styles'
18
- import Animated, {
19
- useSharedValue,
20
- useAnimatedStyle,
21
- withTiming,
22
- interpolateColor,
23
- Easing,
24
- } from 'react-native-reanimated'
22
+ import Animated from 'react-native-reanimated'
25
23
  import { useLiveRelativeTime } from '../../hooks/use_live_relative_time'
26
24
  import { MessageReadReceipts } from './message_read_receipts'
27
25
  import { isNewMessage, useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
28
26
  import { Haptic } from '../../utils/native_adapters'
29
27
  import { TheirReplyConnector, MyReplyConnector } from './reply_connectors'
28
+ import { REPLIES_FEATURE_ENABLED } from '../../utils'
30
29
 
31
30
  /** Message
32
31
  * Component for display of a message within a conversation list
@@ -35,12 +34,14 @@ interface MessageProps extends MessageResource {
35
34
  canDeleteNonAuthoredMessages: boolean
36
35
  conversation_id: number
37
36
  latestReadMessageSortKey?: string
37
+ inReplyScreen?: boolean
38
38
  }
39
39
 
40
40
  export function Message({
41
41
  canDeleteNonAuthoredMessages,
42
42
  conversation_id,
43
43
  latestReadMessageSortKey,
44
+ inReplyScreen,
44
45
  ...message
45
46
  }: MessageProps) {
46
47
  const { text, reactionCounts, pending, error } = message
@@ -56,6 +57,8 @@ export function Message({
56
57
  const isPersisted = !isNewMessage(message)
57
58
  const [temporarilyHidePendingState, setTemporarilyHidePendingState] = React.useState(true)
58
59
  const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)
60
+ const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
61
+ useAnimatedMessageBackgroundColor()
59
62
 
60
63
  useEffect(() => {
61
64
  if (pending) {
@@ -81,30 +84,6 @@ export function Message({
81
84
  }
82
85
  }
83
86
 
84
- const bgFadeProgress = useSharedValue(0)
85
- const pressedColor = Platform.select({
86
- ios: colors.fillColorNeutral050Base,
87
- default: 'transparent',
88
- })
89
- const animatedBackgroundColor = useAnimatedStyle(() => {
90
- const backgroundColor = interpolateColor(
91
- bgFadeProgress.value,
92
- [0, 1],
93
- ['transparent', pressedColor]
94
- )
95
- return {
96
- backgroundColor,
97
- }
98
- })
99
-
100
- const handlePressIn = () => {
101
- bgFadeProgress.value = withTiming(1, { duration: 300, easing: Easing.inOut(Easing.ease) })
102
- }
103
-
104
- const handlePressOut = () => {
105
- bgFadeProgress.value = withTiming(0, { duration: 300, easing: Easing.inOut(Easing.ease) })
106
- }
107
-
108
87
  const handleMessageLongPress = () => {
109
88
  if (!isPersisted) return
110
89
 
@@ -113,6 +92,7 @@ export function Message({
113
92
  message_id: message.id,
114
93
  conversation_id,
115
94
  canDeleteNonAuthoredMessages,
95
+ inReplyScreen,
116
96
  })
117
97
  }
118
98
  const handleReactionLongPress = (reaction: ReactionCountResource) => {
@@ -136,6 +116,14 @@ export function Message({
136
116
  })
137
117
  }
138
118
 
119
+ const handleNavigateToReplyPress = () => {
120
+ navigation.navigate('ConversationReply', {
121
+ conversation_id,
122
+ reply_root_id: message.id,
123
+ // TODO: Add a way to pass the reply root author's name
124
+ })
125
+ }
126
+
139
127
  const metaProps = {
140
128
  authorName: message.author.name,
141
129
  createdAt: message.createdAt,
@@ -144,13 +132,15 @@ export function Message({
144
132
  const renderAuthor = (!message.mine && message.renderAuthor) || false
145
133
  const messageBottomMargin = message.lastInGroup ? 12 : hasReactions || showMessageMeta ? 8 : 4
146
134
  const messagePendingLabel = isPersisted ? 'Saving' : 'Sending'
135
+ const showReplyCountButton =
136
+ !inReplyScreen && message.replyRootId === message.id && REPLIES_FEATURE_ENABLED
147
137
 
148
138
  return (
149
139
  <Pressable
150
140
  onLongPress={handleMessageLongPress}
151
141
  onPress={() => setShowMessageMetaToggle(!showMessageMetaToggle)}
152
- onPressIn={handlePressIn}
153
- onPressOut={handlePressOut}
142
+ onPressIn={handleMessagePressIn}
143
+ onPressOut={handleMessagePressOut}
154
144
  android_ripple={{ color: colors.androidRippleNeutral }}
155
145
  accessibilityHint="Long press to view message actions like reacting and copying."
156
146
  >
@@ -191,6 +181,17 @@ export function Message({
191
181
  </View>
192
182
  )}
193
183
  </View>
184
+ {showReplyCountButton && (
185
+ <TextButton
186
+ variant="footnote"
187
+ onPress={handleNavigateToReplyPress}
188
+ style={styles.messageReplyCount}
189
+ accessibilityHint="Navigates to reply screen for this message"
190
+ accessibilityRole="link"
191
+ >
192
+ {message.replyCount} replies
193
+ </TextButton>
194
+ )}
194
195
  {hasReactions && (
195
196
  <View style={styles.messageReactions}>
196
197
  {reactionCounts.map(reaction => (
@@ -301,6 +302,9 @@ const useMessageStyles = ({ mine }: MessageResource) => {
301
302
  gap: 4,
302
303
  justifyContent: mine ? 'flex-end' : 'flex-start',
303
304
  },
305
+ messageReplyCount: {
306
+ alignSelf: mine ? 'flex-end' : 'flex-start',
307
+ },
304
308
  messageMeta: {
305
309
  flexDirection: 'row',
306
310
  alignItems: 'center',
@@ -15,11 +15,12 @@ import {
15
15
  ViewProps,
16
16
  Keyboard,
17
17
  } from 'react-native'
18
- import { Icon, IconButton, Text, TextButton } from '../../components'
18
+ import { Heading, Icon, IconButton, IconString, Text, TextButton } from '../../components'
19
19
  import {
20
20
  useCreateAndroidRippleColor,
21
21
  useTheme,
22
22
  useInteractionGhostBackgroundColor,
23
+ useScalableNumberOfLines,
23
24
  } from '../../hooks'
24
25
  import { ConversationResource, MessageResource } from '../../types'
25
26
 
@@ -27,7 +28,12 @@ import { ConversationScreenProps } from '../../screens/conversation_screen'
27
28
 
28
29
  import { ChatContext } from '../../contexts/chat_context'
29
30
  import { Haptic, ImagePicker, ImagePickerResult } from '../../utils/native_adapters'
30
- import { platformFontWeightMedium, platformPressedOpacityStyle } from '../../utils'
31
+ import {
32
+ MAX_FONT_SIZE_MULTIPLIER_LANDMARK,
33
+ REPLIES_FEATURE_ENABLED,
34
+ platformFontWeightMedium,
35
+ platformPressedOpacityStyle,
36
+ } from '../../utils'
31
37
  import { useAttachmentUploader } from '../../hooks/use_attachment_uploader'
32
38
  import { useMessageDraft } from '../../hooks/use_message_draft'
33
39
  import {
@@ -53,6 +59,7 @@ export const MessageForm = {
53
59
  interface MessagesFormRootProps extends ViewProps {
54
60
  conversation: ConversationResource
55
61
  currentlyEditingMessage?: MessageResource | null
62
+ replyRootId?: string
56
63
  }
57
64
 
58
65
  const MessageFormContext = React.createContext<{
@@ -66,6 +73,7 @@ const MessageFormContext = React.createContext<{
66
73
  attachmentUploader?: ReturnType<typeof useAttachmentUploader>
67
74
  currentlyEditingMessage?: MessageResource | null
68
75
  reset: () => void
76
+ replyRootId?: string
69
77
  }>({
70
78
  text: '',
71
79
  setText: (_text: string) => {},
@@ -76,6 +84,7 @@ const MessageFormContext = React.createContext<{
76
84
  setUsingGiphy: (_usingGiphy: boolean) => {},
77
85
  currentlyEditingMessage: null,
78
86
  reset: () => {},
87
+ replyRootId: undefined,
79
88
  })
80
89
 
81
90
  const GIPHY_MAX_LENGTH = 50
@@ -85,6 +94,7 @@ function MessageFormRoot({
85
94
  conversation,
86
95
  currentlyEditingMessage,
87
96
  children,
97
+ replyRootId,
88
98
  }: MessagesFormRootProps) {
89
99
  const { giphyApiKey } = useContext(ChatContext)
90
100
  const canGiphy = !!giphyApiKey && !currentlyEditingMessage
@@ -105,6 +115,7 @@ function MessageFormRoot({
105
115
  } = useMessageCreateOrUpdate({
106
116
  conversationId: conversation.id,
107
117
  message: currentlyEditingMessage || undefined,
118
+ replyRootId,
108
119
  })
109
120
  const attachmentUploader = useAttachmentUploader({
110
121
  conversationId: conversation.id,
@@ -236,10 +247,12 @@ function MessageFormRoot({
236
247
  attachmentUploader,
237
248
  currentlyEditingMessage,
238
249
  reset,
250
+ replyRootId,
239
251
  }}
240
252
  >
241
253
  <View style={styles.container}>
242
254
  <EditingIndicator />
255
+ <ReplyIndicator />
243
256
  <View style={styles.textInputContainer}>{children}</View>
244
257
  </View>
245
258
  </MessageFormContext.Provider>
@@ -529,25 +542,76 @@ function MessageFormCommands() {
529
542
 
530
543
  function EditingIndicator() {
531
544
  const { currentlyEditingMessage, reset } = React.useContext(MessageFormContext)
532
- const styles = useMessageFormStyles()
533
545
 
534
546
  if (!currentlyEditingMessage) {
535
547
  return null
536
548
  }
537
549
 
538
550
  return (
539
- <View style={styles.editingIndicator}>
540
- <View style={styles.editingIndicatorTitleContainer}>
541
- <Icon name="accounts.editor" size={16} />
542
- <Text variant="tertiary" style={styles.editingIndicatorTitle}>
543
- Editing message
544
- </Text>
551
+ <FormIndicatorRow
552
+ title="Editing message"
553
+ buttonText="Cancel"
554
+ accessibilityHint="Exit message editing mode without saving"
555
+ iconName="accounts.editor"
556
+ onPress={() => reset()}
557
+ />
558
+ )
559
+ }
560
+
561
+ function ReplyIndicator() {
562
+ const { replyRootId } = React.useContext(MessageFormContext)
563
+ const navigation = useNavigation()
564
+
565
+ if (!REPLIES_FEATURE_ENABLED || !replyRootId) return null
566
+
567
+ return (
568
+ <FormIndicatorRow
569
+ title="Reply" // TODO: Get root reply author
570
+ buttonText="Exit replies"
571
+ accessibilityHint="Return to the main conversation"
572
+ iconName="registrations.undo"
573
+ onPress={() => navigation.goBack()}
574
+ />
575
+ )
576
+ }
577
+
578
+ interface FormIndicatorRowProps {
579
+ title: string
580
+ buttonText: string
581
+ accessibilityHint: string
582
+ onPress: () => void
583
+ iconName: IconString
584
+ }
585
+
586
+ function FormIndicatorRow({
587
+ title,
588
+ buttonText,
589
+ accessibilityHint,
590
+ iconName,
591
+ onPress,
592
+ }: FormIndicatorRowProps) {
593
+ const styles = useMessageFormStyles()
594
+ const scalableNumberOfLines = useScalableNumberOfLines(1)
595
+
596
+ return (
597
+ <View style={styles.formIndicatorRow}>
598
+ <View style={styles.formIndicatorRowTitleContainer}>
599
+ <Icon name={iconName} size={16} style={styles.formIndicatorRowIcon} />
600
+ <Heading
601
+ variant="h4"
602
+ style={styles.formIndicatorRowTitle}
603
+ numberOfLines={scalableNumberOfLines}
604
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
605
+ >
606
+ {title}
607
+ </Heading>
545
608
  </View>
546
609
  <TextButton
547
- onPress={() => reset()}
548
- accessibilityHint="Exit message editing mode without saving"
610
+ onPress={onPress}
611
+ accessibilityHint={accessibilityHint}
612
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
549
613
  >
550
- Cancel
614
+ {buttonText}
551
615
  </TextButton>
552
616
  </View>
553
617
  )
@@ -681,20 +745,26 @@ const useMessageFormStyles = () => {
681
745
  paddingHorizontal: 16,
682
746
  paddingVertical: 4,
683
747
  },
684
- editingIndicator: {
748
+ formIndicatorRow: {
685
749
  flexDirection: 'row',
686
750
  alignItems: 'center',
687
751
  justifyContent: 'space-between',
688
752
  gap: 8,
689
753
  paddingBottom: 12,
690
754
  },
691
- editingIndicatorTitleContainer: {
755
+ formIndicatorRowTitleContainer: {
692
756
  flexDirection: 'row',
693
757
  alignItems: 'center',
694
758
  gap: 8,
759
+ flex: 1,
760
+ },
761
+ formIndicatorRowIcon: {
762
+ color: theme.colors.iconColorDefaultSecondary,
695
763
  },
696
- editingIndicatorTitle: {
764
+ formIndicatorRowTitle: {
697
765
  fontWeight: platformFontWeightMedium,
766
+ textTransform: 'none',
767
+ flexShrink: 1,
698
768
  },
699
769
  })
700
770
  }
@@ -6,7 +6,7 @@ import {
6
6
  CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
7
7
  MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
8
8
  } from '../../utils/styles'
9
- import { REPLIES_FEATURE_ENABLED } from '../../screens/conversation_screen'
9
+ import { REPLIES_FEATURE_ENABLED } from '../../utils'
10
10
 
11
11
  const MY_REPLY_CONNECTOR_WIDTH = 38
12
12
  const CONNECTOR_BORDER_WIDTH = 4