@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.
- package/build/components/conversations/conversation_actions.d.ts.map +1 -1
- package/build/components/conversations/conversation_actions.js +5 -1
- package/build/components/conversations/conversation_actions.js.map +1 -1
- package/build/components/conversations/conversations.d.ts.map +1 -1
- package/build/components/conversations/conversations.js +5 -1
- package/build/components/conversations/conversations.js.map +1 -1
- package/build/components/display/platform_modal_header_buttons.d.ts +8 -0
- package/build/components/display/platform_modal_header_buttons.d.ts.map +1 -1
- package/build/components/display/platform_modal_header_buttons.js +8 -0
- package/build/components/display/platform_modal_header_buttons.js.map +1 -1
- package/build/components/primitive/form_sheet.d.ts +1 -1
- package/build/components/primitive/form_sheet.d.ts.map +1 -1
- package/build/components/primitive/form_sheet.js +1 -1
- package/build/components/primitive/form_sheet.js.map +1 -1
- package/build/hooks/index.d.ts +1 -0
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +1 -0
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/use_features.d.ts +1 -0
- package/build/hooks/use_features.d.ts.map +1 -1
- package/build/hooks/use_features.js +1 -0
- package/build/hooks/use_features.js.map +1 -1
- package/build/hooks/use_new_conversation_entry.d.ts +3 -0
- package/build/hooks/use_new_conversation_entry.d.ts.map +1 -0
- package/build/hooks/use_new_conversation_entry.js +20 -0
- package/build/hooks/use_new_conversation_entry.js.map +1 -0
- package/build/hooks/use_report_message.d.ts +6 -0
- package/build/hooks/use_report_message.d.ts.map +1 -0
- package/build/hooks/use_report_message.js +28 -0
- package/build/hooks/use_report_message.js.map +1 -0
- package/build/hooks/use_teams.d.ts.map +1 -1
- package/build/hooks/use_teams.js +1 -0
- package/build/hooks/use_teams.js.map +1 -1
- package/build/navigation/index.d.ts +52 -10
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +41 -7
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.js +5 -3
- package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.js.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_new_entry_screen.d.ts +4 -0
- package/build/screens/conversation_select_recipients/conversation_new_entry_screen.d.ts.map +1 -0
- package/build/screens/conversation_select_recipients/conversation_new_entry_screen.js +67 -0
- package/build/screens/conversation_select_recipients/conversation_new_entry_screen.js.map +1 -0
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.d.ts.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js +17 -2
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js.map +1 -1
- package/build/screens/conversation_select_type_screen.d.ts +11 -0
- package/build/screens/conversation_select_type_screen.d.ts.map +1 -0
- package/build/screens/conversation_select_type_screen.js +38 -0
- package/build/screens/conversation_select_type_screen.js.map +1 -0
- package/build/screens/conversations/components/list_header_component.d.ts.map +1 -1
- package/build/screens/conversations/components/list_header_component.js +18 -7
- package/build/screens/conversations/components/list_header_component.js.map +1 -1
- package/build/screens/group_notification_settings_screen.d.ts +8 -0
- package/build/screens/group_notification_settings_screen.d.ts.map +1 -0
- package/build/screens/group_notification_settings_screen.js +79 -0
- package/build/screens/group_notification_settings_screen.js.map +1 -0
- package/build/screens/index.d.ts +4 -1
- package/build/screens/index.d.ts.map +1 -1
- package/build/screens/index.js +4 -1
- package/build/screens/index.js.map +1 -1
- package/build/screens/message_actions_screen.js +14 -2
- package/build/screens/message_actions_screen.js.map +1 -1
- package/build/screens/message_report/components/message_preview.d.ts +10 -0
- package/build/screens/message_report/components/message_preview.d.ts.map +1 -0
- package/build/screens/message_report/components/message_preview.js +74 -0
- package/build/screens/message_report/components/message_preview.js.map +1 -0
- package/build/screens/message_report/components/report_reason_list.d.ts +9 -0
- package/build/screens/message_report/components/report_reason_list.d.ts.map +1 -0
- package/build/screens/message_report/components/report_reason_list.js +67 -0
- package/build/screens/message_report/components/report_reason_list.js.map +1 -0
- package/build/screens/message_report_screen.d.ts +10 -0
- package/build/screens/message_report_screen.d.ts.map +1 -0
- package/build/screens/message_report_screen.js +214 -0
- package/build/screens/message_report_screen.js.map +1 -0
- package/build/screens/notification_settings/hooks/groups.d.ts +94 -0
- package/build/screens/notification_settings/hooks/groups.d.ts.map +1 -0
- package/build/screens/notification_settings/hooks/groups.js +92 -0
- package/build/screens/notification_settings/hooks/groups.js.map +1 -0
- package/build/screens/notification_settings_screen.d.ts +5 -0
- package/build/screens/notification_settings_screen.d.ts.map +1 -0
- package/build/screens/notification_settings_screen.js +234 -0
- package/build/screens/notification_settings_screen.js.map +1 -0
- package/build/screens/preferred_app/hooks/use_chat_types.d.ts +39 -0
- package/build/screens/preferred_app/hooks/use_chat_types.d.ts.map +1 -0
- package/build/screens/preferred_app/hooks/use_chat_types.js +12 -0
- package/build/screens/preferred_app/hooks/use_chat_types.js.map +1 -0
- package/build/screens/preferred_app_selection_screen.d.ts +10 -0
- package/build/screens/preferred_app_selection_screen.d.ts.map +1 -0
- package/build/screens/preferred_app_selection_screen.js +128 -0
- package/build/screens/preferred_app_selection_screen.js.map +1 -0
- package/build/types/resources/group_membership.d.ts +6 -0
- package/build/types/resources/group_membership.d.ts.map +1 -0
- package/build/types/resources/group_membership.js +2 -0
- package/build/types/resources/group_membership.js.map +1 -0
- package/build/types/resources/group_resource.d.ts +4 -0
- package/build/types/resources/group_resource.d.ts.map +1 -1
- package/build/types/resources/group_resource.js.map +1 -1
- package/build/types/resources/index.d.ts +2 -0
- package/build/types/resources/index.d.ts.map +1 -1
- package/build/types/resources/index.js +2 -0
- package/build/types/resources/index.js.map +1 -1
- package/build/types/resources/message_report.d.ts +19 -0
- package/build/types/resources/message_report.d.ts.map +1 -0
- package/build/types/resources/message_report.js +9 -0
- package/build/types/resources/message_report.js.map +1 -0
- package/package.json +2 -2
- package/src/components/conversations/conversation_actions.tsx +6 -1
- package/src/components/conversations/conversations.tsx +6 -1
- package/src/components/display/platform_modal_header_buttons.tsx +16 -0
- package/src/components/primitive/form_sheet.tsx +4 -2
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use_features.ts +1 -0
- package/src/hooks/use_new_conversation_entry.ts +31 -0
- package/src/hooks/use_report_message.ts +37 -0
- package/src/hooks/use_teams.ts +1 -0
- package/src/navigation/index.tsx +51 -7
- package/src/screens/conversation_filter_recipients/conversation_filter_recipients_screen.tsx +10 -3
- package/src/screens/conversation_select_recipients/conversation_new_entry_screen.tsx +100 -0
- package/src/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.tsx +23 -3
- package/src/screens/conversation_select_type_screen.tsx +59 -0
- package/src/screens/conversations/components/list_header_component.tsx +20 -7
- package/src/screens/group_notification_settings_screen.tsx +92 -0
- package/src/screens/index.ts +4 -1
- package/src/screens/message_actions_screen.tsx +24 -2
- package/src/screens/message_report/components/message_preview.tsx +92 -0
- package/src/screens/message_report/components/report_reason_list.tsx +106 -0
- package/src/screens/message_report_screen.tsx +278 -0
- package/src/screens/notification_settings/hooks/groups.ts +101 -0
- package/src/screens/notification_settings_screen.tsx +383 -0
- package/src/screens/preferred_app/hooks/use_chat_types.ts +25 -0
- package/src/screens/preferred_app_selection_screen.tsx +169 -0
- package/src/types/images.d.ts +14 -0
- package/src/types/resources/group_membership.ts +6 -0
- package/src/types/resources/group_resource.ts +5 -0
- package/src/types/resources/index.ts +2 -0
- package/src/types/resources/message_report.ts +20 -0
- package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.d.ts +0 -4
- package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.d.ts.map +0 -1
- package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js +0 -146
- package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js.map +0 -1
- package/src/screens/conversation_select_recipients/conversation_select_recipients_screen.tsx +0 -215
|
@@ -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
|
+
}
|
package/src/screens/index.ts
CHANGED
|
@@ -2,9 +2,12 @@ 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
|
-
export * from './conversation_select_recipients/conversation_select_recipients_screen'
|
|
7
9
|
export * from './message_actions_screen'
|
|
10
|
+
export * from './message_report_screen'
|
|
8
11
|
export * from './send_giphy_screen'
|
|
9
12
|
export * from './not_found'
|
|
10
13
|
export * from './reactions_screen'
|
|
@@ -120,6 +120,14 @@ function MessageActionsScreenContent({
|
|
|
120
120
|
navigation.goBack()
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
const handleReportPress = useCallback(() => {
|
|
124
|
+
Haptic.impactLight()
|
|
125
|
+
navigation.navigate('MessageReport', {
|
|
126
|
+
conversation_id,
|
|
127
|
+
message_id: message.id,
|
|
128
|
+
})
|
|
129
|
+
}, [navigation, conversation_id, message.id])
|
|
130
|
+
|
|
123
131
|
const { handleReactionToggle, isPending } = useMessageReactionToggle({
|
|
124
132
|
conversation_id,
|
|
125
133
|
message,
|
|
@@ -212,6 +220,9 @@ function MessageActionsScreenContent({
|
|
|
212
220
|
])
|
|
213
221
|
}, [handleRemoveLinkPreview])
|
|
214
222
|
|
|
223
|
+
const showReportMessageAction =
|
|
224
|
+
!message?.mine && featureEnabled(availableFeatures.message_reporting)
|
|
225
|
+
|
|
215
226
|
return (
|
|
216
227
|
<FormSheet.Root style={styles.formSheetContent}>
|
|
217
228
|
<View style={styles.reactionList}>
|
|
@@ -246,6 +257,14 @@ function MessageActionsScreenContent({
|
|
|
246
257
|
iconName="services.fileCopy"
|
|
247
258
|
accessibilityHint="Copies text and links to clipboard"
|
|
248
259
|
/>
|
|
260
|
+
{showReportMessageAction && (
|
|
261
|
+
<FormSheet.Action
|
|
262
|
+
onPress={handleReportPress}
|
|
263
|
+
title="Report message"
|
|
264
|
+
iconName="chat.reportMessageO"
|
|
265
|
+
accessibilityHint="Opens a form to report this message"
|
|
266
|
+
/>
|
|
267
|
+
)}
|
|
249
268
|
{message?.mine && (
|
|
250
269
|
<FormSheet.Action
|
|
251
270
|
onPress={() => handleEditPress()}
|
|
@@ -306,7 +325,9 @@ const Reaction = ({
|
|
|
306
325
|
android_ripple={{ color: androidRippleColor, borderless: false, foreground: true }}
|
|
307
326
|
onPress={onPress}
|
|
308
327
|
>
|
|
309
|
-
<Text style={styles.reactionEmoji}
|
|
328
|
+
<Text style={styles.reactionEmoji} allowFontScaling={false}>
|
|
329
|
+
{REACTION_EMOJIS[reaction.value]}
|
|
330
|
+
</Text>
|
|
310
331
|
</PlatformPressable>
|
|
311
332
|
)
|
|
312
333
|
}
|
|
@@ -316,7 +337,7 @@ const useStyles = () => {
|
|
|
316
337
|
const fontScale = useFontScale({ maxFontSizeMultiplier: 1.3 })
|
|
317
338
|
|
|
318
339
|
const btnBorderWidth = 1
|
|
319
|
-
const baseSize = 46 * fontScale
|
|
340
|
+
const baseSize = 46 * Math.max(1, fontScale)
|
|
320
341
|
const reactionBtnSize = Platform.select({
|
|
321
342
|
ios: baseSize,
|
|
322
343
|
android: baseSize + btnBorderWidth * 2,
|
|
@@ -346,6 +367,7 @@ const useStyles = () => {
|
|
|
346
367
|
},
|
|
347
368
|
reactionEmoji: {
|
|
348
369
|
fontSize: 24,
|
|
370
|
+
textAlign: 'center',
|
|
349
371
|
},
|
|
350
372
|
actions: {
|
|
351
373
|
paddingTop: 4,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
+
import { Avatar, Text, Icon } from '../../../components'
|
|
4
|
+
import { useTheme } from '../../../hooks'
|
|
5
|
+
import { MessageResource } from '../../../types'
|
|
6
|
+
|
|
7
|
+
interface MessagePreviewProps {
|
|
8
|
+
message: MessageResource
|
|
9
|
+
style?: ViewStyle
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function MessagePreview({ message, style }: MessagePreviewProps) {
|
|
13
|
+
const hasImages = message.attachments.some(
|
|
14
|
+
att => att.type === 'MessageAttachment' && att.attributes?.contentType?.startsWith('image/')
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
const imageOnly = hasImages && !message.text
|
|
18
|
+
const styles = useStyles({ imageOnly })
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<View style={[styles.previewContainer, style]}>
|
|
22
|
+
<View style={styles.previewHeader}>
|
|
23
|
+
<Avatar sourceUri={message.author.avatar} size="md" />
|
|
24
|
+
<Text style={styles.authorName}>{message.author.name}</Text>
|
|
25
|
+
</View>
|
|
26
|
+
|
|
27
|
+
<View style={styles.messageBubble}>
|
|
28
|
+
{hasImages && (
|
|
29
|
+
<View style={styles.imagePlaceholder}>
|
|
30
|
+
<Icon name="general.image" style={styles.placeholderIcon} />
|
|
31
|
+
<Text variant="secondary" style={styles.placeholderText}>
|
|
32
|
+
Image hidden
|
|
33
|
+
</Text>
|
|
34
|
+
</View>
|
|
35
|
+
)}
|
|
36
|
+
|
|
37
|
+
{message.text && (
|
|
38
|
+
<Text style={styles.messageText} numberOfLines={5}>
|
|
39
|
+
{message.text}
|
|
40
|
+
</Text>
|
|
41
|
+
)}
|
|
42
|
+
</View>
|
|
43
|
+
</View>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const useStyles = ({ imageOnly }: { imageOnly: boolean }) => {
|
|
48
|
+
const { colors } = useTheme()
|
|
49
|
+
|
|
50
|
+
return StyleSheet.create({
|
|
51
|
+
previewContainer: {
|
|
52
|
+
gap: 12,
|
|
53
|
+
},
|
|
54
|
+
previewHeader: {
|
|
55
|
+
flexDirection: 'row',
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
gap: 12,
|
|
58
|
+
},
|
|
59
|
+
authorName: {
|
|
60
|
+
fontSize: 14,
|
|
61
|
+
fontWeight: '600',
|
|
62
|
+
color: colors.textColorDefaultPrimary,
|
|
63
|
+
},
|
|
64
|
+
messageBubble: {
|
|
65
|
+
backgroundColor: colors.fillColorNeutral060,
|
|
66
|
+
borderRadius: 16,
|
|
67
|
+
overflow: 'hidden',
|
|
68
|
+
alignSelf: 'flex-start',
|
|
69
|
+
maxWidth: imageOnly ? '40%' : '80%',
|
|
70
|
+
},
|
|
71
|
+
messageText: {
|
|
72
|
+
fontSize: 14,
|
|
73
|
+
color: colors.textColorDefaultPrimary,
|
|
74
|
+
lineHeight: 20,
|
|
75
|
+
paddingHorizontal: 16,
|
|
76
|
+
paddingVertical: 12,
|
|
77
|
+
},
|
|
78
|
+
imagePlaceholder: {
|
|
79
|
+
height: 120,
|
|
80
|
+
justifyContent: 'center',
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
gap: 8,
|
|
83
|
+
},
|
|
84
|
+
placeholderIcon: {
|
|
85
|
+
fontSize: 32,
|
|
86
|
+
color: colors.iconColorDefaultSecondary,
|
|
87
|
+
},
|
|
88
|
+
placeholderText: {
|
|
89
|
+
fontSize: 14,
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, StyleSheet } from 'react-native'
|
|
3
|
+
import { PlatformPressable } from '@react-navigation/elements'
|
|
4
|
+
import { Text } from '../../../components'
|
|
5
|
+
import { useTheme, useCreateAndroidRippleColor } from '../../../hooks'
|
|
6
|
+
import { Haptic } from '../../../utils/native_adapters'
|
|
7
|
+
import {
|
|
8
|
+
MESSAGE_REPORT_REASONS,
|
|
9
|
+
MessageReportReason,
|
|
10
|
+
} from '../../../types/resources/message_report'
|
|
11
|
+
|
|
12
|
+
const MESSAGE_REPORT_REASONS_LIST = Object.entries(MESSAGE_REPORT_REASONS).map(([key, label]) => ({
|
|
13
|
+
key: key as MessageReportReason,
|
|
14
|
+
label,
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
interface ReportReasonListProps {
|
|
18
|
+
selectedReason: MessageReportReason | null
|
|
19
|
+
onReasonChange: (reason: MessageReportReason) => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ReportReasonList({ selectedReason, onReasonChange }: ReportReasonListProps) {
|
|
23
|
+
return (
|
|
24
|
+
<View>
|
|
25
|
+
{MESSAGE_REPORT_REASONS_LIST.map(({ key, label }) => (
|
|
26
|
+
<ReportReasonButton
|
|
27
|
+
key={key}
|
|
28
|
+
reason={key}
|
|
29
|
+
label={label}
|
|
30
|
+
isSelected={selectedReason === key}
|
|
31
|
+
onPress={() => {
|
|
32
|
+
Haptic.impactLight()
|
|
33
|
+
onReasonChange(key)
|
|
34
|
+
}}
|
|
35
|
+
/>
|
|
36
|
+
))}
|
|
37
|
+
</View>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ReportReasonButtonProps {
|
|
42
|
+
reason: MessageReportReason
|
|
43
|
+
label: string
|
|
44
|
+
isSelected: boolean
|
|
45
|
+
onPress: () => void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function ReportReasonButton({ label, isSelected, onPress }: ReportReasonButtonProps) {
|
|
49
|
+
const styles = useStyles({ isSelected })
|
|
50
|
+
const { colors } = useTheme()
|
|
51
|
+
const androidRippleColor = useCreateAndroidRippleColor({ color: colors.fillColorNeutral060 })
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<PlatformPressable
|
|
55
|
+
onPress={onPress}
|
|
56
|
+
style={styles.pressable}
|
|
57
|
+
android_ripple={{ color: androidRippleColor }}
|
|
58
|
+
accessibilityRole="radio"
|
|
59
|
+
accessibilityState={{ selected: isSelected }}
|
|
60
|
+
accessibilityLabel={label}
|
|
61
|
+
>
|
|
62
|
+
<View style={styles.row}>
|
|
63
|
+
<View style={styles.radioButton}>
|
|
64
|
+
{isSelected && <View style={styles.radioButtonInner} />}
|
|
65
|
+
</View>
|
|
66
|
+
<Text style={styles.label}>{label}</Text>
|
|
67
|
+
</View>
|
|
68
|
+
</PlatformPressable>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const useStyles = ({ isSelected }: { isSelected: boolean }) => {
|
|
73
|
+
const { colors } = useTheme()
|
|
74
|
+
|
|
75
|
+
return StyleSheet.create({
|
|
76
|
+
pressable: {
|
|
77
|
+
paddingVertical: 16,
|
|
78
|
+
paddingHorizontal: 16,
|
|
79
|
+
},
|
|
80
|
+
row: {
|
|
81
|
+
flexDirection: 'row',
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
gap: 12,
|
|
84
|
+
},
|
|
85
|
+
radioButton: {
|
|
86
|
+
width: 20,
|
|
87
|
+
height: 20,
|
|
88
|
+
borderRadius: 10,
|
|
89
|
+
borderWidth: 2,
|
|
90
|
+
borderColor: isSelected ? colors.interaction : colors.borderColorDefaultBase,
|
|
91
|
+
justifyContent: 'center',
|
|
92
|
+
alignItems: 'center',
|
|
93
|
+
},
|
|
94
|
+
radioButtonInner: {
|
|
95
|
+
width: 10,
|
|
96
|
+
height: 10,
|
|
97
|
+
borderRadius: 5,
|
|
98
|
+
backgroundColor: colors.interaction,
|
|
99
|
+
},
|
|
100
|
+
label: {
|
|
101
|
+
fontSize: 16,
|
|
102
|
+
color: colors.textColorDefaultPrimary,
|
|
103
|
+
flex: 1,
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import React, { useCallback, useLayoutEffect, useMemo, useState } from 'react'
|
|
2
|
+
import { View, StyleSheet, TextInput, ScrollView } from 'react-native'
|
|
3
|
+
import type {
|
|
4
|
+
NativeStackNavigationOptions,
|
|
5
|
+
NativeStackScreenProps,
|
|
6
|
+
} from '@react-navigation/native-stack'
|
|
7
|
+
import { StaticScreenProps, useNavigation, StackActions } from '@react-navigation/native'
|
|
8
|
+
import { Spinner, Text, KeyboardView } from '../components'
|
|
9
|
+
import BlankState from '../components/primitive/blank_state_primitive'
|
|
10
|
+
import { useTheme } from '../hooks'
|
|
11
|
+
import { useReportMessage } from '../hooks/use_report_message'
|
|
12
|
+
import {
|
|
13
|
+
HeaderDismissButton,
|
|
14
|
+
HeaderTextButton,
|
|
15
|
+
} from '../components/display/platform_modal_header_buttons'
|
|
16
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
17
|
+
import { useConversationMessages } from '../hooks/use_conversation_messages'
|
|
18
|
+
import { MessageResource } from '../types'
|
|
19
|
+
import { MessageReportReason } from '../types/resources/message_report'
|
|
20
|
+
import { MessagePreview } from './message_report/components/message_preview'
|
|
21
|
+
import { ReportReasonList } from './message_report/components/report_reason_list'
|
|
22
|
+
import { platformFontWeightBold } from '../utils'
|
|
23
|
+
|
|
24
|
+
const MAX_DETAIL_LENGTH = 500
|
|
25
|
+
|
|
26
|
+
export const MessageReportScreenOptions = ({
|
|
27
|
+
navigation,
|
|
28
|
+
}: NativeStackScreenProps<any>): NativeStackNavigationOptions => ({
|
|
29
|
+
presentation: 'modal',
|
|
30
|
+
title: 'Report message',
|
|
31
|
+
headerLeft: () => <HeaderDismissButton title="Cancel" onPress={() => navigation.goBack()} />,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export type MessageReportScreenProps = StaticScreenProps<{
|
|
35
|
+
conversation_id: number
|
|
36
|
+
message_id: string
|
|
37
|
+
}>
|
|
38
|
+
|
|
39
|
+
export function MessageReportScreen({ route }: MessageReportScreenProps) {
|
|
40
|
+
const { conversation_id, message_id } = route.params
|
|
41
|
+
|
|
42
|
+
const { messages } = useConversationMessages({ conversation_id }, { refetchOnMount: false })
|
|
43
|
+
const message = messages.find(m => m.id === message_id)
|
|
44
|
+
|
|
45
|
+
if (!message) return null
|
|
46
|
+
|
|
47
|
+
return <MessageReportScreenContent message={message} conversation_id={conversation_id} />
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function MessageReportScreenContent({
|
|
51
|
+
message,
|
|
52
|
+
conversation_id,
|
|
53
|
+
}: {
|
|
54
|
+
message: MessageResource
|
|
55
|
+
conversation_id: number
|
|
56
|
+
}) {
|
|
57
|
+
const styles = useStyles()
|
|
58
|
+
const navigation = useNavigation()
|
|
59
|
+
const [selectedReason, setSelectedReason] = useState<MessageReportReason | null>(null)
|
|
60
|
+
const [reasonDetail, setReasonDetail] = useState('')
|
|
61
|
+
const { mutate: submitReport, status } = useReportMessage(conversation_id, message.id)
|
|
62
|
+
|
|
63
|
+
const handleReasonChange = useCallback((reason: MessageReportReason) => {
|
|
64
|
+
setSelectedReason(reason)
|
|
65
|
+
|
|
66
|
+
if (reason !== 'other') {
|
|
67
|
+
setReasonDetail('')
|
|
68
|
+
}
|
|
69
|
+
}, [])
|
|
70
|
+
|
|
71
|
+
const isFormValid = useMemo(() => {
|
|
72
|
+
if (selectedReason === null) return false
|
|
73
|
+
|
|
74
|
+
if (selectedReason === 'other' && reasonDetail.trim().length === 0) {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (status === 'pending') return false
|
|
79
|
+
|
|
80
|
+
return true
|
|
81
|
+
}, [selectedReason, reasonDetail, status])
|
|
82
|
+
|
|
83
|
+
const handleSubmit = useCallback(() => {
|
|
84
|
+
if (!isFormValid || !selectedReason) return
|
|
85
|
+
|
|
86
|
+
submitReport({
|
|
87
|
+
reason: selectedReason,
|
|
88
|
+
reasonDetail: selectedReason === 'other' ? reasonDetail : undefined,
|
|
89
|
+
})
|
|
90
|
+
}, [isFormValid, selectedReason, reasonDetail, submitReport])
|
|
91
|
+
|
|
92
|
+
const handleClose = useCallback(() => {
|
|
93
|
+
navigation.dispatch(StackActions.popTo('Conversation', { conversation_id }))
|
|
94
|
+
}, [navigation, conversation_id])
|
|
95
|
+
|
|
96
|
+
const HeaderRight = useCallback(
|
|
97
|
+
() => <HeaderTextButton title="Submit" onPress={handleSubmit} disabled={!isFormValid} />,
|
|
98
|
+
[handleSubmit, isFormValid]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
useLayoutEffect(() => {
|
|
102
|
+
if (status === 'pending' || status === 'success') {
|
|
103
|
+
navigation.setOptions({
|
|
104
|
+
headerRight: () => null,
|
|
105
|
+
})
|
|
106
|
+
} else {
|
|
107
|
+
navigation.setOptions({
|
|
108
|
+
headerTitle: 'Report message',
|
|
109
|
+
headerRight: HeaderRight,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}, [HeaderRight, navigation, status])
|
|
113
|
+
|
|
114
|
+
if (status === 'pending') {
|
|
115
|
+
return (
|
|
116
|
+
<View style={styles.fullScreenContainer}>
|
|
117
|
+
<Spinner />
|
|
118
|
+
<Text style={styles.loadingText}>Submitting report...</Text>
|
|
119
|
+
</View>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (status === 'success') {
|
|
124
|
+
return (
|
|
125
|
+
<View style={styles.fullScreenContainer}>
|
|
126
|
+
<BlankState.Root>
|
|
127
|
+
<BlankState.Imagery name="general.checkCircle" style={styles.successIcon} />
|
|
128
|
+
<BlankState.Content>
|
|
129
|
+
<BlankState.Heading style={styles.successTitle}>Message reported</BlankState.Heading>
|
|
130
|
+
<BlankState.Text style={styles.successSubtitle}>
|
|
131
|
+
The message has been reported and a church leader will be contacted.
|
|
132
|
+
</BlankState.Text>
|
|
133
|
+
</BlankState.Content>
|
|
134
|
+
<BlankState.Button title="Close" onPress={handleClose} size="lg" variant="fill" />
|
|
135
|
+
</BlankState.Root>
|
|
136
|
+
</View>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (status === 'error') {
|
|
141
|
+
return (
|
|
142
|
+
<BlankState.Root>
|
|
143
|
+
<BlankState.Imagery name="general.exclamationTriangle" />
|
|
144
|
+
<BlankState.Content>
|
|
145
|
+
<BlankState.Heading>Unable to submit report</BlankState.Heading>
|
|
146
|
+
<BlankState.Text>
|
|
147
|
+
We were unable to report this message. Please try again.
|
|
148
|
+
</BlankState.Text>
|
|
149
|
+
</BlankState.Content>
|
|
150
|
+
<BlankState.Button title="Close" onPress={handleClose} size="lg" variant="outline" />
|
|
151
|
+
</BlankState.Root>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<KeyboardView>
|
|
157
|
+
<ScrollView contentContainerStyle={styles.container}>
|
|
158
|
+
<Text style={styles.warningText}>
|
|
159
|
+
Reporting this message will notify a church leader. The person you are reporting will not
|
|
160
|
+
receive a notification from Church Center. If someone is in immediate danger, call local
|
|
161
|
+
emergency services. Don't wait.
|
|
162
|
+
</Text>
|
|
163
|
+
|
|
164
|
+
<MessagePreview message={message} />
|
|
165
|
+
|
|
166
|
+
<View style={styles.reasonSection}>
|
|
167
|
+
<Text style={styles.fieldLabel}>
|
|
168
|
+
What best describes the problem? <Text style={styles.required}>*</Text>
|
|
169
|
+
</Text>
|
|
170
|
+
|
|
171
|
+
<View style={styles.reasonListContainer}>
|
|
172
|
+
<ReportReasonList selectedReason={selectedReason} onReasonChange={handleReasonChange} />
|
|
173
|
+
</View>
|
|
174
|
+
</View>
|
|
175
|
+
|
|
176
|
+
{selectedReason === 'other' && (
|
|
177
|
+
<View style={styles.detailSection}>
|
|
178
|
+
<Text style={styles.fieldLabel}>
|
|
179
|
+
Please provide more details <Text style={styles.required}>*</Text>
|
|
180
|
+
</Text>
|
|
181
|
+
<TextInput
|
|
182
|
+
style={styles.textInput}
|
|
183
|
+
multiline
|
|
184
|
+
placeholder="Describe the issue..."
|
|
185
|
+
value={reasonDetail}
|
|
186
|
+
onChangeText={setReasonDetail}
|
|
187
|
+
maxLength={MAX_DETAIL_LENGTH}
|
|
188
|
+
accessibilityLabel="Additional details"
|
|
189
|
+
accessibilityHint="Provide more information about why you're reporting this message"
|
|
190
|
+
/>
|
|
191
|
+
{reasonDetail.length >= MAX_DETAIL_LENGTH - 100 && (
|
|
192
|
+
<Text variant="footnote">
|
|
193
|
+
{reasonDetail.length}/{MAX_DETAIL_LENGTH}
|
|
194
|
+
</Text>
|
|
195
|
+
)}
|
|
196
|
+
</View>
|
|
197
|
+
)}
|
|
198
|
+
</ScrollView>
|
|
199
|
+
</KeyboardView>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const useStyles = () => {
|
|
204
|
+
const { bottom } = useSafeAreaInsets()
|
|
205
|
+
const { colors } = useTheme()
|
|
206
|
+
|
|
207
|
+
return StyleSheet.create({
|
|
208
|
+
container: {
|
|
209
|
+
padding: 16,
|
|
210
|
+
paddingBottom: 16 + bottom,
|
|
211
|
+
gap: 24,
|
|
212
|
+
},
|
|
213
|
+
fullHeight: {
|
|
214
|
+
flex: 1,
|
|
215
|
+
justifyContent: 'center',
|
|
216
|
+
alignItems: 'center',
|
|
217
|
+
},
|
|
218
|
+
fullScreenContainer: {
|
|
219
|
+
flex: 1,
|
|
220
|
+
justifyContent: 'center',
|
|
221
|
+
alignItems: 'center',
|
|
222
|
+
backgroundColor: colors.surfaceColor100,
|
|
223
|
+
},
|
|
224
|
+
loadingText: {
|
|
225
|
+
marginTop: 16,
|
|
226
|
+
fontSize: 16,
|
|
227
|
+
fontWeight: '600',
|
|
228
|
+
color: colors.textColorDefaultPrimary,
|
|
229
|
+
},
|
|
230
|
+
warningText: {
|
|
231
|
+
fontSize: 14,
|
|
232
|
+
lineHeight: 20,
|
|
233
|
+
color: colors.textColorDefaultSecondary,
|
|
234
|
+
},
|
|
235
|
+
reasonSection: {
|
|
236
|
+
gap: 8,
|
|
237
|
+
},
|
|
238
|
+
fieldLabel: {
|
|
239
|
+
fontSize: 16,
|
|
240
|
+
fontWeight: platformFontWeightBold,
|
|
241
|
+
color: colors.textColorDefaultPrimary,
|
|
242
|
+
},
|
|
243
|
+
required: {
|
|
244
|
+
color: colors.statusErrorText,
|
|
245
|
+
},
|
|
246
|
+
reasonListContainer: {
|
|
247
|
+
backgroundColor: colors.surfaceColor100,
|
|
248
|
+
},
|
|
249
|
+
detailSection: {
|
|
250
|
+
gap: 8,
|
|
251
|
+
},
|
|
252
|
+
textInput: {
|
|
253
|
+
color: colors.textColorDefaultPrimary,
|
|
254
|
+
fontSize: 16,
|
|
255
|
+
textAlignVertical: 'top',
|
|
256
|
+
minHeight: 120,
|
|
257
|
+
maxHeight: 200,
|
|
258
|
+
borderWidth: 1,
|
|
259
|
+
borderColor: colors.borderColorDefaultBase,
|
|
260
|
+
borderRadius: 8,
|
|
261
|
+
paddingHorizontal: 16,
|
|
262
|
+
paddingVertical: 12,
|
|
263
|
+
backgroundColor: colors.surfaceColor100,
|
|
264
|
+
},
|
|
265
|
+
successIcon: {
|
|
266
|
+
color: colors.statusSuccessIcon,
|
|
267
|
+
fontSize: 48,
|
|
268
|
+
},
|
|
269
|
+
successTitle: {
|
|
270
|
+
fontSize: 24,
|
|
271
|
+
marginBottom: 4,
|
|
272
|
+
},
|
|
273
|
+
successSubtitle: {
|
|
274
|
+
fontSize: 16,
|
|
275
|
+
marginBottom: 4,
|
|
276
|
+
},
|
|
277
|
+
})
|
|
278
|
+
}
|