@planningcenter/chat-react-native 4.4.1-qa-staging.17 → 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 +44 -35
- package/build/components/conversations/conversations.js.map +1 -1
- 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.map +1 -1
- package/build/hooks/use_clear_conversation_filters.js +2 -1
- package/build/hooks/use_clear_conversation_filters.js.map +1 -1
- 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/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 +80 -13
- 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 +75 -49
- 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 +3 -0
- package/src/__tests__/screens/conversation_filters_screen.test.tsx +76 -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 +1 -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 +15 -3
- 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/index.ts +2 -0
- package/src/hooks/use_can_browse_archived_conversations.ts +17 -0
- package/src/hooks/use_clear_conversation_filters.ts +1 -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/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 +32 -2
- 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 +20 -4
- 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
|
@@ -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
|
|
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
|
+
}
|
|
@@ -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
|
},
|
package/src/hooks/use_jolt.ts
CHANGED
|
@@ -140,19 +140,44 @@ const pingSubscribedJoltClients = (queryClient: QueryClient) => {
|
|
|
140
140
|
})
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
const foregroundPingListeners = new Map<QueryClient, { mounts: number; remove: () => void }>()
|
|
144
|
+
|
|
145
|
+
const subscribeToForegroundPing = (queryClient: QueryClient) => {
|
|
146
|
+
const existing = foregroundPingListeners.get(queryClient)
|
|
147
|
+
if (existing) {
|
|
148
|
+
existing.mounts += 1
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let previousState: AppStateStatus = AppState.currentState
|
|
153
|
+
const subscription = AppState.addEventListener('change', nextState => {
|
|
154
|
+
if (nextState === 'active' && previousState !== 'active') {
|
|
155
|
+
pingSubscribedJoltClients(queryClient)
|
|
156
|
+
}
|
|
157
|
+
previousState = nextState
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
foregroundPingListeners.set(queryClient, { mounts: 1, remove: () => subscription.remove() })
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const unsubscribeFromForegroundPing = (queryClient: QueryClient) => {
|
|
164
|
+
const existing = foregroundPingListeners.get(queryClient)
|
|
165
|
+
if (!existing) return
|
|
166
|
+
|
|
167
|
+
existing.mounts -= 1
|
|
168
|
+
if (existing.mounts > 0) return
|
|
169
|
+
|
|
170
|
+
existing.remove()
|
|
171
|
+
foregroundPingListeners.delete(queryClient)
|
|
172
|
+
}
|
|
173
|
+
|
|
143
174
|
export const useJoltForegroundPing = () => {
|
|
144
175
|
const queryClient = useQueryClient()
|
|
145
176
|
|
|
146
177
|
useEffect(() => {
|
|
147
|
-
|
|
148
|
-
const subscription = AppState.addEventListener('change', nextState => {
|
|
149
|
-
if (nextState === 'active' && previousState !== 'active') {
|
|
150
|
-
pingSubscribedJoltClients(queryClient)
|
|
151
|
-
}
|
|
152
|
-
previousState = nextState
|
|
153
|
-
})
|
|
178
|
+
subscribeToForegroundPing(queryClient)
|
|
154
179
|
|
|
155
|
-
return () =>
|
|
180
|
+
return () => unsubscribeFromForegroundPing(queryClient)
|
|
156
181
|
}, [queryClient])
|
|
157
182
|
}
|
|
158
183
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { availableFeatures, useFeatures } from './use_features'
|
|
2
|
+
|
|
3
|
+
export function useManualArchivingEnabled() {
|
|
4
|
+
const { featureEnabled } = useFeatures()
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
featureEnabled(availableFeatures.manual_archiving) ||
|
|
8
|
+
featureEnabled(availableFeatures.manual_archiving_rollout)
|
|
9
|
+
)
|
|
10
|
+
}
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
useConversationUpdate,
|
|
51
51
|
} from '../hooks/use_conversation'
|
|
52
52
|
import { availableFeatures, useFeatures } from '../hooks/use_features'
|
|
53
|
+
import { useManualArchivingEnabled } from '../hooks/use_manual_archiving_enabled'
|
|
53
54
|
import { usePreviewAvatarDiameter } from '../hooks/use_preview_avatar_diameter'
|
|
54
55
|
import { useUnarchiveConversationAction } from '../hooks/use_unarchive_conversation_action'
|
|
55
56
|
import { type ConversationResource, MemberResource, isDefined } from '../types'
|
|
@@ -113,9 +114,7 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
113
114
|
const { featureEnabled } = useFeatures()
|
|
114
115
|
const granularNotificationsEnabled = featureEnabled(availableFeatures.granular_notifications_ui)
|
|
115
116
|
const customAvatarsEnabled = featureEnabled(availableFeatures.custom_conversation_avatars)
|
|
116
|
-
const manualArchivingEnabled =
|
|
117
|
-
featureEnabled(availableFeatures.manual_archiving) ||
|
|
118
|
-
featureEnabled(availableFeatures.manual_archiving_rollout)
|
|
117
|
+
const manualArchivingEnabled = useManualArchivingEnabled()
|
|
119
118
|
const showGenderFilter =
|
|
120
119
|
featureEnabled(availableFeatures.gender_specific_conversations) && !!conversation.genderOption
|
|
121
120
|
const genderLabel = conversation.genderOption ? genderDisplayLabel(conversation.genderOption) : ''
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { DefaultTheme, ThemeProvider } from '@react-navigation/native'
|
|
2
|
+
import { fireEvent, render, within } from '@testing-library/react-native'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { ConversationFilters } from '../components/conversation_filters'
|
|
5
|
+
import { FilterContext } from '../context/conversation_filter_context'
|
|
6
|
+
import { ConversationFiltersParams } from '../screen_props'
|
|
7
|
+
|
|
8
|
+
jest.mock('react-native-gesture-handler', () => {
|
|
9
|
+
const { createElement } = jest.requireActual('react')
|
|
10
|
+
const { FlatList } = jest.requireActual('react-native')
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
FlatList: (props: object) => createElement(FlatList, { ...props, initialNumToRender: 50 }),
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
let mockCanBrowseArchived = false
|
|
18
|
+
jest.mock('../../../hooks/use_can_browse_archived_conversations', () => ({
|
|
19
|
+
useCanBrowseArchivedConversations: () => mockCanBrowseArchived,
|
|
20
|
+
}))
|
|
21
|
+
jest.mock('../../../hooks/use_direct_messages_eligible', () => ({
|
|
22
|
+
useDirectMessagesEligible: () => false,
|
|
23
|
+
}))
|
|
24
|
+
jest.mock('../../../hooks/use_filter_dropdown_enabled', () => ({
|
|
25
|
+
useFilterDropdownEnabled: () => false,
|
|
26
|
+
}))
|
|
27
|
+
jest.mock('../../../hooks/use_has_direct_messages', () => ({
|
|
28
|
+
useHasDirectMessages: () => false,
|
|
29
|
+
}))
|
|
30
|
+
jest.mock('../hooks/filters', () => ({
|
|
31
|
+
useGroupsToFilter: () => ({ groups: [] }),
|
|
32
|
+
useTeamsToFilter: () => ({ teams: [] }),
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
const mockSetAppFilter = jest.fn()
|
|
36
|
+
|
|
37
|
+
const renderFilters = (params: ConversationFiltersParams = {}) =>
|
|
38
|
+
render(
|
|
39
|
+
<ThemeProvider value={DefaultTheme}>
|
|
40
|
+
<FilterContext.Provider
|
|
41
|
+
value={{
|
|
42
|
+
params,
|
|
43
|
+
scrollOffset: 0,
|
|
44
|
+
setScrollOffset: jest.fn(),
|
|
45
|
+
setFormSheetHeaderHeight: jest.fn(),
|
|
46
|
+
resetFilter: jest.fn(),
|
|
47
|
+
setGroupFilter: jest.fn(),
|
|
48
|
+
setAppFilter: mockSetAppFilter,
|
|
49
|
+
applyFilters: jest.fn(),
|
|
50
|
+
platformListPaddingBottom: 0,
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<ConversationFilters />
|
|
54
|
+
</FilterContext.Provider>
|
|
55
|
+
</ThemeProvider>
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
describe('ConversationFilters archived option', () => {
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
mockSetAppFilter.mockClear()
|
|
61
|
+
mockCanBrowseArchived = false
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('hides the Other section when the person cannot browse archived conversations', () => {
|
|
65
|
+
const view = renderFilters()
|
|
66
|
+
|
|
67
|
+
expect(view.queryByText('Other')).toBeNull()
|
|
68
|
+
expect(view.queryByText('Archived conversations')).toBeNull()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('offers Archived under Other when the person can browse archived conversations', () => {
|
|
72
|
+
mockCanBrowseArchived = true
|
|
73
|
+
|
|
74
|
+
const view = renderFilters()
|
|
75
|
+
|
|
76
|
+
expect(view.getByText('Other')).toBeTruthy()
|
|
77
|
+
expect(view.getByText('Archived conversations')).toBeTruthy()
|
|
78
|
+
expect(view.getByText('Archived for everybody')).toBeTruthy()
|
|
79
|
+
expect(view.queryByRole('button', { selected: true })).toBeNull()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('selecting Archived sets the archived filter param', () => {
|
|
83
|
+
mockCanBrowseArchived = true
|
|
84
|
+
|
|
85
|
+
const view = renderFilters()
|
|
86
|
+
|
|
87
|
+
fireEvent.press(view.getByText('Archived conversations'))
|
|
88
|
+
|
|
89
|
+
expect(mockSetAppFilter).toHaveBeenCalledTimes(1)
|
|
90
|
+
expect(mockSetAppFilter).toHaveBeenCalledWith({ show_archived: true })
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('preselects Archived when it is the active filter', () => {
|
|
94
|
+
mockCanBrowseArchived = true
|
|
95
|
+
|
|
96
|
+
const view = renderFilters({ show_archived: true })
|
|
97
|
+
|
|
98
|
+
const selected = view.getByRole('button', { selected: true })
|
|
99
|
+
expect(within(selected).getByText('Archived conversations')).toBeTruthy()
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -4,11 +4,13 @@ import { FlatList } from 'react-native-gesture-handler'
|
|
|
4
4
|
import { Heading } from '../../../components'
|
|
5
5
|
import {
|
|
6
6
|
useAtFontScaleBreakpoint,
|
|
7
|
+
useCanBrowseArchivedConversations,
|
|
7
8
|
useDirectMessagesEligible,
|
|
8
9
|
useHasDirectMessages,
|
|
9
10
|
useTheme,
|
|
10
11
|
} from '../../../hooks'
|
|
11
12
|
import { useFilterDropdownEnabled } from '../../../hooks/use_filter_dropdown_enabled'
|
|
13
|
+
import { faBoxArchive } from '../../../vendor/font_awesome_pro_solid_icons'
|
|
12
14
|
import { FilterContext, useFilterContext } from '../context/conversation_filter_context'
|
|
13
15
|
import { FilterTypes } from '../filter_types'
|
|
14
16
|
import { useGroupsToFilter, useTeamsToFilter } from '../hooks/filters'
|
|
@@ -17,6 +19,8 @@ import {
|
|
|
17
19
|
FilterRow,
|
|
18
20
|
GroupRow,
|
|
19
21
|
GroupRowProps,
|
|
22
|
+
OtherRow,
|
|
23
|
+
OtherRowProps,
|
|
20
24
|
TeamRow,
|
|
21
25
|
TeamRowProps,
|
|
22
26
|
ViewMore,
|
|
@@ -33,6 +37,7 @@ enum SectionTypes {
|
|
|
33
37
|
header,
|
|
34
38
|
hidden,
|
|
35
39
|
more,
|
|
40
|
+
other,
|
|
36
41
|
teams,
|
|
37
42
|
}
|
|
38
43
|
|
|
@@ -42,6 +47,7 @@ type SectionListData = Array<
|
|
|
42
47
|
| DataItem<HeaderProps, SectionTypes.header>
|
|
43
48
|
| DataItem<any, SectionTypes.hidden>
|
|
44
49
|
| DataItem<ViewMoreRowProps, SectionTypes.more>
|
|
50
|
+
| DataItem<OtherRowProps, SectionTypes.other>
|
|
45
51
|
| DataItem<TeamRowProps, SectionTypes.teams>
|
|
46
52
|
>
|
|
47
53
|
|
|
@@ -58,12 +64,18 @@ interface DataItem<T, TName extends SectionTypes> {
|
|
|
58
64
|
export const ConversationFilters = () => {
|
|
59
65
|
const styles = useStyles()
|
|
60
66
|
const { setScrollOffset, params } = useContext(FilterContext)
|
|
61
|
-
const {
|
|
67
|
+
const {
|
|
68
|
+
chat_group_graph_id,
|
|
69
|
+
group_source_app_name = '',
|
|
70
|
+
show_direct_messages,
|
|
71
|
+
show_archived,
|
|
72
|
+
} = params
|
|
62
73
|
|
|
63
74
|
const filterDropdownEnabled = useFilterDropdownEnabled()
|
|
64
75
|
const directMessagesEligible = useDirectMessagesEligible()
|
|
65
76
|
const hasDirectMessages = useHasDirectMessages()
|
|
66
77
|
const canFilterByDMs = directMessagesEligible && hasDirectMessages
|
|
78
|
+
const canFilterByArchived = useCanBrowseArchivedConversations()
|
|
67
79
|
|
|
68
80
|
const activeFilter: FilterTypes = useMemo(() => {
|
|
69
81
|
if (chat_group_graph_id) {
|
|
@@ -74,10 +86,12 @@ export const ConversationFilters = () => {
|
|
|
74
86
|
return FilterTypes.Teams
|
|
75
87
|
} else if (show_direct_messages) {
|
|
76
88
|
return FilterTypes.DirectMessages
|
|
89
|
+
} else if (show_archived) {
|
|
90
|
+
return FilterTypes.Archived
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
return FilterTypes.All
|
|
80
|
-
}, [chat_group_graph_id, group_source_app_name, show_direct_messages])
|
|
94
|
+
}, [chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived])
|
|
81
95
|
|
|
82
96
|
const { groups = [] } = useGroupsToFilter()
|
|
83
97
|
const { teams = [] } = useTeamsToFilter()
|
|
@@ -158,6 +172,20 @@ export const ConversationFilters = () => {
|
|
|
158
172
|
type: teamItems.length > 5 ? SectionTypes.more : SectionTypes.hidden,
|
|
159
173
|
data: { routeName: 'TeamFilters' },
|
|
160
174
|
},
|
|
175
|
+
{
|
|
176
|
+
type: canFilterByArchived ? SectionTypes.header : SectionTypes.hidden,
|
|
177
|
+
data: { title: 'Other', style: styles.extraHeaderScalableSpace },
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
type: canFilterByArchived ? SectionTypes.other : SectionTypes.hidden,
|
|
181
|
+
data: {
|
|
182
|
+
filter: FilterTypes.Archived,
|
|
183
|
+
description: 'Archived for everybody',
|
|
184
|
+
icon: faBoxArchive,
|
|
185
|
+
show_archived: true,
|
|
186
|
+
isActive: activeFilter === FilterTypes.Archived,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
161
189
|
]
|
|
162
190
|
|
|
163
191
|
return (
|
|
@@ -178,6 +206,8 @@ export const ConversationFilters = () => {
|
|
|
178
206
|
return <TeamRow {...item.data} />
|
|
179
207
|
case SectionTypes.more:
|
|
180
208
|
return <ViewMore {...item.data} />
|
|
209
|
+
case SectionTypes.other:
|
|
210
|
+
return <OtherRow {...item.data} />
|
|
181
211
|
default:
|
|
182
212
|
return null
|
|
183
213
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { IconDefinition } from '@fortawesome/fontawesome-svg-core'
|
|
1
2
|
import { PlatformPressable } from '@react-navigation/elements'
|
|
2
3
|
import { NavigationProp, useNavigation } from '@react-navigation/native'
|
|
3
4
|
import React, { PropsWithChildren, useContext } from 'react'
|
|
4
5
|
import { StyleSheet, View, ViewStyle } from 'react-native'
|
|
5
|
-
import { Heading, Icon, Image, Text, TextButton } from '../../../components'
|
|
6
|
+
import { Heading, Icon, IconTile, Image, Text, TextButton } from '../../../components'
|
|
6
7
|
import {
|
|
7
8
|
useAtFontScaleBreakpoint,
|
|
8
9
|
useFontScale,
|
|
@@ -115,6 +116,33 @@ export const TeamRow = ({ team }: TeamRowProps) => {
|
|
|
115
116
|
)
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
export type OtherRowProps = {
|
|
120
|
+
show_archived?: boolean
|
|
121
|
+
isActive: boolean
|
|
122
|
+
filter: FilterTypes
|
|
123
|
+
description: string
|
|
124
|
+
icon: IconDefinition
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const OtherRow = ({ show_archived, isActive, filter, description, icon }: OtherRowProps) => {
|
|
128
|
+
const styles = useRowStyles()
|
|
129
|
+
const { setAppFilter } = useContext(FilterContext)
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<PressableRow onPress={() => setAppFilter({ show_archived })} isActive={isActive}>
|
|
133
|
+
<IconTile icon={icon} size="lg" />
|
|
134
|
+
<View style={styles.rowTextContainer}>
|
|
135
|
+
<Heading variant="h3" style={styles.rowTitle}>
|
|
136
|
+
{filter}
|
|
137
|
+
</Heading>
|
|
138
|
+
<Text variant="footnote" style={styles.rowSubtitle}>
|
|
139
|
+
{description}
|
|
140
|
+
</Text>
|
|
141
|
+
</View>
|
|
142
|
+
</PressableRow>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
118
146
|
export type ViewMoreRowProps = { routeName: keyof ConversationFilterStackParamList }
|
|
119
147
|
|
|
120
148
|
export const ViewMore = ({ routeName }: ViewMoreRowProps) => {
|
|
@@ -22,6 +22,7 @@ export const FilterContext = createContext<{
|
|
|
22
22
|
chat_group_graph_id: undefined,
|
|
23
23
|
group_source_app_name: undefined,
|
|
24
24
|
show_direct_messages: undefined,
|
|
25
|
+
show_archived: undefined,
|
|
25
26
|
},
|
|
26
27
|
scrollOffset: 0,
|
|
27
28
|
setScrollOffset: () => {},
|
|
@@ -57,6 +58,7 @@ export const FilterProvider = ({ children }: PropsWithChildren) => {
|
|
|
57
58
|
chat_group_graph_id: undefined,
|
|
58
59
|
group_source_app_name: undefined,
|
|
59
60
|
show_direct_messages: undefined,
|
|
61
|
+
show_archived: undefined,
|
|
60
62
|
})
|
|
61
63
|
)
|
|
62
64
|
},
|
|
@@ -65,6 +67,7 @@ export const FilterProvider = ({ children }: PropsWithChildren) => {
|
|
|
65
67
|
chat_group_graph_id: params.chat_group_graph_id,
|
|
66
68
|
group_source_app_name: undefined,
|
|
67
69
|
show_direct_messages: undefined,
|
|
70
|
+
show_archived: undefined,
|
|
68
71
|
})
|
|
69
72
|
},
|
|
70
73
|
setAppFilter: (params: Omit<ConversationFiltersParams, 'chat_group_graph_id'>) => {
|
|
@@ -72,6 +75,7 @@ export const FilterProvider = ({ children }: PropsWithChildren) => {
|
|
|
72
75
|
chat_group_graph_id: undefined,
|
|
73
76
|
group_source_app_name: params.group_source_app_name,
|
|
74
77
|
show_direct_messages: params.show_direct_messages,
|
|
78
|
+
show_archived: params.show_archived,
|
|
75
79
|
})
|
|
76
80
|
},
|
|
77
81
|
applyFilters: (params: ConversationFiltersParams) => {
|