@planningcenter/chat-react-native 3.15.0-rc.9 → 3.16.0-rc.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 (63) hide show
  1. package/build/components/conversation/message.d.ts.map +1 -1
  2. package/build/components/conversation/message.js +11 -9
  3. package/build/components/conversation/message.js.map +1 -1
  4. package/build/components/conversation/reply_connectors.d.ts +9 -0
  5. package/build/components/conversation/reply_connectors.d.ts.map +1 -0
  6. package/build/components/conversation/reply_connectors.js +147 -0
  7. package/build/components/conversation/reply_connectors.js.map +1 -0
  8. package/build/components/display/avatar.d.ts +2 -1
  9. package/build/components/display/avatar.d.ts.map +1 -1
  10. package/build/components/display/avatar.js +2 -2
  11. package/build/components/display/avatar.js.map +1 -1
  12. package/build/components/display/avatar_group.d.ts +2 -1
  13. package/build/components/display/avatar_group.d.ts.map +1 -1
  14. package/build/components/display/avatar_group.js +2 -2
  15. package/build/components/display/avatar_group.js.map +1 -1
  16. package/build/components/primitive/avatar_primitive.d.ts +1 -0
  17. package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
  18. package/build/components/primitive/avatar_primitive.js +25 -19
  19. package/build/components/primitive/avatar_primitive.js.map +1 -1
  20. package/build/hooks/use_app_name.d.ts +3 -0
  21. package/build/hooks/use_app_name.d.ts.map +1 -0
  22. package/build/hooks/use_app_name.js +12 -0
  23. package/build/hooks/use_app_name.js.map +1 -0
  24. package/build/hooks/use_report_bug_action.d.ts +1 -1
  25. package/build/hooks/use_report_bug_action.d.ts.map +1 -1
  26. package/build/hooks/use_report_bug_action.js +1 -9
  27. package/build/hooks/use_report_bug_action.js.map +1 -1
  28. package/build/navigation/index.d.ts +20 -5
  29. package/build/navigation/index.d.ts.map +1 -1
  30. package/build/navigation/index.js +23 -15
  31. package/build/navigation/index.js.map +1 -1
  32. package/build/screens/bug_report_screen.d.ts.map +1 -1
  33. package/build/screens/bug_report_screen.js +62 -57
  34. package/build/screens/bug_report_screen.js.map +1 -1
  35. package/build/screens/conversation_screen.d.ts +1 -0
  36. package/build/screens/conversation_screen.d.ts.map +1 -1
  37. package/build/screens/conversation_screen.js +1 -0
  38. package/build/screens/conversation_screen.js.map +1 -1
  39. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  40. package/build/screens/conversations/conversations_screen.js +6 -6
  41. package/build/screens/conversations/conversations_screen.js.map +1 -1
  42. package/build/screens/get_help_screen.d.ts +5 -0
  43. package/build/screens/get_help_screen.d.ts.map +1 -0
  44. package/build/screens/get_help_screen.js +94 -0
  45. package/build/screens/get_help_screen.js.map +1 -0
  46. package/build/utils/styles.d.ts +1 -0
  47. package/build/utils/styles.d.ts.map +1 -1
  48. package/build/utils/styles.js +1 -0
  49. package/build/utils/styles.js.map +1 -1
  50. package/package.json +2 -2
  51. package/src/components/conversation/message.tsx +22 -8
  52. package/src/components/conversation/reply_connectors.tsx +192 -0
  53. package/src/components/display/avatar.tsx +8 -1
  54. package/src/components/display/avatar_group.tsx +8 -1
  55. package/src/components/primitive/avatar_primitive.tsx +28 -17
  56. package/src/hooks/use_app_name.ts +17 -0
  57. package/src/hooks/use_report_bug_action.ts +2 -10
  58. package/src/navigation/index.tsx +38 -25
  59. package/src/screens/bug_report_screen.tsx +79 -67
  60. package/src/screens/conversation_screen.tsx +2 -0
  61. package/src/screens/conversations/conversations_screen.tsx +7 -7
  62. package/src/screens/get_help_screen.tsx +131 -0
  63. package/src/utils/styles.ts +1 -0
@@ -12,6 +12,7 @@ import { MessageMarkdown } from './message_markdown'
12
12
  import { DenormalizedMessageAttachmentResource } from '../../types/resources/denormalized_attachment_resource'
13
13
  import {
14
14
  CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
15
+ MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
15
16
  platformFontWeightMedium,
16
17
  } from '../../utils/styles'
17
18
  import Animated, {
@@ -25,6 +26,7 @@ import { useLiveRelativeTime } from '../../hooks/use_live_relative_time'
25
26
  import { MessageReadReceipts } from './message_read_receipts'
26
27
  import { isNewMessage, useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
27
28
  import { Haptic } from '../../utils/native_adapters'
29
+ import { TheirReplyConnector, MyReplyConnector } from './reply_connectors'
28
30
 
29
31
  /** Message
30
32
  * Component for display of a message within a conversation list
@@ -53,6 +55,7 @@ export function Message({
53
55
  const wasEdited = Boolean(message.textEditedAt)
54
56
  const isPersisted = !isNewMessage(message)
55
57
  const [temporarilyHidePendingState, setTemporarilyHidePendingState] = React.useState(true)
58
+ const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)
56
59
 
57
60
  useEffect(() => {
58
61
  if (pending) {
@@ -153,17 +156,27 @@ export function Message({
153
156
  >
154
157
  <Animated.View style={[styles.message, animatedBackgroundColor]}>
155
158
  {!message.mine && (
156
- <View style={styles.messageAuthor}>
159
+ <View>
157
160
  {renderAuthor ? (
158
- <Avatar size={'md'} sourceUri={message.author.avatar} />
161
+ <Avatar
162
+ size={'md'}
163
+ sourceUri={message.author.avatar}
164
+ style={styles.avatar}
165
+ maxFontSizeMultiplier={1}
166
+ minFontSizeMultiplier={1}
167
+ />
159
168
  ) : (
160
169
  <View style={styles.avatarPlaceholder} />
161
170
  )}
171
+ <TheirReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
162
172
  </View>
163
173
  )}
164
174
  <View style={[styles.messageContent, { marginBottom: messageBottomMargin }]}>
165
175
  {renderAuthor && <Text variant="tertiary">{message.author.name}</Text>}
166
- <View style={styles.messageBubble}>
176
+ <View
177
+ style={styles.messageBubble}
178
+ onLayout={e => setMessageBubbleHeight(e.nativeEvent.layout.height)}
179
+ >
167
180
  <ErrorBoundary>
168
181
  <MessageAttachments
169
182
  attachments={message.attachments}
@@ -241,6 +254,9 @@ export function Message({
241
254
  </View>
242
255
  )}
243
256
  </View>
257
+ {message.mine && (
258
+ <MyReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
259
+ )}
244
260
  </Animated.View>
245
261
  </Pressable>
246
262
  )
@@ -264,13 +280,11 @@ const useMessageStyles = ({ mine }: MessageResource) => {
264
280
  flex: 1,
265
281
  gap: 4,
266
282
  },
267
- messageAuthor: {
268
- flexDirection: 'row',
269
- gap: 8,
283
+ avatar: {
284
+ marginBottom: 8,
270
285
  },
271
286
  avatarPlaceholder: {
272
- width: 32, // Same width as avatar
273
- height: 32, // Same height as avatar
287
+ width: MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH, // Same width as avatar
274
288
  },
275
289
  messageBubble: {
276
290
  backgroundColor: messageBubbleBackgroundColor,
@@ -0,0 +1,192 @@
1
+ import { StyleSheet, View, ViewStyle } from 'react-native'
2
+ import Svg, { Path } from 'react-native-svg'
3
+ import { useTheme } from '../../hooks'
4
+ import { MessageResource } from '../../types'
5
+ import {
6
+ CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
7
+ MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
8
+ } from '../../utils/styles'
9
+ import { REPLIES_FEATURE_ENABLED } from '../../screens/conversation_screen'
10
+
11
+ const MY_REPLY_CONNECTOR_WIDTH = 38
12
+ const CONNECTOR_BORDER_WIDTH = 4
13
+
14
+ interface ReplyConnectorProps {
15
+ message: MessageResource
16
+ messageBubbleHeight: number
17
+ }
18
+
19
+ export function TheirReplyConnector({ message, messageBubbleHeight }: ReplyConnectorProps) {
20
+ const styles = useStyles()
21
+
22
+ if (!REPLIES_FEATURE_ENABLED) return null
23
+
24
+ // TODO: Need to remove, just adding this to remove the lint warning about message being unused
25
+ console.log('message', message)
26
+
27
+ const connectorMap: Record<string, React.ReactNode | null> = {
28
+ shortTailCurve: <ShortTailCurveConnector height={messageBubbleHeight / 2} />,
29
+ verticalLine: <VerticalLineConnector />,
30
+ default: null,
31
+ }
32
+
33
+ // TODO: Currently using fake message attributes. Will need to refactor once we have the message reply logic in place.
34
+ const getConnectorKey = () => {
35
+ // TODO: if (message.previousMessageIsTheirReplyMessage && message.nextMessageIsSomeoneElseReplyMessage) return 'shortTailCurve'
36
+ // TODO: if (message.isRootMessage || (message.isTheirReplyMessage && message.nextMessageIsTheirReplyMessage) || (message.isTheirReplyMessage && message.nextMessageIsSomeoneElseReplyMessage && !message.nextMessageIsMyMessage)) return 'verticalLine'
37
+ return 'default'
38
+ }
39
+
40
+ const ConnectorComponent = connectorMap[getConnectorKey()]
41
+
42
+ if (!ConnectorComponent) return null
43
+
44
+ return <View style={styles.theirReplyConnectorContainer}>{ConnectorComponent}</View>
45
+ }
46
+
47
+ // TODO: Refactor how we choose the correct connector once we have the logic in place
48
+ export function MyReplyConnector({ message, messageBubbleHeight }: ReplyConnectorProps) {
49
+ const styles = useStyles()
50
+
51
+ if (!REPLIES_FEATURE_ENABLED) return null
52
+
53
+ // TODO: Need to remove, just adding this to remove the lint warning about message being unused
54
+ console.log('message', message)
55
+
56
+ const connectorMap: Record<string, React.ReactNode | null> = {
57
+ longTailCurve: (
58
+ <LongTailCurveConnector
59
+ height={messageBubbleHeight / 2}
60
+ style={styles.myReplyConnectorPosition}
61
+ />
62
+ ),
63
+ longHeadCurve: (
64
+ <LongHeadCurveConnector
65
+ height={messageBubbleHeight / 2}
66
+ style={styles.myReplyConnectorPosition}
67
+ />
68
+ ),
69
+ verticalLine: <VerticalLineConnector style={styles.myReplyConnectorPosition} />,
70
+ swirl: <SwirlConnector style={styles.myReplyConnectorPosition} />,
71
+ default: null,
72
+ }
73
+
74
+ // TODO: Currently using fake message attributes. Will need to refactor once we have the message reply logic in place.
75
+ const getConnectorKey = () => {
76
+ // TODO: if (message.currentMessageIsReply && (message.nextMessageIsNotReplyMessage || message.nextMessageIsDifferentReplyRootId)) return 'longTailCurve'
77
+ // TODO: if (message.currentMessageIsReplyRoot && message.nextMessageIsMyReply) return 'longHeadCurve'
78
+ // TODO: if (message.currentMessageIsReply && message.nextMessageIsReply) return 'verticalLine'
79
+ // TODO: if (message.currentMessageIsReply && message.previousMessageSomeoneElseReply && (message.nextMessageIsSomeoneElseReply || message.nextMessageIsMyReply)) return 'swirl'
80
+ return 'default'
81
+ }
82
+
83
+ const ConnectorComponent = connectorMap[getConnectorKey()]
84
+
85
+ if (!ConnectorComponent) return null
86
+
87
+ return <View style={styles.myReplyConnectorContainer}>{ConnectorComponent}</View>
88
+ }
89
+
90
+ function VerticalLineConnector({ style }: { style?: ViewStyle }) {
91
+ const styles = useStyles()
92
+ return <View style={[styles.verticalLineConnector, style]} />
93
+ }
94
+
95
+ function LongHeadCurveConnector({ height, style }: { height: number; style?: ViewStyle }) {
96
+ const styles = useStyles()
97
+ return <View style={[styles.longHeadCurveConnector, { height, marginTop: height }, style]} />
98
+ }
99
+
100
+ function LongTailCurveConnector({ height, style }: { height: number; style?: ViewStyle }) {
101
+ const styles = useStyles()
102
+ return <View style={[styles.longTailCurveConnector, { height }, style]} />
103
+ }
104
+
105
+ function ShortTailCurveConnector({ height }: { height: number }) {
106
+ const styles = useStyles()
107
+ return <View style={[styles.shortTailCurveConnector, { height }]} />
108
+ }
109
+
110
+ function SwirlConnector({ style }: { style?: ViewStyle }) {
111
+ const styles = useStyles()
112
+ const { colors } = useTheme()
113
+ const borderColor = colors.borderColorDefaultBase
114
+
115
+ return (
116
+ <View style={[styles.swirlConnectorContainer, style]}>
117
+ <View style={styles.swirlConnectorVerticalLineHead} />
118
+ <Svg width={27} height={34} fill="none">
119
+ <Path
120
+ stroke={borderColor}
121
+ strokeWidth={CONNECTOR_BORDER_WIDTH}
122
+ d="M2.07 34c0-6.142 1.201-12.283 3.343-17m0 0c2.305-5.075 5.699-8.5 9.857-8.5 4.86 0 8.8 3.806 8.8 8.5s-3.94 8.5-8.8 8.5c-4.158 0-7.552-3.425-9.857-8.5zm0 0C3.271 12.283 2.07 6.142 2.07 0"
123
+ />
124
+ </Svg>
125
+ <View style={styles.swirlConnectorVerticalLineTail} />
126
+ </View>
127
+ )
128
+ }
129
+
130
+ const useStyles = () => {
131
+ const { colors } = useTheme()
132
+ const borderColor = colors.borderColorDefaultBase
133
+
134
+ return StyleSheet.create({
135
+ theirReplyConnectorContainer: {
136
+ width: '50%',
137
+ alignSelf: 'flex-end',
138
+ flex: 1,
139
+ },
140
+ myReplyConnectorContainer: {
141
+ width: MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
142
+ position: 'absolute',
143
+ left: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
144
+ top: 0,
145
+ bottom: 0,
146
+ },
147
+ myReplyConnectorPosition: {
148
+ left: '50%',
149
+ },
150
+ verticalLineConnector: {
151
+ flex: 1,
152
+ borderLeftWidth: CONNECTOR_BORDER_WIDTH,
153
+ borderLeftColor: borderColor,
154
+ },
155
+ longHeadCurveConnector: {
156
+ width: MY_REPLY_CONNECTOR_WIDTH,
157
+ borderColor: borderColor,
158
+ borderLeftWidth: CONNECTOR_BORDER_WIDTH,
159
+ borderTopWidth: CONNECTOR_BORDER_WIDTH,
160
+ borderTopLeftRadius: 16,
161
+ flex: 1,
162
+ },
163
+ longTailCurveConnector: {
164
+ width: MY_REPLY_CONNECTOR_WIDTH,
165
+ borderColor: borderColor,
166
+ borderLeftWidth: CONNECTOR_BORDER_WIDTH,
167
+ borderBottomWidth: CONNECTOR_BORDER_WIDTH,
168
+ borderBottomLeftRadius: 16,
169
+ },
170
+ shortTailCurveConnector: {
171
+ borderColor: borderColor,
172
+ borderLeftWidth: CONNECTOR_BORDER_WIDTH,
173
+ borderBottomWidth: CONNECTOR_BORDER_WIDTH,
174
+ borderBottomLeftRadius: 16,
175
+ },
176
+ swirlConnectorContainer: {
177
+ flex: 1,
178
+ },
179
+ swirlConnectorVerticalLineHead: {
180
+ backgroundColor: borderColor,
181
+ width: CONNECTOR_BORDER_WIDTH,
182
+ flex: 1,
183
+ marginBottom: -1, // Ensures there is no gap between the vertical line and the swirl connector
184
+ },
185
+ swirlConnectorVerticalLineTail: {
186
+ backgroundColor: borderColor,
187
+ width: CONNECTOR_BORDER_WIDTH,
188
+ flex: 1,
189
+ marginTop: -1, // Ensures there is no gap between the vertical line and the swirl connector
190
+ },
191
+ })
192
+ }
@@ -14,6 +14,7 @@ interface AvatarProps {
14
14
  fallbackIconName?: IconString
15
15
  style?: AvatarRootProps['style']
16
16
  maxFontSizeMultiplier?: AvatarRootProps['maxFontSizeMultiplier']
17
+ minFontSizeMultiplier?: AvatarRootProps['minFontSizeMultiplier']
17
18
  }
18
19
 
19
20
  export function Avatar({
@@ -24,11 +25,17 @@ export function Avatar({
24
25
  fallbackIconName = 'general.person',
25
26
  style,
26
27
  maxFontSizeMultiplier,
28
+ minFontSizeMultiplier,
27
29
  }: AvatarProps) {
28
30
  const shouldShowFallback = showFallback || !sourceUri
29
31
 
30
32
  return (
31
- <AvatarPrimitive.Root size={size} style={style} maxFontSizeMultiplier={maxFontSizeMultiplier}>
33
+ <AvatarPrimitive.Root
34
+ size={size}
35
+ style={style}
36
+ maxFontSizeMultiplier={maxFontSizeMultiplier}
37
+ minFontSizeMultiplier={minFontSizeMultiplier}
38
+ >
32
39
  <AvatarPrimitive.Mask>
33
40
  {shouldShowFallback ? (
34
41
  <AvatarPrimitive.ImageFallback name={fallbackIconName} />
@@ -12,6 +12,7 @@ interface AvatarGroupDisplayProps {
12
12
  size?: AvatarRootProps['size']
13
13
  style?: AvatarRootProps['style']
14
14
  maxFontSizeMultiplier?: AvatarRootProps['maxFontSizeMultiplier']
15
+ minFontSizeMultiplier?: AvatarRootProps['minFontSizeMultiplier']
15
16
  }
16
17
 
17
18
  export function AvatarGroup({
@@ -21,11 +22,17 @@ export function AvatarGroup({
21
22
  size = 'lg',
22
23
  style,
23
24
  maxFontSizeMultiplier,
25
+ minFontSizeMultiplier,
24
26
  }: AvatarGroupDisplayProps) {
25
27
  const shouldShowFallback = showFallback || !sourceUris || sourceUris.length === 0
26
28
 
27
29
  return (
28
- <AvatarPrimitive.Root size={size} style={style} maxFontSizeMultiplier={maxFontSizeMultiplier}>
30
+ <AvatarPrimitive.Root
31
+ size={size}
32
+ style={style}
33
+ maxFontSizeMultiplier={maxFontSizeMultiplier}
34
+ minFontSizeMultiplier={minFontSizeMultiplier}
35
+ >
29
36
  <AvatarPrimitive.Mask>
30
37
  {shouldShowFallback ? (
31
38
  <AvatarPrimitive.ImageFallback name={fallbackIconName} />
@@ -86,6 +86,7 @@ interface AvatarContextType {
86
86
  allImagesLoaded: boolean
87
87
  setAllImagesLoaded: React.Dispatch<React.SetStateAction<boolean>>
88
88
  maxFontSizeMultiplier: number
89
+ minFontSizeMultiplier?: number
89
90
  }
90
91
 
91
92
  const AvatarContext = createContext<AvatarContextType | null>(null)
@@ -107,6 +108,7 @@ interface AvatarRootProps {
107
108
  size?: AvatarSize
108
109
  style?: ViewProps['style']
109
110
  maxFontSizeMultiplier?: number
111
+ minFontSizeMultiplier?: number
110
112
  }
111
113
 
112
114
  function AvatarRoot({
@@ -114,13 +116,20 @@ function AvatarRoot({
114
116
  size = 'md',
115
117
  style,
116
118
  maxFontSizeMultiplier = MAX_FONT_SIZE_MULTIPLIER,
119
+ minFontSizeMultiplier = undefined,
117
120
  }: AvatarRootProps) {
118
121
  const [allImagesLoaded, setAllImagesLoaded] = useState(false)
119
- const styles = useStyles({ size, maxFontSizeMultiplier })
122
+ const styles = useStyles({ size, maxFontSizeMultiplier, minFontSizeMultiplier })
120
123
 
121
124
  return (
122
125
  <AvatarContext.Provider
123
- value={{ size, allImagesLoaded, setAllImagesLoaded, maxFontSizeMultiplier }}
126
+ value={{
127
+ size,
128
+ allImagesLoaded,
129
+ setAllImagesLoaded,
130
+ maxFontSizeMultiplier,
131
+ minFontSizeMultiplier,
132
+ }}
124
133
  >
125
134
  <View style={[styles.rootContainer, style]}>{children}</View>
126
135
  </AvatarContext.Provider>
@@ -136,8 +145,8 @@ AvatarRoot.displayName = 'Avatar.Root'
136
145
  type AvatarMaskProps = ViewProps
137
146
 
138
147
  function AvatarMask({ children, ...props }: AvatarMaskProps) {
139
- const { maxFontSizeMultiplier } = useAvatarContext()
140
- const styles = useStyles({ maxFontSizeMultiplier })
148
+ const { maxFontSizeMultiplier, minFontSizeMultiplier } = useAvatarContext()
149
+ const styles = useStyles({ maxFontSizeMultiplier, minFontSizeMultiplier })
141
150
 
142
151
  return (
143
152
  <View style={styles.mask} {...props}>
@@ -157,8 +166,8 @@ interface AvatarImageProps extends Omit<ImageProps, 'source' | 'alt'> {
157
166
  }
158
167
 
159
168
  function AvatarImage({ sourceUri, ...props }: AvatarImageProps) {
160
- const { size, maxFontSizeMultiplier } = useAvatarContext()
161
- const fontScale = useFontScale({ maxFontSizeMultiplier })
169
+ const { size, maxFontSizeMultiplier, minFontSizeMultiplier } = useAvatarContext()
170
+ const fontScale = useFontScale({ maxFontSizeMultiplier, minFontSizeMultiplier })
162
171
  const scaledAvatarSize = AVATAR_PX[size] * fontScale
163
172
 
164
173
  return <Image source={{ uri: sourceUri }} loaderSize={scaledAvatarSize} {...props} alt="" />
@@ -183,9 +192,9 @@ interface AvatarImageFallbackProps {
183
192
  }
184
193
 
185
194
  function AvatarImageFallback({ name = 'general.person' }: AvatarImageFallbackProps) {
186
- const { size, maxFontSizeMultiplier } = useAvatarContext()
187
- const styles = useStyles({ maxFontSizeMultiplier })
188
- const fontScale = useFontScale({ maxFontSizeMultiplier })
195
+ const { size, maxFontSizeMultiplier, minFontSizeMultiplier } = useAvatarContext()
196
+ const styles = useStyles({ maxFontSizeMultiplier, minFontSizeMultiplier })
197
+ const fontScale = useFontScale({ maxFontSizeMultiplier, minFontSizeMultiplier })
189
198
  const scaledIconSize = AVATAR_FALLBACK_ICON_PX[size] * fontScale
190
199
 
191
200
  return (
@@ -213,8 +222,8 @@ interface AvatarGroupProps {
213
222
  type AvatarIndex = 0 | 1 | 2 | 3
214
223
 
215
224
  function AvatarGroup({ sourceUris }: AvatarGroupProps) {
216
- const { setAllImagesLoaded, maxFontSizeMultiplier } = useAvatarContext()
217
- const styles = useStyles({ maxFontSizeMultiplier })
225
+ const { setAllImagesLoaded, maxFontSizeMultiplier, minFontSizeMultiplier } = useAvatarContext()
226
+ const styles = useStyles({ maxFontSizeMultiplier, minFontSizeMultiplier })
218
227
  const [loadingStatus, setLoadingStatus] = useState<Record<AvatarIndex, boolean>>({
219
228
  0: false,
220
229
  1: false,
@@ -323,9 +332,9 @@ AvatarGroup.displayName = 'Avatar.Group'
323
332
  // =================================
324
333
 
325
334
  function AvatarGroupLoader() {
326
- const { size, allImagesLoaded, maxFontSizeMultiplier } = useAvatarContext()
327
- const styles = useStyles({ size, maxFontSizeMultiplier })
328
- const fontScale = useFontScale({ maxFontSizeMultiplier })
335
+ const { size, allImagesLoaded, maxFontSizeMultiplier, minFontSizeMultiplier } = useAvatarContext()
336
+ const styles = useStyles({ size, maxFontSizeMultiplier, minFontSizeMultiplier })
337
+ const fontScale = useFontScale({ maxFontSizeMultiplier, minFontSizeMultiplier })
329
338
  const scaledSpinnerSize = AVATAR_PX[size] * fontScale
330
339
 
331
340
  if (allImagesLoaded) return null
@@ -348,8 +357,8 @@ interface AvatarPresenceProps extends ViewProps {
348
357
  }
349
358
 
350
359
  function AvatarPresence({ presence, ...props }: AvatarPresenceProps) {
351
- const { size, maxFontSizeMultiplier } = useAvatarContext()
352
- const styles = useStyles({ size, presence, maxFontSizeMultiplier })
360
+ const { size, maxFontSizeMultiplier, minFontSizeMultiplier } = useAvatarContext()
361
+ const styles = useStyles({ size, presence, maxFontSizeMultiplier, minFontSizeMultiplier })
353
362
 
354
363
  return <View style={styles.presence} {...props} />
355
364
  }
@@ -364,15 +373,17 @@ interface Styles {
364
373
  size?: AvatarSize
365
374
  presence?: AvatarPresenceType
366
375
  maxFontSizeMultiplier?: number
376
+ minFontSizeMultiplier?: number
367
377
  }
368
378
 
369
379
  const useStyles = ({
370
380
  size = 'md',
371
381
  presence = 'offline',
372
382
  maxFontSizeMultiplier = MAX_FONT_SIZE_MULTIPLIER,
383
+ minFontSizeMultiplier = undefined,
373
384
  }: Styles = {}) => {
374
385
  const { colors } = useTheme()
375
- const fontScale = useFontScale({ maxFontSizeMultiplier })
386
+ const fontScale = useFontScale({ maxFontSizeMultiplier, minFontSizeMultiplier })
376
387
  const PRESENCE_COLOR = {
377
388
  online: colors.fillColorInteractionOnlineDefault,
378
389
  offline: colors.iconColorDefaultDisabled,
@@ -0,0 +1,17 @@
1
+ import DeviceInfo from 'react-native-device-info'
2
+
3
+ export type AppName = 'chat' | 'churchcenter' | 'services'
4
+
5
+ export const useAppName = (): AppName => {
6
+ const applicationName = DeviceInfo.getApplicationName()
7
+
8
+ if (/churchcenter/i.test(applicationName)) {
9
+ return 'churchcenter'
10
+ }
11
+
12
+ if (/services/i.test(applicationName)) {
13
+ return 'services'
14
+ }
15
+
16
+ return 'chat'
17
+ }
@@ -1,12 +1,5 @@
1
1
  import { useMutation } from '@tanstack/react-query'
2
2
  import { useApiClient } from './use_api_client'
3
- import DeviceInfo from 'react-native-device-info'
4
- const brand = DeviceInfo.getBrand()
5
- const model = DeviceInfo.getModel()
6
- const systemName = DeviceInfo.getSystemName()
7
- const systemVersion = DeviceInfo.getSystemVersion()
8
- const readableVersion = DeviceInfo.getReadableVersion()
9
- const appName = DeviceInfo.getApplicationName()
10
3
 
11
4
  export const useReportBugAction = () => {
12
5
  const apiClient = useApiClient()
@@ -18,7 +11,7 @@ export const useReportBugAction = () => {
18
11
  attachmentIds,
19
12
  }: {
20
13
  description: string
21
- description_json: string
14
+ description_json: Record<string, any>
22
15
  attachmentIds: string[]
23
16
  }) => {
24
17
  return apiClient.chat.post({
@@ -28,8 +21,7 @@ export const useReportBugAction = () => {
28
21
  type: '',
29
22
  attributes: {
30
23
  description,
31
- description_json,
32
- device_info: `${appName}/${readableVersion} (${brand}, ${model}, ${systemName}, ${systemVersion})`,
24
+ description_json: JSON.stringify(description_json),
33
25
  attachment_ids: attachmentIds,
34
26
  },
35
27
  },
@@ -4,47 +4,50 @@ import {
4
4
  createNativeStackNavigator,
5
5
  NativeStackHeaderRightProps,
6
6
  } from '@react-navigation/native-stack'
7
+ import { CardStyleInterpolators } from '@react-navigation/stack'
7
8
  import React from 'react'
9
+ import { Platform } from 'react-native'
8
10
  import { Icon } from '../components'
11
+ import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
12
+ import {
13
+ AttachmentActionsScreen,
14
+ AttachmentActionsScreenOptions,
15
+ } from '../screens/attachment_actions/attachment_actions_screen'
16
+ import { BugReportScreen, BugReportScreenOptions } from '../screens/bug_report_screen'
17
+ import {
18
+ MessageReadReceiptsScreen,
19
+ MessageReadReceiptsScreenOptions,
20
+ } from '../screens/conversation/message_read_receipts_screen'
9
21
  import { ConversationDetailsScreen } from '../screens/conversation_details_screen'
22
+ import {
23
+ ConversationFilterReceipientsScreenOptions,
24
+ ConversationFilterRecipientsScreen,
25
+ } from '../screens/conversation_filter_recipients/conversation_filter_recipients_screen'
26
+ import { ConversationFiltersParams } from '../screens/conversation_filters/screen_props'
27
+ import {
28
+ ConversationFiltersScreen,
29
+ ConversationFiltersScreenOptions,
30
+ } from '../screens/conversation_filters_screen'
31
+ import { ConversationNewScreen } from '../screens/conversation_new/conversation_new_screen'
10
32
  import {
11
33
  ConversationRouteProps,
12
34
  ConversationScreen,
13
35
  ConversationScreenTitle,
14
36
  } from '../screens/conversation_screen'
15
- import { ConversationsScreen } from '../screens/conversations/conversations_screen'
16
- import { ConversationNewScreen } from '../screens/conversation_new/conversation_new_screen'
17
- import {
18
- ConversationFilterReceipientsScreenOptions,
19
- ConversationFilterRecipientsScreen,
20
- } from '../screens/conversation_filter_recipients/conversation_filter_recipients_screen'
37
+ import { ConversationSelectGroupRecipientsScreen } from '../screens/conversation_select_recipients/conversation_select_group_recipients_screen'
21
38
  import { ConversationSelectRecipientsScreen } from '../screens/conversation_select_recipients/conversation_select_recipients_screen'
39
+ import { ConversationSelectTeamsILeadRecipientsScreen } from '../screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen'
40
+ import { ConversationsScreen } from '../screens/conversations/conversations_screen'
41
+ import { GetHelpScreen } from '../screens/get_help_screen'
22
42
  import {
23
43
  MessageActionsScreen,
24
44
  MessageActionsScreenOptions,
25
45
  } from '../screens/message_actions_screen'
26
- import { SendGiphyScreen, SendGiphyScreenOptions } from '../screens/send_giphy_screen'
27
46
  import { NotFound } from '../screens/not_found'
28
47
  import { ReactionsScreen, ReactionsScreenOptions } from '../screens/reactions_screen'
29
- import { ScreenLayout } from './screenLayout'
30
- import {
31
- ConversationFiltersScreen,
32
- ConversationFiltersScreenOptions,
33
- } from '../screens/conversation_filters_screen'
34
- import { ConversationFiltersParams } from '../screens/conversation_filters/screen_props'
35
- import { ConversationSelectGroupRecipientsScreen } from '../screens/conversation_select_recipients/conversation_select_group_recipients_screen'
36
- import { ConversationSelectTeamsILeadRecipientsScreen } from '../screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen'
37
- import { AttachmentActionsScreenOptions } from '../screens/attachment_actions/attachment_actions_screen'
38
- import { AttachmentActionsScreen } from '../screens/attachment_actions/attachment_actions_screen'
39
- import { BugReportScreen, BugReportScreenOptions } from '../screens/bug_report_screen'
40
- import {
41
- MessageReadReceiptsScreen,
42
- MessageReadReceiptsScreenOptions,
43
- } from '../screens/conversation/message_read_receipts_screen'
44
- import { Platform } from 'react-native'
45
- import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
48
+ import { SendGiphyScreen, SendGiphyScreenOptions } from '../screens/send_giphy_screen'
46
49
  import { TeamConversationScreen } from '../screens/team_conversation_screen'
47
- import { CardStyleInterpolators } from '@react-navigation/stack'
50
+ import { ScreenLayout } from './screenLayout'
48
51
 
49
52
  const HEADER_BACK_BUTTON_LAYOUT_RESET_STYLES = {
50
53
  marginLeft: Platform.select({ ios: -8, default: -3 }),
@@ -231,6 +234,16 @@ export const ChatStack = createNativeStackNavigator({
231
234
  screen: BugReportScreen,
232
235
  options: BugReportScreenOptions,
233
236
  },
237
+ GetHelp: {
238
+ screen: GetHelpScreen,
239
+ options: ({ navigation }) => ({
240
+ headerTitle: 'Get help',
241
+ presentation: 'modal',
242
+ headerLeft: props => (
243
+ <HeaderTextButton {...props} onPress={navigation.goBack} title="Close" />
244
+ ),
245
+ }),
246
+ },
234
247
  NotFound: {
235
248
  screen: NotFound,
236
249
  options: {