@planningcenter/chat-react-native 3.24.0-rc.7 → 3.24.0-rc.9

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 (56) hide show
  1. package/build/components/display/platform_modal_header_buttons.d.ts +8 -0
  2. package/build/components/display/platform_modal_header_buttons.d.ts.map +1 -1
  3. package/build/components/display/platform_modal_header_buttons.js +8 -0
  4. package/build/components/display/platform_modal_header_buttons.js.map +1 -1
  5. package/build/navigation/index.d.ts +34 -2
  6. package/build/navigation/index.d.ts.map +1 -1
  7. package/build/navigation/index.js +25 -1
  8. package/build/navigation/index.js.map +1 -1
  9. package/build/screens/group_notification_settings_screen.d.ts +8 -0
  10. package/build/screens/group_notification_settings_screen.d.ts.map +1 -0
  11. package/build/screens/group_notification_settings_screen.js +79 -0
  12. package/build/screens/group_notification_settings_screen.js.map +1 -0
  13. package/build/screens/index.d.ts +3 -0
  14. package/build/screens/index.d.ts.map +1 -1
  15. package/build/screens/index.js +3 -0
  16. package/build/screens/index.js.map +1 -1
  17. package/build/screens/notification_settings/hooks/groups.d.ts +94 -0
  18. package/build/screens/notification_settings/hooks/groups.d.ts.map +1 -0
  19. package/build/screens/notification_settings/hooks/groups.js +92 -0
  20. package/build/screens/notification_settings/hooks/groups.js.map +1 -0
  21. package/build/screens/notification_settings_screen.d.ts +5 -0
  22. package/build/screens/notification_settings_screen.d.ts.map +1 -0
  23. package/build/screens/notification_settings_screen.js +234 -0
  24. package/build/screens/notification_settings_screen.js.map +1 -0
  25. package/build/screens/preferred_app/hooks/use_chat_types.d.ts +39 -0
  26. package/build/screens/preferred_app/hooks/use_chat_types.d.ts.map +1 -0
  27. package/build/screens/preferred_app/hooks/use_chat_types.js +12 -0
  28. package/build/screens/preferred_app/hooks/use_chat_types.js.map +1 -0
  29. package/build/screens/preferred_app_selection_screen.d.ts +10 -0
  30. package/build/screens/preferred_app_selection_screen.d.ts.map +1 -0
  31. package/build/screens/preferred_app_selection_screen.js +128 -0
  32. package/build/screens/preferred_app_selection_screen.js.map +1 -0
  33. package/build/types/resources/group_membership.d.ts +6 -0
  34. package/build/types/resources/group_membership.d.ts.map +1 -0
  35. package/build/types/resources/group_membership.js +2 -0
  36. package/build/types/resources/group_membership.js.map +1 -0
  37. package/build/types/resources/group_resource.d.ts +4 -0
  38. package/build/types/resources/group_resource.d.ts.map +1 -1
  39. package/build/types/resources/group_resource.js.map +1 -1
  40. package/build/types/resources/index.d.ts +2 -0
  41. package/build/types/resources/index.d.ts.map +1 -1
  42. package/build/types/resources/index.js +2 -0
  43. package/build/types/resources/index.js.map +1 -1
  44. package/package.json +2 -2
  45. package/src/components/display/platform_modal_header_buttons.tsx +16 -0
  46. package/src/navigation/index.tsx +32 -1
  47. package/src/screens/group_notification_settings_screen.tsx +92 -0
  48. package/src/screens/index.ts +3 -0
  49. package/src/screens/notification_settings/hooks/groups.ts +101 -0
  50. package/src/screens/notification_settings_screen.tsx +383 -0
  51. package/src/screens/preferred_app/hooks/use_chat_types.ts +25 -0
  52. package/src/screens/preferred_app_selection_screen.tsx +169 -0
  53. package/src/types/images.d.ts +14 -0
  54. package/src/types/resources/group_membership.ts +6 -0
  55. package/src/types/resources/group_resource.ts +5 -0
  56. package/src/types/resources/index.ts +2 -0
@@ -3,6 +3,7 @@ import { TextButton } from './text_button'
3
3
  import { HeaderButton } from '@react-navigation/elements'
4
4
  import { Icon } from './icon'
5
5
  import { useTheme } from '../../hooks'
6
+ import type { NativeStackHeaderRightProps } from '@react-navigation/native-stack'
6
7
 
7
8
  interface HeaderTextButtonProps {
8
9
  onPress: () => void
@@ -62,6 +63,21 @@ export const HeaderDismissButton = ({
62
63
  })
63
64
  }
64
65
 
66
+ interface HeaderDoneButtonProps extends NativeStackHeaderRightProps {
67
+ navigation: { goBack: () => void; getState: () => any }
68
+ }
69
+
70
+ export const HeaderDoneButton = ({ navigation, ...props }: HeaderDoneButtonProps) => {
71
+ const state = navigation.getState()
72
+ const isFirstScreen = state.index === 0
73
+
74
+ if (!isFirstScreen) {
75
+ return null
76
+ }
77
+
78
+ return <HeaderTextButton {...props} onPress={navigation.goBack} title="Done" />
79
+ }
80
+
65
81
  const useStyles = () => {
66
82
  const { colors } = useTheme()
67
83
 
@@ -8,7 +8,10 @@ import { CardStyleInterpolators } from '@react-navigation/stack'
8
8
  import React from 'react'
9
9
  import { Platform } from 'react-native'
10
10
  import { Icon } from '../components'
11
- import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
11
+ import {
12
+ HeaderDoneButton,
13
+ HeaderTextButton,
14
+ } from '../components/display/platform_modal_header_buttons'
12
15
  import {
13
16
  AttachmentActionsScreen,
14
17
  AttachmentActionsScreenOptions,
@@ -49,6 +52,9 @@ import {
49
52
  } from '../screens/message_actions_screen'
50
53
  import { MessageReportScreen, MessageReportScreenOptions } from '../screens/message_report_screen'
51
54
  import { NotFound } from '../screens/not_found'
55
+ import { NotificationSettingsScreen } from '../screens/notification_settings_screen'
56
+ import { PreferredAppSelectionScreen } from '../screens/preferred_app_selection_screen'
57
+ import { GroupNotificationSettingsScreen } from '../screens/group_notification_settings_screen'
52
58
  import { ReactionsScreen, ReactionsScreenOptions } from '../screens/reactions_screen'
53
59
  import { ScreenLayoutWithChatAccessGate } from './screenLayout'
54
60
  import { SendGiphyScreen, SendGiphyScreenOptions } from '../screens/send_giphy_screen'
@@ -223,6 +229,31 @@ export const ChatStack = createNativeStackNavigator({
223
229
  ),
224
230
  }),
225
231
  },
232
+ NotificationSettings: {
233
+ screen: NotificationSettingsScreen,
234
+ options: ({ navigation }) => ({
235
+ title: 'Chat',
236
+ headerBackVisible: false,
237
+ headerRight: (props: NativeStackHeaderRightProps) => (
238
+ <HeaderTextButton {...props} onPress={navigation.goBack} title="Done" />
239
+ ),
240
+ }),
241
+ },
242
+ PreferredAppSelection: {
243
+ screen: PreferredAppSelectionScreen,
244
+ options: {
245
+ title: 'Preferred app',
246
+ },
247
+ },
248
+ GroupNotificationSettings: {
249
+ screen: GroupNotificationSettingsScreen,
250
+ options: ({ route, navigation }) => ({
251
+ title: (route.params as { title?: string })?.title || 'Group Settings',
252
+ headerRight: (props: NativeStackHeaderRightProps) => (
253
+ <HeaderDoneButton {...props} navigation={navigation} />
254
+ ),
255
+ }),
256
+ },
226
257
  New: {
227
258
  screen: NewConversationStack,
228
259
  if: useQualifiedByAge,
@@ -0,0 +1,92 @@
1
+ import { StaticScreenProps, useNavigation } from '@react-navigation/native'
2
+ import React, { useEffect } from 'react'
3
+ import { StyleSheet, View } from 'react-native'
4
+ import { Heading, Switch, Text } from '../components'
5
+ import { useTheme } from '../hooks'
6
+ import { platformFontWeightBold } from '../utils/styles'
7
+ import { useGroup, useGroupMembershipUpdate } from './notification_settings/hooks/groups'
8
+
9
+ export type GroupNotificationSettingsScreenProps = StaticScreenProps<{
10
+ groupId: number | string
11
+ title: string
12
+ }>
13
+
14
+ export function GroupNotificationSettingsScreen({ route }: GroupNotificationSettingsScreenProps) {
15
+ const { groupId, title } = route.params
16
+ const navigation = useNavigation()
17
+ const styles = useStyles()
18
+ const { data: group } = useGroup({ groupId })
19
+ const { mutate: updateNotificationLevel } = useGroupMembershipUpdate({ groupId })
20
+
21
+ const notificationsEnabled = group.myGroupMembership?.notificationLevel === 'everything'
22
+
23
+ useEffect(() => {
24
+ if (!group.name || title === group.name) return
25
+
26
+ navigation.setOptions({ title: group.name })
27
+ }, [group.name, title, navigation])
28
+
29
+ const handleToggle = (value: boolean) => {
30
+ const notificationLevel = value ? 'everything' : 'nothing'
31
+ updateNotificationLevel(notificationLevel)
32
+ }
33
+
34
+ return (
35
+ <View style={styles.container}>
36
+ <View style={styles.sectionOuter}>
37
+ <View style={styles.sectionInner}>
38
+ <Heading variant="h3" style={styles.sectionHeading}>
39
+ Group notification settings
40
+ </Heading>
41
+ <Text variant="tertiary" style={styles.sectionSubtitle}>
42
+ The settings are applied to all conversations in{' '}
43
+ <Text style={styles.groupNameBold}>{group.name || title}</Text>
44
+ </Text>
45
+ </View>
46
+ </View>
47
+ <View style={styles.settingRow}>
48
+ <Text>Enable notifications</Text>
49
+ <Switch value={notificationsEnabled} onValueChange={handleToggle} />
50
+ </View>
51
+ </View>
52
+ )
53
+ }
54
+
55
+ const useStyles = () => {
56
+ const { colors } = useTheme()
57
+
58
+ return StyleSheet.create({
59
+ container: {
60
+ flex: 1,
61
+ backgroundColor: colors.surfaceColor100,
62
+ },
63
+ sectionOuter: {
64
+ paddingLeft: 16,
65
+ backgroundColor: colors.surfaceColor100,
66
+ },
67
+ sectionInner: {
68
+ paddingRight: 16,
69
+ paddingTop: 16,
70
+ paddingBottom: 12,
71
+ borderBottomWidth: 1,
72
+ borderBottomColor: colors.borderColorDefaultBase,
73
+ },
74
+ sectionHeading: {
75
+ paddingBottom: 4,
76
+ },
77
+ sectionSubtitle: {
78
+ color: colors.textColorDefaultSecondary,
79
+ },
80
+ groupNameBold: {
81
+ fontWeight: platformFontWeightBold,
82
+ },
83
+ settingRow: {
84
+ flexDirection: 'row',
85
+ justifyContent: 'space-between',
86
+ alignItems: 'center',
87
+ paddingHorizontal: 16,
88
+ paddingVertical: 12,
89
+ backgroundColor: colors.surfaceColor100,
90
+ },
91
+ })
92
+ }
@@ -2,6 +2,9 @@ export * from './design_system_screen'
2
2
  export * from './conversation_details_screen'
3
3
  export * from './conversation_screen'
4
4
  export * from './conversation_new/conversation_new_screen'
5
+ export * from './notification_settings_screen'
6
+ export * from './preferred_app_selection_screen'
7
+ export * from './group_notification_settings_screen'
5
8
  export * from './conversation_filter_recipients/conversation_filter_recipients_screen'
6
9
  export * from './message_actions_screen'
7
10
  export * from './message_report_screen'
@@ -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
+ }