@planningcenter/chat-react-native 3.42.3-qa-staging.5 → 3.43.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 (123) hide show
  1. package/build/components/conversation/messages_disabled_banners.d.ts +3 -2
  2. package/build/components/conversation/messages_disabled_banners.d.ts.map +1 -1
  3. package/build/components/conversation/messages_disabled_banners.js +7 -2
  4. package/build/components/conversation/messages_disabled_banners.js.map +1 -1
  5. package/build/components/display/action_button.d.ts +1 -1
  6. package/build/components/display/action_button.d.ts.map +1 -1
  7. package/build/components/display/action_button.js +3 -13
  8. package/build/components/display/action_button.js.map +1 -1
  9. package/build/components/display/adult_requirement_notice.js +1 -1
  10. package/build/components/display/adult_requirement_notice.js.map +1 -1
  11. package/build/components/display/platform_modal_header_buttons.d.ts +7 -47
  12. package/build/components/display/platform_modal_header_buttons.d.ts.map +1 -1
  13. package/build/components/display/platform_modal_header_buttons.js +9 -119
  14. package/build/components/display/platform_modal_header_buttons.js.map +1 -1
  15. package/build/hooks/index.d.ts +1 -1
  16. package/build/hooks/index.d.ts.map +1 -1
  17. package/build/hooks/index.js +1 -1
  18. package/build/hooks/index.js.map +1 -1
  19. package/build/hooks/use_conversation.d.ts.map +1 -1
  20. package/build/hooks/use_conversation.js +0 -1
  21. package/build/hooks/use_conversation.js.map +1 -1
  22. package/build/hooks/use_conversation_jolt_events.d.ts.map +1 -1
  23. package/build/hooks/use_conversation_jolt_events.js +2 -10
  24. package/build/hooks/use_conversation_jolt_events.js.map +1 -1
  25. package/build/hooks/use_current_person.d.ts.map +1 -1
  26. package/build/hooks/use_current_person.js +0 -1
  27. package/build/hooks/use_current_person.js.map +1 -1
  28. package/build/hooks/use_direct_messages_enabled.d.ts +2 -0
  29. package/build/hooks/use_direct_messages_enabled.d.ts.map +1 -0
  30. package/build/hooks/use_direct_messages_enabled.js +16 -0
  31. package/build/hooks/use_direct_messages_enabled.js.map +1 -0
  32. package/build/hooks/use_features.d.ts +1 -0
  33. package/build/hooks/use_features.d.ts.map +1 -1
  34. package/build/hooks/use_features.js +1 -0
  35. package/build/hooks/use_features.js.map +1 -1
  36. package/build/navigation/index.d.ts +173 -69
  37. package/build/navigation/index.d.ts.map +1 -1
  38. package/build/navigation/index.js +72 -61
  39. package/build/navigation/index.js.map +1 -1
  40. package/build/screens/bug_report_screen.d.ts.map +1 -1
  41. package/build/screens/bug_report_screen.js +13 -10
  42. package/build/screens/bug_report_screen.js.map +1 -1
  43. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  44. package/build/screens/conversation_details_screen.js +12 -11
  45. package/build/screens/conversation_details_screen.js.map +1 -1
  46. package/build/screens/conversation_filters/components/conversation_filters.js +3 -3
  47. package/build/screens/conversation_filters/components/conversation_filters.js.map +1 -1
  48. package/build/screens/conversation_filters/components/rows.js +1 -1
  49. package/build/screens/conversation_filters/components/rows.js.map +1 -1
  50. package/build/screens/conversation_filters_screen.js +2 -2
  51. package/build/screens/conversation_filters_screen.js.map +1 -1
  52. package/build/screens/conversation_screen.d.ts.map +1 -1
  53. package/build/screens/conversation_screen.js +3 -10
  54. package/build/screens/conversation_screen.js.map +1 -1
  55. package/build/screens/conversation_select_recipients/components/groups_recipient_row.js +1 -1
  56. package/build/screens/conversation_select_recipients/components/groups_recipient_row.js.map +1 -1
  57. package/build/screens/conversation_select_recipients/components/restricted_group_row.js +1 -1
  58. package/build/screens/conversation_select_recipients/components/restricted_group_row.js.map +1 -1
  59. package/build/screens/conversation_select_type_screen.d.ts.map +1 -1
  60. package/build/screens/conversation_select_type_screen.js +7 -4
  61. package/build/screens/conversation_select_type_screen.js.map +1 -1
  62. package/build/screens/conversations/components/list_header_component.js +3 -3
  63. package/build/screens/conversations/components/list_header_component.js.map +1 -1
  64. package/build/screens/message_report_screen.d.ts.map +1 -1
  65. package/build/screens/message_report_screen.js +18 -14
  66. package/build/screens/message_report_screen.js.map +1 -1
  67. package/build/screens/notification_settings_screen.d.ts.map +1 -1
  68. package/build/screens/notification_settings_screen.js +14 -0
  69. package/build/screens/notification_settings_screen.js.map +1 -1
  70. package/build/screens/settings_screen.js +3 -3
  71. package/build/screens/settings_screen.js.map +1 -1
  72. package/build/types/jolt_events/conversation_events.d.ts +2 -2
  73. package/build/types/jolt_events/conversation_events.d.ts.map +1 -1
  74. package/build/types/jolt_events/conversation_events.js.map +1 -1
  75. package/build/types/resources/conversation.d.ts +2 -2
  76. package/build/types/resources/conversation.d.ts.map +1 -1
  77. package/build/types/resources/conversation.js.map +1 -1
  78. package/build/types/resources/person.d.ts +0 -1
  79. package/build/types/resources/person.d.ts.map +1 -1
  80. package/build/types/resources/person.js.map +1 -1
  81. package/build/utils/conversation_safety_constants.d.ts +1 -1
  82. package/build/utils/conversation_safety_constants.d.ts.map +1 -1
  83. package/build/utils/conversation_safety_constants.js +1 -1
  84. package/build/utils/conversation_safety_constants.js.map +1 -1
  85. package/build/utils/request/conversation.d.ts.map +1 -1
  86. package/build/utils/request/conversation.js +0 -1
  87. package/build/utils/request/conversation.js.map +1 -1
  88. package/package.json +4 -4
  89. package/src/components/conversation/messages_disabled_banners.tsx +11 -3
  90. package/src/components/display/action_button.tsx +48 -45
  91. package/src/components/display/adult_requirement_notice.tsx +1 -1
  92. package/src/components/display/platform_modal_header_buttons.tsx +13 -254
  93. package/src/hooks/index.ts +1 -1
  94. package/src/hooks/use_conversation.ts +0 -1
  95. package/src/hooks/use_conversation_jolt_events.ts +3 -16
  96. package/src/hooks/use_current_person.ts +0 -1
  97. package/src/hooks/use_direct_messages_enabled.ts +23 -0
  98. package/src/hooks/use_features.ts +1 -0
  99. package/src/navigation/index.tsx +166 -149
  100. package/src/screens/bug_report_screen.tsx +15 -16
  101. package/src/screens/conversation_details_screen.tsx +17 -11
  102. package/src/screens/conversation_filters/components/conversation_filters.tsx +3 -3
  103. package/src/screens/conversation_filters/components/rows.tsx +2 -2
  104. package/src/screens/conversation_filters_screen.tsx +2 -2
  105. package/src/screens/conversation_screen.tsx +3 -11
  106. package/src/screens/conversation_select_recipients/components/groups_recipient_row.tsx +1 -1
  107. package/src/screens/conversation_select_recipients/components/restricted_group_row.tsx +2 -2
  108. package/src/screens/conversation_select_type_screen.tsx +7 -4
  109. package/src/screens/conversations/components/list_header_component.tsx +3 -3
  110. package/src/screens/message_report_screen.tsx +21 -20
  111. package/src/screens/notification_settings_screen.tsx +21 -1
  112. package/src/screens/settings_screen.tsx +3 -3
  113. package/src/types/jolt_events/conversation_events.ts +2 -2
  114. package/src/types/resources/conversation.ts +3 -2
  115. package/src/types/resources/person.ts +0 -1
  116. package/src/utils/conversation_safety_constants.ts +1 -1
  117. package/src/utils/request/conversation.ts +0 -1
  118. package/build/hooks/use_direct_messages_eligible.d.ts +0 -2
  119. package/build/hooks/use_direct_messages_eligible.d.ts.map +0 -1
  120. package/build/hooks/use_direct_messages_eligible.js +0 -9
  121. package/build/hooks/use_direct_messages_eligible.js.map +0 -1
  122. package/src/__tests__/hooks/use_direct_messages_eligible.test.tsx +0 -77
  123. package/src/hooks/use_direct_messages_eligible.ts +0 -11
@@ -1,19 +1,9 @@
1
- import { HeaderBackButton, HeaderButton, PlatformPressable } from '@react-navigation/elements'
2
- import { useNavigation } from '@react-navigation/native'
3
- import type {
4
- NativeStackHeaderItemProps,
5
- NativeStackNavigationOptions,
6
- } from '@react-navigation/native-stack'
7
- import { useLayoutEffect } from 'react'
1
+ import { HeaderButton } from '@react-navigation/elements'
2
+ import type { NativeStackHeaderRightProps } from '@react-navigation/native-stack'
8
3
  import { ColorValue, Platform, StyleSheet } from 'react-native'
9
- import LinearGradient from 'react-native-linear-gradient'
10
- import { useFontScale, useTheme } from '../../hooks'
11
- import { MAX_FONT_SIZE_MULTIPLIER_LANDMARK } from '../../utils'
12
- import { isLiquidGlassEnabled } from '../../utils/liquid_glass'
13
- import { tokens } from '../../vendor/tapestry/tokens'
14
- import { Icon, type IconString } from './icon'
4
+ import { useTheme } from '../../hooks'
5
+ import { Icon } from './icon'
15
6
  import { TextButton } from './text_button'
16
- import { getColorKey, useGradientColorMap } from './utils/button_colors'
17
7
 
18
8
  interface HeaderTextButtonProps {
19
9
  onPress: () => void
@@ -73,250 +63,19 @@ export const HeaderDismissButton = ({
73
63
  })
74
64
  }
75
65
 
76
- const createTextHeaderRight =
77
- ({ onPress, title, disabled }: HeaderTextButtonProps) =>
78
- (props: NativeStackHeaderItemProps) => (
79
- <HeaderTextButton {...props} onPress={onPress} title={title} disabled={disabled} />
80
- )
81
-
82
- export type OptionsDecorator = (
83
- options: NativeStackNavigationOptions
84
- ) => NativeStackNavigationOptions
85
-
86
- export const composeOptions = (
87
- base: NativeStackNavigationOptions,
88
- ...decorators: OptionsDecorator[]
89
- ): NativeStackNavigationOptions => decorators.reduce((options, decorate) => decorate(options), base)
90
-
91
- interface TextButtonDecoratorProps {
92
- text: string
93
- onPress: () => void
94
- disabled?: boolean
95
- }
96
-
97
- // Native bar items pick up liquid glass on iOS 26; hosts on an older
98
- // react-navigation ignore the unstable option and keep using the
99
- // headerRight/headerLeft fallbacks, as does Android.
100
- export const withRightText =
101
- ({ text, onPress, disabled }: TextButtonDecoratorProps): OptionsDecorator =>
102
- options => ({
103
- ...options,
104
- headerRight: createTextHeaderRight({ onPress, title: text, disabled }),
105
- unstable_headerRightItems: () => [{ type: 'button' as const, label: text, onPress, disabled }],
106
- })
107
-
108
- const HEADER_ICON_SIZE = 17
109
- const HEADER_FILL_ICON_SIZE = 16
110
- const HEADER_FILL_PADDING = 14
111
- const HEADER_BAR_INSET = 16
112
- const HEADER_SLOT_GAP = 12
113
-
114
- const headerFillSize = (fontScale: number) => {
115
- const icon = HEADER_FILL_ICON_SIZE * fontScale
116
-
117
- return { icon, circle: icon + HEADER_FILL_PADDING * 2 }
118
- }
119
-
120
- // Liquid glass icons don't communicate
121
- export const useHeaderSlotReserve = (): number => {
122
- const fontScale = useFontScale({ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER_LANDMARK })
123
-
124
- return HEADER_BAR_INSET + headerFillSize(fontScale).circle + HEADER_SLOT_GAP
125
- }
126
-
127
- const HEADER_FALLBACK_HIT_SLOP = 14
128
-
129
- const iconStyles = StyleSheet.create({
130
- disabled: {
131
- opacity: 0.4,
132
- },
133
- fallbackButton: {
134
- alignItems: 'center',
135
- justifyContent: 'center',
136
- paddingHorizontal: 8,
137
- },
138
- fillCircle: {
139
- alignItems: 'center',
140
- justifyContent: 'center',
141
- },
142
- })
143
-
144
- export interface HeaderIconProps {
145
- icon: IconString
146
- accessibilityLabel: string
147
- onPress: () => void
148
- disabled?: boolean
149
- variant?: 'plain' | 'fill'
150
- }
151
-
152
- const HeaderIcon = ({
153
- icon,
154
- disabled,
155
- variant,
156
- }: Pick<HeaderIconProps, 'icon' | 'disabled' | 'variant'>) => {
157
- const { colors } = useTheme()
158
- const gradients = useGradientColorMap()
159
- const fontScale = useFontScale({ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER_LANDMARK })
160
-
161
- if (variant !== 'fill') return <Icon name={icon} size={HEADER_ICON_SIZE} />
162
-
163
- const { icon: iconSize, circle: circleSize } = headerFillSize(fontScale)
164
-
165
- return (
166
- <LinearGradient
167
- start={{ x: 0.1, y: 0.1 }}
168
- end={{ x: 0.9, y: 0.9 }}
169
- colors={gradients[getColorKey({ disabled: Boolean(disabled), appearance: 'interaction' })]}
170
- style={[
171
- iconStyles.fillCircle,
172
- { borderRadius: circleSize, height: circleSize, width: circleSize },
173
- ]}
174
- >
175
- <Icon
176
- name={icon}
177
- size={iconSize}
178
- color={disabled ? colors.iconColorDefaultDisabled : tokens.colorNeutral100White}
179
- />
180
- </LinearGradient>
181
- )
66
+ interface HeaderDoneButtonProps extends NativeStackHeaderRightProps {
67
+ navigation: { goBack: () => void; getState: () => any }
182
68
  }
183
69
 
184
- // Native bar items take SF Symbols, images, or any element, so both paths
185
- // render the same icon: iOS 26 puts this one in the glass circle, and the
186
- // element below covers Android and older iOS.
187
- const createHeaderIconItem = ({
188
- icon,
189
- accessibilityLabel,
190
- onPress,
191
- disabled,
192
- variant,
193
- }: HeaderIconProps) => ({
194
- type: 'custom' as const,
195
- // The gradient circle replaces the glass one rather than sitting on it.
196
- hidesSharedBackground: variant === 'fill',
197
- element: (
198
- <PlatformPressable
199
- onPress={onPress}
200
- disabled={disabled}
201
- hitSlop={12}
202
- accessibilityRole="button"
203
- accessibilityLabel={accessibilityLabel}
204
- style={disabled && variant !== 'fill' && iconStyles.disabled}
205
- >
206
- <HeaderIcon icon={icon} disabled={disabled} variant={variant} />
207
- </PlatformPressable>
208
- ),
209
- })
210
-
211
- const createHeaderIconElement =
212
- ({ icon, accessibilityLabel, onPress, disabled, variant }: HeaderIconProps) =>
213
- () => (
214
- <PlatformPressable
215
- onPress={onPress}
216
- disabled={disabled}
217
- hitSlop={HEADER_FALLBACK_HIT_SLOP}
218
- accessibilityRole="button"
219
- accessibilityLabel={accessibilityLabel}
220
- style={[iconStyles.fallbackButton, disabled && variant !== 'fill' && iconStyles.disabled]}
221
- >
222
- <HeaderIcon icon={icon} disabled={disabled} variant={variant} />
223
- </PlatformPressable>
224
- )
225
-
226
- export const withRightIcon =
227
- (props: HeaderIconProps): OptionsDecorator =>
228
- options => ({
229
- ...options,
230
- headerRight: createHeaderIconElement(props),
231
- unstable_headerRightItems: () => [createHeaderIconItem(props)],
232
- })
233
-
234
- export const withLeftIcon =
235
- (props: HeaderIconProps): OptionsDecorator =>
236
- options => ({
237
- ...options,
238
- headerLeft: createHeaderIconElement(props),
239
- unstable_headerLeftItems: () => [createHeaderIconItem(props)],
240
- })
241
-
242
- export const withRightClose = ({ onPress }: { onPress: () => void }) =>
243
- withRightIcon({ icon: 'general.x', accessibilityLabel: 'Close', onPress })
244
-
245
- export const withLeftClose = ({ onPress }: { onPress: () => void }) =>
246
- withLeftIcon({ icon: 'general.x', accessibilityLabel: 'Close', onPress })
247
-
248
- export const withRightDone = ({ onPress }: { onPress: () => void }) =>
249
- withRightIcon({ icon: 'general.check', accessibilityLabel: 'Done', onPress })
250
-
251
- // The default back icon reserves trailing space for a label that
252
- // displayMode="minimal" never shows; a custom image skips it so the glass
253
- // capsule hugs the glyph.
254
- const renderGlassBackImage = ({ tintColor }: { tintColor: string }) => (
255
- <Icon name="general.leftChevron" size={18} color={tintColor} />
256
- )
257
-
258
- // The iOS 26 glass capsule manages its own insets — negative margins clip
259
- // the button against the capsule edge.
260
- export const createMinimalHeaderBackButton =
261
- ({ onPress }: { onPress: () => void }) =>
262
- (props: NativeStackHeaderItemProps) => (
263
- <HeaderBackButton
264
- style={
265
- isLiquidGlassEnabled()
266
- ? undefined
267
- : {
268
- marginLeft: Platform.select({ ios: -8, default: -3 }),
269
- marginRight: Platform.select({ ios: 0, default: 30 }),
270
- }
271
- }
272
- backImage={isLiquidGlassEnabled() ? renderGlassBackImage : undefined}
273
- displayMode="minimal"
274
- onPress={onPress}
275
- {...props}
276
- />
277
- )
278
-
279
- // Under glass the native minimal back button suffices; pre-26 needs the
280
- // custom button and its layout nudges.
281
- export const withMinimalBack =
282
- ({ onPress }: { onPress: () => void }): OptionsDecorator =>
283
- options => ({
284
- ...options,
285
- ...(isLiquidGlassEnabled() ? {} : { headerLeft: createMinimalHeaderBackButton({ onPress }) }),
286
- })
287
-
288
- // An icon or a title, never both — passing both is a compile error rather
289
- // than a question of which one wins.
290
- type UseHeaderRightOptions = {
291
- onPress: () => void
292
- disabled?: boolean
293
- enabled?: boolean
294
- } & (
295
- | (HeaderIconProps & { title?: never })
296
- | { title: string; icon?: never; accessibilityLabel?: never; variant?: never }
297
- )
298
-
299
- // For screens whose header button depends on live state (form validity,
300
- // mutation callbacks). Static buttons belong in the navigator options.
301
- export const useHeaderRight = (options: UseHeaderRightOptions) => {
302
- const navigation = useNavigation()
303
- const { onPress, disabled, enabled = true } = options
304
- const icon = options.icon
305
- const label = options.icon !== undefined ? options.accessibilityLabel : options.title
306
- const variant = options.variant
307
-
308
- useLayoutEffect(() => {
309
- if (!enabled) {
310
- navigation.setOptions({ headerRight: () => null, unstable_headerRightItems: undefined })
311
- return
312
- }
70
+ export const HeaderDoneButton = ({ navigation, ...props }: HeaderDoneButtonProps) => {
71
+ const state = navigation.getState()
72
+ const isFirstScreen = state.index === 0
313
73
 
314
- const decorate = icon
315
- ? withRightIcon({ icon, accessibilityLabel: label, onPress, disabled, variant })
316
- : withRightText({ text: label, onPress, disabled })
74
+ if (!isFirstScreen) {
75
+ return null
76
+ }
317
77
 
318
- navigation.setOptions(decorate({}))
319
- }, [disabled, enabled, icon, label, navigation, onPress, variant])
78
+ return <HeaderTextButton {...props} onPress={navigation.goBack} title="Done" />
320
79
  }
321
80
 
322
81
  const useStyles = () => {
@@ -8,7 +8,7 @@ export * from './use_chat_permissions'
8
8
  export * from './use_create_android_ripple_color'
9
9
  export * from './use_current_person'
10
10
  export * from './use_deleting_ids'
11
- export * from './use_direct_messages_eligible'
11
+ export * from './use_direct_messages_enabled'
12
12
  export * from './use_font_scale'
13
13
  export * from './use_has_direct_messages'
14
14
  export * from './use_groups_groups'
@@ -32,7 +32,6 @@ export const getConversationRequestArgs = ({ conversation_id }: { conversation_i
32
32
  'direct_message',
33
33
  'disabled',
34
34
  'disabled_reason',
35
- 'disabled_message',
36
35
  'member_ability',
37
36
  'muted',
38
37
  'replies_disabled',
@@ -26,13 +26,8 @@ export function useConversationJoltEvents({ conversationId }: Props) {
26
26
 
27
27
  const handleUpdatedConversation = useCallback(
28
28
  (e: JoltConversationEvent) => {
29
- const {
30
- last_message_idempotent_key,
31
- last_message_author_id,
32
- disabled,
33
- disabled_reason,
34
- disabled_message,
35
- } = e.data.data
29
+ const { last_message_idempotent_key, last_message_author_id, disabled, disabled_reason } =
30
+ e.data.data
36
31
 
37
32
  if (last_message_idempotent_key && last_message_author_id === currentPersonId) {
38
33
  completeMessageCreationConversationTracking({
@@ -43,15 +38,7 @@ export function useConversationJoltEvents({ conversationId }: Props) {
43
38
 
44
39
  queryClient.setQueryData<ApiResource<ConversationResource>>(queryKey, prev => {
45
40
  if (!prev?.data) return prev
46
- return {
47
- ...prev,
48
- data: {
49
- ...prev.data,
50
- disabled,
51
- disabledReason: disabled_reason,
52
- disabledMessage: disabled_message,
53
- },
54
- }
41
+ return { ...prev, data: { ...prev.data, disabled, disabledReason: disabled_reason } }
55
42
  })
56
43
 
57
44
  queryClient.invalidateQueries({ queryKey })
@@ -17,7 +17,6 @@ export const currentPersonRequestArgs = {
17
17
  'pco_chat_enabled',
18
18
  'age_qualification_status',
19
19
  'direct_messages_disabled',
20
- 'direct_messages_eligible',
21
20
  'under_18',
22
21
  ],
23
22
  },
@@ -0,0 +1,23 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { ApiCollection } from '../types'
3
+ import type { FeatureResource } from '../types/resources/feature_resource'
4
+ import { getFeaturesRequestArgs, getFeaturesQueryKey } from '../utils/request/get_features'
5
+ import { useApiClient } from './use_api_client'
6
+ import { availableFeatures } from './use_features'
7
+
8
+ export function useDirectMessagesEnabled(): boolean {
9
+ const apiClient = useApiClient()
10
+ const { data, isFetched } = useQuery<ApiCollection<FeatureResource>>({
11
+ queryKey: getFeaturesQueryKey(),
12
+ queryFn: () => apiClient.chat.get<ApiCollection<FeatureResource>>(getFeaturesRequestArgs()),
13
+ staleTime: 1000 * 60 * 5,
14
+ })
15
+
16
+ if (!isFetched) return false
17
+
18
+ return (
19
+ data?.data.some(
20
+ feature => feature.name === availableFeatures.direct_messages_eap && feature.enabled
21
+ ) ?? false
22
+ )
23
+ }
@@ -43,6 +43,7 @@ export const availableFeatures = {
43
43
  custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',
44
44
  conversation_safety_lock: 'ROLLOUT_conversation_safety_lock',
45
45
  video_moderation: 'ROLLOUT_MOBILE_video_moderation',
46
+ direct_messages_eap: 'ROLLOUT_direct_messages_eap',
46
47
  dm_search_clear_fix: 'ROLLOUT_MOBILE_dm_search_clear_fix',
47
48
  } as const satisfies Record<string, `ROLLOUT_${string}`>
48
49