@planningcenter/chat-react-native 3.24.4 → 3.24.5-qa-563.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.
- package/build/components/conversation/message.d.ts +1 -2
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +5 -5
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +1 -4
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/messages_disabled_banners.d.ts.map +1 -1
- package/build/components/conversation/messages_disabled_banners.js +2 -14
- package/build/components/conversation/messages_disabled_banners.js.map +1 -1
- package/build/components/conversations/conversation_actions.js +3 -3
- 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 +1 -0
- package/build/components/conversations/conversations.js.map +1 -1
- package/build/hooks/use_conversation.d.ts.map +1 -1
- package/build/hooks/use_conversation.js +13 -1
- package/build/hooks/use_conversation.js.map +1 -1
- package/build/hooks/use_conversation_membership.d.ts +5 -0
- package/build/hooks/use_conversation_membership.d.ts.map +1 -0
- package/build/hooks/use_conversation_membership.js +63 -0
- package/build/hooks/use_conversation_membership.js.map +1 -0
- package/build/hooks/use_conversations_actions.d.ts.map +1 -1
- package/build/hooks/use_conversations_actions.js +4 -0
- package/build/hooks/use_conversations_actions.js.map +1 -1
- package/build/hooks/use_features.d.ts +1 -1
- package/build/hooks/use_features.js +1 -1
- package/build/hooks/use_features.js.map +1 -1
- package/build/navigation/index.d.ts +10 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +12 -2
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +51 -19
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_notification_level_select_screen.d.ts +8 -0
- package/build/screens/conversation_notification_level_select_screen.d.ts.map +1 -0
- package/build/screens/conversation_notification_level_select_screen.js +49 -0
- package/build/screens/conversation_notification_level_select_screen.js.map +1 -0
- package/build/screens/conversation_screen.d.ts +4 -3
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +20 -14
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/group_notification_level_select_screen.d.ts +8 -0
- package/build/screens/group_notification_level_select_screen.d.ts.map +1 -0
- package/build/screens/group_notification_level_select_screen.js +40 -0
- package/build/screens/group_notification_level_select_screen.js.map +1 -0
- package/build/screens/group_notification_settings_screen.d.ts.map +1 -1
- package/build/screens/group_notification_settings_screen.js +30 -17
- package/build/screens/group_notification_settings_screen.js.map +1 -1
- package/build/screens/message_actions_screen.js +1 -2
- package/build/screens/message_actions_screen.js.map +1 -1
- package/build/screens/notification_settings/hooks/groups.d.ts +6 -5
- package/build/screens/notification_settings/hooks/groups.d.ts.map +1 -1
- package/build/screens/notification_settings/hooks/groups.js +63 -36
- package/build/screens/notification_settings/hooks/groups.js.map +1 -1
- package/build/screens/notification_settings_screen.d.ts.map +1 -1
- package/build/screens/notification_settings_screen.js +25 -11
- package/build/screens/notification_settings_screen.js.map +1 -1
- package/build/types/resources/conversation.d.ts +2 -3
- package/build/types/resources/conversation.d.ts.map +1 -1
- package/build/types/resources/conversation.js.map +1 -1
- package/build/types/resources/conversation_membership.d.ts +16 -0
- package/build/types/resources/conversation_membership.d.ts.map +1 -0
- package/build/types/resources/conversation_membership.js +2 -0
- package/build/types/resources/conversation_membership.js.map +1 -0
- package/build/types/resources/group_membership.d.ts +7 -0
- package/build/types/resources/group_membership.d.ts.map +1 -1
- package/build/types/resources/group_membership.js.map +1 -1
- package/build/types/resources/index.d.ts +1 -0
- package/build/types/resources/index.d.ts.map +1 -1
- package/build/types/resources/index.js +1 -0
- package/build/types/resources/index.js.map +1 -1
- package/build/utils/deep_snake_case_keys.d.ts +4 -0
- package/build/utils/deep_snake_case_keys.d.ts.map +1 -0
- package/build/utils/deep_snake_case_keys.js +13 -0
- package/build/utils/deep_snake_case_keys.js.map +1 -0
- package/package.json +2 -2
- package/src/components/conversation/message.tsx +4 -9
- package/src/components/conversation/message_form.tsx +1 -4
- package/src/components/conversation/messages_disabled_banners.tsx +9 -17
- package/src/components/conversations/conversation_actions.tsx +3 -3
- package/src/components/conversations/conversations.tsx +1 -0
- package/src/hooks/use_conversation.ts +13 -1
- package/src/hooks/use_conversation_membership.ts +91 -0
- package/src/hooks/use_conversations_actions.ts +4 -0
- package/src/hooks/use_features.ts +1 -1
- package/src/navigation/index.tsx +19 -1
- package/src/screens/conversation_details_screen.tsx +88 -25
- package/src/screens/conversation_notification_level_select_screen.tsx +73 -0
- package/src/screens/conversation_screen.tsx +20 -17
- package/src/screens/group_notification_level_select_screen.tsx +62 -0
- package/src/screens/group_notification_settings_screen.tsx +53 -18
- package/src/screens/message_actions_screen.tsx +1 -2
- package/src/screens/notification_settings/hooks/groups.ts +76 -37
- package/src/screens/notification_settings_screen.tsx +24 -21
- package/src/types/resources/conversation.ts +2 -3
- package/src/types/resources/conversation_membership.ts +17 -0
- package/src/types/resources/group_membership.ts +7 -0
- package/src/types/resources/index.ts +1 -0
- package/src/utils/deep_snake_case_keys.ts +16 -0
|
@@ -39,7 +39,6 @@ import { CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL } from '../utils/styles'
|
|
|
39
39
|
import { useConversationJoltEvents } from '../hooks/use_conversation_jolt_events'
|
|
40
40
|
import { JumpToBottomButton } from '../components/conversation/jump_to_bottom_button'
|
|
41
41
|
import { ReplyShadowMessage } from '../components/conversation/reply_shadow_message'
|
|
42
|
-
import { availableFeatures, useFeatures } from '../hooks/use_features'
|
|
43
42
|
import { ConversationContextProvider } from '../contexts/conversation_context'
|
|
44
43
|
|
|
45
44
|
export type ConversationRouteProps = {
|
|
@@ -53,6 +52,7 @@ export type ConversationRouteProps = {
|
|
|
53
52
|
subtitle?: string
|
|
54
53
|
badge?: ConversationBadgeResource
|
|
55
54
|
deleted?: boolean
|
|
55
|
+
muted?: boolean
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export type ConversationScreenProps = StaticScreenProps<ConversationRouteProps>
|
|
@@ -91,12 +91,9 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
91
91
|
useConversationMessagesJoltEvents({ conversationId: conversation_id })
|
|
92
92
|
useEnsureConversationsRouteExists()
|
|
93
93
|
useMarkLatestMessageRead({ conversation, messages })
|
|
94
|
-
const { featureEnabled } = useFeatures()
|
|
95
|
-
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
96
94
|
const messagesWithSeparators = groupMessages({
|
|
97
95
|
ms: messages,
|
|
98
96
|
inReplyScreen: !!reply_root_id,
|
|
99
|
-
repliesEnabled,
|
|
100
97
|
})
|
|
101
98
|
const noMessages = messagesWithSeparators.length === 0
|
|
102
99
|
|
|
@@ -109,6 +106,8 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
109
106
|
const replyHeaderTitle = replyRootAuthorFirstName
|
|
110
107
|
? `Reply to ${replyRootAuthorFirstName}`
|
|
111
108
|
: 'Reply'
|
|
109
|
+
// Prefer the membership for optimistic updates.
|
|
110
|
+
const muted = conversation.conversationMembership?.muted ?? conversation.muted
|
|
112
111
|
|
|
113
112
|
const listRef = useRef<FlatList>(null)
|
|
114
113
|
const [showJumpToBottomButton, setShowJumpToBottomButton] = useState(false)
|
|
@@ -134,9 +133,10 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
134
133
|
title: title,
|
|
135
134
|
badge: badges?.[0],
|
|
136
135
|
deleted: conversation?.deleted,
|
|
136
|
+
muted,
|
|
137
137
|
})
|
|
138
138
|
}
|
|
139
|
-
}, [navigation, title, badges, conversation?.deleted, reply_root_id, replyHeaderTitle])
|
|
139
|
+
}, [navigation, title, badges, conversation?.deleted, reply_root_id, replyHeaderTitle, muted])
|
|
140
140
|
|
|
141
141
|
if (!conversation || conversation.deleted) {
|
|
142
142
|
return (
|
|
@@ -195,7 +195,6 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
195
195
|
conversation_id={conversation_id}
|
|
196
196
|
latestReadMessageSortKey={conversation?.latestReadMessageSortKey}
|
|
197
197
|
inReplyScreen={!!reply_root_id}
|
|
198
|
-
repliesEnabled={repliesEnabled}
|
|
199
198
|
/>
|
|
200
199
|
)
|
|
201
200
|
}}
|
|
@@ -284,14 +283,9 @@ type ReplyShadowMessage = {
|
|
|
284
283
|
interface GroupMessagesProps {
|
|
285
284
|
ms: MessageResource[]
|
|
286
285
|
inReplyScreen?: boolean
|
|
287
|
-
repliesEnabled?: boolean
|
|
288
286
|
}
|
|
289
287
|
|
|
290
|
-
export const groupMessages = ({
|
|
291
|
-
ms,
|
|
292
|
-
inReplyScreen,
|
|
293
|
-
repliesEnabled = false,
|
|
294
|
-
}: GroupMessagesProps) => {
|
|
288
|
+
export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
295
289
|
let enrichedMessages: (MessageResource | DateSeparator | ReplyShadowMessage)[] = []
|
|
296
290
|
let encounteredOneOfMyMessages = false
|
|
297
291
|
|
|
@@ -335,7 +329,6 @@ export const groupMessages = ({
|
|
|
335
329
|
prevMessageDifferentThread ||
|
|
336
330
|
prevMessageIsDateSeparator)
|
|
337
331
|
const nextIsReplyShadowMessage =
|
|
338
|
-
repliesEnabled &&
|
|
339
332
|
nextMessageInThread &&
|
|
340
333
|
!nextMessageThreadRoot &&
|
|
341
334
|
(nextMessageDifferentThread || nextMessageIsDateSeparator)
|
|
@@ -369,7 +362,7 @@ export const groupMessages = ({
|
|
|
369
362
|
|
|
370
363
|
enrichedMessages.push(message)
|
|
371
364
|
|
|
372
|
-
if (insertReplyShadowMessage
|
|
365
|
+
if (insertReplyShadowMessage) {
|
|
373
366
|
enrichedMessages.push({
|
|
374
367
|
type: 'ReplyShadowMessage',
|
|
375
368
|
id: `${message.id}-${message.replyRootId}`,
|
|
@@ -390,6 +383,7 @@ interface ConversationScreenTitleProps extends HeaderTitleProps {
|
|
|
390
383
|
conversation_id: number
|
|
391
384
|
badge?: ConversationBadgeResource
|
|
392
385
|
deleted?: boolean
|
|
386
|
+
muted?: boolean
|
|
393
387
|
}
|
|
394
388
|
|
|
395
389
|
export const ConversationScreenTitle = ({
|
|
@@ -398,6 +392,7 @@ export const ConversationScreenTitle = ({
|
|
|
398
392
|
children,
|
|
399
393
|
style,
|
|
400
394
|
deleted,
|
|
395
|
+
muted,
|
|
401
396
|
}: ConversationScreenTitleProps) => {
|
|
402
397
|
const styles = usePressableHeaderStyle()
|
|
403
398
|
const navigation = useNavigation()
|
|
@@ -416,9 +411,12 @@ export const ConversationScreenTitle = ({
|
|
|
416
411
|
}}
|
|
417
412
|
>
|
|
418
413
|
<View style={styles.titleWrapper}>
|
|
419
|
-
<
|
|
420
|
-
{
|
|
421
|
-
|
|
414
|
+
<View style={styles.titleTextContainer}>
|
|
415
|
+
<HeaderTitle maxFontSizeMultiplier={1} style={style}>
|
|
416
|
+
{children}
|
|
417
|
+
</HeaderTitle>
|
|
418
|
+
</View>
|
|
419
|
+
{muted && <Icon name="general.bellMuted" size={12} />}
|
|
422
420
|
{!deleted && <Icon name="general.downChevron" size={12} />}
|
|
423
421
|
</View>
|
|
424
422
|
<Badge
|
|
@@ -438,6 +436,7 @@ const usePressableHeaderStyle = () => {
|
|
|
438
436
|
container: {
|
|
439
437
|
alignItems: Platform.select({ android: 'flex-start', default: 'center' }),
|
|
440
438
|
marginRight: Platform.select({ ios: 20, default: 16 }),
|
|
439
|
+
flex: 1,
|
|
441
440
|
},
|
|
442
441
|
titleWrapper: {
|
|
443
442
|
alignItems: 'center',
|
|
@@ -445,6 +444,10 @@ const usePressableHeaderStyle = () => {
|
|
|
445
444
|
flexDirection: 'row',
|
|
446
445
|
flexShrink: 1,
|
|
447
446
|
},
|
|
447
|
+
titleTextContainer: {
|
|
448
|
+
flexShrink: 1,
|
|
449
|
+
minWidth: 0,
|
|
450
|
+
},
|
|
448
451
|
badge: {
|
|
449
452
|
alignSelf: Platform.select({ android: 'flex-start', default: 'center' }),
|
|
450
453
|
marginTop: 2,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { StyleSheet } from 'react-native'
|
|
4
|
+
import { PressableRow } from '../components'
|
|
5
|
+
import FormSheet, { getFormSheetScreenOptions } from '../components/primitive/form_sheet'
|
|
6
|
+
import { useTheme } from '../hooks'
|
|
7
|
+
import { useGroup, useGroupMembershipUpdate } from './notification_settings/hooks/groups'
|
|
8
|
+
|
|
9
|
+
export const GroupNotificationLevelSelectScreenOptions = getFormSheetScreenOptions({
|
|
10
|
+
headerTitle: 'Notification level',
|
|
11
|
+
sheetAllowedDetents: [0.35],
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export type GroupNotificationLevelSelectScreenProps = StaticScreenProps<{
|
|
15
|
+
groupId: number | string
|
|
16
|
+
}>
|
|
17
|
+
|
|
18
|
+
export function GroupNotificationLevelSelectScreen({
|
|
19
|
+
route,
|
|
20
|
+
}: GroupNotificationLevelSelectScreenProps) {
|
|
21
|
+
const { groupId } = route.params
|
|
22
|
+
const navigation = useNavigation()
|
|
23
|
+
const styles = useStyles()
|
|
24
|
+
const { colors } = useTheme()
|
|
25
|
+
const { data: group } = useGroup({ groupId })
|
|
26
|
+
const { mutate: updateNotificationLevel } = useGroupMembershipUpdate({ groupId })
|
|
27
|
+
|
|
28
|
+
const notificationLevelOptions = group.myGroupMembership?.notificationLevelOptions ?? []
|
|
29
|
+
|
|
30
|
+
const handleSelect = (value: string, isActive: boolean) => {
|
|
31
|
+
if (!isActive) {
|
|
32
|
+
updateNotificationLevel({ notificationLevel: value })
|
|
33
|
+
}
|
|
34
|
+
navigation.goBack()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<FormSheet.Root contentStyle={styles.content}>
|
|
39
|
+
{notificationLevelOptions.map(option => (
|
|
40
|
+
<PressableRow
|
|
41
|
+
key={option.value}
|
|
42
|
+
text={option.description}
|
|
43
|
+
isActive={option.enabled}
|
|
44
|
+
onPress={() => handleSelect(option.value, option.enabled)}
|
|
45
|
+
iconColor={colors.statusSuccessIcon}
|
|
46
|
+
style={styles.row}
|
|
47
|
+
/>
|
|
48
|
+
))}
|
|
49
|
+
</FormSheet.Root>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const useStyles = () => {
|
|
54
|
+
return StyleSheet.create({
|
|
55
|
+
content: {
|
|
56
|
+
paddingTop: 20,
|
|
57
|
+
},
|
|
58
|
+
row: {
|
|
59
|
+
borderBottomWidth: 0,
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import { PlatformPressable } from '@react-navigation/elements'
|
|
1
2
|
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
3
|
+
import type { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
|
2
4
|
import React, { useEffect } from 'react'
|
|
3
|
-
import { StyleSheet, View } from 'react-native'
|
|
4
|
-
import { Heading,
|
|
5
|
+
import { Platform, StyleSheet, View } from 'react-native'
|
|
6
|
+
import { Heading, Icon, Text } from '../components'
|
|
5
7
|
import { useTheme } from '../hooks'
|
|
6
8
|
import { platformFontWeightBold } from '../utils/styles'
|
|
7
|
-
import { useGroup
|
|
9
|
+
import { useGroup } from './notification_settings/hooks/groups'
|
|
10
|
+
import type { GroupNotificationLevelSelectScreenProps } from './group_notification_level_select_screen'
|
|
11
|
+
|
|
12
|
+
type GroupNotificationSettingsStackParamList = {
|
|
13
|
+
GroupNotificationLevelSelect: GroupNotificationLevelSelectScreenProps['route']['params']
|
|
14
|
+
}
|
|
8
15
|
|
|
9
16
|
export type GroupNotificationSettingsScreenProps = StaticScreenProps<{
|
|
10
17
|
groupId: number | string
|
|
@@ -13,12 +20,12 @@ export type GroupNotificationSettingsScreenProps = StaticScreenProps<{
|
|
|
13
20
|
|
|
14
21
|
export function GroupNotificationSettingsScreen({ route }: GroupNotificationSettingsScreenProps) {
|
|
15
22
|
const { groupId, title } = route.params
|
|
16
|
-
const navigation =
|
|
23
|
+
const navigation =
|
|
24
|
+
useNavigation<NativeStackNavigationProp<GroupNotificationSettingsStackParamList>>()
|
|
17
25
|
const styles = useStyles()
|
|
18
26
|
const { data: group } = useGroup({ groupId })
|
|
19
|
-
const { mutate: updateNotificationLevel } = useGroupMembershipUpdate({ groupId })
|
|
20
27
|
|
|
21
|
-
const
|
|
28
|
+
const notificationLevelDescription = group.myGroupMembership?.notificationLevelDescription
|
|
22
29
|
|
|
23
30
|
useEffect(() => {
|
|
24
31
|
if (!group.name || title === group.name) return
|
|
@@ -26,9 +33,8 @@ export function GroupNotificationSettingsScreen({ route }: GroupNotificationSett
|
|
|
26
33
|
navigation.setOptions({ title: group.name })
|
|
27
34
|
}, [group.name, title, navigation])
|
|
28
35
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
updateNotificationLevel(notificationLevel)
|
|
36
|
+
const handleOpenSelector = () => {
|
|
37
|
+
navigation.navigate('GroupNotificationLevelSelect', { groupId })
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
return (
|
|
@@ -44,10 +50,30 @@ export function GroupNotificationSettingsScreen({ route }: GroupNotificationSett
|
|
|
44
50
|
</Text>
|
|
45
51
|
</View>
|
|
46
52
|
</View>
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
<PlatformPressable
|
|
54
|
+
style={styles.settingRowPressable}
|
|
55
|
+
onPress={handleOpenSelector}
|
|
56
|
+
accessibilityRole="button"
|
|
57
|
+
accessibilityLabel="Notify me for"
|
|
58
|
+
accessibilityHint={`Currently set to ${notificationLevelDescription}. Tap to change.`}
|
|
59
|
+
>
|
|
60
|
+
<View style={styles.settingRowInner}>
|
|
61
|
+
<View style={styles.settingRowText}>
|
|
62
|
+
<Text>Notify me for</Text>
|
|
63
|
+
{Boolean(notificationLevelDescription) && (
|
|
64
|
+
<Text variant="tertiary">{notificationLevelDescription}</Text>
|
|
65
|
+
)}
|
|
66
|
+
</View>
|
|
67
|
+
{Platform.OS === 'ios' && (
|
|
68
|
+
<Icon
|
|
69
|
+
name="general.rightChevron"
|
|
70
|
+
size={16}
|
|
71
|
+
style={styles.chevron}
|
|
72
|
+
accessibilityElementsHidden
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
</View>
|
|
76
|
+
</PlatformPressable>
|
|
51
77
|
</View>
|
|
52
78
|
)
|
|
53
79
|
}
|
|
@@ -66,7 +92,7 @@ const useStyles = () => {
|
|
|
66
92
|
},
|
|
67
93
|
sectionInner: {
|
|
68
94
|
paddingRight: 16,
|
|
69
|
-
paddingTop:
|
|
95
|
+
paddingTop: 24,
|
|
70
96
|
paddingBottom: 12,
|
|
71
97
|
borderBottomWidth: 1,
|
|
72
98
|
borderBottomColor: colors.borderColorDefaultBase,
|
|
@@ -80,13 +106,22 @@ const useStyles = () => {
|
|
|
80
106
|
groupNameBold: {
|
|
81
107
|
fontWeight: platformFontWeightBold,
|
|
82
108
|
},
|
|
83
|
-
|
|
109
|
+
settingRowPressable: {
|
|
110
|
+
paddingLeft: 16,
|
|
111
|
+
},
|
|
112
|
+
settingRowInner: {
|
|
84
113
|
flexDirection: 'row',
|
|
85
|
-
justifyContent: 'space-between',
|
|
86
114
|
alignItems: 'center',
|
|
87
|
-
|
|
115
|
+
justifyContent: 'space-between',
|
|
116
|
+
paddingRight: 16,
|
|
88
117
|
paddingVertical: 12,
|
|
89
|
-
|
|
118
|
+
},
|
|
119
|
+
settingRowText: {
|
|
120
|
+
flex: 1,
|
|
121
|
+
gap: 2,
|
|
122
|
+
},
|
|
123
|
+
chevron: {
|
|
124
|
+
color: colors.iconColorDefaultDisabled,
|
|
90
125
|
},
|
|
91
126
|
})
|
|
92
127
|
}
|
|
@@ -78,7 +78,6 @@ function MessageActionsScreenContent({
|
|
|
78
78
|
const apiClient = useApiClient()
|
|
79
79
|
const styles = useStyles()
|
|
80
80
|
const { featureEnabled } = useFeatures()
|
|
81
|
-
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
82
81
|
const expandedLink = message.attachments.find(attachment => attachment.type === 'ExpandedLink')
|
|
83
82
|
const hasExpandedLink = !!expandedLink
|
|
84
83
|
|
|
@@ -242,7 +241,7 @@ function MessageActionsScreenContent({
|
|
|
242
241
|
))}
|
|
243
242
|
</View>
|
|
244
243
|
<View style={styles.actions}>
|
|
245
|
-
{
|
|
244
|
+
{!inReplyScreen && (
|
|
246
245
|
<FormSheet.Action
|
|
247
246
|
onPress={handleReplyPress}
|
|
248
247
|
title="Reply to message"
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
1
|
+
import { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { merge } from 'lodash'
|
|
2
3
|
import { getRequestQueryKey, useApiClient } from '../../../hooks'
|
|
3
4
|
import { useSuspenseGet, useSuspensePaginator } from '../../../hooks'
|
|
4
|
-
import { ApiResource } from '../../../types'
|
|
5
|
+
import { ApiCollection, ApiResource } from '../../../types'
|
|
5
6
|
import { GroupMembership, GroupResourceWithMembership } from '../../../types/resources'
|
|
6
7
|
import { throwResponseError } from '../../../utils/response_error'
|
|
8
|
+
import { updateRecordInPagesData } from '../../../utils'
|
|
7
9
|
|
|
8
10
|
export const getGroupsRequestArgs = () => ({
|
|
9
11
|
url: '/me/groups',
|
|
@@ -13,7 +15,11 @@ export const getGroupsRequestArgs = () => ({
|
|
|
13
15
|
filter: 'user_settable_notification_level',
|
|
14
16
|
fields: {
|
|
15
17
|
Group: [],
|
|
16
|
-
GroupMembership: [
|
|
18
|
+
GroupMembership: [
|
|
19
|
+
'notification_level',
|
|
20
|
+
'notification_level_description',
|
|
21
|
+
'notification_level_options',
|
|
22
|
+
],
|
|
17
23
|
},
|
|
18
24
|
order: 'name',
|
|
19
25
|
},
|
|
@@ -25,7 +31,11 @@ export const getGroupRequestArgs = ({ groupId }: { groupId: number | string }) =
|
|
|
25
31
|
include: ['my_group_membership'],
|
|
26
32
|
fields: {
|
|
27
33
|
Group: ['name', 'source_app_name', 'source_type', 'my_group_membership'],
|
|
28
|
-
GroupMembership: [
|
|
34
|
+
GroupMembership: [
|
|
35
|
+
'notification_level',
|
|
36
|
+
'notification_level_description',
|
|
37
|
+
'notification_level_options',
|
|
38
|
+
],
|
|
29
39
|
},
|
|
30
40
|
},
|
|
31
41
|
})
|
|
@@ -39,50 +49,77 @@ export const useGroups = () => {
|
|
|
39
49
|
export const useGroup = ({ groupId }: { groupId: number | string }) => {
|
|
40
50
|
const args = getGroupRequestArgs({ groupId })
|
|
41
51
|
|
|
42
|
-
return useSuspenseGet<GroupResourceWithMembership>(args)
|
|
52
|
+
return useSuspenseGet<GroupResourceWithMembership>(args, { refetchOnMount: false })
|
|
43
53
|
}
|
|
44
54
|
|
|
55
|
+
type GroupsQueryData = InfiniteData<ApiCollection<GroupResourceWithMembership>>
|
|
56
|
+
|
|
45
57
|
export const useGroupMembershipUpdate = ({ groupId }: { groupId: number | string }) => {
|
|
46
58
|
const apiClient = useApiClient()
|
|
47
59
|
const queryClient = useQueryClient()
|
|
48
|
-
const
|
|
49
|
-
const
|
|
60
|
+
const groupRequestArgs = getGroupRequestArgs({ groupId })
|
|
61
|
+
const groupsRequestArgs = getGroupsRequestArgs()
|
|
62
|
+
const groupQueryKey = getRequestQueryKey(groupRequestArgs)
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
const enrichGroupMembership = (
|
|
65
|
+
membership: Partial<GroupMembership>
|
|
66
|
+
): Partial<GroupMembership> => {
|
|
67
|
+
const cachedGroup =
|
|
68
|
+
queryClient.getQueryData<ApiResource<GroupResourceWithMembership>>(groupQueryKey)
|
|
69
|
+
const currentOptions = cachedGroup?.data.myGroupMembership?.notificationLevelOptions ?? []
|
|
70
|
+
const { notificationLevel } = membership
|
|
71
|
+
const selectedOption = notificationLevel
|
|
72
|
+
? currentOptions.find(o => o.value === notificationLevel)
|
|
73
|
+
: undefined
|
|
74
|
+
const notificationLevelDescription =
|
|
75
|
+
membership.notificationLevelDescription ?? selectedOption?.description
|
|
56
76
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
return {
|
|
78
|
+
...membership,
|
|
79
|
+
notificationLevelDescription,
|
|
80
|
+
...(notificationLevel && {
|
|
81
|
+
notificationLevelOptions: currentOptions.map(o => ({
|
|
82
|
+
...o,
|
|
83
|
+
enabled: o.value === notificationLevel,
|
|
84
|
+
})),
|
|
85
|
+
}),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const updateGroupsMembershipCache = (membership: Partial<GroupMembership>) => {
|
|
90
|
+
queryClient.setQueryData<GroupsQueryData>(getRequestQueryKey(groupsRequestArgs), prev =>
|
|
91
|
+
updateRecordInPagesData({
|
|
92
|
+
data: prev,
|
|
93
|
+
record: { id: groupId } as GroupResourceWithMembership,
|
|
94
|
+
processRecord: (_record, current) => merge({}, current, { myGroupMembership: membership }),
|
|
67
95
|
})
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const updateGroupMembershipCache = (membership: Partial<GroupMembership>) => {
|
|
100
|
+
queryClient.setQueryData<ApiResource<GroupResourceWithMembership>>(groupQueryKey, groupData => {
|
|
101
|
+
if (!groupData?.data.myGroupMembership) return groupData
|
|
102
|
+
return merge({}, groupData, { data: { myGroupMembership: membership } })
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return useMutation({
|
|
107
|
+
throwOnError: true,
|
|
108
|
+
onMutate: membership => {
|
|
109
|
+
const enriched = enrichGroupMembership(membership)
|
|
110
|
+
updateGroupsMembershipCache(enriched)
|
|
111
|
+
updateGroupMembershipCache(enriched)
|
|
68
112
|
},
|
|
69
113
|
onSuccess: response => {
|
|
70
|
-
|
|
71
|
-
|
|
114
|
+
const enriched = enrichGroupMembership(response.data)
|
|
115
|
+
updateGroupsMembershipCache(enriched)
|
|
116
|
+
updateGroupMembershipCache(enriched)
|
|
72
117
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
...groupData.data,
|
|
77
|
-
myGroupMembership: {
|
|
78
|
-
...groupData.data.myGroupMembership,
|
|
79
|
-
...response.data,
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
}
|
|
83
|
-
})
|
|
118
|
+
// Updating conversations without access to the current filters
|
|
119
|
+
// is a hard itch to scratch so we just need to refetch.
|
|
120
|
+
queryClient.invalidateQueries({ queryKey: ['/me/conversations'] })
|
|
84
121
|
},
|
|
85
|
-
mutationFn: (
|
|
122
|
+
mutationFn: (membership: Partial<GroupMembership>) => {
|
|
86
123
|
return apiClient.chat
|
|
87
124
|
.patch<ApiResource<GroupMembership>>({
|
|
88
125
|
url: `/me/groups/${groupId}/my_group_membership`,
|
|
@@ -90,7 +127,9 @@ export const useGroupMembershipUpdate = ({ groupId }: { groupId: number | string
|
|
|
90
127
|
data: {
|
|
91
128
|
type: 'GroupMembership',
|
|
92
129
|
attributes: {
|
|
93
|
-
|
|
130
|
+
...(membership.notificationLevel !== undefined && {
|
|
131
|
+
notification_level: membership.notificationLevel,
|
|
132
|
+
}),
|
|
94
133
|
},
|
|
95
134
|
},
|
|
96
135
|
},
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import { PlatformPressable } from '@react-navigation/elements'
|
|
2
2
|
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
3
|
-
import type { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
|
4
3
|
import React, { useCallback, useEffect, type ReactNode } from 'react'
|
|
5
4
|
import { FlatList, Platform, StyleSheet, View, type ViewProps, type ViewStyle } from 'react-native'
|
|
6
5
|
import { Badge, Heading, Icon, Text } from '../components'
|
|
7
6
|
import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
|
|
8
7
|
import { useTheme } from '../hooks'
|
|
8
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
9
9
|
import { isDefined } from '../types'
|
|
10
|
-
import type { GroupNotificationSettingsScreenProps } from './group_notification_settings_screen'
|
|
11
10
|
import { useGroups } from './notification_settings/hooks/groups'
|
|
12
11
|
import { useChatTypes } from './preferred_app/hooks/use_chat_types'
|
|
13
|
-
import type { PreferredAppSelectionScreenProps } from './preferred_app_selection_screen'
|
|
14
12
|
|
|
15
13
|
// =========================================
|
|
16
14
|
// ====== Factory Constants & Types ========
|
|
17
15
|
// =========================================
|
|
18
16
|
|
|
19
|
-
type NotificationSettingsStackParamList = {
|
|
20
|
-
NotificationSettings: {}
|
|
21
|
-
PreferredAppSelection: PreferredAppSelectionScreenProps['route']['params']
|
|
22
|
-
GroupNotificationSettings: GroupNotificationSettingsScreenProps['route']['params']
|
|
23
|
-
}
|
|
24
|
-
|
|
25
17
|
enum SectionTypes {
|
|
26
18
|
header,
|
|
27
19
|
hidden,
|
|
@@ -53,7 +45,7 @@ interface DataItem<T, TName extends SectionTypes> {
|
|
|
53
45
|
export type NotificationSettingsScreenProps = StaticScreenProps<{}>
|
|
54
46
|
|
|
55
47
|
export function NotificationSettingsScreen({}: NotificationSettingsScreenProps) {
|
|
56
|
-
const navigation = useNavigation
|
|
48
|
+
const navigation = useNavigation()
|
|
57
49
|
const styles = useStyles()
|
|
58
50
|
const { data: chatTypes } = useChatTypes()
|
|
59
51
|
const { data: groups } = useGroups()
|
|
@@ -109,14 +101,14 @@ export function NotificationSettingsScreen({}: NotificationSettingsScreenProps)
|
|
|
109
101
|
showBottomBorder: true,
|
|
110
102
|
})),
|
|
111
103
|
{
|
|
112
|
-
type: SectionTypes.header,
|
|
104
|
+
type: groups.length === 0 ? SectionTypes.hidden : SectionTypes.header,
|
|
113
105
|
data: {
|
|
114
106
|
title: 'Manage chat settings',
|
|
115
107
|
},
|
|
116
108
|
sectionInnerStyle: styles.sectionInnerHeader,
|
|
117
109
|
},
|
|
118
110
|
{
|
|
119
|
-
type: SectionTypes.view,
|
|
111
|
+
type: groups.length === 0 ? SectionTypes.hidden : SectionTypes.view,
|
|
120
112
|
data: {
|
|
121
113
|
children: (
|
|
122
114
|
<Text variant="tertiary" style={styles.sectionDescription}>
|
|
@@ -130,6 +122,7 @@ export function NotificationSettingsScreen({}: NotificationSettingsScreenProps)
|
|
|
130
122
|
type: SectionTypes.link,
|
|
131
123
|
data: {
|
|
132
124
|
title: group.name,
|
|
125
|
+
subtitle: group.myGroupMembership?.notificationLevelDescription,
|
|
133
126
|
rightLabel: group.sourceType,
|
|
134
127
|
onPress: () =>
|
|
135
128
|
navigation.navigate('GroupNotificationSettings', {
|
|
@@ -206,6 +199,7 @@ export function NotificationSettingsScreen({}: NotificationSettingsScreenProps)
|
|
|
206
199
|
<LinkRow {...item.data} />
|
|
207
200
|
</ListSection>
|
|
208
201
|
)
|
|
202
|
+
case SectionTypes.hidden:
|
|
209
203
|
default:
|
|
210
204
|
return null
|
|
211
205
|
}
|
|
@@ -268,11 +262,12 @@ function SettingRow({ title, style, rightLabel, rightItem, rightItemStyle = {} }
|
|
|
268
262
|
|
|
269
263
|
interface LinkRowProps {
|
|
270
264
|
title: string
|
|
265
|
+
subtitle?: string
|
|
271
266
|
rightLabel: string
|
|
272
267
|
onPress: () => void
|
|
273
268
|
}
|
|
274
269
|
|
|
275
|
-
function LinkRow({ title, rightLabel, onPress }: LinkRowProps) {
|
|
270
|
+
function LinkRow({ title, subtitle, rightLabel, onPress }: LinkRowProps) {
|
|
276
271
|
const styles = useLinkRowStyles()
|
|
277
272
|
const isSourceType = rightLabel === 'Team' || rightLabel === 'Group' || rightLabel === 'PlanTeam'
|
|
278
273
|
|
|
@@ -285,9 +280,12 @@ function LinkRow({ title, rightLabel, onPress }: LinkRowProps) {
|
|
|
285
280
|
accessibilityHint={`Navigate to ${title} settings`}
|
|
286
281
|
>
|
|
287
282
|
<View style={styles.innerContainer}>
|
|
288
|
-
<
|
|
289
|
-
{title}
|
|
290
|
-
|
|
283
|
+
<View style={styles.leftContent}>
|
|
284
|
+
<Text style={styles.title} numberOfLines={2}>
|
|
285
|
+
{title}
|
|
286
|
+
</Text>
|
|
287
|
+
{Boolean(subtitle) && <Text variant="footnote">{subtitle}</Text>}
|
|
288
|
+
</View>
|
|
291
289
|
<View style={styles.rightContent}>
|
|
292
290
|
{isSourceType ? (
|
|
293
291
|
<Badge label={rightLabel} appearance="neutral" variant="meta" />
|
|
@@ -311,13 +309,16 @@ function LinkRow({ title, rightLabel, onPress }: LinkRowProps) {
|
|
|
311
309
|
|
|
312
310
|
const useStyles = ({}: { isStart?: boolean; isEnd?: boolean } = {}) => {
|
|
313
311
|
const { colors } = useTheme()
|
|
312
|
+
const { bottom } = useSafeAreaInsets()
|
|
314
313
|
const headerBottomPadding = 0
|
|
315
314
|
|
|
316
315
|
return StyleSheet.create({
|
|
317
316
|
listContainer: {
|
|
318
317
|
flex: 1,
|
|
319
318
|
},
|
|
320
|
-
contentContainer: {
|
|
319
|
+
contentContainer: {
|
|
320
|
+
paddingBottom: bottom,
|
|
321
|
+
},
|
|
321
322
|
sectionOuterBase: {
|
|
322
323
|
paddingLeft: 16,
|
|
323
324
|
},
|
|
@@ -326,6 +327,7 @@ const useStyles = ({}: { isStart?: boolean; isEnd?: boolean } = {}) => {
|
|
|
326
327
|
paddingVertical: 16,
|
|
327
328
|
},
|
|
328
329
|
sectionInnerHeader: {
|
|
330
|
+
paddingTop: 24,
|
|
329
331
|
paddingBottom: headerBottomPadding,
|
|
330
332
|
},
|
|
331
333
|
sectionInnerBottomBorder: {
|
|
@@ -364,15 +366,16 @@ const useLinkRowStyles = () => {
|
|
|
364
366
|
justifyContent: 'space-between',
|
|
365
367
|
gap: 12,
|
|
366
368
|
},
|
|
369
|
+
leftContent: {
|
|
370
|
+
flex: 1,
|
|
371
|
+
gap: 4,
|
|
372
|
+
},
|
|
367
373
|
rightContent: {
|
|
368
374
|
flexDirection: 'row',
|
|
369
375
|
alignItems: 'center',
|
|
370
376
|
gap: 8,
|
|
371
377
|
},
|
|
372
|
-
title: {
|
|
373
|
-
flexShrink: 1,
|
|
374
|
-
alignSelf: 'center',
|
|
375
|
-
},
|
|
378
|
+
title: {},
|
|
376
379
|
rightLabelText: {
|
|
377
380
|
color: theme.colors.textColorDefaultSecondary,
|
|
378
381
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AnalyticsMetadataResource } from './analytics_metadata'
|
|
2
2
|
import { ConversationBadgeResource } from './conversation_badge'
|
|
3
|
+
import { ConversationMembershipResource } from './conversation_membership'
|
|
3
4
|
import { GroupResource } from './group_resource'
|
|
4
5
|
import { MemberAbilityResource } from './member_ability'
|
|
5
6
|
|
|
@@ -8,9 +9,7 @@ export interface ConversationResource {
|
|
|
8
9
|
id: number
|
|
9
10
|
badges?: ConversationBadgeResource[]
|
|
10
11
|
analyticsMetadata?: AnalyticsMetadataResource
|
|
11
|
-
conversationMembership?:
|
|
12
|
-
lastReadMessageSortKey: string
|
|
13
|
-
}
|
|
12
|
+
conversationMembership?: Partial<ConversationMembershipResource>
|
|
14
13
|
createdAt: string
|
|
15
14
|
deleted?: boolean
|
|
16
15
|
groups?: GroupResource[]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ResourceObject } from '../api_primitives'
|
|
2
|
+
|
|
3
|
+
export type NotificationLevelValue = 'everything' | 'nothing'
|
|
4
|
+
export type NotificationLevelDescription = string
|
|
5
|
+
|
|
6
|
+
export interface ConversationMembershipResource extends ResourceObject {
|
|
7
|
+
type: 'ConversationMembership'
|
|
8
|
+
lastReadMessageSortKey: string
|
|
9
|
+
muted: boolean
|
|
10
|
+
notificationLevel: NotificationLevelValue
|
|
11
|
+
notificationLevelDescription: NotificationLevelDescription
|
|
12
|
+
notificationLevelOptions: Array<{
|
|
13
|
+
description: NotificationLevelDescription
|
|
14
|
+
enabled: boolean
|
|
15
|
+
value: NotificationLevelValue
|
|
16
|
+
}>
|
|
17
|
+
}
|