@planningcenter/chat-react-native 4.4.1-qa-staging.16 → 4.4.1-qa-staging.18
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/archived_results_footer.d.ts +6 -0
- package/build/components/conversations/archived_results_footer.d.ts.map +1 -0
- package/build/components/conversations/archived_results_footer.js +51 -0
- package/build/components/conversations/archived_results_footer.js.map +1 -0
- package/build/components/conversations/conversation_actions.d.ts.map +1 -1
- package/build/components/conversations/conversation_actions.js +241 -206
- package/build/components/conversations/conversation_actions.js.map +1 -1
- package/build/components/conversations/conversation_preview.d.ts.map +1 -1
- package/build/components/conversations/conversation_preview.js +99 -93
- package/build/components/conversations/conversation_preview.js.map +1 -1
- package/build/components/conversations/conversations.d.ts.map +1 -1
- package/build/components/conversations/conversations.js +65 -40
- package/build/components/conversations/conversations.js.map +1 -1
- package/build/components/conversations/conversations_blank_state.d.ts.map +1 -1
- package/build/components/conversations/conversations_blank_state.js +55 -12
- package/build/components/conversations/conversations_blank_state.js.map +1 -1
- package/build/components/conversations/filtered_results_footer.d.ts +2 -0
- package/build/components/conversations/filtered_results_footer.d.ts.map +1 -0
- package/build/components/conversations/filtered_results_footer.js +77 -0
- package/build/components/conversations/filtered_results_footer.js.map +1 -0
- package/build/components/display/filter_dropdown.d.ts +2 -1
- package/build/components/display/filter_dropdown.d.ts.map +1 -1
- package/build/components/display/filter_dropdown.js +63 -58
- package/build/components/display/filter_dropdown.js.map +1 -1
- package/build/components/display/icon_tile.d.ts +12 -0
- package/build/components/display/icon_tile.d.ts.map +1 -0
- package/build/components/display/icon_tile.js +109 -0
- package/build/components/display/icon_tile.js.map +1 -0
- package/build/components/display/index.d.ts +1 -0
- package/build/components/display/index.d.ts.map +1 -1
- package/build/components/display/index.js +1 -0
- package/build/components/display/index.js.map +1 -1
- package/build/contexts/conversations_context.d.ts.map +1 -1
- package/build/contexts/conversations_context.js +21 -18
- package/build/contexts/conversations_context.js.map +1 -1
- package/build/hooks/index.d.ts +2 -0
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +2 -0
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/use_can_browse_archived_conversations.d.ts +2 -0
- package/build/hooks/use_can_browse_archived_conversations.d.ts.map +1 -0
- package/build/hooks/use_can_browse_archived_conversations.js +29 -0
- package/build/hooks/use_can_browse_archived_conversations.js.map +1 -0
- package/build/hooks/use_clear_conversation_filters.d.ts +2 -0
- package/build/hooks/use_clear_conversation_filters.d.ts.map +1 -0
- package/build/hooks/use_clear_conversation_filters.js +25 -0
- package/build/hooks/use_clear_conversation_filters.js.map +1 -0
- package/build/hooks/use_conversation.d.ts.map +1 -1
- package/build/hooks/use_conversation.js +32 -24
- package/build/hooks/use_conversation.js.map +1 -1
- package/build/hooks/use_current_person.d.ts.map +1 -1
- package/build/hooks/use_current_person.js +1 -1
- package/build/hooks/use_current_person.js.map +1 -1
- package/build/hooks/use_jolt.d.ts.map +1 -1
- package/build/hooks/use_jolt.js +29 -8
- package/build/hooks/use_jolt.js.map +1 -1
- package/build/hooks/use_manual_archiving_enabled.d.ts +2 -0
- package/build/hooks/use_manual_archiving_enabled.d.ts.map +1 -0
- package/build/hooks/use_manual_archiving_enabled.js +18 -0
- package/build/hooks/use_manual_archiving_enabled.js.map +1 -0
- package/build/hooks/use_reduce_motion.d.ts.map +1 -1
- package/build/hooks/use_reduce_motion.js +2 -1
- package/build/hooks/use_reduce_motion.js.map +1 -1
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +2 -1
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_filters/components/conversation_filters.d.ts.map +1 -1
- package/build/screens/conversation_filters/components/conversation_filters.js +88 -18
- package/build/screens/conversation_filters/components/conversation_filters.js.map +1 -1
- package/build/screens/conversation_filters/components/rows.d.ts +9 -0
- package/build/screens/conversation_filters/components/rows.d.ts.map +1 -1
- package/build/screens/conversation_filters/components/rows.js +92 -1
- package/build/screens/conversation_filters/components/rows.js.map +1 -1
- package/build/screens/conversation_filters/context/conversation_filter_context.d.ts.map +1 -1
- package/build/screens/conversation_filters/context/conversation_filter_context.js +8 -4
- package/build/screens/conversation_filters/context/conversation_filter_context.js.map +1 -1
- package/build/screens/conversation_filters/filter_types.d.ts +1 -0
- package/build/screens/conversation_filters/filter_types.d.ts.map +1 -1
- package/build/screens/conversation_filters/filter_types.js +1 -0
- package/build/screens/conversation_filters/filter_types.js.map +1 -1
- package/build/screens/conversation_filters/screen_props.d.ts +1 -0
- package/build/screens/conversation_filters/screen_props.d.ts.map +1 -1
- package/build/screens/conversation_filters/screen_props.js.map +1 -1
- package/build/screens/conversation_filters_screen.d.ts +1 -0
- package/build/screens/conversation_filters_screen.d.ts.map +1 -1
- package/build/screens/conversation_filters_screen.js +79 -48
- package/build/screens/conversation_filters_screen.js.map +1 -1
- package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts +2 -0
- package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts.map +1 -0
- package/build/screens/conversations/components/filter_dropdown_list_header_component.js +587 -0
- package/build/screens/conversations/components/filter_dropdown_list_header_component.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 +95 -90
- package/build/screens/conversations/components/list_header_component.js.map +1 -1
- package/build/screens/conversations/components/search_list_header_component.d.ts.map +1 -1
- package/build/screens/conversations/components/search_list_header_component.js +100 -95
- package/build/screens/conversations/components/search_list_header_component.js.map +1 -1
- package/build/screens/conversations/conversations_screen.d.ts +1 -0
- package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
- package/build/screens/conversations/conversations_screen.js +15 -2
- package/build/screens/conversations/conversations_screen.js.map +1 -1
- package/build/types/resources/person.d.ts +1 -0
- package/build/types/resources/person.d.ts.map +1 -1
- package/build/types/resources/person.js.map +1 -1
- package/build/utils/date.d.ts.map +1 -1
- package/build/utils/date.js +5 -10
- package/build/utils/date.js.map +1 -1
- package/build/utils/native_adapters/menu.d.ts +1 -0
- package/build/utils/native_adapters/menu.d.ts.map +1 -1
- package/build/utils/native_adapters/menu.js +1 -0
- package/build/utils/native_adapters/menu.js.map +1 -1
- package/build/utils/request/conversation.d.ts +2 -1
- package/build/utils/request/conversation.d.ts.map +1 -1
- package/build/utils/request/conversation.js +4 -1
- package/build/utils/request/conversation.js.map +1 -1
- package/build/vendor/font_awesome_pro_solid_icons.d.ts +1 -0
- package/build/vendor/font_awesome_pro_solid_icons.d.ts.map +1 -1
- package/build/vendor/font_awesome_pro_solid_icons.js +5 -0
- package/build/vendor/font_awesome_pro_solid_icons.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/hooks/use_can_browse_archived_conversations.test.tsx +98 -0
- package/src/__tests__/screens/conversation_filters/components/conversation_filters.test.tsx +132 -0
- package/src/__tests__/screens/conversation_filters_screen.test.tsx +76 -0
- package/src/__tests__/screens/conversation_new/components/services_form.test.tsx +1 -0
- package/src/components/conversations/__tests__/conversation_actions.test.tsx +67 -0
- package/src/components/conversations/__tests__/conversation_preview.test.tsx +38 -3
- package/src/components/conversations/__tests__/conversations.test.tsx +82 -0
- package/src/components/conversations/__tests__/conversations_blank_state.test.tsx +73 -0
- package/src/components/conversations/archived_results_footer.tsx +36 -0
- package/src/components/conversations/conversation_actions.tsx +27 -19
- package/src/components/conversations/conversation_preview.tsx +30 -10
- package/src/components/conversations/conversations.tsx +25 -2
- package/src/components/conversations/conversations_blank_state.tsx +21 -0
- package/src/components/conversations/filtered_results_footer.tsx +38 -0
- package/src/components/display/__tests__/filter_dropdown.test.tsx +9 -1
- package/src/components/display/filter_dropdown.tsx +14 -13
- package/src/components/display/icon_tile.tsx +54 -0
- package/src/components/display/index.ts +1 -0
- package/src/contexts/conversations_context.tsx +2 -1
- package/src/hooks/__tests__/use_conversation_archive.test.tsx +22 -0
- package/src/hooks/__tests__/use_jolt.test.tsx +30 -8
- package/src/hooks/__tests__/use_reduce_motion.test.tsx +13 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use_can_browse_archived_conversations.ts +17 -0
- package/src/hooks/use_clear_conversation_filters.ts +16 -0
- package/src/hooks/use_conversation.ts +4 -1
- package/src/hooks/use_current_person.ts +1 -0
- package/src/hooks/use_jolt.ts +33 -8
- package/src/hooks/use_manual_archiving_enabled.ts +10 -0
- package/src/hooks/use_reduce_motion.ts +2 -1
- package/src/screens/conversation_details_screen.tsx +2 -3
- package/src/screens/conversation_filters/__tests__/conversation_filters.test.tsx +101 -0
- package/src/screens/conversation_filters/components/conversation_filters.tsx +40 -7
- package/src/screens/conversation_filters/components/rows.tsx +29 -1
- package/src/screens/conversation_filters/context/conversation_filter_context.tsx +4 -0
- package/src/screens/conversation_filters/filter_types.ts +1 -0
- package/src/screens/conversation_filters/screen_props.ts +1 -0
- package/src/screens/conversation_filters_screen.tsx +25 -5
- package/src/screens/conversations/__tests__/conversations_screen.test.tsx +52 -0
- package/src/screens/conversations/components/__tests__/filter_dropdown_list_header_component.test.tsx +248 -0
- package/src/screens/conversations/components/__tests__/list_header_component.test.tsx +21 -4
- package/src/screens/conversations/components/filter_dropdown_list_header_component.tsx +284 -0
- package/src/screens/conversations/components/list_header_component.tsx +17 -12
- package/src/screens/conversations/components/search_list_header_component.tsx +17 -12
- package/src/screens/conversations/conversations_screen.tsx +15 -2
- package/src/types/resources/person.ts +1 -0
- package/src/utils/__tests__/conversation.test.ts +1 -0
- package/src/utils/__tests__/date.test.ts +4 -0
- package/src/utils/date.ts +5 -4
- package/src/utils/native_adapters/__tests__/menu.test.tsx +6 -2
- package/src/utils/native_adapters/menu.tsx +2 -0
- package/src/utils/request/conversation.ts +4 -0
- package/src/vendor/font_awesome_pro_solid_icons.ts +12 -0
|
@@ -7,10 +7,19 @@ import {
|
|
|
7
7
|
useScalableNumberOfLines,
|
|
8
8
|
} from '../../hooks'
|
|
9
9
|
import { ConversationPreviewData, ConversationResource } from '../../types'
|
|
10
|
-
import { REACTION_EMOJIS, platformFontWeightBold } from '../../utils'
|
|
10
|
+
import { REACTION_EMOJIS, isArchivedConversation, platformFontWeightBold } from '../../utils'
|
|
11
11
|
import { formatDatePreview } from '../../utils/date'
|
|
12
|
+
import { faBoxArchive } from '../../vendor/font_awesome_pro_solid_icons'
|
|
12
13
|
import { tokens } from '../../vendor/tapestry/tokens'
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
AvatarGroup,
|
|
16
|
+
ConversationAvatar,
|
|
17
|
+
Heading,
|
|
18
|
+
IconTile,
|
|
19
|
+
Text,
|
|
20
|
+
Badge,
|
|
21
|
+
type BadgeProps,
|
|
22
|
+
} from '../display'
|
|
14
23
|
import { ConversationActions } from './conversation_actions'
|
|
15
24
|
import { MuteIndicator } from './mute_indicator'
|
|
16
25
|
import { UnreadCountBadge } from './unread_count_badge'
|
|
@@ -42,6 +51,7 @@ export const ConversationPreview = ({
|
|
|
42
51
|
unreadReaction,
|
|
43
52
|
} = conversation
|
|
44
53
|
|
|
54
|
+
const archived = isArchivedConversation(conversation)
|
|
45
55
|
const emptyConversation = !lastMessageCreatedAt
|
|
46
56
|
const fallbackIconName = emptyConversation ? 'people.noTextMessage' : 'general.person'
|
|
47
57
|
const hasMetaContent = lastMessageCreatedAt || unreadCount > 0 || muted
|
|
@@ -57,25 +67,30 @@ export const ConversationPreview = ({
|
|
|
57
67
|
const conversationAccessibilityLabel = getConversationAccessibilityLabel({
|
|
58
68
|
conversation,
|
|
59
69
|
showBadges,
|
|
70
|
+
archived,
|
|
60
71
|
})
|
|
61
72
|
|
|
62
73
|
return (
|
|
63
74
|
<ConversationActions
|
|
64
75
|
conversation={conversation}
|
|
65
|
-
style={[styles.previewRow, disabled && styles.disabled, style]}
|
|
76
|
+
style={[styles.previewRow, disabled && !archived && styles.disabled, style]}
|
|
66
77
|
onPress={onPress}
|
|
67
78
|
accessibilityLabel={conversationAccessibilityLabel}
|
|
68
79
|
>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
{archived ? (
|
|
81
|
+
<IconTile icon={faBoxArchive} size="lg" testID="conversation-archived-icon" />
|
|
82
|
+
) : (
|
|
83
|
+
<ConversationAvatar
|
|
84
|
+
conversation={conversation}
|
|
85
|
+
size="lg"
|
|
86
|
+
fallbackIconName={fallbackIconName}
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
74
89
|
<View style={styles.conversationBody}>
|
|
75
90
|
<Heading
|
|
76
91
|
numberOfLines={numberOfLinesScaledTitle}
|
|
77
92
|
variant="h3"
|
|
78
|
-
style={[styles.title, isUnread && styles.unreadTitle]}
|
|
93
|
+
style={[styles.title, archived && styles.archivedTitle, isUnread && styles.unreadTitle]}
|
|
79
94
|
>
|
|
80
95
|
{title}
|
|
81
96
|
</Heading>
|
|
@@ -193,10 +208,12 @@ function formatConversationPreviewAccessibilityLabel(preview: ConversationPrevie
|
|
|
193
208
|
interface GetConversationAccessibilityLabelProps {
|
|
194
209
|
conversation: ConversationResource
|
|
195
210
|
showBadges: boolean
|
|
211
|
+
archived: boolean
|
|
196
212
|
}
|
|
197
213
|
const getConversationAccessibilityLabel = ({
|
|
198
214
|
conversation,
|
|
199
215
|
showBadges,
|
|
216
|
+
archived,
|
|
200
217
|
}: GetConversationAccessibilityLabelProps) => {
|
|
201
218
|
const { badges, title, conversationPreview, muted, disabled, unreadCount, unreadReaction } =
|
|
202
219
|
conversation
|
|
@@ -207,7 +224,7 @@ const getConversationAccessibilityLabel = ({
|
|
|
207
224
|
const label = {
|
|
208
225
|
title,
|
|
209
226
|
badge: hasBadges ? `for ${badges[0]?.pcoResourceType} ${badges[0]?.text || ''}` : '',
|
|
210
|
-
disabled: disabled ? 'Disabled' : '',
|
|
227
|
+
disabled: archived ? 'Archived' : disabled ? 'Disabled' : '',
|
|
211
228
|
muted: muted ? 'Muted' : '',
|
|
212
229
|
readStatus: hasUnreadMessages
|
|
213
230
|
? `${unreadCount} unread messages`
|
|
@@ -255,6 +272,9 @@ const useStyles = () => {
|
|
|
255
272
|
fontSize: 16,
|
|
256
273
|
color: colors.textColorDefaultPrimary,
|
|
257
274
|
},
|
|
275
|
+
archivedTitle: {
|
|
276
|
+
color: colors.textColorDefaultSecondary,
|
|
277
|
+
},
|
|
258
278
|
unreadTitle: {
|
|
259
279
|
fontWeight: tokens.fontWeightBold,
|
|
260
280
|
},
|
|
@@ -4,10 +4,14 @@ import { FlatList, StyleSheet, View } from 'react-native'
|
|
|
4
4
|
import { useConversationsContext } from '../../contexts/conversations_context'
|
|
5
5
|
import { useTheme } from '../../hooks'
|
|
6
6
|
import { useCanCreateConversations } from '../../hooks/use_chat_permissions'
|
|
7
|
+
import { useClearConversationFilters } from '../../hooks/use_clear_conversation_filters'
|
|
7
8
|
import { useConversationsJoltEvents } from '../../hooks/use_conversations_jolt_events'
|
|
9
|
+
import { useFilterDropdownEnabled } from '../../hooks/use_filter_dropdown_enabled'
|
|
8
10
|
import { ConversationResource } from '../../types'
|
|
11
|
+
import { ArchivedResultsFooter } from './archived_results_footer'
|
|
9
12
|
import { ConversationPreview, ConversationPreviewSkeleton } from './conversation_preview'
|
|
10
13
|
import { ConversationsBlankState } from './conversations_blank_state'
|
|
14
|
+
import { FilteredResultsFooter } from './filtered_results_footer'
|
|
11
15
|
|
|
12
16
|
interface ConversationsProps {
|
|
13
17
|
ListHeaderComponent?:
|
|
@@ -26,11 +30,23 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
|
|
|
26
30
|
refetch,
|
|
27
31
|
isRefetching,
|
|
28
32
|
isFetched,
|
|
29
|
-
|
|
33
|
+
hasNextPage,
|
|
34
|
+
args: { chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived },
|
|
30
35
|
} = useConversationsContext()
|
|
31
36
|
const navigation = useNavigation()
|
|
32
37
|
const canCreateConversations = useCanCreateConversations()
|
|
33
|
-
const
|
|
38
|
+
const clearFilters = useClearConversationFilters()
|
|
39
|
+
const filterDropdownEnabled = useFilterDropdownEnabled()
|
|
40
|
+
const isFilterApplied =
|
|
41
|
+
!!chat_group_graph_id || !!group_source_app_name || !!show_direct_messages || !!show_archived
|
|
42
|
+
const showArchivedResultsFooter = !!show_archived && !isLoading
|
|
43
|
+
const showFilteredResultsFooter =
|
|
44
|
+
filterDropdownEnabled &&
|
|
45
|
+
isFilterApplied &&
|
|
46
|
+
!show_archived &&
|
|
47
|
+
!hasNextPage &&
|
|
48
|
+
!isLoading &&
|
|
49
|
+
conversations.length > 0
|
|
34
50
|
|
|
35
51
|
const showBadges = !chat_group_graph_id
|
|
36
52
|
|
|
@@ -62,6 +78,13 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
|
|
|
62
78
|
canCreateConversations={canCreateConversations}
|
|
63
79
|
/>
|
|
64
80
|
}
|
|
81
|
+
ListFooterComponent={
|
|
82
|
+
showArchivedResultsFooter ? (
|
|
83
|
+
<ArchivedResultsFooter onViewActiveConversations={clearFilters} />
|
|
84
|
+
) : showFilteredResultsFooter ? (
|
|
85
|
+
<FilteredResultsFooter />
|
|
86
|
+
) : null
|
|
87
|
+
}
|
|
65
88
|
renderItem={({ item }) => {
|
|
66
89
|
if (item.type === 'loading') {
|
|
67
90
|
return <ConversationPreviewSkeleton />
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { StyleSheet, View } from 'react-native'
|
|
3
|
+
import { useClearConversationFilters } from '../../hooks/use_clear_conversation_filters'
|
|
4
|
+
import { useFilterDropdownEnabled } from '../../hooks/use_filter_dropdown_enabled'
|
|
3
5
|
import BlankState from '../primitive/blank_state_primitive'
|
|
4
6
|
|
|
5
7
|
interface ConversationsBlankStateProps {
|
|
@@ -11,6 +13,25 @@ export function ConversationsBlankState({
|
|
|
11
13
|
isFilterApplied,
|
|
12
14
|
canCreateConversations,
|
|
13
15
|
}: ConversationsBlankStateProps) {
|
|
16
|
+
const filterDropdownEnabled = useFilterDropdownEnabled()
|
|
17
|
+
const clearFilters = useClearConversationFilters()
|
|
18
|
+
|
|
19
|
+
if (filterDropdownEnabled && isFilterApplied) {
|
|
20
|
+
return (
|
|
21
|
+
<View style={styles.container}>
|
|
22
|
+
<BlankState.Root>
|
|
23
|
+
<BlankState.Imagery name="general.outlinedTextMessage" />
|
|
24
|
+
<BlankState.Content>
|
|
25
|
+
<BlankState.Heading>No matches</BlankState.Heading>
|
|
26
|
+
</BlankState.Content>
|
|
27
|
+
<BlankState.TextButton onPress={clearFilters}>
|
|
28
|
+
View all conversations
|
|
29
|
+
</BlankState.TextButton>
|
|
30
|
+
</BlankState.Root>
|
|
31
|
+
</View>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
14
35
|
return (
|
|
15
36
|
<View style={styles.container}>
|
|
16
37
|
<BlankState.Root>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { StyleSheet, View } from 'react-native'
|
|
3
|
+
import { useTheme } from '../../hooks'
|
|
4
|
+
import { useClearConversationFilters } from '../../hooks/use_clear_conversation_filters'
|
|
5
|
+
import { Text, TextButton } from '../display'
|
|
6
|
+
|
|
7
|
+
export function FilteredResultsFooter() {
|
|
8
|
+
const styles = useStyles()
|
|
9
|
+
const clearFilters = useClearConversationFilters()
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<View style={styles.container}>
|
|
13
|
+
<Text variant="tertiary" style={styles.text}>
|
|
14
|
+
You’re viewing filtered results
|
|
15
|
+
</Text>
|
|
16
|
+
<TextButton variant="tertiary" onPress={clearFilters}>
|
|
17
|
+
View all conversations
|
|
18
|
+
</TextButton>
|
|
19
|
+
</View>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const useStyles = () => {
|
|
24
|
+
const { colors } = useTheme()
|
|
25
|
+
|
|
26
|
+
return StyleSheet.create({
|
|
27
|
+
container: {
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
gap: 8,
|
|
30
|
+
paddingHorizontal: 16,
|
|
31
|
+
paddingVertical: 24,
|
|
32
|
+
},
|
|
33
|
+
text: {
|
|
34
|
+
textAlign: 'center',
|
|
35
|
+
color: colors.textColorDefaultSecondary,
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
}
|
|
@@ -58,7 +58,7 @@ const options = [
|
|
|
58
58
|
{ id: 'teams', label: 'Teams' },
|
|
59
59
|
]
|
|
60
60
|
|
|
61
|
-
const renderFilterDropdown = () => {
|
|
61
|
+
const renderFilterDropdown = ({ showMoreFilters }: { showMoreFilters?: boolean } = {}) => {
|
|
62
62
|
const onSelect = jest.fn()
|
|
63
63
|
const onMoreFilters = jest.fn()
|
|
64
64
|
|
|
@@ -69,6 +69,7 @@ const renderFilterDropdown = () => {
|
|
|
69
69
|
onSelect={onSelect}
|
|
70
70
|
onMoreFilters={onMoreFilters}
|
|
71
71
|
triggerLabel="Groups"
|
|
72
|
+
showMoreFilters={showMoreFilters}
|
|
72
73
|
/>
|
|
73
74
|
)
|
|
74
75
|
|
|
@@ -95,6 +96,13 @@ describe('FilterDropdown', () => {
|
|
|
95
96
|
expect(onMoreFilters).not.toHaveBeenCalled()
|
|
96
97
|
})
|
|
97
98
|
|
|
99
|
+
it('leaves More filters out when showMoreFilters is false', () => {
|
|
100
|
+
renderFilterDropdown({ showMoreFilters: false })
|
|
101
|
+
|
|
102
|
+
expect(screen.queryByRole('menuitem', { name: 'More filters' })).toBeNull()
|
|
103
|
+
expect(screen.getAllByRole('menuitem')).toHaveLength(options.length)
|
|
104
|
+
})
|
|
105
|
+
|
|
98
106
|
it('calls onMoreFilters for the More filters action', () => {
|
|
99
107
|
const { onSelect, onMoreFilters } = renderFilterDropdown()
|
|
100
108
|
|
|
@@ -34,6 +34,7 @@ export interface FilterDropdownProps {
|
|
|
34
34
|
onSelect: (id: string) => void
|
|
35
35
|
onMoreFilters: () => void
|
|
36
36
|
triggerLabel: string
|
|
37
|
+
showMoreFilters?: boolean
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export function FilterDropdown({
|
|
@@ -42,22 +43,22 @@ export function FilterDropdown({
|
|
|
42
43
|
onSelect,
|
|
43
44
|
onMoreFilters,
|
|
44
45
|
triggerLabel,
|
|
46
|
+
showMoreFilters = true,
|
|
45
47
|
}: FilterDropdownProps) {
|
|
46
48
|
const styles = useStyles()
|
|
47
49
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
]
|
|
50
|
+
const optionActions: MenuAction[] = options.map(option => ({
|
|
51
|
+
id: option.id,
|
|
52
|
+
title: option.label,
|
|
53
|
+
state: option.id === selectedId ? ('on' as const) : ('off' as const),
|
|
54
|
+
}))
|
|
55
|
+
const moreFiltersAction: MenuAction = {
|
|
56
|
+
id: MORE_FILTERS_ID,
|
|
57
|
+
title: '',
|
|
58
|
+
displayInline: true,
|
|
59
|
+
subactions: [{ id: MORE_FILTERS_ID, title: MORE_FILTERS_LABEL }],
|
|
60
|
+
}
|
|
61
|
+
const actions = showMoreFilters ? [...optionActions, moreFiltersAction] : optionActions
|
|
61
62
|
|
|
62
63
|
const handlePressAction = ({ nativeEvent }: MenuActionEvent) => {
|
|
63
64
|
if (nativeEvent.event === MORE_FILTERS_ID) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { IconDefinition } from '@fortawesome/fontawesome-svg-core'
|
|
2
|
+
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { StyleSheet, View } from 'react-native'
|
|
5
|
+
import { useFontScale, useTheme } from '../../hooks'
|
|
6
|
+
import { MAX_FONT_SIZE_MULTIPLIER } from '../../utils'
|
|
7
|
+
import AvatarPrimitive, { type AvatarRootProps } from '../primitive/avatar_primitive'
|
|
8
|
+
|
|
9
|
+
const ICON_SIZE_PX = 16
|
|
10
|
+
|
|
11
|
+
interface IconTileProps {
|
|
12
|
+
icon: IconDefinition
|
|
13
|
+
size?: AvatarRootProps['size']
|
|
14
|
+
maxFontSizeMultiplier?: AvatarRootProps['maxFontSizeMultiplier']
|
|
15
|
+
style?: AvatarRootProps['style']
|
|
16
|
+
testID?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function IconTile({
|
|
20
|
+
icon,
|
|
21
|
+
size = 'lg',
|
|
22
|
+
maxFontSizeMultiplier = MAX_FONT_SIZE_MULTIPLIER,
|
|
23
|
+
style,
|
|
24
|
+
testID,
|
|
25
|
+
}: IconTileProps) {
|
|
26
|
+
const styles = useStyles()
|
|
27
|
+
const fontScale = useFontScale({ maxFontSizeMultiplier })
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<AvatarPrimitive.Root size={size} maxFontSizeMultiplier={maxFontSizeMultiplier} style={style}>
|
|
31
|
+
<AvatarPrimitive.Mask>
|
|
32
|
+
<View style={styles.tile} testID={testID} accessibilityElementsHidden>
|
|
33
|
+
<FontAwesomeIcon icon={icon} size={ICON_SIZE_PX * fontScale} color={styles.icon.color} />
|
|
34
|
+
</View>
|
|
35
|
+
</AvatarPrimitive.Mask>
|
|
36
|
+
</AvatarPrimitive.Root>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const useStyles = () => {
|
|
41
|
+
const { colors } = useTheme()
|
|
42
|
+
|
|
43
|
+
return StyleSheet.create({
|
|
44
|
+
tile: {
|
|
45
|
+
flex: 1,
|
|
46
|
+
alignItems: 'center',
|
|
47
|
+
justifyContent: 'center',
|
|
48
|
+
backgroundColor: colors.fillColorNeutral070,
|
|
49
|
+
},
|
|
50
|
+
icon: {
|
|
51
|
+
color: colors.iconColorDefaultPrimary,
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
}
|
|
@@ -45,13 +45,14 @@ export const ConversationsContextProvider = ({
|
|
|
45
45
|
opts?: ApiGetQueryOptions
|
|
46
46
|
}>) => {
|
|
47
47
|
const [activeConversationId, setActiveConversationId] = useState<number | undefined>()
|
|
48
|
-
const { chat_group_graph_id, group_source_app_name, show_direct_messages } = args
|
|
48
|
+
const { chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived } = args
|
|
49
49
|
|
|
50
50
|
const query = useConversations(
|
|
51
51
|
{
|
|
52
52
|
chat_group_graph_id,
|
|
53
53
|
group_source_app_name,
|
|
54
54
|
show_direct_messages,
|
|
55
|
+
show_archived,
|
|
55
56
|
},
|
|
56
57
|
paginatorOptions
|
|
57
58
|
)
|
|
@@ -5,6 +5,7 @@ import { Alert } from 'react-native'
|
|
|
5
5
|
import { buildTestQueryClient } from '../../__utils__/query_client'
|
|
6
6
|
import { getConversationsRequestArgs } from '../../utils/request/conversation'
|
|
7
7
|
import { getConversationRequestArgs, useConversationArchive } from '../use_conversation'
|
|
8
|
+
import { currentPersonQueryKey } from '../use_current_person'
|
|
8
9
|
import { getRequestQueryKey } from '../use_suspense_api'
|
|
9
10
|
|
|
10
11
|
const mockPost = jest.fn()
|
|
@@ -33,6 +34,9 @@ describe('useConversationArchive', () => {
|
|
|
33
34
|
queryClient = buildQueryClient()
|
|
34
35
|
queryClient.setQueryData(conversationKey, { data: { id: conversation_id } })
|
|
35
36
|
queryClient.setQueryData(conversationsKey, { data: [] })
|
|
37
|
+
queryClient.setQueryData(currentPersonQueryKey, {
|
|
38
|
+
data: { id: 1, canBrowseArchivedConversations: false },
|
|
39
|
+
})
|
|
36
40
|
mockPost.mockReset()
|
|
37
41
|
jest.spyOn(Alert, 'alert').mockImplementation(() => {})
|
|
38
42
|
})
|
|
@@ -58,6 +62,24 @@ describe('useConversationArchive', () => {
|
|
|
58
62
|
})
|
|
59
63
|
expect(isInvalidated(conversationKey)).toBe(true)
|
|
60
64
|
expect(isInvalidated(conversationsKey)).toBe(true)
|
|
65
|
+
expect(isInvalidated(currentPersonQueryKey)).toBe(false)
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
it.each([
|
|
70
|
+
[true, true],
|
|
71
|
+
[false, false],
|
|
72
|
+
])(
|
|
73
|
+
'sets archive access in the person cache to %s after archived=%s',
|
|
74
|
+
async (archived, canBrowse) => {
|
|
75
|
+
mockPost.mockResolvedValue({ data: { id: conversation_id } })
|
|
76
|
+
const { result } = renderHook(() => useConversationArchive({ conversation_id }), { wrapper })
|
|
77
|
+
|
|
78
|
+
await act(() => result.current.mutateAsync(archived))
|
|
79
|
+
|
|
80
|
+
expect(queryClient.getQueryData(currentPersonQueryKey)).toEqual({
|
|
81
|
+
data: { id: 1, canBrowseArchivedConversations: canBrowse },
|
|
82
|
+
})
|
|
61
83
|
}
|
|
62
84
|
)
|
|
63
85
|
|
|
@@ -81,13 +81,15 @@ describe('useJoltForegroundPing', () => {
|
|
|
81
81
|
return null
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const renderForegroundPing = (joltClient: Partial<JoltClient
|
|
84
|
+
const renderForegroundPing = (joltClient: Partial<JoltClient>, { mounts = 1 } = {}) => {
|
|
85
85
|
const queryClient = buildTestQueryClient({ defaultOptions: { queries: { gcTime: Infinity } } })
|
|
86
86
|
queryClient.setQueryData(['jolt-client', 'wss://jolt.test', 'a-real-token'], joltClient)
|
|
87
87
|
|
|
88
|
-
render(
|
|
88
|
+
const result = render(
|
|
89
89
|
<QueryClientProvider client={queryClient}>
|
|
90
|
-
|
|
90
|
+
{Array.from({ length: mounts }, (_, index) => (
|
|
91
|
+
<PingProbe key={index} />
|
|
92
|
+
))}
|
|
91
93
|
</QueryClientProvider>
|
|
92
94
|
)
|
|
93
95
|
|
|
@@ -96,7 +98,7 @@ describe('useJoltForegroundPing', () => {
|
|
|
96
98
|
(_state: AppStateStatus) => void,
|
|
97
99
|
]
|
|
98
100
|
|
|
99
|
-
return onChange
|
|
101
|
+
return { onChange, unmount: result.unmount }
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
beforeEach(() => {
|
|
@@ -105,7 +107,7 @@ describe('useJoltForegroundPing', () => {
|
|
|
105
107
|
|
|
106
108
|
it('pings the jolt client once when the app returns to the foreground', () => {
|
|
107
109
|
const ping = jest.fn()
|
|
108
|
-
const onChange = renderForegroundPing({ ping, subscriptions: [confirmedSubscription] })
|
|
110
|
+
const { onChange } = renderForegroundPing({ ping, subscriptions: [confirmedSubscription] })
|
|
109
111
|
|
|
110
112
|
onChange('background')
|
|
111
113
|
onChange('active')
|
|
@@ -113,9 +115,29 @@ describe('useJoltForegroundPing', () => {
|
|
|
113
115
|
expect(ping).toHaveBeenCalledTimes(1)
|
|
114
116
|
})
|
|
115
117
|
|
|
118
|
+
it('registers one listener and pings once no matter how many providers mount it', () => {
|
|
119
|
+
const ping = jest.fn()
|
|
120
|
+
const remove = jest.fn()
|
|
121
|
+
addEventListener.mockReturnValueOnce({ remove })
|
|
122
|
+
const { onChange, unmount } = renderForegroundPing(
|
|
123
|
+
{ ping, subscriptions: [confirmedSubscription] },
|
|
124
|
+
{ mounts: 3 }
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
onChange('background')
|
|
128
|
+
onChange('active')
|
|
129
|
+
|
|
130
|
+
expect(addEventListener).toHaveBeenCalledTimes(1)
|
|
131
|
+
expect(ping).toHaveBeenCalledTimes(1)
|
|
132
|
+
|
|
133
|
+
unmount()
|
|
134
|
+
|
|
135
|
+
expect(remove).toHaveBeenCalledTimes(1)
|
|
136
|
+
})
|
|
137
|
+
|
|
116
138
|
it('does not ping when there are no confirmed subscriptions', () => {
|
|
117
139
|
const ping = jest.fn()
|
|
118
|
-
const onChange = renderForegroundPing({ ping, subscriptions: [] })
|
|
140
|
+
const { onChange } = renderForegroundPing({ ping, subscriptions: [] })
|
|
119
141
|
|
|
120
142
|
onChange('background')
|
|
121
143
|
onChange('active')
|
|
@@ -125,7 +147,7 @@ describe('useJoltForegroundPing', () => {
|
|
|
125
147
|
|
|
126
148
|
it('does not ping when the app leaves the foreground', () => {
|
|
127
149
|
const ping = jest.fn()
|
|
128
|
-
const onChange = renderForegroundPing({ ping, subscriptions: [confirmedSubscription] })
|
|
150
|
+
const { onChange } = renderForegroundPing({ ping, subscriptions: [confirmedSubscription] })
|
|
129
151
|
|
|
130
152
|
onChange('inactive')
|
|
131
153
|
onChange('background')
|
|
@@ -137,7 +159,7 @@ describe('useJoltForegroundPing', () => {
|
|
|
137
159
|
const ping = jest.fn(() => {
|
|
138
160
|
throw new Error('No WebSocket')
|
|
139
161
|
})
|
|
140
|
-
const onChange = renderForegroundPing({ ping, subscriptions: [confirmedSubscription] })
|
|
162
|
+
const { onChange } = renderForegroundPing({ ping, subscriptions: [confirmedSubscription] })
|
|
141
163
|
|
|
142
164
|
onChange('background')
|
|
143
165
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { act, renderHook, waitFor } from '@testing-library/react-native'
|
|
2
2
|
import { AccessibilityInfo, EmitterSubscription } from 'react-native'
|
|
3
|
+
import * as Reanimated from 'react-native-reanimated'
|
|
3
4
|
import { useReduceMotion } from '../use_reduce_motion'
|
|
4
5
|
|
|
5
6
|
type ReduceMotionListener = (enabled: boolean) => void
|
|
@@ -11,6 +12,7 @@ describe('useReduceMotion', () => {
|
|
|
11
12
|
beforeEach(() => {
|
|
12
13
|
listener = undefined
|
|
13
14
|
remove.mockClear()
|
|
15
|
+
jest.spyOn(Reanimated, 'useReducedMotion').mockReturnValue(false)
|
|
14
16
|
jest.spyOn(AccessibilityInfo, 'isReduceMotionEnabled').mockResolvedValue(true)
|
|
15
17
|
const addEventListener: typeof AccessibilityInfo.addEventListener = (_event, handler) => {
|
|
16
18
|
listener = handler as ReduceMotionListener
|
|
@@ -21,7 +23,17 @@ describe('useReduceMotion', () => {
|
|
|
21
23
|
|
|
22
24
|
afterEach(() => jest.restoreAllMocks())
|
|
23
25
|
|
|
24
|
-
it('seeds from the
|
|
26
|
+
it('seeds synchronously from the launch-time setting', async () => {
|
|
27
|
+
jest.spyOn(Reanimated, 'useReducedMotion').mockReturnValue(true)
|
|
28
|
+
jest.mocked(AccessibilityInfo.isReduceMotionEnabled).mockResolvedValue(false)
|
|
29
|
+
|
|
30
|
+
const { result } = renderHook(() => useReduceMotion())
|
|
31
|
+
|
|
32
|
+
expect(result.current).toBe(true)
|
|
33
|
+
await waitFor(() => expect(result.current).toBe(false))
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('updates to the live setting once it resolves', async () => {
|
|
25
37
|
const { result } = renderHook(() => useReduceMotion())
|
|
26
38
|
|
|
27
39
|
expect(result.current).toBe(false)
|
package/src/hooks/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { useApiClient } from './use_api_client'
|
|
|
3
3
|
export * from './use_api'
|
|
4
4
|
export * from './use_app_state'
|
|
5
5
|
export * from './use_async_storage'
|
|
6
|
+
export * from './use_can_browse_archived_conversations'
|
|
6
7
|
export * from './use_at_font_scale_breakpoint'
|
|
7
8
|
export * from './use_chat_permissions'
|
|
8
9
|
export * from './use_create_android_ripple_color'
|
|
@@ -16,6 +17,7 @@ export * from './use_groups_groups'
|
|
|
16
17
|
export * from './use_groups'
|
|
17
18
|
export * from './use_selected_chip_background_color'
|
|
18
19
|
export * from './use_interaction_tinted_background_color'
|
|
20
|
+
export * from './use_manual_archiving_enabled'
|
|
19
21
|
export * from './use_message_reaction_toggle'
|
|
20
22
|
export * from './use_new_conversation_entry'
|
|
21
23
|
export * from './use_organization'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CurrentPersonResource } from '../types'
|
|
2
|
+
import { FIVE_MINUTES } from '../utils/time'
|
|
3
|
+
import { useApiGet } from './use_api'
|
|
4
|
+
import { currentPersonRequestArgs } from './use_current_person'
|
|
5
|
+
import { useManualArchivingEnabled } from './use_manual_archiving_enabled'
|
|
6
|
+
|
|
7
|
+
export const useCanBrowseArchivedConversations = (): boolean => {
|
|
8
|
+
const manualArchivingEnabled = useManualArchivingEnabled()
|
|
9
|
+
const { data: person, isFetched } = useApiGet<CurrentPersonResource>(currentPersonRequestArgs, {
|
|
10
|
+
enabled: manualArchivingEnabled,
|
|
11
|
+
staleTime: FIVE_MINUTES,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
if (!manualArchivingEnabled || !isFetched) return false
|
|
15
|
+
|
|
16
|
+
return person?.canBrowseArchivedConversations ?? false
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useNavigation } from '@react-navigation/native'
|
|
2
|
+
import { useCallback } from 'react'
|
|
3
|
+
|
|
4
|
+
export function useClearConversationFilters() {
|
|
5
|
+
const navigation = useNavigation()
|
|
6
|
+
|
|
7
|
+
return useCallback(() => {
|
|
8
|
+
navigation.setParams({
|
|
9
|
+
title: undefined,
|
|
10
|
+
chat_group_graph_id: undefined,
|
|
11
|
+
group_source_app_name: undefined,
|
|
12
|
+
show_direct_messages: undefined,
|
|
13
|
+
show_archived: undefined,
|
|
14
|
+
})
|
|
15
|
+
}, [navigation])
|
|
16
|
+
}
|
|
@@ -6,6 +6,7 @@ import { transformGetToPost } from '../utils/client/request_helpers'
|
|
|
6
6
|
import { Haptic } from '../utils/native_adapters'
|
|
7
7
|
import { getConversationsRequestArgs } from '../utils/request/conversation'
|
|
8
8
|
import { useApiClient } from './use_api_client'
|
|
9
|
+
import { useCurrentPersonCache } from './use_current_person'
|
|
9
10
|
import { getRequestQueryKey, SuspenseGetQueryOptions, useSuspenseGet } from './use_suspense_api'
|
|
10
11
|
|
|
11
12
|
export const getConversationRequestArgs = ({ conversation_id }: { conversation_id: number }) => ({
|
|
@@ -188,6 +189,7 @@ export const useConversationDelete = ({ conversation_id }: { conversation_id: nu
|
|
|
188
189
|
export const useConversationArchive = ({ conversation_id }: { conversation_id: number }) => {
|
|
189
190
|
const apiClient = useApiClient()
|
|
190
191
|
const queryClient = useQueryClient()
|
|
192
|
+
const currentPersonCache = useCurrentPersonCache()
|
|
191
193
|
const conversationQueryKey = getRequestQueryKey(getConversationRequestArgs({ conversation_id }))
|
|
192
194
|
const { url } = getConversationsRequestArgs()
|
|
193
195
|
|
|
@@ -201,8 +203,9 @@ export const useConversationArchive = ({ conversation_id }: { conversation_id: n
|
|
|
201
203
|
data: { data: { type: 'Conversation', attributes: {} } },
|
|
202
204
|
})
|
|
203
205
|
},
|
|
204
|
-
onSuccess: () => {
|
|
206
|
+
onSuccess: (_response, archived) => {
|
|
205
207
|
Haptic.notificationSuccess()
|
|
208
|
+
if (archived) currentPersonCache.update({ canBrowseArchivedConversations: true })
|
|
206
209
|
queryClient.invalidateQueries({ queryKey: [url] })
|
|
207
210
|
return queryClient.invalidateQueries({ queryKey: conversationQueryKey })
|
|
208
211
|
},
|