@planningcenter/chat-react-native 3.24.0-rc.0 → 3.24.0-rc.10

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 (142) hide show
  1. package/build/components/conversations/conversation_actions.d.ts.map +1 -1
  2. package/build/components/conversations/conversation_actions.js +5 -1
  3. package/build/components/conversations/conversation_actions.js.map +1 -1
  4. package/build/components/conversations/conversations.d.ts.map +1 -1
  5. package/build/components/conversations/conversations.js +5 -1
  6. package/build/components/conversations/conversations.js.map +1 -1
  7. package/build/components/display/platform_modal_header_buttons.d.ts +8 -0
  8. package/build/components/display/platform_modal_header_buttons.d.ts.map +1 -1
  9. package/build/components/display/platform_modal_header_buttons.js +8 -0
  10. package/build/components/display/platform_modal_header_buttons.js.map +1 -1
  11. package/build/components/primitive/form_sheet.d.ts +1 -1
  12. package/build/components/primitive/form_sheet.d.ts.map +1 -1
  13. package/build/components/primitive/form_sheet.js +1 -1
  14. package/build/components/primitive/form_sheet.js.map +1 -1
  15. package/build/hooks/index.d.ts +1 -0
  16. package/build/hooks/index.d.ts.map +1 -1
  17. package/build/hooks/index.js +1 -0
  18. package/build/hooks/index.js.map +1 -1
  19. package/build/hooks/use_features.d.ts +1 -0
  20. package/build/hooks/use_features.d.ts.map +1 -1
  21. package/build/hooks/use_features.js +1 -0
  22. package/build/hooks/use_features.js.map +1 -1
  23. package/build/hooks/use_new_conversation_entry.d.ts +3 -0
  24. package/build/hooks/use_new_conversation_entry.d.ts.map +1 -0
  25. package/build/hooks/use_new_conversation_entry.js +20 -0
  26. package/build/hooks/use_new_conversation_entry.js.map +1 -0
  27. package/build/hooks/use_report_message.d.ts +6 -0
  28. package/build/hooks/use_report_message.d.ts.map +1 -0
  29. package/build/hooks/use_report_message.js +28 -0
  30. package/build/hooks/use_report_message.js.map +1 -0
  31. package/build/hooks/use_teams.d.ts.map +1 -1
  32. package/build/hooks/use_teams.js +1 -0
  33. package/build/hooks/use_teams.js.map +1 -1
  34. package/build/navigation/index.d.ts +52 -10
  35. package/build/navigation/index.d.ts.map +1 -1
  36. package/build/navigation/index.js +41 -7
  37. package/build/navigation/index.js.map +1 -1
  38. package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.js +5 -3
  39. package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.js.map +1 -1
  40. package/build/screens/conversation_select_recipients/conversation_new_entry_screen.d.ts +4 -0
  41. package/build/screens/conversation_select_recipients/conversation_new_entry_screen.d.ts.map +1 -0
  42. package/build/screens/conversation_select_recipients/conversation_new_entry_screen.js +67 -0
  43. package/build/screens/conversation_select_recipients/conversation_new_entry_screen.js.map +1 -0
  44. package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.d.ts.map +1 -1
  45. package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js +17 -2
  46. package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js.map +1 -1
  47. package/build/screens/conversation_select_type_screen.d.ts +11 -0
  48. package/build/screens/conversation_select_type_screen.d.ts.map +1 -0
  49. package/build/screens/conversation_select_type_screen.js +38 -0
  50. package/build/screens/conversation_select_type_screen.js.map +1 -0
  51. package/build/screens/conversations/components/list_header_component.d.ts.map +1 -1
  52. package/build/screens/conversations/components/list_header_component.js +18 -7
  53. package/build/screens/conversations/components/list_header_component.js.map +1 -1
  54. package/build/screens/group_notification_settings_screen.d.ts +8 -0
  55. package/build/screens/group_notification_settings_screen.d.ts.map +1 -0
  56. package/build/screens/group_notification_settings_screen.js +79 -0
  57. package/build/screens/group_notification_settings_screen.js.map +1 -0
  58. package/build/screens/index.d.ts +4 -1
  59. package/build/screens/index.d.ts.map +1 -1
  60. package/build/screens/index.js +4 -1
  61. package/build/screens/index.js.map +1 -1
  62. package/build/screens/message_actions_screen.js +14 -2
  63. package/build/screens/message_actions_screen.js.map +1 -1
  64. package/build/screens/message_report/components/message_preview.d.ts +10 -0
  65. package/build/screens/message_report/components/message_preview.d.ts.map +1 -0
  66. package/build/screens/message_report/components/message_preview.js +74 -0
  67. package/build/screens/message_report/components/message_preview.js.map +1 -0
  68. package/build/screens/message_report/components/report_reason_list.d.ts +9 -0
  69. package/build/screens/message_report/components/report_reason_list.d.ts.map +1 -0
  70. package/build/screens/message_report/components/report_reason_list.js +67 -0
  71. package/build/screens/message_report/components/report_reason_list.js.map +1 -0
  72. package/build/screens/message_report_screen.d.ts +10 -0
  73. package/build/screens/message_report_screen.d.ts.map +1 -0
  74. package/build/screens/message_report_screen.js +214 -0
  75. package/build/screens/message_report_screen.js.map +1 -0
  76. package/build/screens/notification_settings/hooks/groups.d.ts +94 -0
  77. package/build/screens/notification_settings/hooks/groups.d.ts.map +1 -0
  78. package/build/screens/notification_settings/hooks/groups.js +92 -0
  79. package/build/screens/notification_settings/hooks/groups.js.map +1 -0
  80. package/build/screens/notification_settings_screen.d.ts +5 -0
  81. package/build/screens/notification_settings_screen.d.ts.map +1 -0
  82. package/build/screens/notification_settings_screen.js +234 -0
  83. package/build/screens/notification_settings_screen.js.map +1 -0
  84. package/build/screens/preferred_app/hooks/use_chat_types.d.ts +39 -0
  85. package/build/screens/preferred_app/hooks/use_chat_types.d.ts.map +1 -0
  86. package/build/screens/preferred_app/hooks/use_chat_types.js +12 -0
  87. package/build/screens/preferred_app/hooks/use_chat_types.js.map +1 -0
  88. package/build/screens/preferred_app_selection_screen.d.ts +10 -0
  89. package/build/screens/preferred_app_selection_screen.d.ts.map +1 -0
  90. package/build/screens/preferred_app_selection_screen.js +128 -0
  91. package/build/screens/preferred_app_selection_screen.js.map +1 -0
  92. package/build/types/resources/group_membership.d.ts +6 -0
  93. package/build/types/resources/group_membership.d.ts.map +1 -0
  94. package/build/types/resources/group_membership.js +2 -0
  95. package/build/types/resources/group_membership.js.map +1 -0
  96. package/build/types/resources/group_resource.d.ts +4 -0
  97. package/build/types/resources/group_resource.d.ts.map +1 -1
  98. package/build/types/resources/group_resource.js.map +1 -1
  99. package/build/types/resources/index.d.ts +2 -0
  100. package/build/types/resources/index.d.ts.map +1 -1
  101. package/build/types/resources/index.js +2 -0
  102. package/build/types/resources/index.js.map +1 -1
  103. package/build/types/resources/message_report.d.ts +19 -0
  104. package/build/types/resources/message_report.d.ts.map +1 -0
  105. package/build/types/resources/message_report.js +9 -0
  106. package/build/types/resources/message_report.js.map +1 -0
  107. package/package.json +2 -2
  108. package/src/components/conversations/conversation_actions.tsx +6 -1
  109. package/src/components/conversations/conversations.tsx +6 -1
  110. package/src/components/display/platform_modal_header_buttons.tsx +16 -0
  111. package/src/components/primitive/form_sheet.tsx +4 -2
  112. package/src/hooks/index.ts +1 -0
  113. package/src/hooks/use_features.ts +1 -0
  114. package/src/hooks/use_new_conversation_entry.ts +31 -0
  115. package/src/hooks/use_report_message.ts +37 -0
  116. package/src/hooks/use_teams.ts +1 -0
  117. package/src/navigation/index.tsx +51 -7
  118. package/src/screens/conversation_filter_recipients/conversation_filter_recipients_screen.tsx +10 -3
  119. package/src/screens/conversation_select_recipients/conversation_new_entry_screen.tsx +100 -0
  120. package/src/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.tsx +23 -3
  121. package/src/screens/conversation_select_type_screen.tsx +59 -0
  122. package/src/screens/conversations/components/list_header_component.tsx +20 -7
  123. package/src/screens/group_notification_settings_screen.tsx +92 -0
  124. package/src/screens/index.ts +4 -1
  125. package/src/screens/message_actions_screen.tsx +24 -2
  126. package/src/screens/message_report/components/message_preview.tsx +92 -0
  127. package/src/screens/message_report/components/report_reason_list.tsx +106 -0
  128. package/src/screens/message_report_screen.tsx +278 -0
  129. package/src/screens/notification_settings/hooks/groups.ts +101 -0
  130. package/src/screens/notification_settings_screen.tsx +383 -0
  131. package/src/screens/preferred_app/hooks/use_chat_types.ts +25 -0
  132. package/src/screens/preferred_app_selection_screen.tsx +169 -0
  133. package/src/types/images.d.ts +14 -0
  134. package/src/types/resources/group_membership.ts +6 -0
  135. package/src/types/resources/group_resource.ts +5 -0
  136. package/src/types/resources/index.ts +2 -0
  137. package/src/types/resources/message_report.ts +20 -0
  138. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.d.ts +0 -4
  139. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.d.ts.map +0 -1
  140. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js +0 -146
  141. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js.map +0 -1
  142. package/src/screens/conversation_select_recipients/conversation_select_recipients_screen.tsx +0 -215
@@ -0,0 +1,101 @@
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query'
2
+ import { getRequestQueryKey, useApiClient } from '../../../hooks'
3
+ import { useSuspenseGet, useSuspensePaginator } from '../../../hooks'
4
+ import { ApiResource } from '../../../types'
5
+ import { GroupMembership, GroupResourceWithMembership } from '../../../types/resources'
6
+ import { throwResponseError } from '../../../utils/response_error'
7
+
8
+ export const getGroupsRequestArgs = () => ({
9
+ url: '/me/groups',
10
+ data: {
11
+ perPage: 100,
12
+ include: ['my_group_membership'],
13
+ filter: 'user_settable_notification_level',
14
+ fields: {
15
+ Group: [],
16
+ GroupMembership: ['notification_level', 'notification_level_description'],
17
+ },
18
+ order: 'name',
19
+ },
20
+ })
21
+
22
+ export const getGroupRequestArgs = ({ groupId }: { groupId: number | string }) => ({
23
+ url: `/me/groups/${groupId}`,
24
+ data: {
25
+ include: ['my_group_membership'],
26
+ fields: {
27
+ Group: ['name', 'source_app_name', 'source_type', 'my_group_membership'],
28
+ GroupMembership: ['notification_level', 'notification_level_description'],
29
+ },
30
+ },
31
+ })
32
+
33
+ export const useGroups = () => {
34
+ const args = getGroupsRequestArgs()
35
+
36
+ return useSuspensePaginator<GroupResourceWithMembership>(args)
37
+ }
38
+
39
+ export const useGroup = ({ groupId }: { groupId: number | string }) => {
40
+ const args = getGroupRequestArgs({ groupId })
41
+
42
+ return useSuspenseGet<GroupResourceWithMembership>(args)
43
+ }
44
+
45
+ export const useGroupMembershipUpdate = ({ groupId }: { groupId: number | string }) => {
46
+ const apiClient = useApiClient()
47
+ const queryClient = useQueryClient()
48
+ const requestArgs = getGroupRequestArgs({ groupId })
49
+ const queryKey = getRequestQueryKey(requestArgs)
50
+
51
+ return useMutation({
52
+ throwOnError: true,
53
+ onMutate: notificationLevel => {
54
+ queryClient.setQueryData<ApiResource<GroupResourceWithMembership>>(queryKey, groupData => {
55
+ if (!groupData?.data.myGroupMembership) return groupData
56
+
57
+ return {
58
+ ...groupData,
59
+ data: {
60
+ ...groupData.data,
61
+ myGroupMembership: {
62
+ ...groupData.data.myGroupMembership,
63
+ notificationLevel,
64
+ },
65
+ },
66
+ }
67
+ })
68
+ },
69
+ onSuccess: response => {
70
+ queryClient.setQueryData<ApiResource<GroupResourceWithMembership>>(queryKey, groupData => {
71
+ if (!groupData?.data.myGroupMembership) return groupData
72
+
73
+ return {
74
+ ...groupData,
75
+ data: {
76
+ ...groupData.data,
77
+ myGroupMembership: {
78
+ ...groupData.data.myGroupMembership,
79
+ ...response.data,
80
+ },
81
+ },
82
+ }
83
+ })
84
+ },
85
+ mutationFn: (notificationLevel: string) => {
86
+ return apiClient.chat
87
+ .patch<ApiResource<GroupMembership>>({
88
+ url: `/me/groups/${groupId}/my_group_membership`,
89
+ data: {
90
+ data: {
91
+ type: 'GroupMembership',
92
+ attributes: {
93
+ notification_level: notificationLevel,
94
+ },
95
+ },
96
+ },
97
+ })
98
+ .catch(throwResponseError)
99
+ },
100
+ })
101
+ }
@@ -0,0 +1,383 @@
1
+ import { PlatformPressable } from '@react-navigation/elements'
2
+ import { StaticScreenProps, useNavigation } from '@react-navigation/native'
3
+ import type { NativeStackNavigationProp } from '@react-navigation/native-stack'
4
+ import React, { useCallback, useEffect, type ReactNode } from 'react'
5
+ import { FlatList, Platform, StyleSheet, View, type ViewProps, type ViewStyle } from 'react-native'
6
+ import { Badge, Heading, Icon, Text } from '../components'
7
+ import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
8
+ import { useTheme } from '../hooks'
9
+ import { isDefined } from '../types'
10
+ import type { GroupNotificationSettingsScreenProps } from './group_notification_settings_screen'
11
+ import { useGroups } from './notification_settings/hooks/groups'
12
+ import { useChatTypes } from './preferred_app/hooks/use_chat_types'
13
+ import type { PreferredAppSelectionScreenProps } from './preferred_app_selection_screen'
14
+
15
+ // =========================================
16
+ // ====== Factory Constants & Types ========
17
+ // =========================================
18
+
19
+ type NotificationSettingsStackParamList = {
20
+ NotificationSettings: {}
21
+ PreferredAppSelection: PreferredAppSelectionScreenProps['route']['params']
22
+ GroupNotificationSettings: GroupNotificationSettingsScreenProps['route']['params']
23
+ }
24
+
25
+ enum SectionTypes {
26
+ header,
27
+ hidden,
28
+ setting,
29
+ view,
30
+ link,
31
+ }
32
+
33
+ type SectionListData = Array<
34
+ | DataItem<{ title: string }, SectionTypes.header>
35
+ | DataItem<ViewProps, SectionTypes.view>
36
+ | DataItem<SettingRowProps, SectionTypes.setting>
37
+ | DataItem<LinkRowProps, SectionTypes.link>
38
+ | DataItem<any, SectionTypes.hidden>
39
+ >
40
+
41
+ interface DataItem<T, TName extends SectionTypes> {
42
+ type: TName
43
+ data: T
44
+ sectionOuterStyle?: ViewStyle
45
+ sectionInnerStyle?: ViewStyle
46
+ showBottomBorder?: boolean
47
+ }
48
+
49
+ // =================================
50
+ // ====== Components ===============
51
+ // =================================
52
+
53
+ export type NotificationSettingsScreenProps = StaticScreenProps<{}>
54
+
55
+ export function NotificationSettingsScreen({}: NotificationSettingsScreenProps) {
56
+ const navigation = useNavigation<NativeStackNavigationProp<NotificationSettingsStackParamList>>()
57
+ const styles = useStyles()
58
+ const { data: chatTypes } = useChatTypes()
59
+ const { data: groups } = useGroups()
60
+
61
+ // Header configuration
62
+ const HeaderRight = useCallback(() => {
63
+ return (
64
+ <HeaderTextButton
65
+ onPress={() => {
66
+ // Save settings logic would go here
67
+ navigation.goBack()
68
+ }}
69
+ title="Done"
70
+ />
71
+ )
72
+ }, [navigation])
73
+
74
+ useEffect(() => {
75
+ navigation.setOptions({
76
+ headerRight: HeaderRight,
77
+ })
78
+ }, [HeaderRight, navigation])
79
+
80
+ // Build section data
81
+ const listData = [
82
+ {
83
+ type: SectionTypes.header,
84
+ data: { title: 'Preferred app' },
85
+ sectionInnerStyle: styles.sectionInnerHeader,
86
+ },
87
+ {
88
+ type: SectionTypes.view,
89
+ data: {
90
+ children: (
91
+ <Text variant="tertiary" style={styles.sectionDescription}>
92
+ Choose which app receives each type of chat notification
93
+ </Text>
94
+ ),
95
+ },
96
+ showBottomBorder: true,
97
+ },
98
+ ...chatTypes.map(type => ({
99
+ type: SectionTypes.link,
100
+ data: {
101
+ title: `${type.title} Conversations`,
102
+ rightLabel: type.preferredApp,
103
+ onPress: () =>
104
+ navigation.navigate('PreferredAppSelection', {
105
+ chatTypeId: type.id,
106
+ }),
107
+ },
108
+ sectionInnerStyle: { paddingVertical: 0 },
109
+ showBottomBorder: true,
110
+ })),
111
+ {
112
+ type: SectionTypes.header,
113
+ data: {
114
+ title: 'Manage chat settings',
115
+ },
116
+ sectionInnerStyle: styles.sectionInnerHeader,
117
+ },
118
+ {
119
+ type: SectionTypes.view,
120
+ data: {
121
+ children: (
122
+ <Text variant="tertiary" style={styles.sectionDescription}>
123
+ Notification settings for all of your group, team and event related conversations
124
+ </Text>
125
+ ),
126
+ },
127
+ showBottomBorder: true,
128
+ },
129
+ ...groups.map(group => ({
130
+ type: SectionTypes.link,
131
+ data: {
132
+ title: group.name,
133
+ rightLabel: group.sourceType,
134
+ onPress: () =>
135
+ navigation.navigate('GroupNotificationSettings', {
136
+ groupId: group.id,
137
+ title: group.name || 'Group',
138
+ }),
139
+ },
140
+ sectionInnerStyle: { paddingVertical: 0 },
141
+ showBottomBorder: true,
142
+ })),
143
+ ].filter(item => item.type !== SectionTypes.hidden)
144
+
145
+ const headerIndices = listData
146
+ .map(({ type }, index) => (type === SectionTypes.header ? index : undefined))
147
+ .filter(isDefined)
148
+
149
+ return (
150
+ <View style={styles.listContainer}>
151
+ <FlatList
152
+ data={listData as SectionListData}
153
+ contentContainerStyle={styles.contentContainer}
154
+ renderItem={({ item, index }) => {
155
+ const [isStart, isEnd] = [
156
+ index === 0 || headerIndices.includes(index),
157
+ index === listData.length - 1 || headerIndices.includes(index + 1),
158
+ ]
159
+
160
+ switch (item.type) {
161
+ case SectionTypes.header:
162
+ return (
163
+ <ListSection
164
+ isStart={isStart}
165
+ isEnd={isEnd}
166
+ showBottomBorder={item?.showBottomBorder}
167
+ outerStyle={item?.sectionOuterStyle}
168
+ innerStyle={item?.sectionInnerStyle}
169
+ >
170
+ <Heading variant="h2">{item.data.title}</Heading>
171
+ </ListSection>
172
+ )
173
+ case SectionTypes.setting:
174
+ return (
175
+ <ListSection
176
+ isStart={isStart}
177
+ isEnd={isEnd}
178
+ showBottomBorder={item?.showBottomBorder}
179
+ outerStyle={item?.sectionOuterStyle}
180
+ innerStyle={item?.sectionInnerStyle}
181
+ >
182
+ <SettingRow {...item.data} />
183
+ </ListSection>
184
+ )
185
+ case SectionTypes.view:
186
+ return (
187
+ <ListSection
188
+ isStart={isStart}
189
+ isEnd={isEnd}
190
+ showBottomBorder={item?.showBottomBorder}
191
+ outerStyle={item?.sectionOuterStyle}
192
+ innerStyle={item?.sectionInnerStyle}
193
+ >
194
+ <View {...item.data} style={item.data.style} />
195
+ </ListSection>
196
+ )
197
+ case SectionTypes.link:
198
+ return (
199
+ <ListSection
200
+ isStart={isStart}
201
+ isEnd={isEnd}
202
+ showBottomBorder={item?.showBottomBorder}
203
+ outerStyle={item?.sectionOuterStyle}
204
+ innerStyle={item?.sectionInnerStyle}
205
+ >
206
+ <LinkRow {...item.data} />
207
+ </ListSection>
208
+ )
209
+ default:
210
+ return null
211
+ }
212
+ }}
213
+ />
214
+ </View>
215
+ )
216
+ }
217
+
218
+ interface ListSectionProps {
219
+ isStart?: boolean
220
+ isEnd?: boolean
221
+ showBottomBorder?: boolean
222
+ outerStyle?: ViewStyle
223
+ innerStyle?: ViewStyle
224
+ children: ReactNode
225
+ }
226
+
227
+ function ListSection({
228
+ isStart,
229
+ isEnd,
230
+ showBottomBorder,
231
+ outerStyle,
232
+ innerStyle,
233
+ children,
234
+ }: ListSectionProps) {
235
+ const styles = useStyles({ isStart, isEnd })
236
+ const bottomBorder = showBottomBorder ? styles.sectionInnerBottomBorder : {}
237
+
238
+ return (
239
+ <View style={[styles.sectionOuterBase, outerStyle]}>
240
+ <View style={[styles.sectionInnerBase, bottomBorder, innerStyle]}>{children}</View>
241
+ </View>
242
+ )
243
+ }
244
+
245
+ interface SettingRowProps {
246
+ title: string
247
+ style?: ViewStyle
248
+ rightItem?: ReactNode
249
+ rightLabel?: string
250
+ rightItemStyle?: ViewStyle
251
+ }
252
+
253
+ function SettingRow({ title, style, rightLabel, rightItem, rightItemStyle = {} }: SettingRowProps) {
254
+ const styles = useStyles()
255
+
256
+ return (
257
+ <View style={[styles.settingRow, style]}>
258
+ <View style={styles.settingRowContent}>
259
+ <Text variant="plain" style={styles.settingRowText}>
260
+ {title}
261
+ </Text>
262
+ {Boolean(rightLabel) && <Text variant="footnote">{rightLabel}</Text>}
263
+ </View>
264
+ {Boolean(rightItem) && <View style={rightItemStyle}>{rightItem}</View>}
265
+ </View>
266
+ )
267
+ }
268
+
269
+ interface LinkRowProps {
270
+ title: string
271
+ rightLabel: string
272
+ onPress: () => void
273
+ }
274
+
275
+ function LinkRow({ title, rightLabel, onPress }: LinkRowProps) {
276
+ const styles = useLinkRowStyles()
277
+ const isSourceType = rightLabel === 'Team' || rightLabel === 'Group' || rightLabel === 'PlanTeam'
278
+
279
+ return (
280
+ <PlatformPressable
281
+ style={styles.row}
282
+ onPress={onPress}
283
+ accessibilityRole="link"
284
+ accessibilityLabel={title}
285
+ accessibilityHint={`Navigate to ${title} settings`}
286
+ >
287
+ <View style={styles.innerContainer}>
288
+ <Text style={styles.title} numberOfLines={2}>
289
+ {title}
290
+ </Text>
291
+ <View style={styles.rightContent}>
292
+ {isSourceType ? (
293
+ <Badge label={rightLabel} appearance="neutral" variant="meta" />
294
+ ) : (
295
+ <Text numberOfLines={1} style={styles.rightLabelText}>
296
+ {rightLabel}
297
+ </Text>
298
+ )}
299
+ {Platform.OS === 'ios' && (
300
+ <Icon name="general.rightChevron" size={16} style={styles.icon} />
301
+ )}
302
+ </View>
303
+ </View>
304
+ </PlatformPressable>
305
+ )
306
+ }
307
+
308
+ // =================================
309
+ // ====== Styles ===================
310
+ // =================================
311
+
312
+ const useStyles = ({}: { isStart?: boolean; isEnd?: boolean } = {}) => {
313
+ const { colors } = useTheme()
314
+ const headerBottomPadding = 0
315
+
316
+ return StyleSheet.create({
317
+ listContainer: {
318
+ flex: 1,
319
+ },
320
+ contentContainer: {},
321
+ sectionOuterBase: {
322
+ paddingLeft: 16,
323
+ },
324
+ sectionInnerBase: {
325
+ paddingRight: 16,
326
+ paddingVertical: 16,
327
+ },
328
+ sectionInnerHeader: {
329
+ paddingBottom: headerBottomPadding,
330
+ },
331
+ sectionInnerBottomBorder: {
332
+ borderBottomColor: colors.borderColorDefaultBase,
333
+ borderBottomWidth: 1,
334
+ },
335
+ sectionDescription: {
336
+ color: colors.textColorDefaultSecondary,
337
+ },
338
+ settingRow: {
339
+ flexDirection: 'row',
340
+ justifyContent: 'space-between',
341
+ alignItems: 'center',
342
+ gap: 8,
343
+ },
344
+ settingRowContent: {
345
+ flex: 1,
346
+ },
347
+ settingRowText: {
348
+ lineHeight: 20,
349
+ },
350
+ })
351
+ }
352
+
353
+ const useLinkRowStyles = () => {
354
+ const theme = useTheme()
355
+
356
+ return StyleSheet.create({
357
+ row: {
358
+ paddingLeft: 0,
359
+ paddingVertical: 16,
360
+ },
361
+ innerContainer: {
362
+ flexDirection: 'row',
363
+ alignItems: 'center',
364
+ justifyContent: 'space-between',
365
+ gap: 12,
366
+ },
367
+ rightContent: {
368
+ flexDirection: 'row',
369
+ alignItems: 'center',
370
+ gap: 8,
371
+ },
372
+ title: {
373
+ flexShrink: 1,
374
+ alignSelf: 'center',
375
+ },
376
+ rightLabelText: {
377
+ color: theme.colors.textColorDefaultSecondary,
378
+ },
379
+ icon: {
380
+ color: theme.colors.iconColorDefaultDisabled,
381
+ },
382
+ })
383
+ }
@@ -0,0 +1,25 @@
1
+ import { useSuspenseGet } from '../../../hooks'
2
+ import { ResourceObject } from '../../../types'
3
+
4
+ export type SourceAppName = 'Services' | 'Church Center'
5
+ export type PreferredApp = 'Services' | 'Church Center' | 'Chat' | 'None'
6
+
7
+ export interface ChatTypeResource extends ResourceObject {
8
+ type: 'ChatType'
9
+ defaultPreferredApp: PreferredApp
10
+ preferredApp: PreferredApp
11
+ preferredAppOptions: PreferredApp[]
12
+ sourceAppName: SourceAppName
13
+ title: string
14
+ }
15
+
16
+ export const useChatTypes = () => {
17
+ return useSuspenseGet<ChatTypeResource[]>({
18
+ url: '/me/chat_types',
19
+ data: {
20
+ fields: {
21
+ ChatType: [],
22
+ },
23
+ },
24
+ })
25
+ }
@@ -0,0 +1,169 @@
1
+ import { PlatformPressable } from '@react-navigation/elements'
2
+ import { StaticScreenProps } from '@react-navigation/native'
3
+ import { useMutation } from '@tanstack/react-query'
4
+ import React, { type PropsWithChildren } from 'react'
5
+ import { Image, StyleSheet, View, type ViewStyle } from 'react-native'
6
+ import churchCenterLogo from '../../assets/churchCenter.png'
7
+ import servicesLogo from '../../assets/servicesApp.png'
8
+ import chatLogo from '../../assets/chatLogo.png'
9
+ import { Heading, Icon, Text } from '../components'
10
+ import { useApiClient, useTheme } from '../hooks'
11
+ import { PreferredApp, useChatTypes } from './preferred_app/hooks/use_chat_types'
12
+ import { useAppName } from '../hooks/use_app_name'
13
+ import { throwResponseError } from '../utils/response_error'
14
+
15
+ export type PreferredAppSelectionScreenProps = StaticScreenProps<{
16
+ chatTypeId: number | string
17
+ conversationType?: 'group' | 'team'
18
+ currentSelection?: PreferredApp
19
+ }>
20
+
21
+ export function PreferredAppSelectionScreen({ route }: PreferredAppSelectionScreenProps) {
22
+ const { chatTypeId } = route.params
23
+ const { data: chatTypes, refetch: refetchChatTypes } = useChatTypes()
24
+ const appName = useAppName()
25
+
26
+ const apiClient = useApiClient()
27
+
28
+ const { mutate: updateChatType } = useMutation({
29
+ throwOnError: true,
30
+ onSuccess: () => refetchChatTypes(),
31
+ mutationFn: (app: PreferredApp) => {
32
+ return apiClient.chat
33
+ .post({
34
+ url: `/me/chat_types/${chatTypeId}/set_preferred_app`,
35
+ data: { data: { type: 'ChatType', attributes: { app } } },
36
+ })
37
+ .catch(throwResponseError)
38
+ },
39
+ })
40
+ const chatType = chatTypes.find(t => t.id === chatTypeId)
41
+ const { preferredAppOptions } = chatType || {}
42
+
43
+ const handleSelection = (app: PreferredApp) => {
44
+ updateChatType(app)
45
+ }
46
+
47
+ const sectionTitle = `${chatType?.title} Conversations`
48
+
49
+ return (
50
+ <View style={styles.container}>
51
+ <View style={styles.section}>
52
+ <Heading variant="h3" style={styles.sectionHeading}>
53
+ {sectionTitle}
54
+ </Heading>
55
+ {preferredAppOptions?.sort(sortPreferredApp('asc')).map((option, key) => (
56
+ <PressableRow
57
+ key={`${key}-${option}`}
58
+ isActive={chatType?.preferredApp === option}
59
+ onPress={() => handleSelection(option)}
60
+ >
61
+ <Image source={getAppImage(option)} style={styles.logo} />
62
+ <Text>
63
+ {option}
64
+ {appName.includes(option.replace(/\s/, '').toLowerCase()) ? ' ( current )' : ''}
65
+ </Text>
66
+ </PressableRow>
67
+ ))}
68
+ </View>
69
+ </View>
70
+ )
71
+ }
72
+
73
+ const sortPreferredApp = (dir?: 'asc' | 'desc') => (a: PreferredApp, b: PreferredApp) => {
74
+ if (a === 'None') return 1
75
+ if (a > b) return dir === 'asc' ? 1 : -1
76
+ if (b > a) return dir === 'asc' ? -1 : 1
77
+
78
+ return 0
79
+ }
80
+
81
+ const getAppImage = (option: PreferredApp) => {
82
+ switch (option) {
83
+ case 'Church Center':
84
+ return churchCenterLogo
85
+ case 'Services':
86
+ return servicesLogo
87
+ case 'Chat':
88
+ return chatLogo
89
+ default:
90
+ return undefined
91
+ }
92
+ }
93
+
94
+ const PressableRow = ({
95
+ children,
96
+ isActive,
97
+ onPress,
98
+ style,
99
+ }: PropsWithChildren<{ isActive: boolean; onPress: () => void; style?: ViewStyle }>) => {
100
+ const styles = useRowStyles({ isActive })
101
+ return (
102
+ <PlatformPressable
103
+ style={styles.pressable}
104
+ onPress={onPress}
105
+ accessibilityRole="radio"
106
+ accessibilityState={{ selected: isActive }}
107
+ >
108
+ <View style={styles.row}>
109
+ <View style={[styles.rowInner, style]}>{children}</View>
110
+ <Icon
111
+ name="general.check"
112
+ style={styles.rowIconRight}
113
+ accessibilityElementsHidden
114
+ maxFontSizeMultiplier={2.5}
115
+ />
116
+ </View>
117
+ </PlatformPressable>
118
+ )
119
+ }
120
+
121
+ const styles = StyleSheet.create({
122
+ container: {
123
+ flex: 1,
124
+ },
125
+ section: {
126
+ paddingTop: 16,
127
+ },
128
+ sectionHeading: {
129
+ paddingHorizontal: 16,
130
+ paddingBottom: 4,
131
+ },
132
+ logo: {
133
+ width: 32,
134
+ height: 32,
135
+ borderRadius: 8,
136
+ },
137
+ })
138
+
139
+ const useRowStyles = ({ isActive = false }: { isActive?: boolean } = {}) => {
140
+ const theme = useTheme()
141
+
142
+ return StyleSheet.create({
143
+ pressable: {
144
+ paddingLeft: 16,
145
+ backgroundColor: theme.colors.surfaceColor100,
146
+ },
147
+ row: {
148
+ flexDirection: 'row',
149
+ alignItems: 'center',
150
+ justifyContent: 'space-between',
151
+ gap: 12,
152
+ borderBottomWidth: 1,
153
+ borderBottomColor: theme.colors.borderColorDefaultBase,
154
+ paddingVertical: 12,
155
+ paddingRight: 16,
156
+ },
157
+ rowInner: {
158
+ flexDirection: 'row',
159
+ alignItems: 'center',
160
+ gap: 12,
161
+ flexShrink: 1,
162
+ },
163
+ rowIconRight: {
164
+ fontSize: 16,
165
+ color: theme.colors.statusSuccessIcon,
166
+ opacity: isActive ? 1 : 0,
167
+ },
168
+ })
169
+ }
@@ -0,0 +1,14 @@
1
+ declare module '*.png' {
2
+ const value: import('react-native').ImageSourcePropType
3
+ export default value
4
+ }
5
+
6
+ declare module '*.jpg' {
7
+ const value: import('react-native').ImageSourcePropType
8
+ export default value
9
+ }
10
+
11
+ declare module '*.jpeg' {
12
+ const value: import('react-native').ImageSourcePropType
13
+ export default value
14
+ }
@@ -0,0 +1,6 @@
1
+ import { ResourceObject } from '../api_primitives'
2
+
3
+ export interface GroupMembership extends ResourceObject {
4
+ notificationLevel: string
5
+ notificationLevelDescription: string
6
+ }