@planningcenter/chat-react-native 4.4.1-qa-staging.17 → 4.4.1-qa-staging.19

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.
Files changed (157) hide show
  1. package/build/components/conversations/archived_results_footer.d.ts +6 -0
  2. package/build/components/conversations/archived_results_footer.d.ts.map +1 -0
  3. package/build/components/conversations/archived_results_footer.js +51 -0
  4. package/build/components/conversations/archived_results_footer.js.map +1 -0
  5. package/build/components/conversations/conversation_actions.d.ts.map +1 -1
  6. package/build/components/conversations/conversation_actions.js +241 -206
  7. package/build/components/conversations/conversation_actions.js.map +1 -1
  8. package/build/components/conversations/conversation_preview.d.ts.map +1 -1
  9. package/build/components/conversations/conversation_preview.js +99 -93
  10. package/build/components/conversations/conversation_preview.js.map +1 -1
  11. package/build/components/conversations/conversations.d.ts.map +1 -1
  12. package/build/components/conversations/conversations.js +44 -35
  13. package/build/components/conversations/conversations.js.map +1 -1
  14. package/build/components/display/filter_dropdown.d.ts +2 -1
  15. package/build/components/display/filter_dropdown.d.ts.map +1 -1
  16. package/build/components/display/filter_dropdown.js +63 -58
  17. package/build/components/display/filter_dropdown.js.map +1 -1
  18. package/build/components/display/icon_tile.d.ts +12 -0
  19. package/build/components/display/icon_tile.d.ts.map +1 -0
  20. package/build/components/display/icon_tile.js +109 -0
  21. package/build/components/display/icon_tile.js.map +1 -0
  22. package/build/components/display/index.d.ts +1 -0
  23. package/build/components/display/index.d.ts.map +1 -1
  24. package/build/components/display/index.js +1 -0
  25. package/build/components/display/index.js.map +1 -1
  26. package/build/contexts/conversations_context.d.ts.map +1 -1
  27. package/build/contexts/conversations_context.js +21 -18
  28. package/build/contexts/conversations_context.js.map +1 -1
  29. package/build/hooks/index.d.ts +2 -0
  30. package/build/hooks/index.d.ts.map +1 -1
  31. package/build/hooks/index.js +2 -0
  32. package/build/hooks/index.js.map +1 -1
  33. package/build/hooks/use_can_browse_archived_conversations.d.ts +2 -0
  34. package/build/hooks/use_can_browse_archived_conversations.d.ts.map +1 -0
  35. package/build/hooks/use_can_browse_archived_conversations.js +29 -0
  36. package/build/hooks/use_can_browse_archived_conversations.js.map +1 -0
  37. package/build/hooks/use_clear_conversation_filters.d.ts.map +1 -1
  38. package/build/hooks/use_clear_conversation_filters.js +2 -1
  39. package/build/hooks/use_clear_conversation_filters.js.map +1 -1
  40. package/build/hooks/use_conversation.d.ts.map +1 -1
  41. package/build/hooks/use_conversation.js +32 -24
  42. package/build/hooks/use_conversation.js.map +1 -1
  43. package/build/hooks/use_current_person.d.ts.map +1 -1
  44. package/build/hooks/use_current_person.js +1 -1
  45. package/build/hooks/use_current_person.js.map +1 -1
  46. package/build/hooks/use_jolt.d.ts.map +1 -1
  47. package/build/hooks/use_jolt.js +29 -8
  48. package/build/hooks/use_jolt.js.map +1 -1
  49. package/build/hooks/use_manual_archiving_enabled.d.ts +2 -0
  50. package/build/hooks/use_manual_archiving_enabled.d.ts.map +1 -0
  51. package/build/hooks/use_manual_archiving_enabled.js +18 -0
  52. package/build/hooks/use_manual_archiving_enabled.js.map +1 -0
  53. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  54. package/build/screens/conversation_details_screen.js +2 -1
  55. package/build/screens/conversation_details_screen.js.map +1 -1
  56. package/build/screens/conversation_filters/components/conversation_filters.d.ts.map +1 -1
  57. package/build/screens/conversation_filters/components/conversation_filters.js +80 -13
  58. package/build/screens/conversation_filters/components/conversation_filters.js.map +1 -1
  59. package/build/screens/conversation_filters/components/rows.d.ts +9 -0
  60. package/build/screens/conversation_filters/components/rows.d.ts.map +1 -1
  61. package/build/screens/conversation_filters/components/rows.js +92 -1
  62. package/build/screens/conversation_filters/components/rows.js.map +1 -1
  63. package/build/screens/conversation_filters/context/conversation_filter_context.d.ts.map +1 -1
  64. package/build/screens/conversation_filters/context/conversation_filter_context.js +8 -4
  65. package/build/screens/conversation_filters/context/conversation_filter_context.js.map +1 -1
  66. package/build/screens/conversation_filters/filter_types.d.ts +1 -0
  67. package/build/screens/conversation_filters/filter_types.d.ts.map +1 -1
  68. package/build/screens/conversation_filters/filter_types.js +1 -0
  69. package/build/screens/conversation_filters/filter_types.js.map +1 -1
  70. package/build/screens/conversation_filters/screen_props.d.ts +1 -0
  71. package/build/screens/conversation_filters/screen_props.d.ts.map +1 -1
  72. package/build/screens/conversation_filters/screen_props.js.map +1 -1
  73. package/build/screens/conversation_filters_screen.d.ts +1 -0
  74. package/build/screens/conversation_filters_screen.d.ts.map +1 -1
  75. package/build/screens/conversation_filters_screen.js +75 -49
  76. package/build/screens/conversation_filters_screen.js.map +1 -1
  77. package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts +2 -0
  78. package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts.map +1 -0
  79. package/build/screens/conversations/components/filter_dropdown_list_header_component.js +580 -0
  80. package/build/screens/conversations/components/filter_dropdown_list_header_component.js.map +1 -0
  81. package/build/screens/conversations/components/list_header_component.d.ts.map +1 -1
  82. package/build/screens/conversations/components/list_header_component.js +95 -90
  83. package/build/screens/conversations/components/list_header_component.js.map +1 -1
  84. package/build/screens/conversations/components/search_list_header_component.d.ts.map +1 -1
  85. package/build/screens/conversations/components/search_list_header_component.js +100 -95
  86. package/build/screens/conversations/components/search_list_header_component.js.map +1 -1
  87. package/build/screens/conversations/conversations_screen.d.ts +1 -0
  88. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  89. package/build/screens/conversations/conversations_screen.js +15 -2
  90. package/build/screens/conversations/conversations_screen.js.map +1 -1
  91. package/build/types/resources/person.d.ts +1 -0
  92. package/build/types/resources/person.d.ts.map +1 -1
  93. package/build/types/resources/person.js.map +1 -1
  94. package/build/utils/date.d.ts.map +1 -1
  95. package/build/utils/date.js +5 -10
  96. package/build/utils/date.js.map +1 -1
  97. package/build/utils/native_adapters/menu.d.ts +1 -0
  98. package/build/utils/native_adapters/menu.d.ts.map +1 -1
  99. package/build/utils/native_adapters/menu.js +1 -0
  100. package/build/utils/native_adapters/menu.js.map +1 -1
  101. package/build/utils/request/conversation.d.ts +2 -1
  102. package/build/utils/request/conversation.d.ts.map +1 -1
  103. package/build/utils/request/conversation.js +4 -1
  104. package/build/utils/request/conversation.js.map +1 -1
  105. package/build/vendor/font_awesome_pro_solid_icons.d.ts +1 -0
  106. package/build/vendor/font_awesome_pro_solid_icons.d.ts.map +1 -1
  107. package/build/vendor/font_awesome_pro_solid_icons.js +5 -0
  108. package/build/vendor/font_awesome_pro_solid_icons.js.map +1 -1
  109. package/package.json +3 -3
  110. package/src/__tests__/hooks/use_can_browse_archived_conversations.test.tsx +98 -0
  111. package/src/__tests__/screens/conversation_filters/components/conversation_filters.test.tsx +3 -0
  112. package/src/__tests__/screens/conversation_filters_screen.test.tsx +76 -0
  113. package/src/components/conversations/__tests__/conversation_actions.test.tsx +67 -0
  114. package/src/components/conversations/__tests__/conversation_preview.test.tsx +38 -3
  115. package/src/components/conversations/__tests__/conversations.test.tsx +82 -0
  116. package/src/components/conversations/__tests__/conversations_blank_state.test.tsx +1 -0
  117. package/src/components/conversations/archived_results_footer.tsx +36 -0
  118. package/src/components/conversations/conversation_actions.tsx +27 -19
  119. package/src/components/conversations/conversation_preview.tsx +30 -10
  120. package/src/components/conversations/conversations.tsx +15 -3
  121. package/src/components/display/__tests__/filter_dropdown.test.tsx +9 -1
  122. package/src/components/display/filter_dropdown.tsx +14 -13
  123. package/src/components/display/icon_tile.tsx +54 -0
  124. package/src/components/display/index.ts +1 -0
  125. package/src/contexts/conversations_context.tsx +2 -1
  126. package/src/hooks/__tests__/use_conversation_archive.test.tsx +22 -0
  127. package/src/hooks/__tests__/use_jolt.test.tsx +30 -8
  128. package/src/hooks/index.ts +2 -0
  129. package/src/hooks/use_can_browse_archived_conversations.ts +17 -0
  130. package/src/hooks/use_clear_conversation_filters.ts +1 -0
  131. package/src/hooks/use_conversation.ts +4 -1
  132. package/src/hooks/use_current_person.ts +1 -0
  133. package/src/hooks/use_jolt.ts +33 -8
  134. package/src/hooks/use_manual_archiving_enabled.ts +10 -0
  135. package/src/screens/conversation_details_screen.tsx +2 -3
  136. package/src/screens/conversation_filters/__tests__/conversation_filters.test.tsx +101 -0
  137. package/src/screens/conversation_filters/components/conversation_filters.tsx +32 -2
  138. package/src/screens/conversation_filters/components/rows.tsx +29 -1
  139. package/src/screens/conversation_filters/context/conversation_filter_context.tsx +4 -0
  140. package/src/screens/conversation_filters/filter_types.ts +1 -0
  141. package/src/screens/conversation_filters/screen_props.ts +1 -0
  142. package/src/screens/conversation_filters_screen.tsx +20 -4
  143. package/src/screens/conversations/__tests__/conversations_screen.test.tsx +52 -0
  144. package/src/screens/conversations/components/__tests__/filter_dropdown_list_header_component.test.tsx +259 -0
  145. package/src/screens/conversations/components/__tests__/list_header_component.test.tsx +21 -4
  146. package/src/screens/conversations/components/filter_dropdown_list_header_component.tsx +286 -0
  147. package/src/screens/conversations/components/list_header_component.tsx +17 -12
  148. package/src/screens/conversations/components/search_list_header_component.tsx +17 -12
  149. package/src/screens/conversations/conversations_screen.tsx +15 -2
  150. package/src/types/resources/person.ts +1 -0
  151. package/src/utils/__tests__/conversation.test.ts +1 -0
  152. package/src/utils/__tests__/date.test.ts +4 -0
  153. package/src/utils/date.ts +5 -4
  154. package/src/utils/native_adapters/__tests__/menu.test.tsx +6 -2
  155. package/src/utils/native_adapters/menu.tsx +2 -0
  156. package/src/utils/request/conversation.ts +4 -0
  157. package/src/vendor/font_awesome_pro_solid_icons.ts +12 -0
@@ -0,0 +1,286 @@
1
+ import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'
2
+ import React, { useCallback, useMemo } from 'react'
3
+ import { Alert, StyleSheet, View } from 'react-native'
4
+ import { FilterDropdown, FilterDropdownOption, TextButton } from '../../../components'
5
+ import {
6
+ useAtFontScaleBreakpoint,
7
+ useCanBrowseArchivedConversations,
8
+ useCanCreateConversations,
9
+ useCurrentPersonCache,
10
+ useDirectMessagesEligible,
11
+ useHasDirectMessages,
12
+ useNewConversationEntry,
13
+ useTheme,
14
+ } from '../../../hooks'
15
+ import { useApiGet } from '../../../hooks/use_api'
16
+ import { useMarkAllRead } from '../../../hooks/use_conversations_actions'
17
+ import { useCanDisplayGroups } from '../../../hooks/use_groups'
18
+ import { useNewConversationFromFilter } from '../../../hooks/use_new_conversation_from_filter'
19
+ import { useSearchEnabled } from '../../../hooks/use_search_enabled'
20
+ import { GraphId, GroupResource } from '../../../types/resources/group_resource'
21
+ import { MAX_FONT_SIZE_MULTIPLIER_LANDMARK } from '../../../utils'
22
+ import { Haptic } from '../../../utils/native_adapters'
23
+ import { ConversationFiltersParams } from '../../conversation_filters/screen_props'
24
+ import { ConversationsScreenProps } from '../conversations_screen'
25
+ import { ListHeaderActionButton } from './list_header_action_button'
26
+
27
+ const ROW_PADDING_HORIZONTAL = 16
28
+ const ALL_CONVERSATIONS_LABEL = 'All conversations'
29
+ const ARCHIVED_LABEL = 'Archived'
30
+
31
+ enum FilterTypes {
32
+ All = 'All',
33
+ Groups = 'Groups',
34
+ Teams = 'Teams',
35
+ DMs = 'DMs',
36
+ More = 'More',
37
+ }
38
+
39
+ const FILTER_PARAMS: Record<Exclude<FilterTypes, FilterTypes.More>, ConversationFiltersParams> = {
40
+ [FilterTypes.All]: {},
41
+ [FilterTypes.Groups]: { group_source_app_name: 'Groups' },
42
+ [FilterTypes.Teams]: { group_source_app_name: 'Services' },
43
+ [FilterTypes.DMs]: { show_direct_messages: true },
44
+ }
45
+
46
+ export const FilterDropdownListHeaderComponent = () => {
47
+ const navigation = useNavigation()
48
+ const canFilterByTeams = useCanDisplayGroups({ source_app_name: 'Services', source_type: 'Team' })
49
+ const canFilterByGroups = useCanDisplayGroups({ source_app_name: 'Groups', source_type: 'Group' })
50
+ const directMessagesEligible = useDirectMessagesEligible()
51
+ const hasDirectMessages = useHasDirectMessages()
52
+ const canFilterByDMs = directMessagesEligible && hasDirectMessages
53
+ const canBrowseArchived = useCanBrowseArchivedConversations()
54
+ const searchEnabled = useSearchEnabled()
55
+ const route = useRoute<RouteProp<ConversationsScreenProps['route']>>()
56
+ const {
57
+ title,
58
+ chat_group_graph_id,
59
+ group_source_app_name = '',
60
+ show_direct_messages,
61
+ show_archived,
62
+ } = route.params || {}
63
+ const currentPersonCache = useCurrentPersonCache()
64
+ const { markAllRead, isPending } = useMarkAllRead()
65
+ const canCreateConversations = useCanCreateConversations()
66
+ const entryMode = useNewConversationEntry()
67
+ const newConversationFromFilter = useNewConversationFromFilter(
68
+ chat_group_graph_id as GraphId | undefined
69
+ )
70
+ const { data: group } = useApiGet<GroupResource>(
71
+ { url: `/me/groups/${chat_group_graph_id}`, data: { fields: { Group: [] } }, app: 'chat' },
72
+ { enabled: !!chat_group_graph_id }
73
+ )
74
+
75
+ const active: FilterTypes = useMemo(() => {
76
+ if (chat_group_graph_id || show_archived) {
77
+ return FilterTypes.More
78
+ } else if (/groups/i.test(group_source_app_name)) {
79
+ return FilterTypes.Groups
80
+ } else if (/services/i.test(group_source_app_name)) {
81
+ return FilterTypes.Teams
82
+ } else if (show_direct_messages) {
83
+ return FilterTypes.DMs
84
+ }
85
+
86
+ return FilterTypes.All
87
+ }, [chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived])
88
+
89
+ const options: FilterDropdownOption[] = useMemo(
90
+ () => [
91
+ { id: FilterTypes.All, label: FilterTypes.All },
92
+ ...(canFilterByGroups ? [{ id: FilterTypes.Groups, label: FilterTypes.Groups }] : []),
93
+ ...(canFilterByTeams ? [{ id: FilterTypes.Teams, label: FilterTypes.Teams }] : []),
94
+ ...(canFilterByDMs ? [{ id: FilterTypes.DMs, label: FilterTypes.DMs }] : []),
95
+ ],
96
+ [canFilterByGroups, canFilterByTeams, canFilterByDMs]
97
+ )
98
+
99
+ const triggerLabel = useMemo(() => {
100
+ if (chat_group_graph_id) return group?.name || title || ALL_CONVERSATIONS_LABEL
101
+ if (show_archived) return ARCHIVED_LABEL
102
+ if (active === FilterTypes.All) return ALL_CONVERSATIONS_LABEL
103
+
104
+ return active
105
+ }, [chat_group_graph_id, group?.name, title, show_archived, active])
106
+
107
+ const handleNewConversationNavigation = useCallback(() => {
108
+ if (newConversationFromFilter || entryMode === 'select_type') {
109
+ return navigation.navigate('ConversationSelectType', {
110
+ ...route.params,
111
+ newConversationFromFilter,
112
+ })
113
+ }
114
+
115
+ if (entryMode === 'groups') {
116
+ return navigation.navigate('New', {
117
+ screen: 'ConversationSelectGroupRecipients',
118
+ params: { ...route.params },
119
+ })
120
+ }
121
+
122
+ if (entryMode === 'teams') {
123
+ return navigation.navigate('New', {
124
+ screen: 'ConversationSelectTeamsILeadRecipients',
125
+ params: { ...route.params },
126
+ })
127
+ }
128
+
129
+ if (entryMode === 'direct_message') {
130
+ return navigation.navigate('New', {
131
+ screen: 'ConversationSelectDirectMessageRecipients',
132
+ params: { ...route.params },
133
+ })
134
+ }
135
+ }, [navigation, route.params, entryMode, newConversationFromFilter])
136
+
137
+ const handleSetFilter = useCallback(
138
+ (params: ConversationFiltersParams) => {
139
+ navigation.setParams({
140
+ title: undefined,
141
+ chat_group_graph_id: undefined,
142
+ group_source_app_name: undefined,
143
+ show_direct_messages: undefined,
144
+ show_archived: undefined,
145
+ ...params,
146
+ })
147
+ },
148
+ [navigation]
149
+ )
150
+
151
+ const handleSelectFilter = useCallback(
152
+ (id: string) => {
153
+ const params = FILTER_PARAMS[id as keyof typeof FILTER_PARAMS]
154
+ if (params) handleSetFilter(params)
155
+ },
156
+ [handleSetFilter]
157
+ )
158
+
159
+ const handleSearch = useCallback(() => {
160
+ navigation.navigate('Search')
161
+ }, [navigation])
162
+
163
+ const handleChatSettings = useCallback(() => {
164
+ navigation.navigate('ChatSettings', {})
165
+ }, [navigation])
166
+
167
+ const handleOpenConversationFilters = useCallback(() => {
168
+ navigation.navigate('ConversationFilters', {
169
+ chat_group_graph_id,
170
+ group_source_app_name,
171
+ show_direct_messages,
172
+ show_archived,
173
+ })
174
+ }, [navigation, chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived])
175
+
176
+ const handleMarkAllRead = useCallback(() => {
177
+ currentPersonCache.update({ unreadCount: 0 })
178
+ Haptic.notificationSuccess()
179
+ markAllRead()
180
+ }, [currentPersonCache, markAllRead])
181
+
182
+ const alertMarkAllRead = useCallback(() => {
183
+ if (active === FilterTypes.All) return handleMarkAllRead()
184
+
185
+ Alert.alert('Mark all read', 'This includes conversations not shown by the current filter.', [
186
+ { text: 'Cancel' },
187
+ { text: 'OK', onPress: () => handleMarkAllRead() },
188
+ ])
189
+ }, [active, handleMarkAllRead])
190
+
191
+ const showMoreFilters = canFilterByGroups || canFilterByTeams || canBrowseArchived
192
+
193
+ const styles = useStyles()
194
+
195
+ return (
196
+ <View style={styles.listHeader}>
197
+ <View style={styles.headingRow}>
198
+ <View style={styles.titleContainer}>
199
+ <FilterDropdown
200
+ options={options}
201
+ selectedId={active}
202
+ onSelect={handleSelectFilter}
203
+ onMoreFilters={handleOpenConversationFilters}
204
+ showMoreFilters={showMoreFilters}
205
+ triggerLabel={triggerLabel}
206
+ />
207
+ </View>
208
+ <View style={styles.actionsContainer}>
209
+ <ListHeaderActionButton
210
+ name="general.cog"
211
+ onPress={handleChatSettings}
212
+ accessibilityLabel="Chat settings"
213
+ testID="conversations-chat-settings-button"
214
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
215
+ />
216
+ {canCreateConversations && (
217
+ <ListHeaderActionButton
218
+ name="churchCenter.signups"
219
+ onPress={handleNewConversationNavigation}
220
+ accessibilityLabel="New conversation"
221
+ testID="conversations-new-conversation-button"
222
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
223
+ />
224
+ )}
225
+ {searchEnabled && (
226
+ <ListHeaderActionButton
227
+ name="general.search"
228
+ onPress={handleSearch}
229
+ accessibilityLabel="Search"
230
+ testID="conversations-search-button"
231
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
232
+ />
233
+ )}
234
+ </View>
235
+ </View>
236
+ {!show_archived && (
237
+ <View style={styles.markAllReadRow}>
238
+ <TextButton
239
+ onPress={alertMarkAllRead}
240
+ disabled={isPending}
241
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
242
+ accessibilityShowsLargeContentViewer
243
+ accessibilityLargeContentTitle="Mark all read"
244
+ >
245
+ Mark all read
246
+ </TextButton>
247
+ </View>
248
+ )}
249
+ </View>
250
+ )
251
+ }
252
+
253
+ const useStyles = () => {
254
+ const theme = useTheme()
255
+ const atFontScaleBreakpoint = useAtFontScaleBreakpoint()
256
+
257
+ return StyleSheet.create({
258
+ listHeader: {
259
+ borderBottomWidth: 1,
260
+ borderBottomColor: theme.colors.fillColorNeutral050Base,
261
+ paddingBottom: 16,
262
+ gap: 8,
263
+ },
264
+ headingRow: {
265
+ gap: 12,
266
+ flexDirection: atFontScaleBreakpoint ? 'column' : 'row',
267
+ alignItems: atFontScaleBreakpoint ? 'flex-start' : 'center',
268
+ justifyContent: 'space-between',
269
+ paddingBottom: atFontScaleBreakpoint ? 4 : 0,
270
+ paddingHorizontal: ROW_PADDING_HORIZONTAL,
271
+ },
272
+ titleContainer: {
273
+ flexShrink: 1,
274
+ minWidth: 0,
275
+ },
276
+ actionsContainer: {
277
+ flexDirection: 'row',
278
+ gap: 8,
279
+ alignItems: 'center',
280
+ },
281
+ markAllReadRow: {
282
+ alignItems: 'flex-start',
283
+ paddingHorizontal: ROW_PADDING_HORIZONTAL,
284
+ },
285
+ })
286
+ }
@@ -44,6 +44,7 @@ export const ListHeaderComponent = () => {
44
44
  chat_group_graph_id,
45
45
  group_source_app_name = '',
46
46
  show_direct_messages,
47
+ show_archived,
47
48
  } = route.params || {}
48
49
  const currentPersonCache = useCurrentPersonCache()
49
50
  const { markAllRead, isPending } = useMarkAllRead()
@@ -54,7 +55,7 @@ export const ListHeaderComponent = () => {
54
55
  )
55
56
 
56
57
  const active: FilterTypes = useMemo(() => {
57
- if (chat_group_graph_id) {
58
+ if (chat_group_graph_id || show_archived) {
58
59
  return FilterTypes.More
59
60
  } else if (/groups/i.test(group_source_app_name)) {
60
61
  return FilterTypes.Groups
@@ -65,7 +66,7 @@ export const ListHeaderComponent = () => {
65
66
  }
66
67
 
67
68
  return FilterTypes.All
68
- }, [chat_group_graph_id, group_source_app_name, show_direct_messages])
69
+ }, [chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived])
69
70
 
70
71
  const handleNewConversationNavigation = useCallback(() => {
71
72
  if (newConversationFromFilter || entryMode === 'select_type') {
@@ -104,6 +105,7 @@ export const ListHeaderComponent = () => {
104
105
  chat_group_graph_id: undefined,
105
106
  group_source_app_name: undefined,
106
107
  show_direct_messages: undefined,
108
+ show_archived: undefined,
107
109
  ...params,
108
110
  })
109
111
  },
@@ -119,8 +121,9 @@ export const ListHeaderComponent = () => {
119
121
  chat_group_graph_id,
120
122
  group_source_app_name,
121
123
  show_direct_messages,
124
+ show_archived,
122
125
  })
123
- }, [navigation, chat_group_graph_id, group_source_app_name, show_direct_messages])
126
+ }, [navigation, chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived])
124
127
 
125
128
  const handleMarkAllRead = useCallback(() => {
126
129
  currentPersonCache.update({ unreadCount: 0 })
@@ -161,15 +164,17 @@ export const ListHeaderComponent = () => {
161
164
  <Heading numberOfLines={1} variant="h2">
162
165
  Conversations
163
166
  </Heading>
164
- <TextButton
165
- onPress={alertMarkAllRead}
166
- disabled={isPending}
167
- maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
168
- accessibilityShowsLargeContentViewer
169
- accessibilityLargeContentTitle="Mark all read"
170
- >
171
- Mark all read
172
- </TextButton>
167
+ {!show_archived && (
168
+ <TextButton
169
+ onPress={alertMarkAllRead}
170
+ disabled={isPending}
171
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
172
+ accessibilityShowsLargeContentViewer
173
+ accessibilityLargeContentTitle="Mark all read"
174
+ >
175
+ Mark all read
176
+ </TextButton>
177
+ )}
173
178
  </View>
174
179
  <View style={styles.actionsContainer}>
175
180
  <ListHeaderActionButton
@@ -44,6 +44,7 @@ export const SearchListHeaderComponent = () => {
44
44
  chat_group_graph_id,
45
45
  group_source_app_name = '',
46
46
  show_direct_messages,
47
+ show_archived,
47
48
  } = route.params || {}
48
49
  const currentPersonCache = useCurrentPersonCache()
49
50
  const { markAllRead, isPending } = useMarkAllRead()
@@ -54,7 +55,7 @@ export const SearchListHeaderComponent = () => {
54
55
  )
55
56
 
56
57
  const active: FilterTypes = useMemo(() => {
57
- if (chat_group_graph_id) {
58
+ if (chat_group_graph_id || show_archived) {
58
59
  return FilterTypes.More
59
60
  } else if (/groups/i.test(group_source_app_name)) {
60
61
  return FilterTypes.Groups
@@ -65,7 +66,7 @@ export const SearchListHeaderComponent = () => {
65
66
  }
66
67
 
67
68
  return FilterTypes.All
68
- }, [chat_group_graph_id, group_source_app_name, show_direct_messages])
69
+ }, [chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived])
69
70
 
70
71
  const handleNewConversationNavigation = useCallback(() => {
71
72
  if (newConversationFromFilter || entryMode === 'select_type') {
@@ -104,6 +105,7 @@ export const SearchListHeaderComponent = () => {
104
105
  chat_group_graph_id: undefined,
105
106
  group_source_app_name: undefined,
106
107
  show_direct_messages: undefined,
108
+ show_archived: undefined,
107
109
  ...params,
108
110
  })
109
111
  },
@@ -123,8 +125,9 @@ export const SearchListHeaderComponent = () => {
123
125
  chat_group_graph_id,
124
126
  group_source_app_name,
125
127
  show_direct_messages,
128
+ show_archived,
126
129
  })
127
- }, [navigation, chat_group_graph_id, group_source_app_name, show_direct_messages])
130
+ }, [navigation, chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived])
128
131
 
129
132
  const handleMarkAllRead = useCallback(() => {
130
133
  currentPersonCache.update({ unreadCount: 0 })
@@ -165,15 +168,17 @@ export const SearchListHeaderComponent = () => {
165
168
  <Heading numberOfLines={1} variant="h2">
166
169
  Conversations
167
170
  </Heading>
168
- <TextButton
169
- onPress={alertMarkAllRead}
170
- disabled={isPending}
171
- maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
172
- accessibilityShowsLargeContentViewer
173
- accessibilityLargeContentTitle="Mark all read"
174
- >
175
- Mark all read
176
- </TextButton>
171
+ {!show_archived && (
172
+ <TextButton
173
+ onPress={alertMarkAllRead}
174
+ disabled={isPending}
175
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
176
+ accessibilityShowsLargeContentViewer
177
+ accessibilityLargeContentTitle="Mark all read"
178
+ >
179
+ Mark all read
180
+ </TextButton>
181
+ )}
177
182
  </View>
178
183
  <View style={styles.actionsContainer}>
179
184
  <ListHeaderActionButton
@@ -3,10 +3,13 @@ import React from 'react'
3
3
  import { StyleSheet, View } from 'react-native'
4
4
  import { Conversations } from '../../components'
5
5
  import { ConversationsContextProvider } from '../../contexts/conversations_context'
6
+ import { useFilterDropdownEnabled } from '../../hooks/use_filter_dropdown_enabled'
6
7
  import { analyticsEvents, usePublishProductAnalyticsEvent } from '../../hooks/use_product_analytics'
7
8
  import { useSearchEnabled } from '../../hooks/use_search_enabled'
8
9
  import { AppName } from '../../types/resources/app_name'
9
10
  import { GraphId } from '../../types/resources/group_resource'
11
+ import { Menu } from '../../utils/native_adapters'
12
+ import { FilterDropdownListHeaderComponent } from './components/filter_dropdown_list_header_component'
10
13
  import { ListHeaderComponent } from './components/list_header_component'
11
14
  import { SearchListHeaderComponent } from './components/search_list_header_component'
12
15
 
@@ -15,12 +18,12 @@ export type ConversationsScreenProps = StaticScreenProps<{
15
18
  chat_group_graph_id?: GraphId
16
19
  group_source_app_name?: AppName
17
20
  show_direct_messages?: boolean
21
+ show_archived?: boolean
18
22
  }>
19
23
 
20
24
  export function ConversationsScreen({ route }: ConversationsScreenProps) {
21
25
  const styles = useStyles()
22
- const searchEnabled = useSearchEnabled()
23
- const Header = searchEnabled ? SearchListHeaderComponent : ListHeaderComponent
26
+ const Header = useListHeaderComponent()
24
27
 
25
28
  usePublishProductAnalyticsEvent(analyticsEvents.conversation_index_opened)
26
29
 
@@ -33,6 +36,16 @@ export function ConversationsScreen({ route }: ConversationsScreenProps) {
33
36
  )
34
37
  }
35
38
 
39
+ const useListHeaderComponent = () => {
40
+ const filterDropdownEnabled = useFilterDropdownEnabled()
41
+ const searchEnabled = useSearchEnabled()
42
+
43
+ if (filterDropdownEnabled && Menu.isConfigured) return FilterDropdownListHeaderComponent
44
+ if (searchEnabled) return SearchListHeaderComponent
45
+
46
+ return ListHeaderComponent
47
+ }
48
+
36
49
  const useStyles = () => {
37
50
  return StyleSheet.create({
38
51
  container: {
@@ -23,5 +23,6 @@ export interface CurrentPersonResource extends PersonResource {
23
23
  ageQualificationStatus?: AgeQualificationStatus
24
24
  directMessagesDisabled: boolean
25
25
  directMessagesEligible: boolean
26
+ canBrowseArchivedConversations?: boolean
26
27
  under_18: boolean
27
28
  }
@@ -12,6 +12,7 @@ describe('getConversationsRequestArgs', () => {
12
12
  ],
13
13
  ['chat_group_graph_id', { chat_group_graph_id: groupId }, 'mine_or_not_empty,group'],
14
14
  ['show_direct_messages', { show_direct_messages: true }, 'mine_or_not_empty,direct_message'],
15
+ ['show_archived', { show_archived: true }, 'mine_or_not_empty,archived'],
15
16
  ])('%s uses filter: %s', (_label, args, expectedFilter) => {
16
17
  expect(getConversationsRequestArgs(args).data.filter).toBe(expectedFilter)
17
18
  })
@@ -16,4 +16,8 @@ describe('formatEventDateTime', () => {
16
16
  it('includes the year for dates in other years', () => {
17
17
  expect(formatEventDateTime(new Date(2025, 1, 3, 14, 18))).toBe('Feb 3, 2025 at 2:18pm')
18
18
  })
19
+
20
+ it('drops the minutes on the hour', () => {
21
+ expect(formatEventDateTime(new Date(2026, 1, 3, 14, 0))).toBe('Feb 3 at 2pm')
22
+ })
19
23
  })
package/src/utils/date.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { date as formatDate, time as formatTime } from '@planningcenter/datetime-fmt'
1
+ import { date as formatDate } from '@planningcenter/datetime-fmt'
2
2
  import dayjs from './dayjs'
3
3
 
4
4
  export type DateProps = string | number | Date
@@ -64,7 +64,8 @@ export function relativeTime(date: DateProps): string {
64
64
 
65
65
  export function formatEventDateTime(date: DateProps) {
66
66
  const { isThisYear } = getRelativeDateStatus(date)
67
- const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
68
- const day = formatDate(date, { style: 'abbreviated', year: !isThisYear, timeZone })
69
- return `${day} at ${formatTime(date, { timeZone })}`
67
+ const local = dayjs(date)
68
+ const day = local.format(isThisYear ? 'MMM D' : 'MMM D, YYYY')
69
+ const time = local.format(local.minute() === 0 ? 'ha' : 'h:mma')
70
+ return `${day} at ${time}`
70
71
  }
@@ -8,7 +8,9 @@ const actions = [{ id: 'all', title: 'All' }]
8
8
  describe('MenuAdapter', () => {
9
9
  it('renders the trigger and warns when the host configured no menu', () => {
10
10
  const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
11
- const { View } = new MenuAdapter()
11
+ const { View, isConfigured } = new MenuAdapter()
12
+
13
+ expect(isConfigured).toBe(false)
12
14
 
13
15
  render(
14
16
  <View actions={actions}>
@@ -28,7 +30,9 @@ describe('MenuAdapter', () => {
28
30
  <Text>{hostActions.map(action => action.title).join(',')}</Text>
29
31
  </>
30
32
  )
31
- const { View } = new MenuAdapter({ View: HostMenuView })
33
+ const { View, isConfigured } = new MenuAdapter({ View: HostMenuView })
34
+
35
+ expect(isConfigured).toBe(true)
32
36
 
33
37
  render(
34
38
  <View actions={actions}>
@@ -61,8 +61,10 @@ function UnconfiguredMenuView({ children, style, testID }: MenuViewProps) {
61
61
 
62
62
  export class MenuAdapter {
63
63
  View: MenuView
64
+ isConfigured: boolean
64
65
 
65
66
  constructor(methods?: { View: MenuView }) {
66
67
  this.View = methods?.View ?? UnconfiguredMenuView
68
+ this.isConfigured = !!methods?.View
67
69
  }
68
70
  }
@@ -6,6 +6,7 @@ export interface ConversationRequestArgs {
6
6
  chat_group_graph_id?: GraphId
7
7
  group_source_app_name?: string
8
8
  show_direct_messages?: boolean
9
+ show_archived?: boolean
9
10
  }
10
11
 
11
12
  export const getConversationsRequestArgs = ({
@@ -13,10 +14,13 @@ export const getConversationsRequestArgs = ({
13
14
  chat_group_graph_id,
14
15
  group_source_app_name,
15
16
  show_direct_messages,
17
+ show_archived,
16
18
  }: Partial<ConversationRequestArgs> = {}): GetRequest => {
17
19
  let filter: string
18
20
  if (chat_group_graph_id) {
19
21
  filter = 'mine_or_not_empty,group'
22
+ } else if (show_archived) {
23
+ filter = 'mine_or_not_empty,archived'
20
24
  } else if (show_direct_messages) {
21
25
  filter = 'mine_or_not_empty,direct_message'
22
26
  } else if (group_source_app_name) {
@@ -608,3 +608,15 @@ export const faTag: IconDefinition = {
608
608
  'M32.5 96l0 149.5c0 17 6.7 33.3 18.7 45.3l192 192c25 25 65.5 25 90.5 0L483.2 333.3c25-25 25-65.5 0-90.5l-192-192C279.2 38.7 263 32 246 32L96.5 32c-35.3 0-64 28.7-64 64zm112 16a32 32 0 1 1 0 64 32 32 0 1 1 0-64z',
609
609
  ],
610
610
  }
611
+
612
+ export const faBoxArchive: IconDefinition = {
613
+ prefix: 'fas',
614
+ iconName: 'box-archive',
615
+ icon: [
616
+ 512,
617
+ 512,
618
+ ['archive'],
619
+ 'f187',
620
+ 'M0 64C0 46.3 14.3 32 32 32l448 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96L0 64zM32 176l448 0 0 240c0 35.3-28.7 64-64 64L96 480c-35.3 0-64-28.7-64-64l0-240zm152 64c-13.3 0-24 10.7-24 24s10.7 24 24 24l144 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-144 0z',
621
+ ],
622
+ }