@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
|
@@ -45,7 +45,6 @@ interface MessageProps extends MessageResource {
|
|
|
45
45
|
conversation_id: number
|
|
46
46
|
latestReadMessageSortKey?: string
|
|
47
47
|
inReplyScreen?: boolean
|
|
48
|
-
repliesEnabled?: boolean
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
export function Message({
|
|
@@ -53,7 +52,6 @@ export function Message({
|
|
|
53
52
|
conversation_id,
|
|
54
53
|
latestReadMessageSortKey,
|
|
55
54
|
inReplyScreen,
|
|
56
|
-
repliesEnabled,
|
|
57
55
|
...message
|
|
58
56
|
}: MessageProps) {
|
|
59
57
|
const { text, reactionCounts, pending, error, attachments, author } = message
|
|
@@ -85,11 +83,10 @@ export function Message({
|
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
const renderAuthor = (!message.mine && message.renderAuthor) || false
|
|
88
|
-
const showReplyCountButton =
|
|
89
|
-
!inReplyScreen && message.replyRootId === message.id && repliesEnabled
|
|
86
|
+
const showReplyCountButton = !inReplyScreen && message.replyRootId === message.id
|
|
90
87
|
const isReplyRootMessage = message.replyRootId === message.id
|
|
91
88
|
const isDeletedReplyRootMessage = isReplyRootMessage && !!message.deletedAt
|
|
92
|
-
const replyToReplyRootMessage =
|
|
89
|
+
const replyToReplyRootMessage = !isReplyRootMessage && !!message.replyRootId
|
|
93
90
|
|
|
94
91
|
const replyCountText = pluralize(message.replyCount, 'reply')
|
|
95
92
|
const messagePendingLabel = isPersisted ? 'Saving' : 'Sending'
|
|
@@ -221,9 +218,7 @@ export function Message({
|
|
|
221
218
|
) : (
|
|
222
219
|
<View style={styles.avatarPlaceholder} />
|
|
223
220
|
)}
|
|
224
|
-
{
|
|
225
|
-
<TheirReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
|
|
226
|
-
)}
|
|
221
|
+
<TheirReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
|
|
227
222
|
</View>
|
|
228
223
|
)}
|
|
229
224
|
<View style={[styles.messageContent, { marginBottom: messageBottomMargin }]}>
|
|
@@ -332,7 +327,7 @@ export function Message({
|
|
|
332
327
|
</View>
|
|
333
328
|
)}
|
|
334
329
|
</View>
|
|
335
|
-
{
|
|
330
|
+
{message.mine && (
|
|
336
331
|
<MyReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
|
|
337
332
|
)}
|
|
338
333
|
</Animated.View>
|
|
@@ -29,7 +29,6 @@ import {
|
|
|
29
29
|
platformFontWeightMedium,
|
|
30
30
|
platformPressedOpacityStyle,
|
|
31
31
|
} from '../../utils'
|
|
32
|
-
import { availableFeatures, useFeatures } from '../../hooks/use_features'
|
|
33
32
|
import { useAttachmentUploader } from '../../hooks/use_attachment_uploader'
|
|
34
33
|
import { useMessageDraft } from '../../hooks/use_message_draft'
|
|
35
34
|
import {
|
|
@@ -571,11 +570,9 @@ function EditingIndicator() {
|
|
|
571
570
|
function ReplyIndicator() {
|
|
572
571
|
const { replyRootId, replyRootAuthorFirstName } = React.useContext(MessageFormContext)
|
|
573
572
|
const navigation = useNavigation()
|
|
574
|
-
const { featureEnabled } = useFeatures()
|
|
575
|
-
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
576
573
|
const title = replyRootAuthorFirstName ? `Reply to ${replyRootAuthorFirstName}` : 'Reply'
|
|
577
574
|
|
|
578
|
-
if (!
|
|
575
|
+
if (!replyRootId) return null
|
|
579
576
|
|
|
580
577
|
return (
|
|
581
578
|
<FormIndicatorRow
|
|
@@ -2,30 +2,22 @@ import { StyleSheet, View, type ViewStyle } from 'react-native'
|
|
|
2
2
|
import { useTheme } from '../../hooks'
|
|
3
3
|
import { Text } from '../display'
|
|
4
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
5
|
-
import { useFeatures } from '../../hooks/use_features'
|
|
6
|
-
import { availableFeatures } from '../../hooks/use_features'
|
|
7
5
|
|
|
8
6
|
export const LeaderMessagesDisabledBanner = () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const description = repliesEnabled
|
|
13
|
-
? 'Only leaders can send messages in this conversation.'
|
|
14
|
-
: 'Replies are frozen for everyone else.'
|
|
15
|
-
|
|
16
|
-
return <MessagesDisabledBanner description={description} />
|
|
7
|
+
return (
|
|
8
|
+
<MessagesDisabledBanner description="Only leaders can send messages in this conversation." />
|
|
9
|
+
)
|
|
17
10
|
}
|
|
18
11
|
|
|
19
12
|
export const MemberMessagesDisabledBanner = () => {
|
|
20
13
|
const styles = useStyles()
|
|
21
|
-
const { featureEnabled } = useFeatures()
|
|
22
|
-
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
15
|
+
return (
|
|
16
|
+
<MessagesDisabledBanner
|
|
17
|
+
description="Only leaders can send messages, but you can still add reactions."
|
|
18
|
+
style={styles.memberBanner}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
29
21
|
}
|
|
30
22
|
|
|
31
23
|
interface MessagesDisabledBannerProps {
|
|
@@ -91,7 +91,7 @@ export function ConversationActions({
|
|
|
91
91
|
const accessibilityActions = [
|
|
92
92
|
{
|
|
93
93
|
name: muted ? 'unmute' : 'mute',
|
|
94
|
-
label: muted ? '
|
|
94
|
+
label: muted ? 'Enable' : 'Silence',
|
|
95
95
|
},
|
|
96
96
|
...readAccessibilityAction,
|
|
97
97
|
]
|
|
@@ -182,8 +182,8 @@ function LeftActions({
|
|
|
182
182
|
const styles = useStyles()
|
|
183
183
|
|
|
184
184
|
const muteToggleContent = {
|
|
185
|
-
true: { iconName: 'general.bell', label: '
|
|
186
|
-
false: { iconName: 'general.bellMuted', label: '
|
|
185
|
+
true: { iconName: 'general.bell', label: 'Enable' },
|
|
186
|
+
false: { iconName: 'general.bellMuted', label: 'Silence' },
|
|
187
187
|
} as const
|
|
188
188
|
|
|
189
189
|
const latestMessageUnreadToggleContent = {
|
|
@@ -38,7 +38,13 @@ export const getConversationRequestArgs = ({ conversation_id }: { conversation_i
|
|
|
38
38
|
'can_reply',
|
|
39
39
|
'can_delete_non_authored_messages',
|
|
40
40
|
],
|
|
41
|
-
ConversationMembership: [
|
|
41
|
+
ConversationMembership: [
|
|
42
|
+
'last_read_message_sort_key',
|
|
43
|
+
'notification_level',
|
|
44
|
+
'notification_level_description',
|
|
45
|
+
'notification_level_options',
|
|
46
|
+
'muted',
|
|
47
|
+
],
|
|
42
48
|
ConversationBadge: ['app_name', 'pco_resource_type', 'text'],
|
|
43
49
|
Group: ['type', 'id', 'links', 'name', 'source_app_name', 'source_type'],
|
|
44
50
|
},
|
|
@@ -72,6 +78,9 @@ export const useConversationMute = ({ conversation_id }: { conversation_id: numb
|
|
|
72
78
|
if (!prev?.data) return prev
|
|
73
79
|
setValue(muted)
|
|
74
80
|
prev.data.muted = muted
|
|
81
|
+
if (prev.data.conversationMembership) {
|
|
82
|
+
prev.data.conversationMembership.muted = muted
|
|
83
|
+
}
|
|
75
84
|
|
|
76
85
|
return prev
|
|
77
86
|
})
|
|
@@ -93,6 +102,9 @@ export const useConversationMute = ({ conversation_id }: { conversation_id: numb
|
|
|
93
102
|
// Posting to the mute action endpoint can't return all the fields
|
|
94
103
|
// so we need to set only the fields we require
|
|
95
104
|
prev.data.muted = response.data.muted
|
|
105
|
+
if (prev.data.conversationMembership) {
|
|
106
|
+
prev.data.conversationMembership.muted = response.data.muted
|
|
107
|
+
}
|
|
96
108
|
setValue(response.data.muted)
|
|
97
109
|
|
|
98
110
|
return prev
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Updater, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { ApiResource, ConversationMembershipResource, ConversationResource } from '../types'
|
|
3
|
+
import { useApiClient } from './use_api_client'
|
|
4
|
+
import { getConversationRequestArgs } from './use_conversation'
|
|
5
|
+
import { getRequestQueryKey } from './use_suspense_api'
|
|
6
|
+
import { deepSnakeCaseKeys } from '../utils/deep_snake_case_keys'
|
|
7
|
+
import { throwResponseError } from '../utils/response_error'
|
|
8
|
+
|
|
9
|
+
export const useConversationMembershipUpdate = ({
|
|
10
|
+
conversation_id,
|
|
11
|
+
}: {
|
|
12
|
+
conversation_id: number
|
|
13
|
+
}) => {
|
|
14
|
+
const apiClient = useApiClient()
|
|
15
|
+
const queryClient = useQueryClient()
|
|
16
|
+
const requestArgs = getConversationRequestArgs({ conversation_id })
|
|
17
|
+
const conversationMembershipFields = requestArgs.data.fields.ConversationMembership.join(',')
|
|
18
|
+
const queryKey = getRequestQueryKey(requestArgs)
|
|
19
|
+
|
|
20
|
+
return useMutation({
|
|
21
|
+
throwOnError: true,
|
|
22
|
+
onMutate: async (attributes: Partial<ConversationMembershipResource>) => {
|
|
23
|
+
queryClient.setQueryData<ApiResource<ConversationResource>>(
|
|
24
|
+
queryKey,
|
|
25
|
+
updateConversationMembershipAttributes(attributes)
|
|
26
|
+
)
|
|
27
|
+
},
|
|
28
|
+
mutationKey: ['updateConversationMembership', conversation_id],
|
|
29
|
+
mutationFn: async (attributes: Partial<ConversationMembershipResource>) => {
|
|
30
|
+
return apiClient.chat
|
|
31
|
+
.patch<ApiResource<ConversationMembershipResource>>({
|
|
32
|
+
url: `/me/conversations/${conversation_id}/conversation_membership`,
|
|
33
|
+
data: {
|
|
34
|
+
data: {
|
|
35
|
+
type: 'ConversationMembership',
|
|
36
|
+
attributes: deepSnakeCaseKeys(attributes),
|
|
37
|
+
},
|
|
38
|
+
fields: {
|
|
39
|
+
ConversationMembership: conversationMembershipFields,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
.catch(throwResponseError)
|
|
44
|
+
},
|
|
45
|
+
onSuccess: response => {
|
|
46
|
+
const membership = response.data
|
|
47
|
+
|
|
48
|
+
queryClient.setQueryData<ApiResource<ConversationResource>>(
|
|
49
|
+
queryKey,
|
|
50
|
+
updateConversationMembershipAttributes(membership)
|
|
51
|
+
)
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const updateConversationMembershipAttributes =
|
|
57
|
+
(
|
|
58
|
+
membership: Partial<ConversationMembershipResource>
|
|
59
|
+
): Updater<
|
|
60
|
+
ApiResource<ConversationResource> | undefined,
|
|
61
|
+
ApiResource<ConversationResource> | undefined
|
|
62
|
+
> =>
|
|
63
|
+
conversationData =>
|
|
64
|
+
mergeConversationMembership(conversationData, membership)
|
|
65
|
+
|
|
66
|
+
const mergeConversationMembership = (
|
|
67
|
+
conversationData: ApiResource<ConversationResource> | undefined,
|
|
68
|
+
membership: Partial<ConversationMembershipResource>
|
|
69
|
+
) => {
|
|
70
|
+
if (!conversationData) return undefined
|
|
71
|
+
if (!conversationData.data) return conversationData
|
|
72
|
+
if (!conversationData.data.conversationMembership) return conversationData
|
|
73
|
+
|
|
74
|
+
const previousMembership: Partial<ConversationMembershipResource> =
|
|
75
|
+
conversationData.data.conversationMembership || {}
|
|
76
|
+
|
|
77
|
+
const mergedMembership: Partial<ConversationMembershipResource> = {
|
|
78
|
+
...previousMembership,
|
|
79
|
+
...membership,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const data: ConversationResource = {
|
|
83
|
+
...conversationData.data,
|
|
84
|
+
conversationMembership: mergedMembership,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
...conversationData,
|
|
89
|
+
data,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -67,6 +67,10 @@ export const useConversationsMute = ({ conversation }: { conversation: Conversat
|
|
|
67
67
|
update({
|
|
68
68
|
...conversation,
|
|
69
69
|
muted,
|
|
70
|
+
conversationMembership: {
|
|
71
|
+
...conversation?.conversationMembership,
|
|
72
|
+
muted,
|
|
73
|
+
},
|
|
70
74
|
})
|
|
71
75
|
},
|
|
72
76
|
mutationKey: ['muteConversation'],
|
|
@@ -34,8 +34,8 @@ export function useFeatures() {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export const availableFeatures = {
|
|
37
|
-
threaded_replies: 'ROLLOUT_MOBILE_threaded_replies_v1',
|
|
38
37
|
message_reporting: 'ROLLOUT_MOBILE_message_reporting',
|
|
38
|
+
granular_notifications_ui: 'ROLLOUT_granular_notification_preferences_ui',
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const stableEmptyFeatures: ApiCollection<FeatureResource> = {
|
package/src/navigation/index.tsx
CHANGED
|
@@ -56,6 +56,14 @@ import { NotFound } from '../screens/not_found'
|
|
|
56
56
|
import { NotificationSettingsScreen } from '../screens/notification_settings_screen'
|
|
57
57
|
import { PreferredAppSelectionScreen } from '../screens/preferred_app_selection_screen'
|
|
58
58
|
import { GroupNotificationSettingsScreen } from '../screens/group_notification_settings_screen'
|
|
59
|
+
import {
|
|
60
|
+
GroupNotificationLevelSelectScreen,
|
|
61
|
+
GroupNotificationLevelSelectScreenOptions,
|
|
62
|
+
} from '../screens/group_notification_level_select_screen'
|
|
63
|
+
import {
|
|
64
|
+
ConversationNotificationLevelSelectScreen,
|
|
65
|
+
ConversationNotificationLevelSelectScreenOptions,
|
|
66
|
+
} from '../screens/conversation_notification_level_select_screen'
|
|
59
67
|
import { ReactionsScreen, ReactionsScreenOptions } from '../screens/reactions_screen'
|
|
60
68
|
import { ScreenLayoutWithChatAccessGate } from './screenLayout'
|
|
61
69
|
import { SendGiphyScreen, SendGiphyScreenOptions } from '../screens/send_giphy_screen'
|
|
@@ -163,13 +171,15 @@ export const ChatStack = createNativeStackNavigator({
|
|
|
163
171
|
screen: ConversationScreen,
|
|
164
172
|
options: ({ route, navigation }) => ({
|
|
165
173
|
headerTitle: (props: NativeStackHeaderRightProps) => {
|
|
166
|
-
const { conversation_id, title, badge, deleted } =
|
|
174
|
+
const { conversation_id, title, badge, deleted, muted } =
|
|
175
|
+
route.params as ConversationRouteProps
|
|
167
176
|
|
|
168
177
|
return (
|
|
169
178
|
<ConversationScreenTitle
|
|
170
179
|
conversation_id={conversation_id}
|
|
171
180
|
badge={badge}
|
|
172
181
|
deleted={deleted}
|
|
182
|
+
muted={muted}
|
|
173
183
|
{...props}
|
|
174
184
|
>
|
|
175
185
|
{title ?? 'Conversation'}
|
|
@@ -259,6 +269,14 @@ export const ChatStack = createNativeStackNavigator({
|
|
|
259
269
|
),
|
|
260
270
|
}),
|
|
261
271
|
},
|
|
272
|
+
GroupNotificationLevelSelect: {
|
|
273
|
+
screen: GroupNotificationLevelSelectScreen,
|
|
274
|
+
options: GroupNotificationLevelSelectScreenOptions,
|
|
275
|
+
},
|
|
276
|
+
ConversationNotificationLevelSelect: {
|
|
277
|
+
screen: ConversationNotificationLevelSelectScreen,
|
|
278
|
+
options: ConversationNotificationLevelSelectScreenOptions,
|
|
279
|
+
},
|
|
262
280
|
New: {
|
|
263
281
|
screen: NewConversationStack,
|
|
264
282
|
if: useQualifiedByAge,
|
|
@@ -1,25 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HeaderTitle as ElementsHeaderTitle,
|
|
3
|
+
HeaderTitleProps,
|
|
4
|
+
PlatformPressable,
|
|
5
|
+
} from '@react-navigation/elements'
|
|
1
6
|
import { StackActions, StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
2
7
|
import React, {
|
|
3
8
|
useCallback,
|
|
4
9
|
useEffect,
|
|
10
|
+
useRef,
|
|
5
11
|
useState,
|
|
6
|
-
type SetStateAction,
|
|
7
12
|
type Dispatch,
|
|
8
13
|
type ReactNode,
|
|
9
|
-
|
|
14
|
+
type SetStateAction,
|
|
10
15
|
} from 'react'
|
|
11
16
|
import {
|
|
17
|
+
Alert,
|
|
12
18
|
FlatList,
|
|
19
|
+
Platform,
|
|
20
|
+
Pressable,
|
|
13
21
|
StyleSheet,
|
|
14
22
|
TextInput,
|
|
15
23
|
View,
|
|
16
|
-
type ViewStyle,
|
|
17
24
|
type ViewProps,
|
|
18
|
-
|
|
19
|
-
Alert,
|
|
20
|
-
Platform,
|
|
25
|
+
type ViewStyle,
|
|
21
26
|
} from 'react-native'
|
|
22
|
-
import { HeaderTitle as ElementsHeaderTitle, HeaderTitleProps } from '@react-navigation/elements'
|
|
23
27
|
import {
|
|
24
28
|
Badge,
|
|
25
29
|
ChildNotice,
|
|
@@ -31,6 +35,8 @@ import {
|
|
|
31
35
|
TextButton,
|
|
32
36
|
type TextStyle,
|
|
33
37
|
} from '../components'
|
|
38
|
+
import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
|
|
39
|
+
import { ButtonAppearanceUnion } from '../components/display/utils/button_colors'
|
|
34
40
|
import { useSuspensePaginator, useTheme } from '../hooks'
|
|
35
41
|
import {
|
|
36
42
|
useConversation,
|
|
@@ -39,14 +45,11 @@ import {
|
|
|
39
45
|
useConversationMute,
|
|
40
46
|
useConversationUpdate,
|
|
41
47
|
} from '../hooks/use_conversation'
|
|
48
|
+
|
|
49
|
+
import { availableFeatures, useFeatures } from '../hooks/use_features'
|
|
42
50
|
import { MemberResource, isDefined } from '../types'
|
|
43
|
-
import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
|
|
44
|
-
import { tokens } from '../vendor/tapestry/tokens'
|
|
45
|
-
import { ButtonAppearanceUnion } from '../components/display/utils/button_colors'
|
|
46
51
|
import { GroupResource } from '../types/resources/group_resource'
|
|
47
|
-
import {
|
|
48
|
-
import { availableFeatures } from '../hooks/use_features'
|
|
49
|
-
|
|
52
|
+
import { tokens } from '../vendor/tapestry/tokens'
|
|
50
53
|
// =========================================
|
|
51
54
|
// ====== Factory Constants & Types ========
|
|
52
55
|
// =========================================
|
|
@@ -56,6 +59,7 @@ enum SectionTypes {
|
|
|
56
59
|
hidden,
|
|
57
60
|
members,
|
|
58
61
|
loadingMembers,
|
|
62
|
+
navigableSetting,
|
|
59
63
|
setting,
|
|
60
64
|
view,
|
|
61
65
|
}
|
|
@@ -64,6 +68,7 @@ type SectionListData = Array<
|
|
|
64
68
|
| DataItem<{ title: string }, SectionTypes.header>
|
|
65
69
|
| DataItem<MemberResource, SectionTypes.members>
|
|
66
70
|
| DataItem<any, SectionTypes.loadingMembers>
|
|
71
|
+
| DataItem<NavigableSettingRowProps, SectionTypes.navigableSetting>
|
|
67
72
|
| DataItem<ViewProps, SectionTypes.view>
|
|
68
73
|
| DataItem<SettingRowProps, SectionTypes.setting>
|
|
69
74
|
| DataItem<any, SectionTypes.hidden>
|
|
@@ -91,16 +96,19 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
91
96
|
|
|
92
97
|
const { data: conversation } = useConversation(route.params)
|
|
93
98
|
const [title, setTitle] = useState(conversation.title)
|
|
94
|
-
const {
|
|
99
|
+
const { conversation_id } = route.params
|
|
95
100
|
const { repliesDisabled, setRepliesDisabled } = useConversationDisableReplies(route.params)
|
|
96
101
|
const { mutate: saveTitle } = useConversationUpdate(route.params)
|
|
97
102
|
const { mutate: deleteConversation } = useConversationDelete(route.params)
|
|
98
103
|
const { featureEnabled } = useFeatures()
|
|
99
|
-
const
|
|
104
|
+
const granularNotificationsEnabled = featureEnabled(availableFeatures.granular_notifications_ui)
|
|
105
|
+
const { muted, setMuted } = useConversationMute({ conversation_id })
|
|
100
106
|
|
|
101
107
|
const trimmedTitle = title.trim()
|
|
102
108
|
const emptyTitle = trimmedTitle === '' || title === null
|
|
103
109
|
|
|
110
|
+
const notificationLevelDescription =
|
|
111
|
+
conversation?.conversationMembership?.notificationLevelDescription
|
|
104
112
|
const canUpdate = conversation.memberAbility?.canUpdate || false
|
|
105
113
|
const canDelete = conversation.memberAbility?.canDelete || false
|
|
106
114
|
const isLeader = conversation.memberAbility?.leader || false
|
|
@@ -243,19 +251,29 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
243
251
|
showBottomBorder: true,
|
|
244
252
|
sectionInnerStyle: styles.sectionInnerHeaderWithBottomBorder,
|
|
245
253
|
},
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
+
granularNotificationsEnabled
|
|
255
|
+
? {
|
|
256
|
+
type: SectionTypes.navigableSetting,
|
|
257
|
+
data: {
|
|
258
|
+
title: 'Notifications',
|
|
259
|
+
subtitle: notificationLevelDescription,
|
|
260
|
+
onPress: () =>
|
|
261
|
+
navigation.navigate('ConversationNotificationLevelSelect', { conversation_id }),
|
|
262
|
+
},
|
|
263
|
+
showBottomBorder: true,
|
|
264
|
+
}
|
|
265
|
+
: {
|
|
266
|
+
type: SectionTypes.setting,
|
|
267
|
+
data: {
|
|
268
|
+
title: 'Mute',
|
|
269
|
+
rightItem: <Switch value={muted} onValueChange={value => setMuted(value)} />,
|
|
270
|
+
},
|
|
271
|
+
showBottomBorder: true,
|
|
272
|
+
},
|
|
254
273
|
{
|
|
255
274
|
type: canUpdate ? SectionTypes.setting : SectionTypes.hidden,
|
|
256
275
|
data: {
|
|
257
|
-
title:
|
|
258
|
-
subtitle: repliesEnabled ? undefined : 'Disables replies for everyone except leaders.',
|
|
276
|
+
title: 'Only leaders can message',
|
|
259
277
|
rightItem: (
|
|
260
278
|
<Switch value={repliesDisabled} onChange={() => setRepliesDisabled(!repliesDisabled)} />
|
|
261
279
|
),
|
|
@@ -350,6 +368,18 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
350
368
|
<Text>Loading more...</Text>
|
|
351
369
|
</ListSection>
|
|
352
370
|
)
|
|
371
|
+
case SectionTypes.navigableSetting:
|
|
372
|
+
return (
|
|
373
|
+
<ListSection
|
|
374
|
+
isStart={isStart}
|
|
375
|
+
isEnd={isEnd}
|
|
376
|
+
showBottomBorder={item?.showBottomBorder}
|
|
377
|
+
outerStyle={item?.sectionOuterStyle}
|
|
378
|
+
innerStyle={item?.sectionInnerStyle}
|
|
379
|
+
>
|
|
380
|
+
<NavigableSettingRow {...item.data} />
|
|
381
|
+
</ListSection>
|
|
382
|
+
)
|
|
353
383
|
case SectionTypes.setting:
|
|
354
384
|
return (
|
|
355
385
|
<ListSection
|
|
@@ -459,6 +489,12 @@ function TitleInput({ canUpdate, title, setTitle, style, isEmpty }: InputProps)
|
|
|
459
489
|
)
|
|
460
490
|
}
|
|
461
491
|
|
|
492
|
+
interface NavigableSettingRowProps {
|
|
493
|
+
title: string
|
|
494
|
+
subtitle?: string
|
|
495
|
+
onPress: () => void
|
|
496
|
+
}
|
|
497
|
+
|
|
462
498
|
interface SettingRowProps {
|
|
463
499
|
title: string
|
|
464
500
|
style?: ViewStyle
|
|
@@ -510,6 +546,30 @@ function SettingRow({
|
|
|
510
546
|
)
|
|
511
547
|
}
|
|
512
548
|
|
|
549
|
+
function NavigableSettingRow({ title, subtitle, onPress }: NavigableSettingRowProps) {
|
|
550
|
+
const styles = useStyles()
|
|
551
|
+
return (
|
|
552
|
+
<PlatformPressable onPress={onPress} accessibilityRole="button">
|
|
553
|
+
<View style={styles.settingRow}>
|
|
554
|
+
<View style={styles.settingRowContent}>
|
|
555
|
+
<Text variant="plain" style={styles.settingRowText}>
|
|
556
|
+
{title}
|
|
557
|
+
</Text>
|
|
558
|
+
{Boolean(subtitle) && <Text variant="footnote">{subtitle}</Text>}
|
|
559
|
+
</View>
|
|
560
|
+
{Platform.OS === 'ios' && (
|
|
561
|
+
<Icon
|
|
562
|
+
name="general.rightChevron"
|
|
563
|
+
size={16}
|
|
564
|
+
style={styles.navigableSettingChevron}
|
|
565
|
+
accessibilityElementsHidden
|
|
566
|
+
/>
|
|
567
|
+
)}
|
|
568
|
+
</View>
|
|
569
|
+
</PlatformPressable>
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
|
|
513
573
|
function TeamsGroup({ teams }: { teams: GroupResource[] }) {
|
|
514
574
|
const styles = useStyles()
|
|
515
575
|
|
|
@@ -628,6 +688,9 @@ const useStyles = ({ isStart, isEnd }: { isStart?: boolean; isEnd?: boolean } =
|
|
|
628
688
|
settingRowText: {
|
|
629
689
|
lineHeight: 20,
|
|
630
690
|
},
|
|
691
|
+
navigableSettingChevron: {
|
|
692
|
+
color: colors.iconColorDefaultDisabled,
|
|
693
|
+
},
|
|
631
694
|
teamGroup: {
|
|
632
695
|
flexDirection: 'row',
|
|
633
696
|
gap: 4,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { StyleSheet } from 'react-native'
|
|
4
|
+
import FormSheet, { getFormSheetScreenOptions } from '../components/primitive/form_sheet'
|
|
5
|
+
import { PressableRow } from '../components'
|
|
6
|
+
import { useTheme } from '../hooks'
|
|
7
|
+
import { useConversation } from '../hooks/use_conversation'
|
|
8
|
+
import { useConversationMembershipUpdate } from '../hooks/use_conversation_membership'
|
|
9
|
+
import { NotificationLevelValue } from '../types'
|
|
10
|
+
|
|
11
|
+
export const ConversationNotificationLevelSelectScreenOptions = getFormSheetScreenOptions({
|
|
12
|
+
headerTitle: 'Notification level',
|
|
13
|
+
sheetAllowedDetents: [0.35],
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export type ConversationNotificationLevelSelectScreenProps = StaticScreenProps<{
|
|
17
|
+
conversation_id: number
|
|
18
|
+
}>
|
|
19
|
+
|
|
20
|
+
export function ConversationNotificationLevelSelectScreen({
|
|
21
|
+
route,
|
|
22
|
+
}: ConversationNotificationLevelSelectScreenProps) {
|
|
23
|
+
const { conversation_id } = route.params
|
|
24
|
+
const navigation = useNavigation()
|
|
25
|
+
const styles = useStyles()
|
|
26
|
+
const { colors } = useTheme()
|
|
27
|
+
const { data: conversation } = useConversation({ conversation_id })
|
|
28
|
+
const { mutate: updateNotificationLevel } = useConversationMembershipUpdate({ conversation_id })
|
|
29
|
+
|
|
30
|
+
const notificationLevel = conversation.conversationMembership?.notificationLevel
|
|
31
|
+
const notificationLevelOptions =
|
|
32
|
+
conversation.conversationMembership?.notificationLevelOptions ?? []
|
|
33
|
+
|
|
34
|
+
const handleSelect = (value: NotificationLevelValue, isActive: boolean) => {
|
|
35
|
+
if (!isActive) {
|
|
36
|
+
const muted = value === 'nothing'
|
|
37
|
+
updateNotificationLevel({
|
|
38
|
+
muted,
|
|
39
|
+
notificationLevel: value as typeof notificationLevel,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
navigation.goBack()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<FormSheet.Root contentStyle={styles.content}>
|
|
47
|
+
{notificationLevelOptions.map(option => {
|
|
48
|
+
const isActive = option.enabled
|
|
49
|
+
return (
|
|
50
|
+
<PressableRow
|
|
51
|
+
key={option.value}
|
|
52
|
+
text={option.description}
|
|
53
|
+
isActive={isActive}
|
|
54
|
+
onPress={() => handleSelect(option.value, isActive)}
|
|
55
|
+
iconColor={colors.statusSuccessIcon}
|
|
56
|
+
style={styles.row}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
59
|
+
})}
|
|
60
|
+
</FormSheet.Root>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const useStyles = () => {
|
|
65
|
+
return StyleSheet.create({
|
|
66
|
+
content: {
|
|
67
|
+
paddingTop: 20,
|
|
68
|
+
},
|
|
69
|
+
row: {
|
|
70
|
+
borderBottomWidth: 0,
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
}
|